Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

@@ -88,25 +88,6 @@ class _purchases extends \IPS\Dispatcher\Controller
return;
}
/* Create associated invoices table */
try
{
$originalInvoice = $this->purchase->original_invoice;
$invoices = \IPS\nexus\Invoice::table( array( array( 'i_id=? OR ' . \IPS\Db::i()->findInSet( 'i_renewal_ids', array( $this->purchase->id ) ), $originalInvoice->id ) ), $this->purchase->acpUrl(), "p.{$this->purchase->id}" );
}
catch ( \OutOfRangeException $e )
{
$invoices = \IPS\nexus\Invoice::table( array( array( \IPS\Db::i()->findInSet( 'i_renewal_ids', array( $this->purchase->id ) ) ) ), $this->purchase->acpUrl(), "p.{$this->purchase->id}" );
}
$invoices->filters = array(
'paid' => array( 'i_status=?', \IPS\nexus\Invoice::STATUS_PAID )
);
$invoices->advancedSearch = array(
'i_status' => array( \IPS\Helpers\Table\SEARCH_SELECT, array( 'options' => \IPS\nexus\Invoice::statuses(), 'multiple' => TRUE ) ),
'i_total' => \IPS\Helpers\Table\SEARCH_NUMERIC,
'i_date' => \IPS\Helpers\Table\SEARCH_DATE_RANGE,
);
/* Custom Fields */
$customFields = array();
foreach ( $this->purchase->custom_fields as $k => $v )
@@ -133,7 +114,38 @@ class _purchases extends \IPS\Dispatcher\Controller
/* Display */
\IPS\Output::i()->sidebar['actions'] = $this->purchase->buttons();
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'purchases' )->view( $this->purchase, $customer, $children, $invoices, $customFields );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'purchases' )->view( $this->purchase, $customer, $children, $customFields );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_members.js', 'core' ) );
}
/**
* Show the associated invoices table for a purchase
*
* @note This is lazy loaded after the purchase page is generated
* @return void
*/
protected function showInvoices()
{
/* Create associated invoices table */
try
{
$originalInvoice = $this->purchase->original_invoice;
$invoices = \IPS\nexus\Invoice::table( array( array( 'i_id=? OR ' . \IPS\Db::i()->findInSet( 'i_renewal_ids', array( $this->purchase->id ) ), $originalInvoice->id ) ), $this->purchase->acpUrl()->setQueryString( 'do', 'showInvoices' ), "p.{$this->purchase->id}" );
}
catch ( \OutOfRangeException $e )
{
$invoices = \IPS\nexus\Invoice::table( array( array( \IPS\Db::i()->findInSet( 'i_renewal_ids', array( $this->purchase->id ) ) ) ), $this->purchase->acpUrl()->setQueryString( 'do', 'showInvoices' ), "p.{$this->purchase->id}" );
}
$invoices->filters = array(
'paid' => array( 'i_status=?', \IPS\nexus\Invoice::STATUS_PAID )
);
$invoices->advancedSearch = array(
'i_status' => array( \IPS\Helpers\Table\SEARCH_SELECT, array( 'options' => \IPS\nexus\Invoice::statuses(), 'multiple' => TRUE ) ),
'i_total' => \IPS\Helpers\Table\SEARCH_NUMERIC,
'i_date' => \IPS\Helpers\Table\SEARCH_DATE_RANGE,
);
\IPS\Output::i()->output = (string) $invoices;
}
/**
@@ -414,6 +426,12 @@ class _purchases extends \IPS\Dispatcher\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'purchases_cancel' );
\IPS\Session::i()->csrfCheck();
$error = NULL;
if ( !$this->purchase->canAcpReactivate( $error ) )
{
\IPS\Output::i()->error( $error ?? 'can_not_reactivate', '2X195/K', 403, '' );
}
$parent = $this->purchase->parent();
if ( ( $this->purchase->billing_agreement and !$this->purchase->billing_agreement->canceled ) or ( $this->purchase->grouped_renewals and $parent->billing_agreement and !$parent->billing_agreement->canceled ) )
@@ -96,6 +96,13 @@ class _billingagreements extends \IPS\Dispatcher\Controller
{
if ( $billingAgreement->status() == $billingAgreement::STATUS_ACTIVE )
{
\IPS\Output::i()->sidebar['actions']['refresh'] = array(
'icon' => 'refresh',
'title' => 'billing_agreement_check',
'link' => $billingAgreement->acpUrl()->setQueryString( array( 'do' => 'act', 'act' => 'refresh' ) )->csrf(),
'data' => array( 'confirm' => '' )
);
\IPS\Output::i()->sidebar['actions']['suspend'] = array(
'icon' => 'times',
'title' => 'billing_agreement_suspend',
@@ -184,7 +191,7 @@ class _billingagreements extends \IPS\Dispatcher\Controller
/* Check act */
$act = \IPS\Request::i()->act;
if ( !\in_array( $act, array( 'suspend', 'reactivate', 'cancel' ) ) )
if ( !\in_array( $act, array( 'suspend', 'reactivate', 'cancel', 'refresh' ) ) )
{
\IPS\Output::i()->error( 'node_error', '3X320/8', 403, '' );
}
@@ -1368,6 +1368,39 @@ class _invoices extends \IPS\Dispatcher\Controller
\IPS\Output::i()->output = $form;
}
/**
* Track
*
* @return void
*/
protected function track()
{
\IPS\Session::i()->csrfCheck();
try
{
$invoice = \IPS\nexus\Invoice::load( \IPS\Request::i()->id );
if ( \IPS\Request::i()->track )
{
\IPS\Db::i()->insert( 'nexus_invoice_tracker', array(
'member_id' => \IPS\Member::loggedIn()->member_id,
'invoice_id' => $invoice->id
), TRUE );
}
else
{
\IPS\Db::i()->delete( 'nexus_invoice_tracker', array( 'member_id=? AND invoice_id=?', \IPS\Member::loggedIn()->member_id, $invoice->id ) );
}
$this->_redirect( $invoice );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2X190/G', 404, '' );
}
}
/**
* Redirect
*
@@ -72,11 +72,16 @@ class _payouts extends \IPS\Dispatcher\Controller
$classname = \IPS\nexus\Gateway::payoutGateways()[ $gateway ];
if ( class_exists( $classname ) and method_exists( $classname, 'massProcess' ) )
{
\IPS\Output::i()->sidebar['actions'][] = array(
\IPS\Output::i()->sidebar['actions'][] = [
'icon' => 'check',
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'account_credit_mass_payout', TRUE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'payout__admin_' . $gateway ) ) ) ),
'link' => \IPS\Http\Url::internal( 'app=nexus&module=payments&controller=payouts&do=massprocess&gateway=' . $gateway )->csrf()
);
'link' => \IPS\Http\Url::internal( 'app=nexus&module=payments&controller=payouts&do=massprocess&gateway=' . $gateway )->csrf(),
'data' => [
'confirm' => '',
'confirmMessage' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_payout_confirm_title' ),
'confirmSubMessage' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_payout_confirm_text' )
]
];
}
}
}
@@ -156,7 +161,7 @@ class _payouts extends \IPS\Dispatcher\Controller
protected function massprocess()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'payouts_process' );
\IPS\Session::i()->csrfCheck();
\IPS\Request::i()->confirmedDelete( 'nexus_payout_confirm_title', 'nexus_payout_confirm_text', 'nexus_payout_proceed' );
$classname = \IPS\nexus\Gateway::payoutGateways()[ \IPS\Request::i()->gateway ];
if ( !class_exists( $classname ) or !method_exists( $classname, 'massProcess' ) )
@@ -301,7 +306,7 @@ class _payouts extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form;
$form->addMessage('payout_description');
$form->addHeader('commission_settings');
$form->add( new \IPS\Helpers\Form\Select( 'nexus_no_commission', explode( ',', \IPS\Settings::i()->nexus_no_commission ), FALSE, array( 'multiple' => TRUE, 'options' => $groups ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'nexus_no_commission', explode( ',', \IPS\Settings::i()->nexus_no_commission ), FALSE, array( 'multiple' => TRUE, 'options' => $groups ) ) );
$form->addHeader('withdrawal_methods');
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'nexus_payout', $settings, FALSE, array( 'options' => $options, 'toggles' => $toggles ) ) );
foreach ( $fields as $field )
@@ -319,10 +324,13 @@ class _payouts extends \IPS\Dispatcher\Controller
} ), NULL, NULL, NULL, 'nexus_payout_maximum' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_payout_approve', \IPS\Settings::i()->nexus_payout_approve, FALSE, array(), NULL, NULL, NULL, 'nexus_payout_approve' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_payout_unlimited_times', \IPS\Settings::i()->nexus_payout_unlimited_times, TRUE ) );
$form->addHeader('topup_settings');
$form->add( new \IPS\Helpers\Form\YesNo( 'allow_topups', \IPS\Settings::i()->nexus_min_topup, FALSE, array( 'togglesOn' => array( 'nexus_min_topup', 'nexus_max_credit' ) ) ) );
$form->add( new \IPS\nexus\Form\Money( 'nexus_min_topup', \IPS\Settings::i()->nexus_min_topup ?: '*', FALSE, array( 'unlimitedLang' => 'no_restriction' ), NULL, NULL, NULL, 'nexus_min_topup' ) );
$form->add( new \IPS\nexus\Form\Money( 'nexus_max_credit', \IPS\Settings::i()->nexus_max_credit ?: '*', FALSE, array( 'unlimitedLang' => 'no_restriction' ), NULL, NULL, NULL, 'nexus_max_credit' ) );
if ( $values = $form->values() )
{
$payoutSettings = array();
@@ -65,7 +65,7 @@ class _shippingrates extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
$haveNoLowerLimit = FALSE;
$haveNoUpperLimit = FALSE;
@@ -27,6 +27,11 @@ class _income extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -27,6 +27,11 @@ class _markets extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -83,7 +88,7 @@ class _markets extends \IPS\Dispatcher\Controller
foreach ( \IPS\GeoLocation::$countries as $countryCode )
{
$chart->addSeries( \IPS\Member::loggedIn()->language()->get( 'country-' . $countryCode ), 'number', $activeTab === 'count' ? 'COUNT(*)' : 'SUM(i_total)', TRUE, $countryCode );
$chart->addSeries( [ 'value' => \IPS\Member::loggedIn()->language()->get( 'country-' . $countryCode ), 'key' => $countryCode ], 'number', $activeTab === 'count' ? 'COUNT(*)' : 'SUM(i_total)', TRUE, $countryCode );
}
if ( $chart->type === 'GeoChart' )
@@ -27,6 +27,11 @@ class _purchases extends \IPS\Dispatcher\Controller
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* @brief Allow MySQL RW separation for efficiency
*/
public static $allowRWSeparation = TRUE;
/**
* Execute
@@ -77,7 +77,7 @@ class _fields extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
if ( $old AND $old->extra != $new->extra )
{
@@ -89,7 +89,7 @@ class _packages extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
if ( !( $new instanceof \IPS\nexus\Package ) )
{
@@ -777,7 +777,7 @@ class _packages extends \IPS\Node\Controller
try
{
$themeFunction = 'IPS\\Theme\\'. $functionName;
$output = \IPS\Email::buildFromContent( 'Test', $themeFunction( $dummyPurchase ) )->compileContent( 'html', \IPS\Member::loggedIn() );
$output = \IPS\Email::buildFromContent( 'Test', $themeFunction( $dummyPurchase ), NULL, \IPS\Email::TYPE_TRANSACTIONAL )->compileContent( 'html', \IPS\Member::loggedIn() );
}
catch ( \Exception $e )
{
@@ -91,7 +91,7 @@ class _settings extends \IPS\Dispatcher\Controller
$form->addTab( 'nexus_purchase_settings' );
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_reg_force', \IPS\Settings::i()->nexus_reg_force, FALSE ) );
$form->add( new \IPS\nexus\Form\Money( 'nexus_minimum_order', json_decode( \IPS\Settings::i()->nexus_minimum_order, TRUE ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'cm_protected', explode( ',', \IPS\Settings::i()->cm_protected ), FALSE, array( 'options' => $groups, 'multiple' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'cm_protected', explode( ',', \IPS\Settings::i()->cm_protected ), FALSE, array( 'options' => $groups, 'multiple' => TRUE ) ) );
$form->addTab('nexus_gift_vouchers');
$form->add( new \IPS\Helpers\Form\Stack( 'nexus_gift_vouchers', $giftVouchers, FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\Money' ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_gift_vouchers_free', \IPS\Settings::i()->nexus_gift_vouchers_free ) );
@@ -356,8 +356,8 @@ class _subscriptions extends \IPS\Node\Controller
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_subs_enabled', \IPS\Settings::i()->nexus_subs_enabled, FALSE, array(), NULL, NULL, NULL, 'nexus_subs_enabled' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_subs_register', \IPS\Settings::i()->nexus_subs_register, FALSE, array(), NULL, NULL, NULL, 'nexus_subs_register' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'nexus_subs_show_public', \IPS\Settings::i()->nexus_subs_show_public, FALSE, array(), NULL, NULL, NULL, 'nexus_subs_show_public' ) );
$form->add( new \IPS\Helpers\Form\Interval( 'nexus_subs_invoice_grace', \IPS\Settings::i()->nexus_subs_invoice_grace, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::DAYS ), NULL, NULL, NULL, 'nexus_subs_invoice_grace' ) );
$form->add( new \IPS\Helpers\Form\Select( 'nexus_subs_exclude_groups', explode( ',', \IPS\Settings::i()->nexus_subs_exclude_groups ), FALSE, array( 'options' => $groups, 'multiple' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\Interval( 'nexus_subs_invoice_grace', \IPS\Settings::i()->nexus_subs_invoice_grace ?: 0, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'min' => NULL ), NULL, NULL, NULL, 'nexus_subs_invoice_grace' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'nexus_subs_exclude_groups', explode( ',', \IPS\Settings::i()->nexus_subs_exclude_groups ), FALSE, array( 'options' => $groups, 'multiple' => TRUE ) ) );
$form->addHeader('package_upgrade_downgrade');
@@ -411,7 +411,7 @@ class _subscriptions extends \IPS\Node\Controller
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
protected function _afterSave( ?\IPS\Node\Model $old, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
$changes = array();
if ( $old )
@@ -526,7 +526,7 @@ class _requests extends \IPS\Dispatcher\Controller
$form->addMessage( 'support_notify_blurb' );
foreach ( $types as $k => $t )
{
$form->add( new \IPS\Helpers\Form\Select( $k, isset( $existing[ $t ] ) ? ( $existing[ $t ] === '*' ? 0 : explode( ',', $existing[ $t ] ) ) : NULL, FALSE, array( 'options' => $myDepartments, 'multiple' => TRUE, 'unlimited' => 0, 'unlimitedLang' => 'all', 'noDefault' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( $k, isset( $existing[ $t ] ) ? ( $existing[ $t ] === '*' ? 0 : explode( ',', $existing[ $t ] ) ) : NULL, FALSE, array( 'options' => $myDepartments, 'multiple' => TRUE, 'unlimited' => 0, 'unlimitedLang' => 'all', 'noDefault' => TRUE, 'impliedUnlimited' => TRUE ) ) );
}
if ( $values = $form->values() )
@@ -114,19 +114,17 @@ class _checkout extends \IPS\Dispatcher\Controller
{
$showFirstStep = TRUE;
}
if ( !$showFirstStep )
foreach ( \IPS\nexus\Customer\CustomField::roots() as $field )
{
foreach ( \IPS\nexus\Customer\CustomField::roots() as $field )
$column = $field->column;
if ( $field->purchase_show and $field->purchase_require and !$this->invoice->member->$column )
{
$column = $field->column;
if ( $field->purchase_show and $field->purchase_require and !$this->invoice->member->$column )
{
$showFirstStep = TRUE;
$canSkipFirstStepIfNameAndBillingAddressIsKnown = FALSE;
break;
}
$showFirstStep = TRUE;
$canSkipFirstStepIfNameAndBillingAddressIsKnown = FALSE;
break;
}
}
}
}
else
{
@@ -182,6 +180,7 @@ class _checkout extends \IPS\Dispatcher\Controller
/* Init */
$buttonLang = 'continue_to_review';
$needBillingInfo = ( $this->invoice->hasItemsRequiringBillingAddress() or $this->invoice->hasPhysicalItems() );
$needTaxStatus = NULL;
if ( $this->invoice->hasPhysicalItems() )
{
@@ -254,6 +253,26 @@ class _checkout extends \IPS\Dispatcher\Controller
}
if ( $needBillingInfo )
{
/* Do we need to know if they are a business or consumer? */
foreach ( $this->invoice->items as $item )
{
if ( $tax = $item->tax )
{
if ( $tax->type === 'eu' )
{
$needTaxStatus = 'eu';
break;
}
if ( $tax->type === 'business' )
{
$needTaxStatus = 'business';
}
}
}
$addressHelperClass = $needTaxStatus ? 'IPS\nexus\Form\BusinessAddress' : 'IPS\Helpers\Form\Address';
$addressHelperOptions = ( $needTaxStatus === 'eu' ) ? array( 'vat' => TRUE ) : array();
/* The actual billing address */
$addresses = \IPS\Db::i()->select( '*', 'nexus_customer_addresses', array( '`member`=?', \IPS\Member::loggedIn()->member_id ) );
if ( \count( $addresses ) )
{
@@ -261,7 +280,7 @@ class _checkout extends \IPS\Dispatcher\Controller
$options = array();
foreach ( new \IPS\Patterns\ActiveRecordIterator( $addresses, 'IPS\nexus\Customer\Address' ) as $address )
{
$options[ $address->id ] = $address->address->toString('<br>');
$options[ $address->id ] = $address->address->toString('<br>') . ( ( isset( $address->address->business ) and $address->address->business and isset( $address->address->vat ) and $address->address->vat ) ? ( '<br>' . \IPS\Member::loggedIn()->language()->addToStack('cm_checkout_vat_number') . ': ' . mb_strtoupper( preg_replace( '/[^A-Z0-9]/', '', $address->address->vat ) ) ) : '' );
if ( ( !$this->invoice->billaddress and $address->primary_billing ) or $this->invoice->billaddress == $address->address )
{
$billing = $address->id;
@@ -270,13 +289,13 @@ class _checkout extends \IPS\Dispatcher\Controller
$options[0] = \IPS\Member::loggedIn()->language()->addToStack( 'other' );
$form->add( new \IPS\Helpers\Form\Radio( 'billing_address', $billing, TRUE, array( 'options' => $options, 'toggles' => array( 0 => array( 'new_billing_address' ) ), 'parse' => 'raw' ) ) );
$newAddress = new \IPS\Helpers\Form\Address( 'new_billing_address', !$billing ? $this->invoice->billaddress : NULL, FALSE, array(), NULL, NULL, NULL, 'new_billing_address' );
$newAddress = new $addressHelperClass( 'new_billing_address', !$billing ? $this->invoice->billaddress : NULL, FALSE, $addressHelperOptions, NULL, NULL, NULL, 'new_billing_address' );
$newAddress->label = ' ';
$form->add( $newAddress );
}
else
{
$form->add( new \IPS\Helpers\Form\Address( 'new_billing_address', $this->invoice->billaddress, TRUE ) );
$form->add( new $addressHelperClass( 'new_billing_address', $this->invoice->billaddress, TRUE, $addressHelperOptions ) );
}
}
@@ -521,10 +540,24 @@ class _checkout extends \IPS\Dispatcher\Controller
$member->cm_first_name = $values['cm_first_name'];
$member->cm_last_name = $values['cm_last_name'];
if( !\IPS\Login::usernameIsInUse( "{$values['cm_first_name']} {$values['cm_last_name']}" ) )
/* If this name is available, use that, otherwise append a number to the name to avoid an incomplete account. */
$i = 0;
do
{
$member->name = "{$values['cm_first_name']} {$values['cm_last_name']}";
$name = "{$values['cm_first_name']} {$values['cm_last_name']}";
if ( $i > 0 )
{
$name .= $i;
}
if ( !\IPS\Login::usernameIsInUse( $name ) )
{
$member->name = $name;
break;
}
$i++;
}
while( TRUE );
}
$member->email = $values['email_address'];
$member->setLocalPassword( $values['password'] );
@@ -935,7 +968,7 @@ class _checkout extends \IPS\Dispatcher\Controller
/* Remove any existing shipping charges on the invoice */
foreach ( $this->invoice->items as $k => $v )
{
if ( $v instanceof \IPS\nexus\extensions\nexus\Item\ ShippingCharge )
if ( $v instanceof \IPS\nexus\extensions\nexus\Item\ShippingCharge )
{
$this->invoice->removeItem( $k );
}
@@ -1134,6 +1167,21 @@ class _checkout extends \IPS\Dispatcher\Controller
{
\IPS\Output::i()->error( 'err_no_methods', '5X196/8', 500, '' );
}
/* Verify stock level one last time. It's possible someone added an item to their cart, then someone else did and checked out and the stock level is now 0. */
foreach ( $this->invoice->items as $item )
{
if( $item->appKey == 'nexus' AND $item->typeKey == 'package' )
{
$package = \IPS\nexus\Package::load( $item->id );
$data = $package->optionValuesStockAndPrice( $package->optionValues( $item->details ) );
if ( $data['stock'] != -1 and $data['stock'] < $item->quantity )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( 'not_enough_in_stock_checkout', FALSE, array( 'pluralize' => array( $data['stock'] ), 'sprintf' => array( $item->name ) ) ), '1X196/G', 403, '' );
}
}
}
/* Work out recurring payments */
$recurrings = array();
@@ -1223,50 +1271,54 @@ class _checkout extends \IPS\Dispatcher\Controller
}
}
/* If there's something to pay, we'll need to look at the gateways... */
/* Get available payment methods, removing any that aren't supported by the items on this invoice */
$paymentMethods = array();
foreach ( \IPS\nexus\Gateway::roots() as $gateway )
{
$paymentMethods[ $gateway->id ] = $gateway;
}
$canUseAccountCredit = TRUE;
foreach ( $this->invoice->items as $item )
{
if ( $item->paymentMethodIds )
{
foreach ( $paymentMethods as $k => $v )
{
if ( \in_array( '*', $item->paymentMethodIds ) ) // This looks odd but in older versions IPS\nexus\extensions\nexus\Item\Subscription::renewalPaymentMethodIds() was mistakenly returning array('*') and since the value is stored in the invoice we have to keep this for compatibility with invoices created at that time
{
continue;
}
if ( !\in_array( $k, $item->paymentMethodIds ) )
{
unset( $paymentMethods[ $k ] );
}
}
}
if ( !$item::$canUseAccountCredit )
{
$canUseAccountCredit = FALSE;
}
}
/* If there's something to pay, show a form for it... */
$paymentType = NULL;
$hiddenValues = array();
if ( $amountToPay->amount->isGreaterThanZero() )
{
$paymentType = 'pay';
/* Get available payment methods */
$paymentMethods = array();
foreach ( \IPS\nexus\Gateway::roots() as $gateway )
/* Remove any payment methods that can't be used for this transaction */
foreach ( $paymentMethods as $gateway )
{
if ( $gateway->checkValidity( $amountToPay, $this->invoice->billaddress, \IPS\nexus\Customer::loggedIn()->member_id ? \IPS\nexus\Customer::loggedIn() : \IPS\nexus\Customer::constructFromData( $this->invoice->guest_data['member'] ), $recurrings ) )
if ( !$gateway->checkValidity( $amountToPay, $this->invoice->billaddress, \IPS\nexus\Customer::loggedIn()->member_id ? \IPS\nexus\Customer::loggedIn() : \IPS\nexus\Customer::constructFromData( $this->invoice->guest_data['member'] ), $recurrings ) )
{
$paymentMethods[ $gateway->id ] = $gateway;
unset( $paymentMethods[ $gateway->id ] );
}
}
/* Remove any not supported by items */
$canUseAccountCredit = TRUE;
foreach ( $this->invoice->items as $item )
{
if ( $item->paymentMethodIds )
{
foreach ( $paymentMethods as $k => $v )
{
if ( \in_array( '*', $item->paymentMethodIds ) ) // This looks odd but in older versions IPS\nexus\extensions\nexus\Item\Subscription::renewalPaymentMethodIds() was mistakenly returning array('*') and since the value is stored in the invoice we have to keep this for compatibility with invoices created at that time
{
continue;
}
if ( !\in_array( $k, $item->paymentMethodIds ) )
{
unset( $paymentMethods[ $k ] );
}
}
}
if ( !$item::$canUseAccountCredit )
{
$canUseAccountCredit = FALSE;
}
}
/* If we don't have any, show an error */
/* If we don't have any available payment methods, show an error */
if ( \count( $paymentMethods ) === 0 and !$amountToPay->amount->isZero() )
{
\IPS\Output::i()->error( 'err_no_methods', '4X196/3', 500, 'err_no_methods_admin' );
@@ -1312,7 +1364,7 @@ class _checkout extends \IPS\Dispatcher\Controller
}
}
/* If there's nothing to pay now, but there will be something to pay later... if we DON'T have any stored payment methods, we may be able to prompt the user to create one */
elseif ( $recurrings and \IPS\nexus\Gateway::cardStorageGateways() and !\IPS\Db::i()->select( 'COUNT(*)', 'nexus_customer_cards', array( 'card_member=?', \IPS\nexus\Customer::loggedIn()->member_id ) )->first() and $elements = \IPS\nexus\Customer\CreditCard::createFormElements( \IPS\nexus\Customer::loggedIn(), FALSE, $showSubmitButton, $hiddenValues ) )
elseif ( $recurrings and array_intersect( array_keys( $paymentMethods ), array_keys( \IPS\nexus\Gateway::cardStorageGateways() ) ) and !\IPS\Db::i()->select( 'COUNT(*)', 'nexus_customer_cards', array( 'card_member=?', \IPS\nexus\Customer::loggedIn()->member_id ) )->first() and $elements = \IPS\nexus\Customer\CreditCard::createFormElements( \IPS\nexus\Customer::loggedIn(), FALSE, $showSubmitButton, $hiddenValues ) )
{
$paymentType = 'card';
}
@@ -1444,8 +1496,8 @@ class _checkout extends \IPS\Dispatcher\Controller
$transaction->save();
$credits[ $this->invoice->currency ]->amount = $credits[ $this->invoice->currency ]->amount->subtract( $transaction->amount->amount );
$this->invoice->member->cm_credits = $credits;
$this->invoice->member->save();
\IPS\nexus\Customer::loggedIn()->cm_credits = $credits;
\IPS\nexus\Customer::loggedIn()->save();
$this->invoice->member->log( 'transaction', array(
'type' => 'paid',
@@ -1565,9 +1617,6 @@ class _checkout extends \IPS\Dispatcher\Controller
/* Check if there's anything still to pay */
if ( $this->invoice->amountToPay()->amount->isZero() )
{
/* Mark the invoice paid */
$this->invoice->markPaid();
/* Redirect */
$destination = $this->invoice->return_uri ?: $this->invoice->url();
if ( \IPS\Member::loggedIn()->member_id )
@@ -35,6 +35,7 @@ class _addresses extends \IPS\Dispatcher\Controller
\IPS\Output::i()->error( 'no_module_permission_guest', '2X235/1', 403, '' );
}
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_forms.js', 'nexus', 'global' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'clients.css', 'nexus' ) );
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( 'app=nexus&module=clients&controller=addresses', 'front', 'clientsaddresses' ), \IPS\Member::loggedIn()->language()->addToStack('client_addresses') );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('client_addresses');
@@ -79,6 +79,18 @@ class _credit extends \IPS\Dispatcher\Controller
$perPage
);
$pastWithdrawals = new \IPS\Patterns\ActiveRecordIterator( $pastWithdrawalsSelect, 'IPS\nexus\Payout' );
$activePayouts = [];
if( !\IPS\Settings::i()->nexus_payout_unlimited_times )
{
foreach ( $pastWithdrawals as $payout )
{
if ( $payout->status == \IPS\nexus\Payout::STATUS_PENDING )
{
$activePayouts[ $payout->currency ] = TRUE;
}
}
}
/* Can we withdraw? */
$canWithdraw = FALSE;
@@ -87,7 +99,7 @@ class _credit extends \IPS\Dispatcher\Controller
{
foreach ( \IPS\nexus\Money::currencies() as $currency )
{
if ( isset( $balance[ $currency ] ) and $balance[ $currency ]->amount->isGreaterThanZero() )
if ( isset( $balance[ $currency ] ) and $balance[ $currency ]->amount->isGreaterThanZero() and !isset( $activePayouts[ $currency ] ) )
{
$canWithdraw = TRUE;
}
@@ -130,6 +142,7 @@ class _credit extends \IPS\Dispatcher\Controller
*/
protected function withdraw()
{
$activeWithdrawals = \IPS\Settings::i()->nexus_payout_unlimited_times ? [] : iterator_to_array( \IPS\Db::i()->select( 'po_currency', 'nexus_payouts', array( 'po_status=? AND po_member=?', \IPS\nexus\Payout::STATUS_PENDING, \IPS\nexus\Customer::loggedIn()->member_id ) ) );
$withdrawForm = NULL;
$withdrawOptions = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
if ( isset( $withdrawOptions['Stripe'] ) )
@@ -154,7 +167,7 @@ class _credit extends \IPS\Dispatcher\Controller
$currencyToggles = array();
foreach ( \IPS\nexus\Money::currencies() as $currency )
{
if ( isset( $balance[ $currency ] ) )
if ( isset( $balance[ $currency ] ) AND !\in_array( $currency, $activeWithdrawals ) )
{
$currencyOptions[ $currency ] = $currency;
$currencyToggles[ $currency ] = array( 'withdraw_amount_' . $currency );
@@ -163,11 +176,11 @@ class _credit extends \IPS\Dispatcher\Controller
$withdrawForm->add( new \IPS\Helpers\Form\Radio( 'withdraw_currency', NULL, TRUE, array( 'options' => $currencyOptions, 'toggles' => $currencyToggles ) ) );
}
$canWithdraw = FALSE;
foreach ( \IPS\nexus\Money::currencies() as $currency )
{
if ( !isset( $balance[ $currency ] ) )
if ( !isset( $balance[ $currency ] ) OR ( !\IPS\Settings::i()->nexus_payout_unlimited_times AND \in_array( $currency, $activeWithdrawals ) ) )
{
continue;
}
@@ -177,10 +190,10 @@ class _credit extends \IPS\Dispatcher\Controller
/* Default 'max' is our balance */
$max = (string) $balance[ $currency ]->amount;
/* Do we have a maximum withdrawl amount set up? */
/* Do we have a maximum withdrawal amount set up? */
if( $maximumWithdrawalsAmounts AND $maximumWithdrawalsAmounts[ $currency ] )
{
/* If our maximum withdrawl amount is less than our balance, then the max we can withdraw is that */
/* If our maximum withdrawal amount is less than our balance, then the max we can withdraw is that */
$upperLimit = $maximumWithdrawalsAmounts[ $currency ];
/* Determine our date cutoff */
@@ -46,7 +46,13 @@ class _donations extends \IPS\Dispatcher\Controller
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('client_donations');
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'donations.css' ) );
/* "Nowt 'ere lad, may as well knock it over tut 404" - Marc */
if( \count( \IPS\nexus\Donation\Goal::roots() ) === 0 )
{
\IPS\Output::i()->error( 'nexus_no_donations_found', '1X238/2', 404, '' );
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('clients')->donations();
}
@@ -0,0 +1,36 @@
<?php
/**
* @brief Legacy Referrals Controller
* @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 10 Sep 2020
*/
namespace IPS\nexus\modules\front\clients;
/* 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;
}
/**
* Legacy Referrals Controller
*/
class _referrals extends \IPS\Dispatcher\Controller
{
/**
* Manage
*
* @return void
*/
protected function manage()
{
/* Referrals have moved to the core. */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=settings&area=referrals", 'front', 'settings_referrals' ) );
}
}
@@ -435,6 +435,8 @@ class _product extends \IPS\Content\Controller
}
}
ksort( $options );
$form->add( new \IPS\Helpers\Form\Radio( 'renewal_term', NULL, TRUE, array( 'options' => $options ) ) );
}
@@ -54,6 +54,8 @@ class _store extends \IPS\Dispatcher\Controller
{
$_SESSION['cart'] = array();
\IPS\Request::i()->setCookie( 'currency', \IPS\Request::i()->currency );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=nexus&module=store&controller=store', 'front', 'store' ) );
}
$this->currency = \IPS\Request::i()->currency;
}
@@ -37,6 +37,8 @@ class _subscriptions extends \IPS\Dispatcher\Controller
{
$_SESSION['cart'] = array();
\IPS\Request::i()->setCookie( 'currency', \IPS\Request::i()->currency );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=nexus&module=subscriptions&controller=subscriptions', 'front', 'nexus_subscriptions' ) );
}
}
@@ -57,7 +59,12 @@ class _subscriptions extends \IPS\Dispatcher\Controller
/* Create the table */
$table = new \IPS\nexus\Subscription\Table( \IPS\Http\Url::internal( 'app=nexus&module=subscriptions&controller=subscriptions', 'front', 'nexus_subscriptions' ) );
$table->activeSubscription = \IPS\nexus\Subscription::loadByMember( \IPS\Member::loggedIn(), FALSE );
$current = \IPS\nexus\Subscription::loadByMember( \IPS\Member::loggedIn(), FALSE );
if ( $current AND ( !$current->purchase OR !$current->purchase->cancelled OR $current->purchase->can_reactivate ) )
{
$table->activeSubscription = $current;
}
if ( isset( \IPS\Request::i()->purchased ) and $table->activeSubscription )
{
@@ -118,12 +125,15 @@ class _subscriptions extends \IPS\Dispatcher\Controller
/* Fetch purchase */
$purchase = NULL;
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( \IPS\nexus\Customer::loggedIn(), $subscription->package->id ) as $row )
if( $subscription )
{
if ( $row->active and ! $row->cancelled )
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( \IPS\nexus\Customer::loggedIn(), $subscription->package->id ) as $row )
{
$purchase = $row;
break;
if ( $row->active and ! $row->cancelled )
{
$purchase = $row;
break;
}
}
}
@@ -162,8 +172,8 @@ class _subscriptions extends \IPS\Dispatcher\Controller
/* CSRF Check */
\IPS\Session::i()->csrfCheck();
/* Already purchased a subscription? */
if ( \IPS\nexus\Subscription::loadByMember( \IPS\nexus\Customer::loggedIn(), FALSE ) )
/* Already purchased a subscription which was not cancelled? */
if ( $current = \IPS\nexus\Subscription::loadByMember( \IPS\nexus\Customer::loggedIn(), FALSE ) AND ( $current->purchase AND !$current->cancelled AND ( !$current->purchase->cancelled OR $current->purchase->can_reactivate ) ) )
{
\IPS\Output::i()->error( 'nexus_subs_already_got_package', '2X379/6', 403, '' );
}
@@ -235,7 +245,7 @@ class _subscriptions extends \IPS\Dispatcher\Controller
\IPS\Output::i()->error( 'nexus_no_subs_subs', '2X379/8', 404, '' );
}
$purchase = NULL;
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( \IPS\nexus\Customer::loggedIn(), $subscription->package->id ) as $row )
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( \IPS\nexus\Customer::loggedIn(), $subscription->package->id, TRUE, TRUE ) as $row )
{
if ( $row->can_reactivate )
{
@@ -256,9 +266,10 @@ class _subscriptions extends \IPS\Dispatcher\Controller
$purchase->member->log( 'purchase', array( 'type' => 'info', 'id' => $purchase->id, 'name' => $purchase->name, 'info' => 'change_renewals', 'to' => array( 'cost' => $purchase->renewals->cost->amount, 'currency' => $purchase->renewals->cost->currency, 'term' => $purchase->renewals->getTerm() ) ) );
/* Either send to renewal invoice or just back to subscriptions list */
if ( !$purchase->active and $purchase->canRenewUntil() !== FALSE )
if ( !$purchase->active and $cycles = $purchase->canRenewUntil( NULL, TRUE ) AND $cycles !== FALSE )
{
\IPS\Output::i()->redirect( $purchase->url()->setQueryString( 'do', 'renew' )->csrf() );
$url = $cycles === 1 ? $purchase->url()->setQueryString( 'do', 'renew' )->csrf() : $purchase->url()->setQueryString( 'do', 'renew' );
\IPS\Output::i()->redirect( $url );
}
else
{
@@ -266,4 +277,45 @@ class _subscriptions extends \IPS\Dispatcher\Controller
}
}
/**
* Cancel an expired subscription so that we can purchase another subscription
*/
public function cancel()
{
/* CSRF Check */
\IPS\Session::i()->csrfCheck();
try
{
$purchase = \IPS\nexus\Purchase::load( \IPS\Request::i()->id );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'nexus_no_subs_package', '2X379/8', 404, '' );
}
try
{
$subscription = \IPS\nexus\Subscription::loadByMemberAndPurchase( \IPS\Member::loggedIn(), $purchase, FALSE );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'nexus_no_subs_subs', '2X379/9', 404, '' );
}
if( !$subscription->canCancel() )
{
\IPS\Output::i()->error( 'no_module_permission', '1X379/A', 403, '' );
}
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( \IPS\nexus\Customer::loggedIn(), $subscription->package->id, TRUE, TRUE ) as $row )
{
$row->cancelled = TRUE;
$row->save();
}
$subscription->cancelled = TRUE;
$subscription->save();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=nexus&module=subscriptions&controller=subscriptions', 'front', 'nexus_subscriptions' ), 'cancelled' );
}
}
@@ -0,0 +1,63 @@
<?php
/**
* @brief Handle actions from emails
* @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 17 Nov 2020
*/
namespace IPS\nexus\modules\front\support;
/* 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;
}
/**
* Handle actions from emails
*/
class _email extends \IPS\Dispatcher\Controller
{
/**
* Send or discard reply
*
* @return void
*/
public function sendDiscard()
{
try
{
$reply = \IPS\nexus\Support\Reply::load( \IPS\Request::i()->id );
if ( !\IPS\Login::compareHashes( md5( $reply->item()->email_key . $reply->date ), (string) \IPS\Request::i()->key ) )
{
throw new \OutOfRangeException;
}
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2X206/1', 404, '' );
}
if ( $reply->type !== \IPS\nexus\Support\Reply::REPLY_PENDING )
{
\IPS\Output::i()->error( 'support_reply_not_pending', '1X206/2', 403, '' );
}
if ( \IPS\Request::i()->send )
{
$reply->sendPending();
}
else
{
$reply->delete();
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( \IPS\Request::i()->send ? 'support_pending_sent' : 'support_pending_discarded' );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'support', 'nexus', 'front' )->pendingDone( \IPS\Request::i()->send ? 'support_pending_sent' : 'support_pending_discarded' );
}
}