Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+308 -67
View File
@@ -79,7 +79,7 @@ class _Package extends \IPS\Node\Model
*/
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
{
$classname = 'IPS\nexus\Package\\' . ucfirst( $data['p_type'] );
$classname = 'IPS\nexus\Package\\' . mb_ucfirst( $data['p_type'] );
if ( isset( $classname::$packageDatabaseTable ) )
{
$data = array_merge( $data, \IPS\Db::i()->select( '*', $classname::$packageDatabaseTable, array( 'p_id=?', $data['p_id'] ) )->first() );
@@ -163,7 +163,7 @@ class _Package extends \IPS\Node\Model
$secondaryTable[ "p_{$k}" ] = $v;
unset( $this->changed[ $k ] );
}
elseif ( !in_array( "p_{$k}", array( 'p_id', 'p_name', 'p_seo_name', 'p_desc', 'p_group', 'p_stock', 'p_reg', 'p_store', 'p_member_groups', 'p_allow_upgrading', 'p_upgrade_charge', 'p_allow_downgrading', 'p_downgrade_refund', 'p_base_price', 'p_tax', 'p_renewal_days', 'p_primary_group', 'p_secondary_group', 'p_return_primary', 'p_return_secondary', 'p_position', 'p_associable', 'p_force_assoc', 'p_assoc_error', 'p_discounts', 'p_page', 'p_support', 'p_support_department', 'p_support_severity', 'p_featured', 'p_upsell', 'p_notify', 'p_type', 'p_custom', 'p_reviewable', 'p_review_moderate', 'p_image', 'p_methods', 'p_renew_options', 'p_group_renewals', 'p_rebuild_thumb', 'p_renewal_days_advance', 'p_date_added', 'p_reviews', 'p_rating', 'p_grace_period' ) ) )
elseif ( !in_array( "p_{$k}", array( 'p_id', 'p_name', 'p_seo_name', 'p_desc', 'p_group', 'p_stock', 'p_reg', 'p_store', 'p_member_groups', 'p_allow_upgrading', 'p_upgrade_charge', 'p_allow_downgrading', 'p_downgrade_refund', 'p_base_price', 'p_tax', 'p_renewal_days', 'p_primary_group', 'p_secondary_group', 'p_return_primary', 'p_return_secondary', 'p_position', 'p_associable', 'p_force_assoc', 'p_assoc_error', 'p_discounts', 'p_page', 'p_support', 'p_support_department', 'p_support_severity', 'p_featured', 'p_upsell', 'p_notify', 'p_type', 'p_custom', 'p_reviewable', 'p_review_moderate', 'p_image', 'p_methods', 'p_renew_options', 'p_group_renewals', 'p_rebuild_thumb', 'p_renewal_days_advance', 'p_date_added', 'p_reviews', 'p_rating', 'p_grace_period', 'p_email_purchase_type', 'p_email_purchase', 'p_email_expire_soon_type', 'p_email_expire_soon', 'p_email_expire_type', 'p_email_expire' ) ) )
{
unset( $this->changed[ $k ] );
}
@@ -189,7 +189,8 @@ class _Package extends \IPS\Node\Model
\IPS\Db::i()->replace( 'nexus_package_base_prices', $prices );
}
}
$this->date_updated = time();
parent::save();
$this->changed = $data;
@@ -208,6 +209,7 @@ class _Package extends \IPS\Node\Model
{
parent::delete();
\IPS\Db::i()->delete( static::$packageDatabaseTable, array( 'p_id=?', $this->id ) );
\IPS\Content\Search\Index::i()->removeFromSearchIndex( $this->item() );
}
/**
@@ -332,20 +334,53 @@ class _Package extends \IPS\Node\Model
* @param bool $bulkDiscounts Account for bulk discounts?
* @param int $initialCount Will assume that number of additional purchases for this package
* @return \IPS\nexus\Money
* @throws \OutOfBoundsException
*/
public function price( \IPS\nexus\Customer $customer = NULL, $usergroupDiscounts = TRUE, $loyaltyDiscounts = TRUE, $bulkDiscounts = TRUE, $initialCount = 0 )
{
return static::priceFromData(
$this->id,
json_decode( $this->base_price, TRUE ),
json_decode( $this->discounts, TRUE ),
$customer,
NULL,
$usergroupDiscounts,
$loyaltyDiscounts,
$bulkDiscounts,
$initialCount
);
}
/**
* Basic purchase price
* May be further adjusted by renewal terms which add
*
* @param int $packageId Package ID
* @param array $prices Base Price data
* @param array $discounts Discount data
* @param \IPS\nexus\Customer $customer The customer (NULL for currently logged in member)
* @param string $currency The currency (NULL to autodetect based on $customer)
* @param bool $usergroupDiscounts Account for usergroup discounts?
* @param bool $loyaltyDiscounts Account for loyalty discounts?
* @param bool $bulkDiscounts Account for bulk discounts?
* @param int $initialCount Will assume that number of additional purchases for this package
* @return \IPS\nexus\Money
* @throws \OutOfBoundsException
*/
public static function priceFromData( $packageId, $prices, $discounts, \IPS\nexus\Customer $customer = NULL, $currency = NULL, $usergroupDiscounts = TRUE, $loyaltyDiscounts = TRUE, $bulkDiscounts = TRUE, $initialCount = 0 )
{
/* Base */
$customer = $customer ?: \IPS\nexus\Customer::loggedIn();
$currency = ( $customer->member_id === \IPS\nexus\Customer::loggedIn()->member_id ) ? ( ( isset( $_SESSION['currency'] ) and in_array( $_SESSION['currency'], \IPS\nexus\Money::currencies() ) ) ? $_SESSION['currency'] : $customer->defaultCurrency() ) : $customer->defaultCurrency();
$prices = json_decode( $this->base_price, TRUE );
if ( !$currency )
{
$currency = ( $customer->member_id === \IPS\nexus\Customer::loggedIn()->member_id ) ? ( ( isset( $_SESSION['currency'] ) and in_array( $_SESSION['currency'], \IPS\nexus\Money::currencies() ) ) ? $_SESSION['currency'] : $customer->defaultCurrency() ) : $customer->defaultCurrency();
}
if ( !isset( $prices[ $currency ] ) )
{
throw new \OutOfBoundsException;
}
$price = $prices[ $currency ]['amount'];
$discounts = json_decode( $this->discounts, TRUE );
/* Usergroup discounts */
if ( $usergroupDiscounts and isset( $discounts['usergroup'] ) )
{
@@ -375,11 +410,11 @@ class _Package extends \IPS\Node\Model
{
foreach ( $discounts['loyalty'] as $discount )
{
$comparingPackageId = ( $discount['package'] ?: $this->id );
$count = ( $comparingPackageId == $this->id ) ? $initialCount : 0;
$comparingPackageId = ( $discount['package'] ?: $packageId );
$count = ( $comparingPackageId == $packageId ) ? $initialCount : 0;
if ( $discount['price'][ $currency ] < $price )
{
$count += $customer->member_id ? $customer->previousPurchasesCount( $discount['package'] ?: $this->id, $discount['active'] ) : 0;
$count += $customer->member_id ? $customer->previousPurchasesCount( $discount['package'] ?: $packageId, $discount['active'] ) : 0;
}
if ( $customer->member_id === \IPS\Member::loggedIn()->member_id and isset( $_SESSION['cart'] ) )
{
@@ -406,8 +441,8 @@ class _Package extends \IPS\Node\Model
rsort( $discounts['bulk'] );
$discount = array_shift( $discounts['bulk'] );
$discountPackageId = ( $discount['package'] ?: $this->id );
$count = ( $discountPackageId == $this->id ) ? $initialCount : 0;
$discountPackageId = ( $discount['package'] ?: $packageId );
$count = ( $discountPackageId == $packageId ) ? $initialCount : 0;
if ( $customer->member_id === \IPS\Member::loggedIn()->member_id and isset( $_SESSION['cart'] ) )
{
foreach ( $_SESSION['cart'] as $item )
@@ -440,15 +475,46 @@ class _Package extends \IPS\Node\Model
*/
protected function _lowestPrice( \IPS\nexus\Customer $customer, $currency, &$priceMayChange = FALSE, &$priceNotIncludingDiscounts = NULL )
{
/* What's the base price with and without discounts? */
$baseNoDiscounts = $this->price( $customer, FALSE, FALSE, FALSE )->amount;
$base = $this->price( $customer )->amount;
$renewOptions = $this->renew_options ? json_decode( $this->renew_options, TRUE ) : array();
$tax = NULL;
if ( \IPS\Settings::i()->nexus_show_tax and $this->tax )
{
try
{
$tax = \IPS\nexus\Tax::load( $this->tax );
}
catch ( \OutOfRangeException $e ) { }
}
return static::lowestPriceFromData( $customer, $currency, $this->id, json_decode( $this->base_price, TRUE ), json_decode( $this->discounts, TRUE ), $renewOptions, $this->stock, $tax, $priceMayChange, $priceNotIncludingDiscounts );
}
/**
* Get the lowest price
*
* @param \IPS\nexus\Customer $customer The customer
* @param int $packageId Package ID
* @param array $prices Base Price data
* @param array $discounts Discount data
* @param array $renewOptions Renewal options data
* @param int $stock Stock type (-2 for if it adjusts on fields)
* @param \IPS\nexus\Tax|NULL $tax Tax rate
* @param bool $priceMayChange [Reference] will be set to a value indicating if the price might change (i.e. should be displayed as "From $x")
* @param float $priceNotIncludingDiscounts [Reference] will be set to the lowest price not taking discounts into consideration
* @return float
*/
static function lowestPriceFromData( \IPS\nexus\Customer $customer, $currency, $packageId, $prices, $discounts, $renewOptions, $stock, $tax, &$priceMayChange = FALSE, &$priceNotIncludingDiscounts = NULL )
{
/* What's the base price with and without discounts? */
$baseNoDiscounts = static::priceFromData( $packageId, $prices, $discounts, $customer, $currency, FALSE, FALSE, FALSE )->amount;
$base = static::priceFromData( $packageId, $prices, $discounts, $customer, $currency )->amount;
/* Adjustments based on custom fields */
if ( $this->stock == -2 )
if ( $stock == -2 )
{
$mostReducedOption = NULL;
foreach ( \IPS\Db::i()->select( '*', 'nexus_product_options', array( 'opt_package=?', $this->id ) ) as $option )
foreach ( \IPS\Db::i()->select( '*', 'nexus_product_options', array( 'opt_package=?', $packageId ) ) as $option )
{
$basePriceAdjustments = json_decode( $option['opt_base_price'], TRUE );
if ( isset( $basePriceAdjustments[ $currency ] ) and ( $basePriceAdjustments[ $currency ] or $basePriceAdjustments[ $currency ] === '0' ) )
@@ -472,7 +538,6 @@ class _Package extends \IPS\Node\Model
$lowest = $base;
/* It's possible the base price is $0, but all the renewal options add, so let's look at that */
$renewOptions = $this->renew_options ? json_decode( $this->renew_options, TRUE ) : array();
if ( !empty( $renewOptions ) )
{
$lowestNoDiscounts = NULL;
@@ -506,21 +571,14 @@ class _Package extends \IPS\Node\Model
}
/* Add tax? */
if ( \IPS\Settings::i()->nexus_show_tax and $this->tax )
if ( \IPS\Settings::i()->nexus_show_tax and $tax )
{
try
{
/* Load tax rate */
$tax = \IPS\nexus\Tax::load( $this->tax );
/* What is the rate? */
$taxRate = new \IPS\Math\Number( $tax->rate( $customer->estimatedLocation() ) );
/* Add it on */
$lowest = $lowest->add( $lowest->multiply( $taxRate ) );
$lowestNoDiscounts = $lowestNoDiscounts->add( $lowestNoDiscounts->multiply( $taxRate ) );
}
catch ( \OutOfRangeException $e ) { }
/* What is the rate? */
$taxRate = new \IPS\Math\Number( $tax->rate( $customer->estimatedLocation() ) );
/* Add it on */
$lowest = $lowest->add( $lowest->multiply( $taxRate ) );
$lowestNoDiscounts = $lowestNoDiscounts->add( $lowestNoDiscounts->multiply( $taxRate ) );
}
/* Return */
@@ -708,6 +766,15 @@ class _Package extends \IPS\Node\Model
}
$buttons = array_merge( $buttons, parent::getButtons( $url, $subnode ) );
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'subscriptions', 'subscriptions_manage' ) and \IPS\nexus\Subscription\Package::canConvert( $this ) )
{
$buttons['convert_subs'] = array(
'icon' => 'certificate',
'title' => 'nexus_subs_convert',
'link' => \IPS\Http\Url::internal( 'app=nexus&module=subscriptions&controller=subscriptions&do=convertToSubscription&id=' . $this->_id )
);
}
return $buttons;
}
@@ -721,13 +788,18 @@ class _Package extends \IPS\Node\Model
* @return void
*/
public function form( &$form )
{
{
$groups = array();
foreach ( \IPS\Member\Group::groups() as $group )
{
$groups[ $group->g_id ] = $group->name;
}
$groupsExcludingGuestsAndAdmins = array();
foreach ( \IPS\Member\Group::groups( FALSE, FALSE ) as $group )
{
$groupsExcludingGuestsAndAdmins[ $group->g_id ] = $group->name;
}
$types = array();
$typeFields = array();
$typeFieldToggles = array();
@@ -856,15 +928,19 @@ class _Package extends \IPS\Node\Model
$form->addTab( 'package_pricing' );
$form->addHeader( 'package_purchase_price' );
$form->add( new \IPS\nexus\Form\Money( 'p_base_price', $this->base_price, TRUE ) );
$form->add( new \IPS\Helpers\Form\Node( 'p_tax', (int) $this->tax, FALSE, array( 'class' => 'IPS\nexus\Tax', 'zeroVal' => 'do_not_tax' ) ) );
$form->addHeader('package_discounts');
$discounts = json_decode( $this->discounts, TRUE );
$form->add( new \IPS\Helpers\Form\Stack( 'p_usergroup_discounts', isset( $discounts['usergroup'] ) ? $discounts['usergroup'] : array(), FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\DiscountUsergroup' ) ) );
$form->add( new \IPS\Helpers\Form\Stack( 'p_loyalty_discounts', isset( $discounts['loyalty'] ) ? $discounts['loyalty'] : array(), FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\DiscountLoyalty' ) ) );
$form->add( new \IPS\Helpers\Form\Stack( 'p_bulk_discounts', isset( $discounts['bulk'] ) ? $discounts['bulk'] : array(), FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\DiscountBulk' ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_set_discounts', ( is_array( $discounts ) AND count( $discounts ) ), FALSE, array( 'togglesOn' => array( 'p_usergroup_discounts', 'p_loyalty_discounts', 'p_bulk_discounts' ) ), NULL, NULL, NULL, 'p_set_discounts' ) );
$form->add( new \IPS\Helpers\Form\Stack( 'p_usergroup_discounts', isset( $discounts['usergroup'] ) ? $discounts['usergroup'] : array(), FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\DiscountUsergroup' ), NULL, NULL, NULL, 'p_usergroup_discounts' ) );
$form->add( new \IPS\Helpers\Form\Stack( 'p_loyalty_discounts', isset( $discounts['loyalty'] ) ? $discounts['loyalty'] : array(), FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\DiscountLoyalty' ), NULL, NULL, NULL, 'p_loyalty_discounts' ) );
$form->add( new \IPS\Helpers\Form\Stack( 'p_bulk_discounts', isset( $discounts['bulk'] ) ? $discounts['bulk'] : array(), FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\DiscountBulk' ), NULL, NULL, NULL, 'p_bulk_discounts' ) );
$form->addHeader( 'package_renewals' );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_renews', !empty( $renewOptions ), FALSE, array( 'togglesOn' => array( 'p_renew_options', 'p_grace_period', 'p_renew', ( $this->id ? "form_{$this->id}" : 'form_new' ) . '_header_package_client_area_renewals' ) ), NULL, NULL, NULL, 'p_renews' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_renews', !empty( $renewOptions ), FALSE, array( 'togglesOn' => array( 'p_renew_options', 'p_grace_period', 'p_renew', ( $this->id ? "form_{$this->id}" : 'form_new' ) . '_header_package_client_area_renewals', 'p_email_expire_soon_type', 'p_email_expire_type' ) ), NULL, NULL, NULL, 'p_renews' ) );
$form->add( new \IPS\Helpers\Form\Stack( 'p_renew_options', $renewOptions, FALSE, array(
'stackFieldType' => 'IPS\nexus\Form\RenewalTerm',
'allCurrencies' => TRUE,
@@ -903,6 +979,7 @@ class _Package extends \IPS\Node\Model
}
), NULL, NULL, NULL, 'p_dynamic_stock' ) );
$form->addTab('package_store');
$form->addHeader('package_store_permissions');
$form->add( new \IPS\Helpers\Form\YesNo( 'p_store', $this->store, FALSE, array( 'togglesOn' => array( 'p_member_groups', 'p_featured', 'p_desc_editor', 'p_images', 'p_reviewable', "{$form->id}_header_package_store_display", "{$form->id}_header_package_reviews" ) ) ) );
@@ -928,21 +1005,20 @@ class _Package extends \IPS\Node\Model
$form->addHeader('package_reviews');
$form->add( new \IPS\Helpers\Form\YesNo( 'p_reviewable', $this->reviewable, FALSE, array( 'togglesOn' => array( 'p_review_moderate' ) ), NULL, NULL, NULL, 'p_reviewable' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_review_moderate', $this->review_moderate ?: FALSE, FALSE, array(), NULL, NULL, NULL, 'p_review_moderate' ) );
$form->addHeader( 'package_notify' );
$form->add( new \IPS\Helpers\Form\Stack( 'p_notify', $this->notify ? explode( ',', $this->notify ) : array(), FALSE, array( 'stackFieldType' => 'Email' ) ) );
$form->addHeader( 'package_benefits' );
unset( $groups[ \IPS\Settings::i()->guest_group ] );
$form->add( new \IPS\Helpers\Form\Select( 'p_primary_group', $this->primary_group ?: '*', FALSE, array( 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'do_not_change', 'unlimitedToggles' => array( 'p_return_primary' ), 'unlimitedToggleOn' => FALSE ) ) );
$form->addTab( 'package_benefits' );
$form->add( new \IPS\Helpers\Form\Select( 'p_primary_group', $this->primary_group ?: '*', FALSE, array( 'options' => $groupsExcludingGuestsAndAdmins, 'unlimited' => '*', 'unlimitedLang' => 'do_not_change', 'unlimitedToggles' => array( 'p_return_primary' ), 'unlimitedToggleOn' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_return_primary', $this->return_primary, FALSE, array(), NULL, NULL, NULL, 'p_return_primary' ) );
$form->add( new \IPS\Helpers\Form\Select( 'p_secondary_group', $this->secondary_group ? explode( ',', $this->secondary_group ) : '*', FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'do_not_change', 'unlimitedToggles' => array( 'p_return_secondary' ), 'unlimitedToggleOn' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'p_secondary_group', $this->secondary_group ? explode( ',', $this->secondary_group ) : '*', FALSE, array( 'options' => $groupsExcludingGuestsAndAdmins, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'do_not_change', 'unlimitedToggles' => array( 'p_return_secondary' ), 'unlimitedToggleOn' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_return_secondary', $this->return_secondary, FALSE, array(), NULL, NULL, NULL, 'p_return_secondary' ) );
$form->add( new \IPS\Helpers\Form\Node( 'p_support_severity', $this->support_severity ?: 0, FALSE, array( 'class' => 'IPS\nexus\Support\Severity', 'zeroVal' => 'none' ), NULL, NULL, NULL, 'p_support_severity' ) );
foreach ( $typeFields['package_benefits'] as $field )
{
$form->add( $field );
}
$form->addHeader( 'package_notify' );
$form->add( new \IPS\Helpers\Form\Stack( 'p_notify', $this->notify ? explode( ',', $this->notify ) : array(), FALSE, array( 'stackFieldType' => 'Email' ) ) );
$form->addTab('package_client_area');
$form->addHeader('package_client_area_display');
$form->add( new \IPS\Helpers\Form\Translatable( 'p_page', NULL, FALSE, array(
@@ -975,6 +1051,41 @@ class _Package extends \IPS\Node\Model
1 => 'p_downgrade_refund_full',
2 => 'p_downgrade_refund_prorate'
)), NULL, NULL, NULL, 'p_downgrade_refund' ) );
$form->addTab('package_custom_emails');
$form->addMessage('package_custom_emails_blurb');
foreach ( \IPS\Settings::i()->cm_invoice_warning ? array( 'purchase', 'expire_soon', 'expire' ) : array( 'purchase', 'expire' ) as $k )
{
$typeKey = "email_{$k}_type";
$valueKey = "email_{$k}";
$form->add( new \IPS\Helpers\Form\Radio( "p_email_{$k}_type", $this->$typeKey, FALSE, array(
'options' => array(
'' => 'p_email_none',
'wysiwyg' => 'p_email_wysiwyg',
'html_template' => 'p_email_html_template',
'html_raw' => 'p_email_html_raw',
),
'toggles' => array(
'wysiwyg' => array( "p_email_{$k}_subject", "p_email_{$k}_wysiwyg" ),
'html_template' => array( "p_email_{$k}_subject", "p_email_{$k}_html" ),
'html_raw' => array( "p_email_{$k}_subject", "p_email_{$k}_html" )
)
), NULL, NULL, NULL, "p_email_{$k}_type" ) );
$form->add( new \IPS\Helpers\Form\Translatable( "p_email_{$k}_subject", NULL, FALSE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_package_{$this->id}_email_{$k}_subject" : NULL ), NULL, NULL, NULL, "p_email_{$k}_subject" ) );
$form->add( new \IPS\Helpers\Form\Codemirror( "p_email_{$k}_html", $this->$valueKey, FALSE, array(
'preview' => \IPS\Http\Url::internal('app=nexus&module=store&controller=packages&do=emailPreview'),
'tags' => array(
'{$purchase->name}' => \IPS\Member::loggedIn()->language()->addToStack('ps_name'),
'{$purchase->expires}' => \IPS\Member::loggedIn()->language()->addToStack('p_email_tag_expire'),
'{$purchase->renewals}' => \IPS\Member::loggedIn()->language()->addToStack('ps_renewals'),
'{$purchase->custom_fields[1]}' => \IPS\Member::loggedIn()->language()->addToStack('p_email_tag_field'),
'{$purchase->url()}' => \IPS\Member::loggedIn()->language()->addToStack('p_email_tag_url'),
'{$purchase->licenseKey()->key}'=> \IPS\Member::loggedIn()->language()->addToStack('license_key'),
)
), NULL, NULL, NULL, "p_email_{$k}_html" ) );
$form->add( new \IPS\Helpers\Form\Editor( "p_email_{$k}_wysiwyg", $this->$valueKey, FALSE, array( 'app' => 'nexus', 'key' => 'Admin', 'autoSaveKey' => $this->id ? "nexus-pkg-{$this->id}-{$k}-email" : "nexus-new-pkg-{$k}-email" ), NULL, NULL, NULL, "p_email_{$k}_wysiwyg" ) );
}
}
/**
@@ -995,7 +1106,7 @@ class _Package extends \IPS\Node\Model
\IPS\File::claimAttachments( 'nexus-new-pkg-assoc', $this->id, NULL, 'pkg-assoc', TRUE );
\IPS\File::claimAttachments( 'nexus-new-pkg-pg', $this->id, NULL, 'pkg-pg', TRUE );
$class = 'IPS\nexus\Package\\' . ucfirst( $values['p_type'] );
$class = 'IPS\nexus\Package\\' . mb_ucfirst( $values['p_type'] );
if ( isset( $class::$packageDatabaseTable ) )
{
@@ -1006,7 +1117,14 @@ class _Package extends \IPS\Node\Model
return $obj->saveForm( $obj->formatFormValues( $values ) );
}
return parent::saveForm( $values );
$return = parent::saveForm( $values );
if ( !$this->custom )
{
\IPS\Content\Search\Index::i()->index( $this->item() );
}
return $return;
}
/**
@@ -1022,8 +1140,30 @@ class _Package extends \IPS\Node\Model
return $values;
}
/* Email content */
foreach ( array( 'purchase', 'expire_soon', 'expire' ) as $k )
{
if ( isset( $values["p_email_{$k}_type"] ) )
{
if ( $values["p_email_{$k}_type"] == 'wysiwyg' )
{
$values["p_email_{$k}"] = $values["p_email_{$k}_wysiwyg"];
}
elseif ( in_array( $values["p_email_{$k}_type"], array( 'html_template', 'html_raw' ) ) )
{
$values["p_email_{$k}"] = $values["p_email_{$k}_html"];
}
else
{
$values["p_email_{$k}"] = NULL;
}
}
unset( $values["p_email_{$k}_wysiwyg"] );
unset( $values["p_email_{$k}_html"] );
}
/* Translatables */
foreach ( array( 'name' => '', 'desc' => '_desc', 'assoc_error' => '_assoc', 'page' => '_page' ) as $key => $suffix )
foreach ( array( 'name' => '', 'desc' => '_desc', 'assoc_error' => '_assoc', 'page' => '_page', 'email_purchase_subject' => '_email_purchase_subject', 'email_expire_soon_subject' => '_email_expire_soon_subject', 'email_expire_subject' => '_email_expire_subject' ) as $key => $suffix )
{
if ( isset( $values[ 'p_' . $key ] ) )
{
@@ -1472,7 +1612,7 @@ class _Package extends \IPS\Node\Model
}
}
if ( array_key_exists( 'primary_group', $changes ) or array_key_exists( 'secondary_group', $changes ) )
if ( array_key_exists( 'primary_group', $changes ) or array_key_exists( 'secondary_group', $changes ) AND ( !$purchase->expire OR $purchase->expire->getTimestamp() > time() ) )
{
$this->_removeUsergroups( $purchase, $changes );
$this->_addUsergroups( $purchase );
@@ -1632,9 +1772,8 @@ class _Package extends \IPS\Node\Model
}
/**
* Create Item ibject
* Create Item Object
*
* @param \IPS\nexus\Package $package The package
* @param \IPS\nexus\Money $price Price
* @return \IPS\nexus\extensions\nexus\Item\Package
*/
@@ -1855,7 +1994,14 @@ class _Package extends \IPS\Node\Model
if ( isset( $values[ 'nexus_pfield_' . $field->id ] ) )
{
$customFieldsSave[ $field->id ] = $class::stringValue( $values[ 'nexus_pfield_' . $field->id ] );
if ( $class instanceof \IPS\Helpers\Form\Upload )
{
$customFieldsSave[ $field->id ] = (string) $values[ 'nexus_pfield_' . $field->id ];
}
else
{
$customFieldsSave[ $field->id ] = $class::stringValue( $values[ 'nexus_pfield_' . $field->id ] );
}
}
}
@@ -1952,7 +2098,7 @@ class _Package extends \IPS\Node\Model
{
\IPS\Output::i()->redirect( $invoice->checkoutUrl() );
}
$purchase->member->log( 'purchase', array( 'type' => 'change', 'id' => $purchase->id, 'old' => $purchase->name, 'name' => $newPackage->_title, 'system' => FALSE ) );
$purchase->member->log( 'purchase', array( 'type' => 'change', 'id' => $purchase->id, 'old' => $purchase->name, 'name' => $newPackage->titleForLog(), 'system' => FALSE ) );
\IPS\Output::i()->redirect( $purchase->url() );
}
return (string) $form;
@@ -1998,7 +2144,7 @@ class _Package extends \IPS\Node\Model
{
$options[ $k ] = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $term['cost'][$currency]['amount'], $currency ), new \DateInterval( 'P' . $term['term'] . mb_strtoupper( $term['unit'] ) ) );
}
$form->add( new \IPS\Helpers\Form\Radio( 'ps_renewals', NULL, TRUE, array( 'options' => $options, 'parse' => 'none' ) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'ps_renewals', NULL, TRUE, array( 'options' => $options, 'parse' => 'normal' ) ) );
if ( $values = $form->values() )
{
$term = $renewalOptions[ $values['ps_renewals'] ];
@@ -2148,8 +2294,15 @@ class _Package extends \IPS\Node\Model
{
$class = 'IPS\Helpers\Form\\' . $field->type;
}
$customFields[ $field->id ] = $class::stringValue( $values[ 'nexus_pfield_' . $field->id ] );
if ( $class instanceof \IPS\Helpers\Form\Upload )
{
$customFields[ $field->id ] = (string) $values[ 'nexus_pfield_' . $field->id ];
}
else
{
$customFields[ $field->id ] = $class::stringValue( $values[ 'nexus_pfield_' . $field->id ] );
}
}
$purchase->custom_fields = $customFields;
@@ -2292,6 +2445,8 @@ class _Package extends \IPS\Node\Model
$email->send( $to );
}
}
$this->sendCustomEmail( 'purchase', $purchase );
}
/**
@@ -2317,6 +2472,18 @@ class _Package extends \IPS\Node\Model
}
/**
* On expire soon
* If returns TRUE, the normal expire warning email will not be sent
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
*/
public function onExpireWarning( \IPS\nexus\Purchase $purchase )
{
return $this->sendCustomEmail( 'expire_soon', $purchase );
}
/**
* On Purchase Expired
*
@@ -2326,6 +2493,8 @@ class _Package extends \IPS\Node\Model
public function onExpire( \IPS\nexus\Purchase $purchase )
{
$this->_removeUsergroups( $purchase );
$this->sendCustomEmail( 'expire', $purchase );
}
/**
@@ -2390,6 +2559,39 @@ class _Package extends \IPS\Node\Model
}
/**
* Send a custom email
*
* @param string $type Which custom email
* @param \IPS\nexus\Purchase $purchase The purchase
* @return bool
*/
public function sendCustomEmail( $type, \IPS\nexus\Purchase $purchase )
{
$typeKey = "email_{$type}_type";
$valueKey = "email_{$type}";
if ( $this->$typeKey )
{
if ( $this->$typeKey == 'wysiwyg' )
{
$email = \IPS\Email::buildFromContent( $purchase->member->language()->get( "nexus_package_{$this->id}_email_{$type}_subject" ), $this->$valueKey, NULL, \IPS\Email::TYPE_TRANSACTIONAL );
}
else
{
$functionName = 'nexus_customPurchaseEmail_' . $this->id;
\IPS\Theme::makeProcessFunction( $this->$valueKey, $functionName, '$purchase' );
$email = \IPS\Email::buildFromContent( $purchase->member->language()->get( "nexus_package_{$this->id}_email_{$type}_subject" ), call_user_func( 'IPS\\Theme\\'.$functionName, $purchase ), NULL, \IPS\Email::TYPE_TRANSACTIONAL, $this->$typeKey == 'html_template' );
}
$email->send( $purchase->member );
return TRUE;
}
return FALSE;
}
/* !Upgrading/Downgrading */
/**
@@ -2403,14 +2605,15 @@ class _Package extends \IPS\Node\Model
*/
protected function upgradeDowngradeOptions( \IPS\nexus\Purchase $purchase, $overrideRestrictions = FALSE, &$includesUpgrades=FALSE, &$includesDowngrades=FALSE )
{
/* Cannot upgrade/downgrade custom or inactive packages */
if ( $this->custom or !$purchase->active )
/* Cannot upgrade/downgrade custom or cancelled packages */
if ( $this->custom or $purchase->cancelled )
{
return array();
}
/* Cannot upgrade/downgrade without permission unless $overrideRestrictions is set */
if ( !$this->allow_upgrading and !$overrideRestrictions )
if ( ( !$this->allow_upgrading AND !$this->allow_downgrading ) AND !$overrideRestrictions )
{
return array();
}
@@ -2570,7 +2773,7 @@ class _Package extends \IPS\Node\Model
$desciptions[ $package->id ] = \IPS\Member::loggedIn()->language()->addToStack( 'downgrade_refund_and_renew', FALSE, array( 'sprintf' => array( $costToUpgrade, $renewalOptionsString ) ) );
}
$renewFields[] = new \IPS\Helpers\Form\Radio( 'renew_option_' . $package->id, NULL, NULL, array( 'options' => $renewalOptions, 'descriptions' => $renewFieldsDescriptions, 'parse' => 'none' ), NULL, NULL, NULL, 'renew_option_' . $package->id );
$renewFields[] = new \IPS\Helpers\Form\Radio( 'renew_option_' . $package->id, NULL, NULL, array( 'options' => $renewalOptions, 'descriptions' => $renewFieldsDescriptions, 'parse' => 'normal' ), NULL, NULL, NULL, 'renew_option_' . $package->id );
\IPS\Member::loggedIn()->language()->words['renew_option_' . $package->id] = \IPS\Member::loggedIn()->language()->addToStack( 'renewal_term', FALSE );
$toggles[ $package->id ] = array( 'renew_option_' . $package->id );
}
@@ -2578,7 +2781,7 @@ class _Package extends \IPS\Node\Model
$form = new \IPS\Helpers\Form;
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\Radio( 'change_package_to', NULL, TRUE, array( 'options' => $options, 'descriptions' => $desciptions, 'toggles' => $toggles, 'parse' => 'none' ) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'change_package_to', NULL, TRUE, array( 'options' => $options, 'descriptions' => $desciptions, 'toggles' => $toggles, 'parse' => 'normal' ) ) );
foreach ( $renewFields as $field )
{
$form->add( $field );
@@ -2675,6 +2878,7 @@ class _Package extends \IPS\Node\Model
$renewalOptionsInDays[ $term->days() ] = $term;
}
$closestRenewalOption = null;
$numberOfDaysInCurrentRenewalTerm = $purchase->renewals->days();
foreach ( $renewalOptionsInDays as $days => $term )
{
if ( $closestRenewalOption === null or abs( $numberOfDaysInCurrentRenewalTerm - $closestRenewalOption ) > abs( $days - $numberOfDaysInCurrentRenewalTerm ) )
@@ -2688,7 +2892,7 @@ class _Package extends \IPS\Node\Model
$diff = $purchase->renewals->diff( $renewalTermToUse );
/* Multiply that by how many days are left */
$numberOfDaysInCurrentRenewalTerm = new \IPS\Math\Number( $purchase->renewals->days() );
$numberOfDaysInCurrentRenewalTerm = new \IPS\Math\Number( $numberOfDaysInCurrentRenewalTerm );
$daysLeftUntilExpiry = new \IPS\Math\Number( (string) ceil( ( $purchase->expire->getTimestamp() - time() ) / 86400 ) );
if ( $numberOfDaysInCurrentRenewalTerm->compare( $daysLeftUntilExpiry ) === 0 )
{
@@ -2725,6 +2929,11 @@ class _Package extends \IPS\Node\Model
$item->tax = $newPackage->tax ? \IPS\nexus\Tax::load( $newPackage->tax ) : NULL;
$item->id = $purchase->id;
$item->extra = array( 'newPackage' => $newPackage->id, 'oldPackage' => $this->id, 'renewalOption' => $chosenRenewalOption, 'previousRenewalTerms' => $purchase->renewals ? array( 'cost' => $purchase->renewals->cost->amount, 'currency' => $purchase->renewals->cost->currency, 'term' => $purchase->renewals->getTerm() ) : NULL );
if ( $newPackage->methods and $newPackage->methods != '*' )
{
$item->paymentMethodIds = explode( ',', $newPackage->methods );
}
$invoice = new \IPS\nexus\Invoice;
$invoice->member = $purchase->member;
@@ -2985,11 +3194,12 @@ class _Package extends \IPS\Node\Model
$purchase->member->member_group_id = $this->primary_group;
$purchase->member->members_bitoptions['ignore_promotions'] = true;
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'purchase', 'action' => 'add', 'id' => $purchase->id, 'name' => $purchase->name, 'old' => $extra['old_primary_group'], 'new' => $purchase->member->member_group_id ) );
}
}
/* Secondary Groups */
$secondary = array_filter( explode( ',', $this->secondary_group ), create_function( '$v', 'return (bool) $v;' ) );
$secondary = array_filter( explode( ',', $this->secondary_group ), function( $v ){ return (bool) $v; } );
$current_secondary = $purchase->member->mgroup_others ? explode( ',', $purchase->member->mgroup_others ) : array();
$new_secondary = $current_secondary;
if ( !empty( $secondary ) )
@@ -3010,6 +3220,7 @@ class _Package extends \IPS\Node\Model
$purchase->member->mgroup_others = ',' . implode( ',', $new_secondary ) . ',';
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'secondary', 'by' => 'purchase', 'action' => 'add', 'id' => $purchase->id, 'name' => $purchase->name, 'old' => $current_secondary, 'new' => $new_secondary ) );
}
}
@@ -3031,23 +3242,37 @@ class _Package extends \IPS\Node\Model
/* We only want to move them back if they haven't been moved again since */
if ( $purchase->member->member_group_id == $basis['primary_group'] )
{
$oldGroup = $purchase->member->member_group_id;
/* Have we made other purchases that have changed their primary group? */
try
{
$purchase->member->member_group_id = \IPS\Db::i()->select( 'p_primary_group', 'nexus_purchases', array( 'ps_member=? AND ps_app=? AND ps_type=? AND ps_active=1 AND p_primary_group<>0 AND ps_id<>?', $purchase->member->member_id, 'nexus', 'package', $purchase->id ) )
$next = \IPS\Db::i()->select( array( 'ps_id', 'ps_name', 'p_primary_group' ), 'nexus_purchases', array( 'ps_member=? AND ps_app=? AND ps_type=? AND ps_active=1 AND p_primary_group<>0 AND ps_id<>?', $purchase->member->member_id, 'nexus', 'package', $purchase->id ) )
->join( 'nexus_packages', 'p_id=ps_item_id' )
->first();
/* Make sure this group exists */
try
{
\IPS\Member\Group::load( $next['p_primary_group'] );
}
catch( \OutOfRangeException $e )
{
throw new \UnderflowException;
}
$purchase->member->member_group_id = $next['p_primary_group'];
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'purchase', 'action' => 'change', 'remove_id' => $purchase->id, 'remove_name' => $purchase->name, 'id' => $next['ps_id'], 'name' => $next['ps_name'], 'old' => $oldGroup, 'new' => $purchase->member->member_group_id ) );
}
/* No, move them to their original group */
catch ( \UnderflowException $e )
{
$purchase->member->member_group_id = $purchase->member->cm_return_group;
/* Does this group exist? */
try
{
\IPS\Member\Group::load( $purchase->member->member_group_id );
\IPS\Member\Group::load( $purchase->member->cm_return_group );
$purchase->member->member_group_id = $purchase->member->cm_return_group;
}
catch ( \OutOfRangeException $e )
{
@@ -3058,6 +3283,7 @@ class _Package extends \IPS\Node\Model
$purchase->member->cm_return_group = 0;
$purchase->member->members_bitoptions['ignore_promotions'] = false;
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'purchase', 'action' => 'remove', 'id' => $purchase->id, 'name' => $purchase->name, 'old' => $oldGroup, 'new' => $purchase->member->member_group_id ) );
}
}
}
@@ -3067,6 +3293,7 @@ class _Package extends \IPS\Node\Model
{
/* Work some stuff out */
$membersSecondaryGroups = $purchase->member->mgroup_others ? array_unique( array_filter( explode( ',', $purchase->member->mgroup_others ) ) ) : array();
$currentSecondaryGroups = $membersSecondaryGroups;
$membersPreviousSecondaryGroupsBeforeThisPurchase = array_unique( array_filter( explode( ',', $extra['old_secondary_groups'] ) ) );
$secondaryGroupsAwardedByThisPurchase = array_unique( array_filter( explode( ',', $basis['secondary_group'] ) ) );
@@ -3096,10 +3323,24 @@ class _Package extends \IPS\Node\Model
/* If we're still here, remove it */
unset( $membersSecondaryGroups[ array_search( $groupId, $membersSecondaryGroups ) ] );
}
/* And make sure only valid groups are saved */
$membersSecondaryGroups = array_filter( $membersSecondaryGroups, function( $group ){
try
{
\IPS\Member\Group::load( $group );
return TRUE;
}
catch( \OutOfRangeException $e )
{
return FALSE;
}
});
/* Save */
$purchase->member->mgroup_others = implode( ',', $membersSecondaryGroups );
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'secondary', 'by' => 'purchase', 'action' => 'remove', 'id' => $purchase->id, 'name' => $purchase->name, 'old' => $currentSecondaryGroups, 'new' => $membersSecondaryGroups ) );
}
}
}