1644 lines
37 KiB
PHP
1644 lines
37 KiB
PHP
<?php
|
|
/**
|
|
* @brief Purchase Model
|
|
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
|
* @copyright (c) Invision Power Services, Inc.
|
|
* @license https://www.invisioncommunity.com/legal/standards/
|
|
* @package Invision Community
|
|
* @subpackage Nexus
|
|
* @since 10 Feb 2014
|
|
*/
|
|
|
|
namespace IPS\nexus;
|
|
|
|
/* To prevent PHP errors (extending class does not exist) revealing path */
|
|
|
|
use BadMethodCallException;
|
|
use DateInterval;
|
|
use ErrorException;
|
|
use Exception;
|
|
use IPS\Application;
|
|
use IPS\DateTime;
|
|
use IPS\Db;
|
|
use IPS\Events\Event;
|
|
use IPS\File;
|
|
use IPS\Helpers\Form;
|
|
use IPS\Helpers\Tree\Tree;
|
|
use IPS\Http\Url;
|
|
use IPS\Http\Url\Friendly;
|
|
use IPS\Member;
|
|
use IPS\Member\Group;
|
|
use IPS\nexus\Customer\BillingAgreement;
|
|
use IPS\nexus\Invoice\Item\Purchase as ItemPurchase;
|
|
use IPS\nexus\Package\CustomField;
|
|
use IPS\nexus\Purchase\LicenseKey;
|
|
use IPS\nexus\Purchase\RenewalTerm;
|
|
use IPS\Node\Model;
|
|
use IPS\Patterns\ActiveRecordIterator;
|
|
use IPS\Request;
|
|
use IPS\Theme;
|
|
use LogicException;
|
|
use OutOfRangeException;
|
|
use UnexpectedValueException;
|
|
use function call_user_func_array;
|
|
use function count;
|
|
use function defined;
|
|
use function intval;
|
|
use function is_null;
|
|
|
|
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
|
{
|
|
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Purchase Model
|
|
* @method onExpirationDateChange() : void
|
|
* @method onExpireWarning() : bool
|
|
* @method onRenew(int $cycles = 1): void
|
|
* @method onTransfer( Member $newCustomer ) : void
|
|
* @method onCancelWarning() : string|null
|
|
* @property Customer $member
|
|
*/
|
|
class Purchase extends Model
|
|
{
|
|
/**
|
|
* Tree
|
|
*
|
|
* @param Url $url URL
|
|
* @param array $where Where clause
|
|
* @param string $ref Referer
|
|
* @param Purchase|NULL $root Root (NULL for all root purchases)
|
|
* @param bool $includeRoot Show the root?
|
|
* @return Tree
|
|
*/
|
|
public static function tree(Url $url, array $where, string $ref = 'c', ?Purchase $root = NULL, bool $includeRoot = TRUE ) : Tree
|
|
{
|
|
$where[] = array( 'ps_show=1' );
|
|
|
|
return new Tree(
|
|
$url,
|
|
'Purchases',
|
|
function( $limit ) use ( $url, $where, $ref, $root, $includeRoot )
|
|
{
|
|
if ( $root )
|
|
{
|
|
if ( $includeRoot )
|
|
{
|
|
$where[] = array( 'ps_id=?', $root->id );
|
|
}
|
|
else
|
|
{
|
|
$where[] = array( 'ps_parent=?', $root->id );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$where[] = array( 'ps_parent=0' );
|
|
}
|
|
|
|
$rows = array();
|
|
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'nexus_purchases', $where, 'ps_start DESC', $limit ), 'IPS\nexus\Purchase' ) as $purchase )
|
|
{
|
|
/* @var Purchase $purchase */
|
|
$rows[ $purchase->id ] = $purchase->treeRow( $url, $ref );
|
|
}
|
|
return $rows;
|
|
},
|
|
function( $id ) use ( $url, $ref )
|
|
{
|
|
return Purchase::load( $id )->treeRow( $url, $ref );
|
|
},
|
|
function( $id )
|
|
{
|
|
return Purchase::load( $id )->parent();
|
|
},
|
|
function( $id ) use ( $url, $ref, $where )
|
|
{
|
|
$rows = array();
|
|
foreach (Purchase::load( $id )->children( NULL, NULL, TRUE, NULL, $where ) as $child )
|
|
{
|
|
$rows[ $child->id ] = $child->treeRow( $url, $ref );
|
|
}
|
|
return $rows;
|
|
},
|
|
NULL,
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
NULL,
|
|
function()
|
|
{
|
|
$where[] = array( 'ps_parent=0' );
|
|
return Db::i()->select( 'COUNT(*)', 'nexus_purchases', $where )->first();
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Tree Row
|
|
*
|
|
* @param Url $url URL
|
|
* @param string $ref Referer
|
|
* @return string
|
|
*/
|
|
public function treeRow( Url $url, string $ref ): string
|
|
{
|
|
$childCount = $this->childrenCount( NULL, NULL, TRUE, array( array( 'ps_show=1' ) ) );
|
|
|
|
$hasCustomFields = FALSE;
|
|
foreach ( $this->custom_fields as $k => $v )
|
|
{
|
|
try
|
|
{
|
|
$customFieldValue = CustomField::load( $k )->displayValue( $v, TRUE );
|
|
if ( $customFieldValue and trim( $customFieldValue ) )
|
|
{
|
|
$hasCustomFields = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
catch( OutOfRangeException ) {}
|
|
}
|
|
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$description = $extension::getPurchaseNameInfo( $this );
|
|
|
|
if( empty( $description ) )
|
|
{
|
|
$description[] = Member::loggedIn()->language()->addToStack( 'purchase_number', FALSE, array( 'sprintf' => array( $this->id ) ) );
|
|
}
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
$description = array();
|
|
}
|
|
if ( $this->grouped_renewals )
|
|
{
|
|
$description[] = Member::loggedIn()->language()->addToStack('purchase_grouped');
|
|
}
|
|
elseif ( $this->renewals )
|
|
{
|
|
$renewals = (string) $this->renewals;
|
|
if ( $this->renewals->tax )
|
|
{
|
|
$renewals .= Member::loggedIn()->language()->addToStack( 'plus_tax_rate', FALSE, array( 'sprintf' => array( $this->renewals->tax->_title ) ) );
|
|
}
|
|
$description[] = $renewals;
|
|
}
|
|
|
|
$description = implode( ' · ', $description );
|
|
|
|
return Theme::i()->getTemplate( 'trees', 'core' )->row(
|
|
$url,
|
|
$this->id,
|
|
( $hasCustomFields and !$childCount ) ? Theme::i()->getTemplate( 'purchases', 'nexus' )->link( $this, FALSE, FALSE, TRUE ) : $this->_name,
|
|
$childCount,
|
|
array_merge( array(
|
|
'view' => array(
|
|
'link' => $this->acpUrl()->setQueryString( 'popup', true ),
|
|
'title' => 'view',
|
|
'icon' => 'search',
|
|
)
|
|
), $this->buttons( $ref ) ),
|
|
$description,
|
|
$this->getIcon(),
|
|
NULL,
|
|
$this->id == Request::i()->root,
|
|
NULL,
|
|
NULL,
|
|
!$this->active ? ( $this->cancelled ? array( 'style5', 'purchase_canceled' ) : array( 'style6', 'purchase_expired' ) ) : NULL,
|
|
$hasCustomFields
|
|
);
|
|
}
|
|
|
|
/* ActiveRecord */
|
|
|
|
/**
|
|
* @brief Multiton Store
|
|
*/
|
|
protected static array $multitons;
|
|
|
|
/**
|
|
* @brief Database Table
|
|
*/
|
|
public static ?string $databaseTable = 'nexus_purchases';
|
|
|
|
/**
|
|
* @brief Database Prefix
|
|
*/
|
|
public static string $databasePrefix = 'ps_';
|
|
|
|
/* !Node */
|
|
|
|
/**
|
|
* @brief Node Title
|
|
*/
|
|
public static string $nodeTitle = 'purchases';
|
|
|
|
/**
|
|
* @brief [Node] Parent ID Database Column
|
|
*/
|
|
public static ?string $databaseColumnParent = 'parent';
|
|
|
|
/**
|
|
* @brief [Node] If the node can be "owned", the owner "type" (typically "member" or "group") and the associated database column
|
|
*/
|
|
public static ?array $ownerTypes = array(
|
|
'member' => 'member'
|
|
);
|
|
|
|
/**
|
|
* Get title
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get__title(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/* !Columns */
|
|
|
|
/**
|
|
* Set Default Values
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setDefaultValues() : void
|
|
{
|
|
$this->_data['active'] = TRUE; // do directly so it doesn't call onReactivate
|
|
$this->start = new DateTime;
|
|
$this->renewal_price = 0;
|
|
$this->renewal_currency = '';
|
|
$this->invoice_pending = NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Name with sticky fields
|
|
*/
|
|
protected string $nameWithStickyFields = '';
|
|
|
|
/**
|
|
* Get name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_name() : string
|
|
{
|
|
if ( !$this->nameWithStickyFields )
|
|
{
|
|
$this->nameWithStickyFields = $this->_data['name'];
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$info = $extension::getPurchaseNameInfo( $this );
|
|
if ( count( $info ) )
|
|
{
|
|
$this->nameWithStickyFields .= ' (' . implode( ' · ', $info ) . ')';
|
|
}
|
|
}
|
|
catch ( OutOfRangeException ) { }
|
|
}
|
|
return $this->nameWithStickyFields;
|
|
}
|
|
|
|
/**
|
|
* Get name without sticky fields
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get__name() : string
|
|
{
|
|
return $this->_data['name'];
|
|
}
|
|
|
|
/**
|
|
* Get member
|
|
*
|
|
* @return Member
|
|
*/
|
|
public function get_member() : Member
|
|
{
|
|
return Customer::load( $this->_data['member'] );
|
|
}
|
|
|
|
/**
|
|
* Set member
|
|
*
|
|
* @param Member $member
|
|
* @return void
|
|
*/
|
|
public function set_member( Member $member ) : void
|
|
{
|
|
$this->_data['member'] = $member->member_id;
|
|
}
|
|
|
|
/**
|
|
* Set active
|
|
*
|
|
* @param bool $active Is active?
|
|
* @return void
|
|
*/
|
|
public function set_active( bool $active ) : void
|
|
{
|
|
if ( $this->id )
|
|
{
|
|
if ( $this->_data['active'] and !$active )
|
|
{
|
|
$this->_data['active'] = FALSE;
|
|
$this->onExpire();
|
|
}
|
|
elseif ( !$this->_data['active'] and $active )
|
|
{
|
|
$this->_data['active'] = TRUE;
|
|
$this->onReactivate();
|
|
}
|
|
}
|
|
|
|
$this->_data['active'] = $active;
|
|
}
|
|
|
|
/**
|
|
* Set cancelled
|
|
*
|
|
* @param bool $cancelled Is cancelled?
|
|
* @return void
|
|
*/
|
|
public function set_cancelled( bool $cancelled ) : void
|
|
{
|
|
if ( $cancelled )
|
|
{
|
|
$this->_data['active'] = FALSE; // We call directly so onExpire doesn't run (as onCancel will run)
|
|
$this->onCancel();
|
|
}
|
|
else
|
|
{
|
|
if ( !$this->expire or $this->expire->getTimestamp() > time() )
|
|
{
|
|
$this->active = TRUE;
|
|
if ( $this->_data['cancelled'] )
|
|
{
|
|
$this->onReactivate();
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->_data['cancelled'] = $cancelled;
|
|
}
|
|
|
|
/**
|
|
* Get start date
|
|
*
|
|
* @return DateTime
|
|
*/
|
|
public function get_start() : DateTime
|
|
{
|
|
return DateTime::ts( $this->_data['start'] );
|
|
}
|
|
|
|
/**
|
|
* Set start date
|
|
*
|
|
* @param DateTime $date The invoice date
|
|
* @return void
|
|
*/
|
|
public function set_start( DateTime $date ) : void
|
|
{
|
|
$this->_data['start'] = $date->getTimestamp();
|
|
}
|
|
|
|
/**
|
|
* Get expire date
|
|
*
|
|
* @return DateTime|NULL
|
|
*/
|
|
public function get_expire() : DateTime|null
|
|
{
|
|
return ( isset( $this->_data['expire'] ) and $this->_data['expire'] ) ? DateTime::ts( $this->_data['expire'] ) : NULL;
|
|
}
|
|
|
|
/**
|
|
* Set expire date
|
|
*
|
|
* @param DateTime|NULL $date The invoice date
|
|
* @return void
|
|
*/
|
|
public function set_expire( ?DateTime $date = NULL ) : void
|
|
{
|
|
if ( $date === NULL )
|
|
{
|
|
$this->_data['expire'] = 0;
|
|
$this->active = !$this->cancelled;
|
|
}
|
|
else
|
|
{
|
|
$this->_data['expire'] = $date->getTimestamp();
|
|
$this->active = ( !$this->cancelled and $date->add( new DateInterval( 'PT' . intval( $this->grace_period ) . 'S' ) )->getTimestamp() > time() );
|
|
}
|
|
if ( $this->id )
|
|
{
|
|
$this->onExpirationDateChange();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get renewal term
|
|
*
|
|
* @return RenewalTerm|NULL
|
|
*/
|
|
public function get_renewals() : RenewalTerm|null
|
|
{
|
|
if( isset( $this->_data['renewals'] ) and $this->_data['renewals'] )
|
|
{
|
|
$tax = NULL;
|
|
if ( $this->_data['tax'] )
|
|
{
|
|
try
|
|
{
|
|
$tax = Tax::load( $this->_data['tax'] );
|
|
}
|
|
catch ( Exception ) { }
|
|
}
|
|
|
|
return new RenewalTerm( new Money( $this->_data['renewal_price'], $this->_data['renewal_currency'] ), new DateInterval( 'P' . $this->_data['renewals'] . mb_strtoupper( $this->_data['renewal_unit'] ) ), $tax );
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Set renewal term
|
|
*
|
|
* @param RenewalTerm|NULL $term The renewal term
|
|
* @return void
|
|
*/
|
|
public function set_renewals( ?RenewalTerm $term = NULL ) : void
|
|
{
|
|
if ( $term === NULL OR $term->cost->amount->isZero() )
|
|
{
|
|
$this->_data['renewals'] = 0;
|
|
}
|
|
else
|
|
{
|
|
$data = $term->getTerm();
|
|
$this->_data['renewals'] = $data['term'];
|
|
$this->_data['renewal_unit'] = $data['unit'];
|
|
$this->_data['renewal_price'] = $term->cost->amount;
|
|
$this->_data['renewal_currency'] = $term->cost->currency;
|
|
$this->_data['tax'] = $term->tax ? $term->tax->id : 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get custom fields
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_custom_fields() : array
|
|
{
|
|
return json_decode( $this->_data['custom_fields'], TRUE ) ?: array();
|
|
}
|
|
|
|
/**
|
|
* Set custom fields
|
|
*
|
|
* @param array|null $customFields The data
|
|
* @return void
|
|
*/
|
|
public function set_custom_fields( ?array $customFields ) : void
|
|
{
|
|
$this->_data['custom_fields'] = json_encode( $customFields );
|
|
}
|
|
|
|
/**
|
|
* Get extra information
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_extra() : array
|
|
{
|
|
return json_decode( $this->_data['extra'], TRUE ) ?: array();
|
|
}
|
|
|
|
/**
|
|
* Set extra information
|
|
*
|
|
* @param array|null $extra The data
|
|
* @return void
|
|
*/
|
|
public function set_extra( ?array $extra ) : void
|
|
{
|
|
$this->_data['extra'] = json_encode( $extra );
|
|
}
|
|
|
|
/**
|
|
* Set parent purchase
|
|
*
|
|
* @param Purchase|null $purchase
|
|
* @return void
|
|
*/
|
|
public function set_parent( ?Purchase $purchase = NULL ) : void
|
|
{
|
|
$this->_data['parent'] = $purchase ? $purchase->id : 0;
|
|
}
|
|
|
|
/**
|
|
* Get pending invoice
|
|
*
|
|
* @return Invoice|null
|
|
*/
|
|
public function get_invoice_pending() : Invoice|null
|
|
{
|
|
try
|
|
{
|
|
return $this->_data['invoice_pending'] ? Invoice::load( $this->_data['invoice_pending'] ) : NULL;
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set pending invoice
|
|
*
|
|
* @param Invoice|null $invoice
|
|
* @return void
|
|
*/
|
|
public function set_invoice_pending( ?Invoice $invoice = NULL ) : void
|
|
{
|
|
$this->_data['invoice_pending'] = $invoice ? $invoice->id : 0;
|
|
|
|
if ( !$invoice )
|
|
{
|
|
$this->_data['invoice_warning_sent'] = FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get member to receive payments
|
|
*
|
|
* @return Member|null
|
|
*/
|
|
public function get_pay_to() : Member|null
|
|
{
|
|
try
|
|
{
|
|
return $this->_data['pay_to'] ? Customer::load( $this->_data['pay_to'] ) : NULL;
|
|
}
|
|
catch ( Exception )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set member to receive payments
|
|
*
|
|
* @param Member $member
|
|
* @return void
|
|
*/
|
|
public function set_pay_to( Member $member ) : void
|
|
{
|
|
$this->_data['pay_to'] = $member->member_id;
|
|
}
|
|
|
|
/**
|
|
* Get fee to commission on renewal charges
|
|
*
|
|
* @return Money|NULL
|
|
*/
|
|
public function get_fee() : Money|null
|
|
{
|
|
if ( $this->_data['fee'] and $this->renewals )
|
|
{
|
|
try
|
|
{
|
|
return new Money( $this->_data['fee'], $this->renewals->cost->currency );
|
|
}
|
|
catch ( Exception ) { }
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Set fee to commission on renewal charges
|
|
*
|
|
* @param Money|NULL $fee The fee
|
|
* @return void
|
|
*/
|
|
public function set_fee( ?Money $fee = NULL ) : void
|
|
{
|
|
$this->_data['fee'] = $fee?->amount;
|
|
}
|
|
|
|
/**
|
|
* Get original invoice
|
|
*
|
|
* @return Invoice
|
|
*/
|
|
public function get_original_invoice() : Invoice
|
|
{
|
|
return Invoice::load( $this->_data['original_invoice'] );
|
|
}
|
|
|
|
/**
|
|
* Set original invoice
|
|
*
|
|
* @param Invoice $invoice
|
|
* @return void
|
|
*/
|
|
public function set_original_invoice( Invoice $invoice ) : void
|
|
{
|
|
$this->_data['original_invoice'] = $invoice->id;
|
|
}
|
|
|
|
/**
|
|
* Get grouped renewals information
|
|
*
|
|
* @return array|null
|
|
*/
|
|
public function get_grouped_renewals() : array|null
|
|
{
|
|
return $this->_data['grouped_renewals'] ? json_decode( $this->_data['grouped_renewals'], TRUE ) : NULL;
|
|
}
|
|
|
|
/**
|
|
* Set grouped renewals information
|
|
*
|
|
* @param array|NULL $data The data
|
|
* @return void
|
|
*/
|
|
public function set_grouped_renewals( ?array $data ) : void
|
|
{
|
|
$this->_data['grouped_renewals'] = !is_null( $data ) ? json_encode( $data ) : NULL;
|
|
}
|
|
|
|
/**
|
|
* Get billing agreement
|
|
*
|
|
* @return BillingAgreement|NULL
|
|
*/
|
|
public function get_billing_agreement() : BillingAgreement|null
|
|
{
|
|
if ( $this->_data['billing_agreement'] )
|
|
{
|
|
try
|
|
{
|
|
return BillingAgreement::load( $this->_data['billing_agreement'] );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
$this->_data['billing_agreement'] = NULL;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Set billing agreement
|
|
*
|
|
* @param BillingAgreement|NULL $billingAgreement The billing agreement
|
|
* @return void
|
|
*/
|
|
public function set_billing_agreement( ?BillingAgreement $billingAgreement = NULL ) : void
|
|
{
|
|
$this->_data['billing_agreement'] = $billingAgreement?->id;
|
|
}
|
|
|
|
/* !Properties and syncing */
|
|
|
|
/**
|
|
* @brief Extension
|
|
*/
|
|
protected ?string $extension = null;
|
|
|
|
/**
|
|
* Get extension
|
|
*
|
|
* @return string
|
|
* @throws OutOfRangeException
|
|
*/
|
|
protected function extension() : string
|
|
{
|
|
if ( $this->extension === NULL )
|
|
{
|
|
/* 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 = Application::load( $this->app );
|
|
}
|
|
catch ( UnexpectedValueException )
|
|
{
|
|
throw new OutOfRangeException;
|
|
}
|
|
|
|
/* Find the extension */
|
|
foreach ( $app->extensions( 'nexus', 'Item', FALSE ) as $ext )
|
|
{
|
|
if ( $ext::$type == $this->type )
|
|
{
|
|
$this->extension = $ext;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Don't have it? */
|
|
if ( !$this->extension )
|
|
{
|
|
throw new OutOfRangeException;
|
|
}
|
|
}
|
|
return $this->extension;
|
|
}
|
|
|
|
/**
|
|
* Get icon
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getIcon() : string
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::getIcon( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return 'question';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get icon
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getTypeTitle() : string|null
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::getTypeTitle( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get image
|
|
*
|
|
* @return File|null
|
|
*/
|
|
public function image() : File|null
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::purchaseImage( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get ACP Page HTML
|
|
*
|
|
* @return string
|
|
*/
|
|
public function acpPage(): string
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::acpPage( $this );
|
|
}
|
|
catch ( OutOfRangeException | BadMethodCallException )
|
|
{
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ACP Edit Form
|
|
*
|
|
* @param Form $form The form
|
|
* @param RenewalTerm|null $renewals The renewal term
|
|
* @return void
|
|
*/
|
|
public function acpEdit(Form $form, ?RenewalTerm $renewals ): void
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$extension::acpEdit( $this, $form, $renewals );
|
|
}
|
|
catch ( OutOfRangeException ){}
|
|
}
|
|
|
|
/**
|
|
* ACP Edit Save
|
|
*
|
|
* @param array $values Values from form
|
|
* @return void
|
|
*/
|
|
public function acpEditSave( array $values ): void
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$extension::acpEditSave( $this, $values );
|
|
}
|
|
catch ( OutOfRangeException ){}
|
|
}
|
|
|
|
/**
|
|
* Get Client Area Page HTML
|
|
*
|
|
* @return array
|
|
*/
|
|
public function clientAreaPage(): array
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::clientAreaPage( $this );
|
|
}
|
|
catch ( OutOfRangeException | BadMethodCallException | ErrorException )
|
|
{
|
|
return array( 'packageInfo' => '', 'purchaseInfo' => '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform ACP Action
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function acpAction(): string|null
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::acpAction( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform Client Area Action
|
|
*
|
|
* @return void
|
|
*/
|
|
public function clientAreaAction(): void
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$extension::clientAreaAction( $this );
|
|
}
|
|
catch ( OutOfRangeException ){}
|
|
}
|
|
|
|
/**
|
|
* Get renewal payment methods IDs
|
|
*
|
|
* @return array|NULL
|
|
*/
|
|
public function renewalPaymentMethodIds(): array|null
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::renewalPaymentMethodIds( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call on*
|
|
*
|
|
* @param string $method Method
|
|
* @param array $params Params
|
|
* @return mixed
|
|
*/
|
|
public function __call(string $method, array $params ): mixed
|
|
{
|
|
if ( mb_substr( $method, 0, 2 ) === 'on' )
|
|
{
|
|
try
|
|
{
|
|
$result = call_user_func_array( array( $this->extension(), $method ), array_merge( array( $this ), $params ) );
|
|
|
|
/* Fire the event, but do it on the original invoice item, not on the purchase itself.
|
|
This way we support all possible invoice items. */
|
|
|
|
$price = new Money( "0", $this->member->defaultCurrency() );
|
|
if( $this->renewals instanceof RenewalTerm )
|
|
{
|
|
$price = $this->renewals->cost;
|
|
}
|
|
$item = new $this->extension( $this->name, $price );
|
|
Event::fire( $method, $item, array_merge( array( $this ), $params ) );
|
|
|
|
return $result;
|
|
}
|
|
catch ( OutOfRangeException ) { }
|
|
}
|
|
throw new BadMethodCallException;
|
|
}
|
|
|
|
/**
|
|
* Purchase can be reactivated in the ACP?
|
|
*
|
|
* @param string|NULL $error Error to show, passed by reference
|
|
* @return bool
|
|
*/
|
|
public function canAcpReactivate( ?string &$error=NULL ): bool
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::canAcpReactivate( $this, $error );
|
|
}
|
|
|
|
/**
|
|
* Get output for API
|
|
*
|
|
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
|
* @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( Member $authorizedMember = NULL ): array
|
|
{
|
|
$parent = NULL;
|
|
if ( $this->parent )
|
|
{
|
|
try
|
|
{
|
|
$parent = static::load( $this->parent )->apiOutput( $authorizedMember );
|
|
}
|
|
catch ( OutOfRangeException ) {}
|
|
}
|
|
|
|
return array(
|
|
'id' => $this->id,
|
|
'name' => $this->name,
|
|
'itemApp' => $this->app,
|
|
'itemType' => $this->type,
|
|
'itemId' => $this->item_id,
|
|
'customer' => $this->member->apiOutput( $authorizedMember ),
|
|
'purchased' => $this->start->rfc3339(),
|
|
'expires' => $this->expire ? $this->expire->rfc3339() : null,
|
|
'active' => $this->active,
|
|
'canceled' => (bool) $this->cancelled,
|
|
'renewalTerm' => $this->renewals?->apiOutput($authorizedMember),
|
|
'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
|
|
*/
|
|
public LicenseKey|null|bool $licenseKey = null;
|
|
|
|
/**
|
|
* Get license key
|
|
*
|
|
* @return LicenseKey|NULL|bool
|
|
*/
|
|
public function licenseKey() : LicenseKey|null|bool
|
|
{
|
|
if ( $this->licenseKey === NULL )
|
|
{
|
|
try
|
|
{
|
|
$this->licenseKey = LicenseKey::load( $this->id, 'lkey_purchase' );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
$this->licenseKey = FALSE;
|
|
}
|
|
}
|
|
return $this->licenseKey ?: NULL;
|
|
}
|
|
|
|
/**
|
|
* onExpire
|
|
*
|
|
* @return void
|
|
*/
|
|
public function onExpire() : void
|
|
{
|
|
if ( $licenseKey = $this->licenseKey() )
|
|
{
|
|
$licenseKey->active = FALSE;
|
|
$licenseKey->save();
|
|
}
|
|
|
|
$this->__call( 'onExpire', array() );
|
|
}
|
|
|
|
/**
|
|
* onCancel
|
|
*
|
|
* @return void
|
|
*/
|
|
public function onCancel() : void
|
|
{
|
|
if ( $licenseKey = $this->licenseKey() )
|
|
{
|
|
$licenseKey->active = FALSE;
|
|
$licenseKey->save();
|
|
}
|
|
|
|
$this->__call( 'onCancel', array() );
|
|
}
|
|
|
|
/**
|
|
* onReactivate
|
|
*
|
|
* @return void
|
|
*/
|
|
public function onReactivate() : void
|
|
{
|
|
if ( $licenseKey = $this->licenseKey() )
|
|
{
|
|
$licenseKey->active = TRUE;
|
|
$licenseKey->save();
|
|
}
|
|
|
|
$this->__call( 'onReactivate', array() );
|
|
}
|
|
|
|
/**
|
|
* onDelete
|
|
*
|
|
* @return void
|
|
*/
|
|
public function onDelete() : void
|
|
{
|
|
if ( $licenseKey = $this->licenseKey() )
|
|
{
|
|
$licenseKey->delete();
|
|
}
|
|
|
|
$this->__call( 'onDelete', array() );
|
|
}
|
|
|
|
/* !Client Area */
|
|
|
|
/**
|
|
* Can view?
|
|
*
|
|
* @param Member|Group|null $member The member to check (NULL for currently logged in member)
|
|
* @return bool
|
|
*/
|
|
public function canView( Member|Group|null $member=null ): bool
|
|
{
|
|
$member = $member ?: Member::loggedIn();
|
|
|
|
/* Show only purchases from active applications */
|
|
try
|
|
{
|
|
$app = Application::load( $this->app );
|
|
}
|
|
catch ( UnexpectedValueException )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if( !$app->canAccess() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( $this->member->member_id === $member->member_id or array_key_exists( $member->member_id, iterator_to_array( $this->member->alternativeContacts( array( Db::i()->findInSet( 'purchases', array( $this->id ) ) ) ) ) ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Admin can change expire date / renewal term?
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function canChangeExpireDate() : bool
|
|
{
|
|
if ( $this->billing_agreement and !$this->billing_agreement->canceled )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::canChangeExpireDate( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Can Renew Until
|
|
*
|
|
* @param Member|NULL $member The member to check (NULL for currently logged in member)
|
|
* @param bool $inCycles Get in cycles rather than a date?
|
|
* @param bool $admin If TRUE, is for ACP. If FALSE, is for front-end.
|
|
* @return bool|DateTime|int TRUE means can renew as much as they like. FALSE means cannot renew at all. \IPS\DateTime (or int if $inCycles is TRUE) means can renew until that date
|
|
*/
|
|
public function canRenewUntil(?Member $member = NULL, bool $inCycles = FALSE, bool $admin = FALSE ): DateTime|int|bool
|
|
{
|
|
/* Can this purchase be renewed at all? */
|
|
if ( !$this->canBeRenewed() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
$member = $member ?: Member::loggedIn();
|
|
|
|
if ( !$admin and $this->member->member_id !== $member->member_id and !array_key_exists( $member->member_id, iterator_to_array( $this->member->alternativeContacts( array( Db::i()->findInSet( 'purchases', array( $this->id ) ) . ' AND billing=1' ) ) ) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
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
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$date = $extension::canRenewUntil( $this, $admin );
|
|
|
|
if ( $inCycles and $date instanceof DateTime )
|
|
{
|
|
$now = DateTime::create();
|
|
$cycles = 0;
|
|
while ( $now->add( $this->renewals->interval )->getTimestamp() < $date->getTimestamp() )
|
|
{
|
|
$cycles++;
|
|
}
|
|
return $cycles;
|
|
}
|
|
else
|
|
{
|
|
return $date;
|
|
}
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Purchase can be renewed?
|
|
*
|
|
* @return boolean|NULL
|
|
*/
|
|
public function canBeRenewed(): bool|null
|
|
{
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
return $extension::canBeRenewed( $this );
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Can Cancel
|
|
*
|
|
* @param Member|NULL $member The member to check (NULL for currently logged in member)
|
|
* @return bool
|
|
*/
|
|
public function canCancel(?Member $member = NULL ): bool
|
|
{
|
|
$member = $member ?: Member::loggedIn();
|
|
if ( $this->member->member_id !== $member->member_id and !array_key_exists( $member->member_id, iterator_to_array( $this->member->alternativeContacts( array( Db::i()->findInSet( 'purchases', array( $this->id ) ) . ' AND billing=1' ) ) ) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return !$this->grouped_renewals and ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->renewals and $this->active;
|
|
}
|
|
|
|
/**
|
|
* @brief Cached URL
|
|
*/
|
|
protected mixed $_url = NULL;
|
|
|
|
/**
|
|
* Get URL
|
|
*
|
|
* @return Url\Internal|string|null
|
|
*/
|
|
function url(): Url\Internal|string|null
|
|
{
|
|
if( $this->_url === NULL )
|
|
{
|
|
$this->_url = Url::internal( "app=nexus&module=clients&controller=purchases&do=view&id={$this->id}", 'front', 'clientspurchase', array( Friendly::seoTitle( $this->name ) ) );
|
|
}
|
|
|
|
return $this->_url;
|
|
}
|
|
|
|
/* !Actions */
|
|
|
|
/**
|
|
* Transfer
|
|
*
|
|
* @param Member $newCustomer
|
|
* @return void
|
|
*/
|
|
public function transfer( Member $newCustomer ) : void
|
|
{
|
|
$this->onTransfer( $newCustomer );
|
|
$this->member = $newCustomer;
|
|
$this->save();
|
|
|
|
foreach ( $this->children() as $child )
|
|
{
|
|
$child->transfer( $newCustomer );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete
|
|
*
|
|
* @param bool $deleteChildren If TRUE, child purchases will also be deleted. If FALSE, they will become unassociated.
|
|
* @return void
|
|
*/
|
|
public function delete( bool $deleteChildren = TRUE ): void
|
|
{
|
|
File::unclaimAttachments( 'nexus_Purchases', $this->id, NULL, 'purchase' );
|
|
|
|
foreach ( $this->children( NULL ) as $child )
|
|
{
|
|
if ( $deleteChildren )
|
|
{
|
|
$child->delete();
|
|
}
|
|
else
|
|
{
|
|
$child->parent = NULL;
|
|
$child->save();
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
$this->onDelete();
|
|
}
|
|
catch ( BadMethodCallException ) {}
|
|
|
|
parent::delete();
|
|
}
|
|
|
|
/* !Grouping */
|
|
|
|
/**
|
|
* Group with parent
|
|
*
|
|
* @return void
|
|
* @throws LogicException
|
|
*/
|
|
public function groupWithParent() : void
|
|
{
|
|
/* Get the parent */
|
|
$parent = $this->parent();
|
|
if ( !$parent )
|
|
{
|
|
throw new BadMethodCallException('no_parent');
|
|
}
|
|
|
|
/* If we have a renewal term, we need to merge it with the parent... */
|
|
if ( $this->renewals )
|
|
{
|
|
/* 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();
|
|
|
|
/* Add our term to the parent... */
|
|
if ( !$this->cancelled )
|
|
{
|
|
/* If the parent also has a renewal term, merge them */
|
|
if ( $parent->renewals )
|
|
{
|
|
$parent->renewals = new RenewalTerm( $parent->renewals->add( $this->renewals ), $parent->renewals->interval, $parent->renewals->tax );
|
|
}
|
|
/* Otherwise just set the parent to this */
|
|
else
|
|
{
|
|
$parent->renewals = $this->renewals;
|
|
if ( !$parent->expire )
|
|
{
|
|
$parent->expire = $this->expire;
|
|
}
|
|
}
|
|
|
|
$parent->save();
|
|
}
|
|
|
|
/* Cancel any pending invoices as they're no longer valid */
|
|
if ( $invoice = $parent->invoice_pending )
|
|
{
|
|
$invoice->status = $invoice::STATUS_CANCELED;
|
|
$invoice->save();
|
|
}
|
|
if ( $invoice = $this->invoice_pending )
|
|
{
|
|
$invoice->status = $invoice::STATUS_CANCELED;
|
|
$invoice->save();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->grouped_renewals = array();
|
|
}
|
|
|
|
/* Update this purchase */
|
|
$this->expire = NULL;
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Ungroup from parent
|
|
*
|
|
* @return void
|
|
* @throws BadMethodCallException
|
|
*/
|
|
public function ungroupFromParent() : void
|
|
{
|
|
/* Get the parent */
|
|
$parent = $this->parent();
|
|
if ( !$parent )
|
|
{
|
|
throw new BadMethodCallException('no_parent');
|
|
}
|
|
|
|
/* If we have a renewal term, we need to remove it from the parent... */
|
|
if ( $this->renewals )
|
|
{
|
|
/* Restore parent renewal term */
|
|
if ( $parent->renewals and !$this->cancelled )
|
|
{
|
|
$newRenewalPrice = $parent->renewals->subtract( $this->renewals );
|
|
if ( $newRenewalPrice->amount->isZero() )
|
|
{
|
|
$parent->renewals = NULL;
|
|
}
|
|
else
|
|
{
|
|
$parent->renewals = new RenewalTerm( $newRenewalPrice, $parent->renewals->interval, $parent->renewals->tax );
|
|
}
|
|
$parent->save();
|
|
}
|
|
|
|
/* Restore this purchase renewal term */
|
|
$groupedRenewals = $this->grouped_renewals;
|
|
if ( $groupedRenewals['term'] and $groupedRenewals['unit'] )
|
|
{
|
|
try
|
|
{
|
|
$tax = ( isset( $groupedRenewals['tax'] ) and $groupedRenewals['tax'] ) ? Tax::load( $groupedRenewals['tax'] ) : NULL;
|
|
}
|
|
catch ( OutOfRangeException )
|
|
{
|
|
$tax = NULL;
|
|
}
|
|
$this->renewals = new RenewalTerm(
|
|
new Money( $groupedRenewals['price'], $groupedRenewals['currency'] ?? $this->member->defaultCurrency()),
|
|
new DateInterval( 'P' . $groupedRenewals['term'] . mb_strtoupper( $groupedRenewals['unit'] ) ),
|
|
$tax
|
|
);
|
|
}
|
|
$this->expire = $parent->expire;
|
|
}
|
|
|
|
/* Ungroup */
|
|
$this->grouped_renewals = NULL;
|
|
$this->save();
|
|
|
|
/* Cancel any pending invoices as they're no longer valid */
|
|
if ( $invoice = $parent->invoice_pending )
|
|
{
|
|
$invoice->status = $invoice::STATUS_CANCELED;
|
|
$invoice->save();
|
|
}
|
|
if ( $invoice = $this->invoice_pending )
|
|
{
|
|
$invoice->status = $invoice::STATUS_CANCELED;
|
|
$invoice->save();
|
|
}
|
|
}
|
|
|
|
/* !ACP Display */
|
|
|
|
/**
|
|
* Get the URL of the AdminCP page for this node
|
|
*
|
|
* @param string|null $do The "do" query parameter of the url (e.g. 'form', 'permissions', etc).
|
|
*
|
|
* @return Url | NULL
|
|
*/
|
|
public function acpUrl( ?string $do="form" ): ?Url
|
|
{
|
|
return Url::internal( "app=nexus&module=customers&controller=purchases&do=view&id={$this->id}", 'admin' );
|
|
}
|
|
|
|
/**
|
|
* ACP Buttons
|
|
*
|
|
* @param string $ref Referer
|
|
* @return array
|
|
*/
|
|
public function buttons( string $ref='v' ) : array
|
|
{
|
|
$return = array();
|
|
|
|
$url = $this->acpUrl()->setQueryString( 'r', $ref );
|
|
|
|
if( Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_edit' ) )
|
|
{
|
|
$return['edit'] = array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => $url->setQueryString( 'do', 'edit' ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('edit') )
|
|
);
|
|
}
|
|
|
|
$extension = NULL;
|
|
try
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
$extension = $this->extension();
|
|
$return = array_merge( $return, $extension::acpButtons( $this, $url ) );
|
|
}
|
|
catch ( OutOfRangeException ) { }
|
|
|
|
$parent = $this->parent();
|
|
|
|
if ( !$this->grouped_renewals and ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->renewals and Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
|
|
{
|
|
$return['renew'] = array(
|
|
'icon' => 'refresh',
|
|
'title' => 'generate_renewal_invoice',
|
|
'link' => $url->setQueryString( 'do', 'renew' ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('generate_renewal_invoice') )
|
|
);
|
|
}
|
|
|
|
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->parent() and Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_edit' ) )
|
|
{
|
|
if ( !$this->grouped_renewals )
|
|
{
|
|
$return['group'] = array(
|
|
'icon' => 'compress',
|
|
'title' => 'group_with_parent',
|
|
'link' => $url->setQueryString( 'do', 'group' )->csrf(),
|
|
'data' => array( 'confirm' => '', 'confirmSubMessage' => Member::loggedIn()->language()->addToStack('group_with_parent_info') )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$return['group'] = array(
|
|
'icon' => 'expand',
|
|
'title' => 'ungroup_from_parent',
|
|
'link' => $url->setQueryString( 'do', 'ungroup' )->csrf(),
|
|
'data' => array( 'confirm' => '', 'confirmSubMessage' => Member::loggedIn()->language()->addToStack('ungroup_from_parent_info') )
|
|
);
|
|
}
|
|
}
|
|
|
|
if( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and !$this->grouped_renewals and Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_transfer' ) )
|
|
{
|
|
$return['transfer'] = array(
|
|
'icon' => 'user',
|
|
'title' => 'transfer',
|
|
'link' => $url->setQueryString( 'do', 'transfer' ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('transfer') )
|
|
);
|
|
}
|
|
|
|
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 Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_cancel' ) )
|
|
{
|
|
if ( $this->cancelled )
|
|
{
|
|
/* @var ItemPurchase $extension */
|
|
if ( $extension AND $extension::canAcpReactivate( $this ) )
|
|
{
|
|
$return['reactivate'] = array(
|
|
'icon' => 'check',
|
|
'title' => 'reactivate',
|
|
'link' => $url->setQueryString( 'do', 'reactivate' )->csrf(),
|
|
'data' => array( 'confirm' => '' )
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$return['cancel'] = array(
|
|
'icon' => 'times',
|
|
'title' => 'cancel',
|
|
'link' => $url->setQueryString( 'do', 'cancel' ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('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 Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_delete' ) )
|
|
{
|
|
$return['delete'] = array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => $url->setQueryString( 'do', 'delete' ),
|
|
'data' => array( 'delete' => '' )
|
|
);
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Allow for individual classes to override and
|
|
* specify a primary image. Used for grid views, etc.
|
|
*
|
|
* @return File|null
|
|
*/
|
|
public function primaryImage() : ?File
|
|
{
|
|
return $this->image();
|
|
}
|
|
} |