Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

@@ -0,0 +1,247 @@
<?php
/**
* @brief Achievement Action Extension
* @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/
* @subpackage Commerce
* @since 01 Oct 2021
*/
namespace IPS\nexus\extensions\core\AchievementAction;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Achievement Action Extension
*/
class _Package extends \IPS\core\Achievements\Actions\AbstractAchievementAction // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
{
/**
* Get filter form elements
*
* @param array|NULL $filters Current filter values (if editing)
* @param \IPS\Http\Url $url The URL the form is being shown on
* @return array
*/
public function filters( ?array $filters, \IPS\Http\Url $url ): array
{
$return = array();
$return['nodes'] = new \IPS\Helpers\Form\Node( 'achievement_filter_Package', ( $filters and isset( $filters['nodes'] ) and $filters['nodes'] ) ? $filters['nodes'] : 0, FALSE, [
'url' => $url,
'class' => 'IPS\nexus\Package\Group',
'subnodes' => FALSE,
'showAllNodes' => TRUE,
'multiple' => TRUE
], NULL, \IPS\Member::loggedIn()->language()->addToStack( 'achievement_filter_Package_node_prefix', FALSE, [ 'sprintf' => [
\IPS\Member::loggedIn()->language()->addToStack( 'nexus_sub_package_id', FALSE ),
\IPS\Member::loggedIn()->language()->addToStack( 'purchase_groups_lc', FALSE )
] ] ) );
$return['milestone'] = new \IPS\Helpers\Form\Number( 'achievement_filter_Package_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_Package_nth_suffix') );
$return['milestone']->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewContentItem_nth');
return $return;
}
/**
* Format filter form values
*
* @param array $values The values from the form
* @return array
*/
public function formatFilterValues( array $values ): array
{
$return = [];
if ( isset( $values['achievement_filter_Package'] ) )
{
$return['nodes'] = [];
if ( \is_array( $values['achievement_filter_Package'] ) )
{
foreach ( $values['achievement_filter_Package'] as $node )
{
$return['nodes'][] = $node->id;
}
}
}
if ( isset( $values['achievement_filter_Package_nth'] ) )
{
$return['milestone'] = $values['achievement_filter_Package_nth'];
}
return $return;
}
/**
* Work out if the filters applies for a given action
*
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
* 1 to the value being considered for milestones
*
* @param \IPS\Member $subject The subject member
* @param array $filters The value returned by formatFilterValues()
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @return bool
*/
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
{
if ( isset( $filters['nodes'] ) and \is_array( $filters['nodes'] ) and \count( $filters['nodes'] ) )
{
if ( !\in_array( $extra->item()->container()->_id, $filters['nodes'] ) )
{
return FALSE;
}
}
if ( isset( $filters['milestone'] ) )
{
if ( \IPS\Db::i()->select( 'COUNT(*)', 'nexus_purchases', [ 'ps_cancelled !=1 AND ps_member=?', $subject->member_id ] )->first() < $filters['milestone'] )
{
return FALSE;
}
}
return TRUE;
}
/**
* Get identifier to prevent the member being awarded points for the same action twice
* Must be unique within within of this domain, must not exceed 32 chars.
*
* @param \IPS\Member $subject The subject member
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @return string
*/
public function identifier( \IPS\Member $subject, $extra = NULL ): string
{
return $extra->id . '.' . $subject->member_id;
}
/**
* Return a description for this action to show in the log
*
* @param string $identifier The identifier as returned by identifier()
* @param array $actor If the member was the "subject", "other", or both
* @return string
*/
public function logRow( string $identifier, array $actor ): string
{
try
{
list( $identifier, $memberId ) = explode( '.', $identifier );
$package = \IPS\nexus\Package::load( $identifier );
$sprintf = [ 'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $package->url(), TRUE, $package->_title, FALSE )
] ];
}
catch ( \OutOfRangeException $e )
{
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
}
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Package_log', FALSE, $sprintf );
}
/**
* Get "description" for rule
*
* @param \IPS\core\Achievements\Rule $rule The rule
* @return string|NULL
*/
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
{
$conditions = [];
if ( isset( $rule->filters['milestone'] ) )
{
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
],
'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__Package_title_generic') ]
] );
}
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
{
$conditions[] = $nodeCondition;
}
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Package_title' ),
$conditions
);
}
/**
* Get "description" for rule (usually a description of the rule's filters)
*
* @param \IPS\core\Achievements\Rule $rule The rule
* @return string|NULL
*/
protected function _nodeFilterDescription( \IPS\core\Achievements\Rule $rule ): ?string
{
if ( isset( $rule->filters['nodes'] ) )
{
$nodeNames = [];
foreach ( $rule->filters['nodes'] as $id )
{
try
{
$nodeNames[] = \IPS\nexus\Package\Group::load( $id )->_title;
}
catch ( \OutOfRangeException $e ) {}
}
if ( $nodeNames )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location', FALSE, [
'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'location',
\count( $nodeNames ) === 1 ? $nodeNames[0] : \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location_val', FALSE, [ 'sprintf' => [
\count( $nodeNames ),
\IPS\Member::loggedIn()->language()->addToStack( 'purchase_groups_lc', FALSE )
] ] ),
\count( $nodeNames ) === 1 ? NULL : $nodeNames
)
],
] );
}
}
return NULL;
}
/**
* Get rebuild data
*
* @return array
*/
static public function rebuildData()
{
return [ [
'table' => 'nexus_purchases',
'pkey' => 'ps_id',
'date' => 'ps_start',
'where' => [ [ 'ps_cancelled != 1' ] ],
] ];
}
/**
* Process the rebuild row
*
* @param array $row Row from database
* @param array $data Data collected when starting rebuild [table, pkey...]
* @return void
*/
public static function rebuildRow( $row, $data )
{
\IPS\Member::load( $row['ps_member'] )->achievementAction( 'nexus', 'Package', \IPS\nexus\Package::load( $row['ps_item_id'] ) );
}
}
@@ -96,7 +96,7 @@ class _Subscription extends \IPS\core\Achievements\Actions\AbstractAchievementAc
{
if ( isset( $filters['subscriptions'] ) )
{
if ( !\in_array( $extra->_id, $filters['subscriptions'] ) )
if ( !\in_array( $extra->package_id, $filters['subscriptions'] ) )
{
return FALSE;
}
@@ -108,44 +108,21 @@ class _Subscription extends \IPS\core\Achievements\Actions\AbstractAchievementAc
{
try
{
$package = \IPS\nexus\Subscription\Package::load( $id );
\IPS\nexus\Subscription::loadByMemberAndPackage( $subject, $package, TRUE );
}
catch ( \OutOfRangeException $e ) {
return FALSE;
\IPS\nexus\Subscription::loadByMemberAndPackage( $subject, \IPS\nexus\Subscription\Package::load( $id ), TRUE );
/* Still here? */
return TRUE;
}
catch ( \OutOfRangeException $e ) { }
}
/* No active matches */
return FALSE;
}
return TRUE;
}
/**
* Get the labels for the people this action might give awards to
*
* @return array
*/
// You don't need to implement this if your action only gives points to the subject
// public function awardOptions(): array
// {
// return [
// 'subject' => 'achievement_filter_Subscription_receiver',
// 'other' => 'achievement_filter_Subscription_giver'
// ];
// }
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @return array
*/
// You don't need to implement this if your action only gives points to the subject
// public function awardOther( $extra = NULL ): array
// {
// return [ \IPS\Member::loggedIn() ];
// }
/**
* Get identifier to prevent the member being awarded points for the same action twice
* Must be unique within within of this domain, must not exceed 32 chars.
@@ -251,7 +228,7 @@ class _Subscription extends \IPS\core\Achievements\Actions\AbstractAchievementAc
'table' => 'nexus_member_subscriptions',
'pkey' => 'sub_id',
'date' => 'sub_start',
'where' => [ ['sub_active=?', 1 ] ],
'where' => [],
] ];
}
@@ -264,7 +241,7 @@ class _Subscription extends \IPS\core\Achievements\Actions\AbstractAchievementAc
*/
public static function rebuildRow( $row, $data )
{
\IPS\Member::load( $row['member_id'] )->achievementAction( 'nexus', 'Subscription', \IPS\nexus\Subscription::load( $row['sub_id'] ) );
\IPS\Member::load( $row['sub_member_id'] )->achievementAction( 'nexus', 'Subscription', \IPS\nexus\Subscription::load( $row['sub_id'] ) );
}
}
@@ -80,7 +80,7 @@ class _SupportRequest
$request->last_new_reply = time();
$request->started = time();
$request->replies = 1;
$request->title = \IPS\Member::loggedIn()->language()->get( 'contact_nexus_title' );
$request->title = ( $values['contact_name'] ) ? sprintf( \IPS\Member::loggedIn()->language()->get( 'contact_nexus_title_with_name' ), $values['contact_name'] ) : \IPS\Member::loggedIn()->language()->get( 'contact_nexus_title' );
if ( \IPS\Member::loggedIn()->member_id)
{
@@ -45,6 +45,32 @@ class _Store
{
return NULL;
}
/**
* Can whatever is posted in this editor be moderated?
* If this returns TRUE, we must ensure the content is ran through word, link and image filters
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @return bool
*/
public function canBeModerated( $member, $field )
{
/* Creating/editing a review */
if ( preg_match( '/^(?:editComment|review)\-nexus\/store\-\d+/', $field->options['autoSaveKey'] ) )
{
return TRUE;
}
/* Unknown */
else
{
if ( \IPS\IN_DEV )
{
throw new \RuntimeException( 'Unknown canBeModerated: ' . $field->options['autoSaveKey'] );
}
return FALSE;
}
}
/**
* Permission check for attachments
@@ -0,0 +1,99 @@
<?php
/**
* @brief File Storage Extension: Gateways
* @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 Commerce
* @since 11 May 2022
*/
namespace IPS\nexus\extensions\core\FileStorage;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* File Storage Extension: Gateways
*/
class _Gateways
{
/**
* Count stored files
*
* @return int
*/
public function count()
{
return \IPS\Db::i()->select( 'COUNT(*)', 'nexus_paymethods', array( 'm_validationfile<>?', '' ) )->first();
}
/**
* Move stored files
*
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
* @param int $storageConfiguration New storage configuration ID
* @param int|NULL $oldConfiguration Old storage configuration ID
* @throws \UnderflowException When file record doesn't exist. Indicating there are no more files to move
* @return void|int An offset integer to use on the next cycle, or nothing
*/
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
{
$record = \IPS\Db::i()->select( '*', 'nexus_paymethods', array( 'm_validationfile<>?', '' ), 'm_id', array( $offset, 1 ) )->first();
try
{
$file = \IPS\File::get( $oldConfiguration ?: 'nexus_Gateways', $record['m_id'] )->move( $storageConfiguration );
if ( (string) $file != $record['m_validationfile'] )
{
\IPS\Db::i()->update( 'nexus_paymethods', array( 'm_validationfile' => (string) $file ), array( 'm_id=?', $record['m_id'] ) );
}
}
catch( \Exception $e )
{
/* Any issues are logged */
}
}
/**
* Check if a file is valid
*
* @param string $file The file path to check
* @return bool
*/
public function isValidFile( $file )
{
try
{
\IPS\Db::i()->select( '*', 'nexus_paymethods', array( 'm_validationfile=?', (string) $file ) )->first();
return TRUE;
}
catch ( \UnderflowException $e )
{
return FALSE;
}
}
/**
* Delete all stored files
*
* @return void
*/
public function delete()
{
foreach( \IPS\Db::i()->select( '*', 'nexus_paymethods', array( 'm_validationfile<>?', '' ) ) as $product )
{
try
{
\IPS\File::get( 'nexus_Gateways', $product['m_validationfile'] )->delete();
}
catch( \Exception $e ){}
}
}
}
@@ -42,7 +42,7 @@ class _Gifts extends \IPS\core\FrontNavigation\FrontNavigationAbstract
*/
public static function isEnabled()
{
return ( ( \IPS\Settings::i()->nexus_gift_vouchers and \count( json_decode( \IPS\Settings::i()->nexus_gift_vouchers, TRUE ) ) ) or \IPS\Settings::i()->nexus_gift_vouchers_free );
return ( ( $giftVouchers = json_decode( \IPS\Settings::i()->nexus_gift_vouchers, TRUE ) and \count( $giftVouchers ) ) or \IPS\Settings::i()->nexus_gift_vouchers_free );
}
/**
@@ -0,0 +1,80 @@
<?php
/**
* @brief Admin CP Group Form
* @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 Commerce
* @since 12 Oct 2021
*/
namespace IPS\nexus\extensions\core\GroupForm;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Admin CP Group Form
*/
class _PreventDeletion
{
/**
* Process Form
*
* @param \IPS\Helpers\Form $form The form
* @param \IPS\Member\Group $group Existing Group
* @return void
*/
public function process( &$form, $group )
{
}
/**
* Save
*
* @param array $values Values from form
* @param \IPS\Member\Group $group The group
* @return void
*/
public function save( $values, &$group )
{
}
/**
* Can this group be deleted?
*
* @param \IPS\Member\Group $group The group
* @return void
*/
public function canDelete( $group ) : bool
{
// Is this group used for group promotion after a product purchase?
try
{
\IPS\Db::i()->select( '*', 'nexus_packages', ['p_primary_group=? OR ' . \IPS\Db::i()->findInSet('p_secondary_group', [$group->g_id] ), $group->g_id ] )->first();
return FALSE;
}
catch(\UnderflowException $e )
{
}
// Is this group used for group promotion after a subscription purchase?
try
{
\IPS\Db::i()->select( '*', 'nexus_member_subscription_packages', ['sp_primary_group=? OR ' . \IPS\Db::i()->findInSet('sp_secondary_group', [$group->g_id] ), $group->g_id ] )->first();
return FALSE;
}
catch(\UnderflowException $e )
{
}
return TRUE;
}
}
@@ -714,11 +714,11 @@ class _Nexus
case 'cancelrenewals':
try
{
return \IPS\Member::loggedIn()->language()->addToStack( 'nexus_history_cancelrenewals_with_name', NULL, array( 'sprintf' => array( \IPS\nexus\Subscription\Package::load( $val['id'] )->_title, $byCustomer ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'nexus_history_cancelrenewals_with_name', FALSE, array( 'sprintf' => array( \IPS\nexus\Subscription\Package::load( $val['id'] )->_title, $byCustomer ) ) );
}
catch ( \OutOfRangeException $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'nexus_history_cancelrenewals', NULL, array( 'sprintf' => array( $byCustomer ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'nexus_history_cancelrenewals', FALSE, array( 'sprintf' => array( $byCustomer ) ) );
}
break;
@@ -729,7 +729,7 @@ class _Nexus
}
catch ( \OutOfRangeException $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'nexus_history_subscription_changed', NULL, array( 'sprintf' => array( ( isset( $val['system'] ) and $val['system'] ) ? '' : $byStaff ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'nexus_history_subscription_changed', FALSE, array( 'sprintf' => array( ( isset( $val['system'] ) and $val['system'] ) ? '' : $byStaff ) ) );
}
break;
}
@@ -0,0 +1,140 @@
<?php
/**
* @brief Background Task
* @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 Commerce
* @since 22 Apr 2022
*/
namespace IPS\nexus\extensions\core\Queue;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Background Task
*/
class _FixMissingSubscriptionPurchases
{
/**
* @brief Number of subscriptions to check per cycle
*/
public $batch = \IPS\REBUILD_NORMAL;
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
try
{
$data['count'] = \IPS\Db::i()->select( 'count(sub_id)', 'nexus_member_subscriptions' )->first();
}
catch( \UnderflowException $e )
{
throw new \OutOfRangeException;
}
if( $data['count'] == 0 )
{
return NULL;
}
$data['completed'] = 0;
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int New offset
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( &$data, $offset )
{
$last = null;
foreach( \IPS\Db::i()->select( '*', 'nexus_member_subscriptions', [ 'sub_id>?', $offset ], NULL, $this->batch ) as $subscription )
{
if( $subscription['sub_purchase_id'] )
{
try
{
\IPS\nexus\Purchase::load( $subscription['sub_purchase_id' ] );
/* Nothing to do here */
$data['completed']++;
$last = $subscription['sub_id'];
continue;
}
catch( \OutOfRangeException $e ) { }
}
$package = \IPS\nexus\Subscription\Package::load( $subscription['sub_package_id'] );
if( !$subscription['sub_added_manually'] AND $subscription['sub_renews'] )
{
$term = $package->renewalTerm( \IPS\nexus\Customer::load( $subscription['sub_member_id'] )->defaultCurrency() );
$termData = $term->getTerm();
}
/* Create purchase manually - we don't want to trigger any on* methods */
$purchaseId = \IPS\Db::i()->insert( 'nexus_purchases', [
'ps_member' => $subscription['sub_member_id'],
'ps_name' => \IPS\nexus\Customer::load( $subscription['sub_member_id'] )->language()->get( $package::$titleLangPrefix . $subscription['sub_package_id'] ),
'ps_active' => $subscription['sub_active'],
'ps_cancelled' => $subscription['sub_cancelled'],
'ps_start' => $subscription['sub_start'],
'ps_expire' => $subscription['sub_added_manually'] ? 0 : $subscription['sub_expire'],
'ps_renewals' => $termData['term'] ?? 0,
'ps_renewal_price' => $term->cost->amount ?? 0,
'ps_renewal_unit' => $termData['unit'] ?? '',
'ps_app' => 'nexus',
'ps_type' => 'subscription',
'ps_item_id' => $subscription['sub_package_id'],
'ps_renewal_currency' => $term->cost->currency ?? '',
'ps_original_invoice' => $subscription['sub_invoice_id'],
'ps_tax' => isset( $term->tax ) ? $term->tax->id : 0
]);
/* update sub with purchase id */
\IPS\Db::i()->update( 'nexus_member_subscriptions', [ 'sub_purchase_id' => $purchaseId ], [ 'sub_id=?', $subscription['sub_id'] ] );
$data['completed']++;
$last = $subscription['sub_id'];
}
if( $last === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
return $last;
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_queue_missing_sub_purchases' ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $data['completed'], 2 ) ) : 100 );
}
}
@@ -46,7 +46,15 @@ class _MassChangePurchases
*/
public function run( $data, $offset )
{
$package = \IPS\nexus\Package::load( $data['id'] );
try
{
$package = \IPS\nexus\Package::load( $data['id'] );
}
catch( \OutOfRangeException $e )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$admin = \IPS\Member::load( $data['admin'] );
if ( $data['cancel_type'] == 'change' )
{