Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+246 -67
View File
@@ -2,9 +2,9 @@
/**
* @brief Purchase Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -107,11 +107,34 @@ class _Purchase extends \IPS\Node\Model
{
$childCount = $this->childrenCount( NULL, NULL, TRUE, array( array( 'ps_show=1' ) ) );
$hasCustomFields = count( $this->custom_fields );
try
{
$description = call_user_func( array( $this->extension(), 'getPurchaseNameInfo' ), $this );
}
catch ( \OutOfRangeException $e )
{
$description = array();
}
if ( $this->grouped_renewals )
{
$description[] = \IPS\Member::loggedIn()->language()->addToStack('purchase_grouped');
}
elseif ( $this->renewals )
{
$renewals = (string) $this->renewals;
if ( $this->renewals->tax )
{
$renewals .= \IPS\Member::loggedIn()->language()->addToStack( 'plus_tax_rate', NULL, array( 'sprintf' => array( $this->renewals->tax->_title ) ) );
}
$description[] = $renewals;
}
$description = implode( ' &middot; ', $description );
return \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row(
$url,
$this->id,
$hasCustomFields ? \IPS\Theme::i()->getTemplate( 'purchases', 'nexus' )->link( $this ) : $this->name,
( $hasCustomFields and !$childCount ) ? \IPS\Theme::i()->getTemplate( 'purchases', 'nexus' )->link( $this, FALSE, FALSE, TRUE ) : $this->_name,
$childCount,
array_merge( array(
'view' => array(
@@ -120,13 +143,13 @@ class _Purchase extends \IPS\Node\Model
'icon' => 'search',
)
), $this->buttons( $ref ) ),
$this->grouped_renewals ? \IPS\Member::loggedIn()->language()->addToStack('purchase_grouped') : ( (string) ( $this->renewals ?: '' ) ),
$description,
$this->getIcon(),
NULL,
$this->id == \IPS\Request::i()->root,
NULL,
NULL,
!$this->active ? ( $this->cancelled ? array( 'style5', 'canceled' ) : array( 'style6', 'expired' ) ) : NULL,
!$this->active ? ( $this->cancelled ? array( 'style5', 'purchase_canceled' ) : array( 'style6', 'purchase_expired' ) ) : NULL,
$hasCustomFields
);
}
@@ -293,6 +316,10 @@ class _Purchase extends \IPS\Node\Model
if ( !$this->expire or $this->expire->getTimestamp() > time() )
{
$this->active = TRUE;
if ( $this->_data['cancelled'] )
{
$this->onReactivate();
}
}
}
@@ -361,7 +388,7 @@ class _Purchase extends \IPS\Node\Model
*/
public function get_renewals()
{
if( $this->_data['renewals'] )
if( isset( $this->_data['renewals'] ) and $this->_data['renewals'] )
{
$tax = NULL;
if ( $this->_data['tax'] )
@@ -558,6 +585,38 @@ class _Purchase extends \IPS\Node\Model
$this->_data['grouped_renewals'] = !is_null( $data ) ? json_encode( $data ) : NULL;
}
/**
* Get billing agreement
*
* @return \IPS\nexus\Customer\BillingAgreement|NULL
*/
public function get_billing_agreement()
{
if ( $this->_data['billing_agreement'] )
{
try
{
return \IPS\nexus\Customer\BillingAgreement::load( $this->_data['billing_agreement'] );
}
catch ( \OutOfRangeException $e )
{
$this->_data['billing_agreement'] = NULL;
}
}
return NULL;
}
/**
* Set billing agreement
*
* @param \IPS\nexus\Customer\BillingAgreement|NULL $billingAgreement The billing agreement
* @return void
*/
public function set_billing_agreement( \IPS\nexus\Customer\BillingAgreement $billingAgreement = NULL )
{
$this->_data['billing_agreement'] = $billingAgreement === NULL ? NULL : $billingAgreement->id;
}
/* !Properties and syncing */
/**
@@ -569,12 +628,27 @@ class _Purchase extends \IPS\Node\Model
* Get extension
*
* @return \IPS\nexus\Invoice\Item\Purchase
* @throws \OutOfRangeException
*/
protected function extension()
{
if ( $this->extension === NULL )
{
foreach ( \IPS\Application::load( $this->app )->extensions( 'nexus', 'Item', FALSE ) as $ext )
/* Load the app. \IPS\Application::load() throws UnexpectedValueException
if the Application.php does not exist, which we'll standardize to
OutOfRangeException so areas calling this method only need to check
for that */
try
{
$app = \IPS\Application::load( $this->app );
}
catch ( \UnexpectedValueException $e )
{
throw new \OutOfRangeException;
}
/* Find the extension */
foreach ( $app->extensions( 'nexus', 'Item', FALSE ) as $ext )
{
if ( $ext::$type == $this->type )
{
@@ -582,6 +656,12 @@ class _Purchase extends \IPS\Node\Model
break;
}
}
/* Don't have it? */
if ( !$this->extension )
{
throw new \OutOfRangeException;
}
}
return $this->extension;
}
@@ -742,6 +822,23 @@ class _Purchase extends \IPS\Node\Model
}
}
/**
* Get ACP Support View HTML
*
* @return string
*/
public function acpSupportView()
{
try
{
return call_user_func( array( $this->extension(), 'acpSupportView' ), $this );
}
catch ( \OutOfRangeException $e )
{
return '';
}
}
/**
* Support Severity
*
@@ -758,6 +855,23 @@ class _Purchase extends \IPS\Node\Model
return NULL;
}
}
/**
* Get renewal payment methods IDs
*
* @return array|NULL
*/
public function renewalPaymentMethodIds()
{
try
{
return call_user_func( array( $this->extension(), 'renewalPaymentMethodIds' ), $this );
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
/**
* Call on*
@@ -779,12 +893,67 @@ class _Purchase extends \IPS\Node\Model
throw new \BadMethodCallException;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
* @apiresponse string itemApp Key for application. For example, 'nexus' for products and renewals; 'downloads' for Downloads files
* @apiresponse string itemType Key for item type. For example, 'package' for products; 'file' for Downloads files.
* @apiresponse int itemId The ID for the item. For example, the product ID or the file ID.
* @apiresponse \IPS\nexus\Customer customer Customer
* @apiresponse datetime purchased Purchased date
* @apiresponse datetime expires Expiration date
* @apiresponse bool active If purchase is currently active (not expired)
* @apiresponse bool canceled If purchase has been canceled
* @apiresponse \IPS\nexus\Purchase\RenewalTerm renewalTerm Renewal term
* @apiresponse object customFields Values for custom fields
* @apiresponse \IPS\nexus\Purchase parent Parent purchase
* @apiresponse bool show If this purchase shows in the client area and AdminCP
* @apiresponse string licenseKey License key
* @apiresponse string image If the item has a relevant image (for exmaple, product image, Downloads file screenshot), the URL to it
* @apiresponse string url The URL for the customer to view this purchase in the client area
*/
public function apiOutput()
{
$parent = NULL;
if ( $this->parent )
{
try
{
$parent = static::load( $this->parent )->apiOutput();
}
catch ( \OutOfRangeException $e ) {}
}
return array(
'id' => $this->id,
'name' => $this->name,
'itemApp' => $this->app,
'itemType' => $this->type,
'itemId' => $this->item_id,
'customer' => $this->member->apiOutput(),
'purchased' => $this->start->rfc3339(),
'expires' => $this->expire ? $this->expire->rfc3339() : null,
'active' => (bool) $this->active,
'canceled' => (bool) $this->cancelled,
'renewalTerm' => $this->renewals ? $this->renewals->apiOutput() : null,
'customFields' => $this->custom_fields,
'parent' => $parent,
'show' => (bool) $this->show,
'licenseKey' => $this->licenseKey() ? $this->licenseKey()->key : null,
'image' => $this->image() ? ( (string) $this->image()->url ) : null,
'url' => (string) $this->url(),
);
}
/* !License Keys */
/**
* @brief License key
*/
protected $licenseKey;
public $licenseKey;
/**
* Get license key
@@ -897,6 +1066,11 @@ class _Purchase extends \IPS\Node\Model
*/
public function canChangeExpireDate()
{
if ( $this->billing_agreement and !$this->billing_agreement->canceled )
{
return FALSE;
}
try
{
return call_user_func( array( $this->extension(), 'canChangeExpireDate' ), $this );
@@ -924,14 +1098,20 @@ class _Purchase extends \IPS\Node\Model
return FALSE;
}
if ( !$admin and $this->cancelled or !$this->expire or !$this->renewals )
if ( $this->cancelled or !$this->renewals or ( $this->billing_agreement and !$this->billing_agreement->canceled ) )
{
return FALSE;
}
if ( !$admin and $pendingInvoice = $this->invoice_pending and $pendingInvoice->status === $pendingInvoice::STATUS_PENDING )
{
return FALSE;
}
try
{
$date = call_user_func( array( $this->extension(), 'canRenewUntil' ), $this, $admin );
if ( $inCycles and $date instanceof \IPS\DateTime )
{
$now = \IPS\DateTime::create();
@@ -967,7 +1147,7 @@ class _Purchase extends \IPS\Node\Model
return FALSE;
}
return !$this->grouped_renewals and $this->renewals and $this->active;
return !$this->grouped_renewals and ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->renewals and $this->active;
}
/**
@@ -984,7 +1164,7 @@ class _Purchase extends \IPS\Node\Model
{
if( $this->_url === NULL )
{
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=clients&controller=purchases&do=view&id={$this->id}", 'front', 'clientspurchase', array( \IPS\Http\Url::seoTitle( $this->name ) ), \IPS\Settings::i()->nexus_https );
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=clients&controller=purchases&do=view&id={$this->id}", 'front', 'clientspurchase', array( \IPS\Http\Url\Friendly::seoTitle( $this->name ) ), \IPS\Settings::i()->nexus_https );
}
return $this->_url;
@@ -1017,12 +1197,19 @@ class _Purchase extends \IPS\Node\Model
*/
public function delete()
{
\IPS\File::unclaimAttachments( 'nexus_Purchases', $this->id, NULL, 'purchase' );
foreach ( $this->children() as $child )
{
$child->delete();
}
$this->onDelete();
try
{
$this->onDelete();
}
catch ( \BadMethodCallException $e ) {}
parent::delete();
}
@@ -1049,33 +1236,27 @@ class _Purchase extends \IPS\Node\Model
/* Remember what our term is */
$term = $this->renewals->getTerm();
$this->grouped_renewals = array( 'term' => $term['term'], 'unit' => $term['unit'], 'price' => $this->renewals->cost->amount, 'currency' => $this->renewals->cost->currency, 'tax' => $this->renewals->tax ? $this->renewals->tax->id : 0 );
$this->save();
/* If the parent also has a renewal term, merge them */
if ( $parent->renewals )
/* Add our term to the parent... */
if ( !$this->cancelled )
{
$parentCostPerDay = $parent->renewals->costPerDay();
$itemCostPerDay = $this->renewals->costPerDay();
if ( $parentCostPerDay->currency !== $itemCostPerDay->currency )
/* If the parent also has a renewal term, merge them */
if ( $parent->renewals )
{
throw new \DomainException('currencies_dont_match');
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $parent->renewals->add( $this->renewals ), $parent->renewals->interval, $parent->renewals->tax );
$parent->save();
}
$sum = $parentCostPerDay->amount + $itemCostPerDay->amount;
$increase = 1 / ( $parentCostPerDay->amount / $sum );
$newRenewalPrice = new \IPS\nexus\Money( $parent->renewals->cost->amount * $increase, $itemCostPerDay->currency );
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $newRenewalPrice->round(), $parent->renewals->interval, $parent->renewals->tax );
$parent->save();
}
/* Otherwise just set the parent to this */
else
{
$parent->renewals = $this->renewals;
if ( !$parent->expire )
/* Otherwise just set the parent to this */
else
{
$parent->expire = $this->expire;
$parent->renewals = $this->renewals;
if ( !$parent->expire )
{
$parent->expire = $this->expire;
}
$parent->save();
}
$parent->save();
}
/* Cancel any pending invoices as they're no longer valid */
@@ -1119,49 +1300,45 @@ class _Purchase extends \IPS\Node\Model
if ( $this->renewals )
{
/* Restore parent renewal term */
if ( $parent->renewals )
if ( $parent->renewals and !$this->cancelled )
{
$parentCostPerDay = $parent->renewals->costPerDay();
$itemCostPerDay = $this->renewals->costPerDay();
if ( $parentCostPerDay->currency !== $itemCostPerDay->currency )
$newRenewalPrice = $parent->renewals->subtract( $this->renewals );
if ( $newRenewalPrice->amount->isZero() )
{
throw new \DomainException('currencies_dont_match');
}
$sum = $parentCostPerDay->amount - $itemCostPerDay->amount;
if ( $sum and ( $parentCostPerDay->amount / $sum ) )
{
$increase = 1 / ( $parentCostPerDay->amount / $sum );
$newRenewalPrice = new \IPS\nexus\Money( $parent->renewals->cost->amount * $increase, $parent->renewals->cost->currency );
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $newRenewalPrice->round(), $parent->renewals->interval, $parent->renewals->tax );
$parent->renewals = NULL;
}
else
{
$parent->renewals = NULL;
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $newRenewalPrice, $parent->renewals->interval, $parent->renewals->tax );
}
$parent->save();
}
/* Restore this purchase renewal term */
$groupedRenewals = $this->grouped_renewals;
try
if ( $groupedRenewals['term'] and $groupedRenewals['unit'] )
{
$tax = ( isset( $groupedRenewals['tax'] ) and $groupedRenewals['tax'] ) ? \IPS\nexus\Tax::load( $groupedRenewals['tax'] ) : NULL;
try
{
$tax = ( isset( $groupedRenewals['tax'] ) and $groupedRenewals['tax'] ) ? \IPS\nexus\Tax::load( $groupedRenewals['tax'] ) : NULL;
}
catch ( \OutOfRangeException $e )
{
$tax = NULL;
}
$this->renewals = new \IPS\nexus\Purchase\RenewalTerm(
new \IPS\nexus\Money( $groupedRenewals['price'], isset( $groupedRenewals['currency'] ) ? $groupedRenewals['currency'] : $this->member->defaultCurrency() ),
new \DateInterval( 'P' . $groupedRenewals['term'] . mb_strtoupper( $groupedRenewals['unit'] ) ),
$tax
);
}
catch ( \OutOfRangeException $e )
{
$tax = NULL;
}
$this->renewals = new \IPS\nexus\Purchase\RenewalTerm(
new \IPS\nexus\Money( $groupedRenewals['price'], isset( $groupedRenewals['currency'] ) ? $groupedRenewals['currency'] : $this->member->defaultCurrency() ),
new \DateInterval( 'P' . $groupedRenewals['term'] . mb_strtoupper( $groupedRenewals['unit'] ) ),
$tax
);
$this->expire = $parent->expire;
$this->grouped_renewals = NULL;
$this->save();
}
/* Ungroup */
$this->grouped_renewals = NULL;
$this->save();
/* Cancel any pending invoices as they're no longer valid */
if ( $invoice = $parent->invoice_pending )
{
@@ -1214,8 +1391,10 @@ class _Purchase extends \IPS\Node\Model
$return = array_merge( $return, call_user_func( array( $this->extension(), 'acpButtons' ), $this, $url ) );
}
catch ( \OutOfRangeException $e ) { }
$parent = $this->parent();
if ( !$this->grouped_renewals and $this->renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
if ( !$this->grouped_renewals and ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
{
$return['renew'] = array(
'icon' => 'refresh',
@@ -1225,7 +1404,7 @@ class _Purchase extends \IPS\Node\Model
);
}
if ( $this->parent() and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_edit' ) )
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->parent() and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_edit' ) )
{
if ( !$this->grouped_renewals )
{
@@ -1247,7 +1426,7 @@ class _Purchase extends \IPS\Node\Model
}
}
if( !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_transfer' ) )
if( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_transfer' ) )
{
$return['transfer'] = array(
'icon' => 'user',
@@ -1257,7 +1436,7 @@ class _Purchase extends \IPS\Node\Model
);
}
if ( !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_cancel' ) )
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and ( !$this->grouped_renewals or !$parent or !$parent->billing_agreement or $parent->billing_agreement->canceled ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_cancel' ) )
{
if ( $this->cancelled )
{
@@ -1279,7 +1458,7 @@ class _Purchase extends \IPS\Node\Model
}
}
if ( !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_delete' ) )
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and ( !$this->grouped_renewals or !$parent or !$parent->billing_agreement or $parent->billing_agreement->canceled ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_delete' ) )
{
$return['delete'] = array(
'icon' => 'times-circle',