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

@@ -132,11 +132,11 @@ class _CommissionRule extends \IPS\Node\Model
$form->addHeader('rrule_referrer');
$form->add( new \IPS\Helpers\Form\Custom( 'rrule_by_purchases', array( $this->by_purchases_type, $this->by_purchases_op, $this->by_purchases_unit ), FALSE, $numberOfPurchasesField ) );
$form->add( new \IPS\Helpers\Form\Select( 'rrule_by_group', ( $this->by_group and $this->by_group != '*' ) ? explode( ',', $this->by_group ) : '*', FALSE, array( 'options' => $groupsExcludingGuests, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'any' ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'rrule_by_group', ( $this->by_group and $this->by_group != '*' ) ? explode( ',', $this->by_group ) : '*', FALSE, array( 'options' => $groupsExcludingGuests, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'any', 'impliedUnlimited' => TRUE ) ) );
$form->addHeader('rrule_referree');
$form->add( new \IPS\Helpers\Form\Custom( 'rrule_for_purchases', array( $this->for_purchases_type, $this->for_purchases_op, $this->for_purchases_unit ), FALSE, $numberOfPurchasesField ) );
$form->add( new \IPS\Helpers\Form\Select( 'rrule_for_group', ( $this->for_group and $this->for_group != '*' ) ? explode( ',', $this->for_group ) : '*', FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'any' ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'rrule_for_group', ( $this->for_group and $this->for_group != '*' ) ? explode( ',', $this->for_group ) : '*', FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'any', 'impliedUnlimited' => TRUE ) ) );
$form->addHeader('rrule_purchase');
$form->add( new \IPS\Helpers\Form\Custom( 'rrule_purchase_amount', array( $this->purchase_amount_op, $this->purchase_amount_unit ), FALSE, array(
+1 -1
View File
@@ -163,7 +163,7 @@ class _Coupon extends \IPS\Node\Model
$form->add( new \IPS\Helpers\Form\YesNo( 'c_combine', $this->combine ) );
$form->add( new \IPS\Helpers\Form\Number( 'c_uses', $this->id ? $this->uses : -1, FALSE, array( 'unlimited' => -1 ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'c_member_uses', $this->id ? $this->member_uses : 1, FALSE, array( 'unlimited' => -1 ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'c_groups', ( $this->id and $this->groups !== '*' ) ? explode( ',', $this->groups ) : '*', FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*' ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'c_groups', ( $this->id and $this->groups !== '*' ) ? explode( ',', $this->groups ) : '*', FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*', 'impliedUnlimited' => TRUE ) ) );
}
@@ -206,7 +206,28 @@ class _BillingAgreement extends \IPS\Patterns\ActiveRecord
{
$this->_data['started'] = $date->getTimestamp();
}
/**
* Suspend
*
* @return void
* @throws \DomainException
*/
public function refresh()
{
try
{
while( true )
{
$this->checkForLatestTransaction();
}
}
catch( \OutOfRangeException | \UnderflowException $e )
{
return;
}
}
/**
* Suspend
*
@@ -272,4 +293,96 @@ class _BillingAgreement extends \IPS\Patterns\ActiveRecord
{
return \IPS\Http\Url::internal( "app=nexus&module=payments&controller=billingagreements&id={$this->id}", 'admin' );
}
/**
* Check for latest unclaimed transactions
*
* @return void
*/
public function checkForLatestTransaction()
{
/* Fetch the latest unclaimed transaction */
try
{
$transaction = $this->latestUnclaimedTransaction();
}
/* If there isn't one yet, it might just be that PayPal hasn't taken it yet. Let it try for up to 5 days before giving up, which is how long PayPal will try for if there's an issue. */
catch ( \OutOfRangeException $e )
{
if ( $this->next_cycle->getTimestamp() > ( time() - ( 86400 * 5 ) ) )
{
throw new \UnderflowException;
}
else
{
/* We are here because we could not find any unclaimed transactions and it has been at least 5 days since we expected to. Verify the next cycle before proceeding because it may not have been set properly in the past so before we say there's a problem let's check. */
$nextPaymentDate = $this->nextPaymentDate();
if( $nextPaymentDate->getTimestamp() > time() )
{
$this->next_cycle = $nextPaymentDate;
$this->save();
throw new \UnderflowException;
}
throw $e;
}
}
/* Get purchases */
$purchases = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_purchases', array( 'ps_billing_agreement=?', $this->id ) ), 'IPS\nexus\Purchase' );
/* Generate an invoice */
$invoice = new \IPS\nexus\Invoice;
$invoice->system = TRUE;
$invoice->currency = $transaction->amount->currency;
$invoice->member = $this->member;
$invoice->billaddress = $this->member->primaryBillingAddress();
foreach ( $purchases as $purchase )
{
$invoice->addItem( \IPS\nexus\Invoice\Item\Renewal::create( $purchase ) );
}
$invoice->save();
/* Assign the transaction to it */
$transaction->invoice = $invoice;
$transaction->save();
$transaction->approve();
$invoice->status = $transaction->invoice->status;
/* Log */
$invoice->member->log( 'transaction', array(
'type' => 'paid',
'status' => \IPS\nexus\Transaction::STATUS_PAID,
'id' => $transaction->id,
'invoice_id' => $invoice->id,
'invoice_title' => $invoice->title,
'automatic' => TRUE,
), FALSE );
/* Update the purchase */
if ( $invoice->status !== $invoice::STATUS_PAID )
{
foreach ( $purchases as $purchase )
{
$purchase->invoice_pending = $invoice;
$purchase->save();
}
}
/* Send notification */
$invoice->sendNotification();
/* Update billing agreement */
if ( $invoice->status === $invoice::STATUS_PAID )
{
$this->next_cycle = $this->nextPaymentDate();
}
else
{
$this->next_cycle = NULL;
}
$this->save();
}
}
@@ -101,7 +101,7 @@ class _CustomField extends \IPS\CustomField
/**
* @brief [CustomField] Upload Storage Extension
*/
public static $uploadStorageExtension = 'nexus_';
public static $uploadStorageExtension = 'nexus_Customer';
/**
* [Node] Add/Edit Form
@@ -307,7 +307,7 @@ class _Customer extends \IPS\Member
{
if ( $this->member_id === \IPS\nexus\Customer::loggedIn()->member_id )
{
if ( isset( \IPS\Request::i()->cookie['location'] ) AND ( !\IPS\Request::i()->cookie['location'] OR ( \IPS\Request::i()->cookie['location'] AND $data = json_decode( \IPS\Request::i()->cookie['location'], true ) AND isset( $data['member_id'] ) AND $data['member_id'] == $this->member_id ) ) )
if ( isset( \IPS\Request::i()->cookie['location'] ) AND ( !\IPS\Request::i()->cookie['location'] OR ( \IPS\Request::i()->cookie['location'] AND $data = json_decode( \IPS\Request::i()->cookie['location'], true ) AND array_key_exists( 'member_id', $data ) AND $data['member_id'] == $this->member_id ) ) )
{
$location = NULL;
@@ -72,19 +72,22 @@ class _DomainLookup
$this->sld = mb_substr( $domain, 0, - mb_strlen( $this->tld ) - 1 );
$this->type = ( mb_substr_count( $this->sld, '.' ) > 0 ) ? static::DOMAIN_TYPE_SUBDOMAIN : static::DOMAIN_TYPE_DOMAIN;
foreach ( $domainData['whoisServers'] as $whoisServer )
if ( $performWhoisLookup )
{
if ( $resource = @fsockopen( $whoisServer, 43 ) )
foreach ( $domainData['whoisServers'] as $whoisServer )
{
\fwrite( $resource, $domain . "\r\n" );
$response = '';
while( !feof( $resource ) )
if ( $resource = @fsockopen( $whoisServer, 43 ) )
{
$response .= \fgets( $resource, 8192 );
\fwrite( $resource, $domain . "\r\n" );
$response = '';
while( !feof( $resource ) )
{
$response .= \fgets( $resource, 8192 );
}
fclose( $resource );
$this->whois[] = $response;
}
fclose( $resource );
$this->whois[] = $response;
}
}
}
@@ -53,6 +53,11 @@ class _Goal extends \IPS\Node\Model
*/
public static $titleLangPrefix = 'nexus_donategoal_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_desc';
/**
* @brief [Node] ACP Restrictions
* @code
@@ -46,6 +46,8 @@ class _BusinessAddress extends \IPS\Helpers\Form\Address
*/
public function html()
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_forms.js', 'nexus', 'global' ) );
/* If we don't have a value, set their country based on the HTTP headers */
if ( !$this->value OR ( $this->value instanceof \IPS\GeoLocation AND !$this->value->country ) )
{
@@ -128,10 +128,71 @@ class _Request
*/
public function setMember( \IPS\Member $member )
{
$this->data['domain'] = mb_substr( $member->email, mb_strrpos( $member->email, '@' ) + 1 );
$this->data['emailMD5'] = md5( $member->email );
$this->_setEmailProperties( $member->email );
$this->data['usernameMD5'] = md5( $member->name );
}
/**
* @brief MaxMind common typo domains list
* @see https://github.com/maxmind/minfraud-api-php/blob/b08158b6c096bde8560b1b7fb8bb548c79f8d57b/src/MinFraud/Util.php#L18
*/
protected static $typoDomains = [
// gmail.com
'35gmai.com' => 'gmail.com',
'636gmail.com' => 'gmail.com',
'gamil.com' => 'gmail.com',
'gmail.comu' => 'gmail.com',
'gmial.com' => 'gmail.com',
'gmil.com' => 'gmail.com',
'yahoogmail.com' => 'gmail.com',
// outlook.com
'putlook.com' => 'outlook.com',
];
/**
* Set the email properties after applying MaxMind's recommended normalization
*
* @see https://dev.maxmind.com/normalizing-email-addresses-for-minfraud/
* @param string $email The member's email address
* @return void
*/
protected function _setEmailProperties( $email )
{
$email = trim( \strtolower( $email ) );
$local = \substr( $email, 0, \strrpos( $email, '@' ) );
$domain = \substr( $email, \strrpos( $email, '@' ) + 1 );
/* Trim the domain and remove trailing dots */
$domain = rtrim( trim( $domain ), '.' );
/* Convert IDNs to ASCII */
if ( !\function_exists('idn_to_ascii') )
{
\IPS\IPS::$PSR0Namespaces['TrueBV'] = \IPS\ROOT_PATH . "/system/3rd_party/php-punycode";
require_once \IPS\ROOT_PATH . "/system/3rd_party/php-punycode/polyfill.php";
}
$asciiDomain = idn_to_ascii( $domain, \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46 );
if( $asciiDomain !== FALSE )
{
$domain = $asciiDomain;
}
/* Then address common typos */
$domain = isset( static::$typoDomains[ $domain ] ) ? static::$typoDomains[ $domain ] : $domain;
/* Now remove email aliases from the local part */
$divider = ( $domain === 'yahoo.com' ) ? '-' : '+';
if( $alias = \strpos( $local, $divider ) )
{
$local = \substr( $local, 0, $alias );
}
$this->data['domain'] = $domain;
$this->data['emailMD5'] = md5( $local . '@' . $domain );
}
/**
* Set Phone Number
+1 -1
View File
@@ -455,7 +455,7 @@ class _Rule extends \IPS\Node\Model
{
$groups[ $group->g_id ] = $group->name;
}
$form->add( new \IPS\Helpers\Form\Select( 'f_customer_groups', ( !$this->customer_groups or $this->customer_groups === '*' ) ? '*' : explode( ',', $this->customer_groups ), FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'class' => 'ipsField_long', 'unlimited' => '*', 'unlimitedLang' => 'any' ) ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'f_customer_groups', ( !$this->customer_groups or $this->customer_groups === '*' ) ? '*' : explode( ',', $this->customer_groups ), FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'class' => 'ipsField_long', 'unlimited' => '*', 'unlimitedLang' => 'any', 'impliedUnlimited' => TRUE ) ) );
$form->add( $this->_combine( 'f_email', 'IPS\Helpers\Form\Text' ) );
$form->add( $this->_combine( 'f_ip', 'IPS\Helpers\Form\Text' ) );
$form->add( new \IPS\Helpers\Form\Select( 'f_country', ( !$this->country or $this->country === '*' ) ? '*' : explode( ',', $this->country ), FALSE, array( 'options' => $countries, 'multiple' => TRUE, 'class' => 'ipsField_long', 'unlimited' => '*', 'unlimitedLang' => 'any' ) ) );
@@ -451,7 +451,7 @@ class _Braintree extends \IPS\nexus\Gateway
$summary = $transaction->invoice->summary();
foreach ( $summary['items'] as $item )
{
$unitTaxAmount = $item->tax ? $item->price->amount->multiply( new \IPS\Math\Number("{$item->tax->rate( $transaction->invoice->billaddress )}") ) : new \IPS\Math\Number('0');
$unitTaxAmount = $item->tax ? $item->price->amount->multiply( new \IPS\Math\Number( number_format( "{$item->tax->rate( $transaction->invoice->billaddress )}", \IPS\nexus\Money::numberOfDecimalsForCurrency( $transaction->amount->currency ), '.', '' ) ) ) : new \IPS\Math\Number('0');
$data['lineItems'][] = array(
'description' => mb_strlen( $item->name ) > 127 ? mb_substr( $item->name, 0, 124 ) . '...' : $item->name,
'kind' => $item->price->amount->isGreaterThanZero() ? 'debit' : 'credit',
@@ -36,10 +36,21 @@ class _Payout extends \IPS\nexus\Payout
public static function settings()
{
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$return = array();
$return[] = new \IPS\Helpers\Form\Text( 'manual_name', isset( $settings['Manual'] ) ? $settings['Manual']['name'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'manual_title', isset( $settings['Manual'] ) ? $settings['Manual']['title'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'manual_name', isset( $settings['Manual'] ) ? $settings['Manual']['name'] : '' , NULL, array(), function( $val ) {
if ( !$val AND isset( \IPS\Request::i()->nexus_payout['Manual'] ) AND \IPS\Request::i()->nexus_payout['Manual'] == 1 )
{
throw new \DomainException( 'form_required' );
}
});
$return[] = new \IPS\Helpers\Form\Text( 'manual_title', isset( $settings['Manual'] ) ? $settings['Manual']['title'] : '', NULL, array(), function( $val ) {
if ( !$val AND isset( \IPS\Request::i()->nexus_payout['Manual'] ) AND \IPS\Request::i()->nexus_payout['Manual'] == 1 )
{
throw new \DomainException( 'form_required' );
}
});
return $return;
}
@@ -107,6 +107,51 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
return new \IPS\DateTime( $data['agreement_details']['next_billing_date'] );
}
}
/**
* Get latest unclaimed transaction (only gets transactions within the last 31 days which do not yet have a matching transaction)
*
* @return \IPS\nexus\Transaction
* @throws \IPS\nexus\Gateway\PayPal\Exception
* @throws \OutOfRangeException
* @throws \IPS\Http\Url\Exception
*/
public function latestUnclaimedTransaction()
{
$url = "payments/billing-agreements/{$this->gw_id}/transactions?start_date=" . date('Y-m-d', time() - (86400 * 31)) . '&end_date=' . date('Y-m-d');
try
{
$transactions = $this->method->api( $url, NULL, 'get' );
foreach ( array_reverse( $transactions['agreement_transaction_list'] ) as $t )
{
if ( $t['status'] == 'Completed' )
{
try
{
$existingTransaction = \IPS\Db::i()->select( 't_id', 'nexus_transactions', array( 't_method=? AND t_gw_id=?', $this->method->id, $t['transaction_id'] ) )->first();
}
catch ( \UnderflowException $e )
{
$transaction = new \IPS\nexus\Transaction;
$transaction->member = $this->member;
$transaction->method = $this->method;
$transaction->amount = new \IPS\nexus\Money( $t['amount']['value'], $t['amount']['currency'] );
$transaction->date = new \IPS\DateTime( $t['time_stamp'] );
$transaction->extra = array( 'automatic' => TRUE );
$transaction->gw_id = $t['transaction_id'];
$transaction->billing_agreement = $this;
return $transaction;
}
}
}
}
catch ( \IPS\Http\Url\Exception $e )
{
throw $e;
}
throw new \OutOfRangeException( "{$this->id} ({$this->gw_id})\n{$url}\n\n" . print_r( $transactions, TRUE ) );
}
/**
* Suspend
@@ -183,6 +228,11 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
try
{
$this->_payPalData = $this->method->api( "billing/subscriptions/{$this->gw_id}", NULL, 'get' );
if( !isset( $this->_payPalData['plan_id'] ) )
{
throw new \RuntimeException;
}
}
catch ( \Exception $e )
{
@@ -471,7 +471,7 @@ class _PayPal extends \IPS\nexus\Gateway
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
protected function _billingAgreementAuth( \IPS\nexus\Transaction $transaction, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, \IPS\nexus\Purchase\RenewalTerm $term, $items )
protected function _billingAgreementAuth( \IPS\nexus\Transaction $transaction, ?\IPS\nexus\Fraud\MaxMind\Request $maxMind, \IPS\nexus\Purchase\RenewalTerm $term, $items )
{
$settings = json_decode( $this->settings, TRUE );
@@ -496,7 +496,7 @@ class _PayPal extends \IPS\nexus\Gateway
) );
/* Create a plan */
$renewalAmountIncludingTax = $term->cost->amount->multiply( new \IPS\Math\Number( $term->tax ? ( (string) ( 1 + $term->tax->rate( $transaction->invoice->billaddress ) ) ) : '1' ) );
$renewalAmountIncludingTax = $term->cost->amount->multiply( new \IPS\Math\Number( number_format( $term->tax ? ( ( 1 + $term->tax->rate( $transaction->invoice->billaddress ) ) ) : 1, 4, '.', '' ) ) );
$billingCycle = array(
'pricing_scheme' => array(
'fixed_price' => array(
@@ -676,6 +676,37 @@ class _PayPal extends \IPS\nexus\Gateway
*/
public function capture( \IPS\nexus\Transaction $transaction )
{
/* If this is for a billing agreement, it should have automatically been captured - all we need to do is check its status, and if
it has been captured, record the ID - otherwise return an error explaining what's going on.
The only time we normally get here is if its the initial transaction and PayPal didn't take payment before the user was redirected back
(in which case the status will be STATUS_GATEWAY_PENDING) but if has been an issue (with Maxmind, for example) it could be STATUS_HELD */
if ( $billingAgreement = $transaction->billing_agreement )
{
$response = 'WAIT';
$transactions = $this->api( "payments/billing-agreements/{$transaction->billing_agreement->gw_id}/transactions?start_date=" . date( 'Y-m-d', $transaction->date->getTimestamp() - 86400 ) . '&end_date=' . date( 'Y-m-d' ), NULL, 'get' );
foreach ( $transactions['agreement_transaction_list'] as $t )
{
if ( ( !$transaction->gw_id or $transaction->gw_id == $t['transaction_id'] ) and $transaction->amount->currency == $t['amount']['currency'] and $transaction->amount->amountAsString() == $t['amount']['value'] )
{
if ( $t['status'] == 'Completed' )
{
$transaction->gw_id = $t['transaction_id'];
$transaction->save();
return TRUE;
}
elseif ( $t['status'] == 'Refunded' )
{
$response = 'RFND';
}
elseif ( $t['status'] == 'Failed' )
{
$response = 'FAIL';
}
}
}
throw new \RuntimeException( $response );
}
/* Try to find the authorization ID */
if ( mb_substr( $transaction->gw_id, 0, 4 ) === 'PAY-' )
{
@@ -760,6 +760,13 @@ class _Stripe extends \IPS\nexus\Gateway
{
return;
}
/* PaymentIntent returns a different error for an already captured payment */
elseif( $e->details['code'] == 'payment_intent_unexpected_state'
AND mb_strpos( $e->details['message'], 'already been captured' ) !== FALSE
AND $e->details['payment_intent']['amount_capturable'] === 0 )
{
return;
}
throw $e;
}
+53 -2
View File
@@ -87,7 +87,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
* @param string $ref Referer
* @return \IPS\Helpers\Table\Db
*/
public static function table( $where = array(), \IPS\Http\Url $url, $ref = 't' )
public static function table( ?array $where, \IPS\Http\Url $url, $ref = 't' )
{
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'nexus_invoices', $url, $where );
@@ -261,6 +261,11 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
protected $_itemsIterator;
/**
* @brief Tracked
*/
protected $_tracked = array();
/**
* Get items
*
@@ -1380,6 +1385,15 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'target' => '_blank',
'link' => $url->setQueryString( 'do', 'printout' ),
);
$trackUrl = $url->setQueryString( array( 'do' => 'track', 'id' => $this->id ) )->csrf();
$return['track'] = array(
'title' => $this->tracked() ? 'invoice_untrack' : 'invoice_track',
'icon' => 'bullhorn',
'link' => $this->tracked() ? $trackUrl : $trackUrl->setQueryString( 'track', 1 ),
'data' => array( 'confirm' => '' )
);
if ( $this->status !== \IPS\nexus\Invoice::STATUS_CANCELED and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
{
$return['unpaid'] = array(
@@ -1846,7 +1860,9 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
if ( $item->payTo and !$item->payTo->inGroup( explode( ',', \IPS\Settings::i()->nexus_no_commission ) ) )
{
$recipientAmounts = $item->recipientAmounts();
/* We explicitly re-load $item->payTo in order to ensure the account credit amounts have not changed since the time this invoice request began and now. If an admin began a payout that processed after this page load began, the account credit amount may have been reduced. */
$item->payTo = \IPS\nexus\Customer::constructFromData( \IPS\Db::i()->select( '*, core_members.member_id AS _member_id', 'core_members', array( 'core_members.member_id=?', $item->payTo->member_id) )->join( 'nexus_customers', 'nexus_customers.member_id=core_members.member_id' )->first() );
$credits = $item->payTo->cm_credits;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->add( $recipientAmounts['recipient_final']->amount );
$item->payTo->cm_credits = $credits;
@@ -2042,6 +2058,14 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$this->billaddress = $this->member->primaryBillingAddress();
}
/* Send tracked notifications */
foreach ( \IPS\Db::i()->select( 'member_id', 'nexus_invoice_tracker', array( 'invoice_id=?', $this->id ) ) as $trackingMember )
{
$trackingMember = \IPS\Member::load( $trackingMember );
\IPS\Email::buildFromTemplate( 'nexus', 'invoiceNotify', array( $this, $this->summary( $trackingMember->language() ) ), \IPS\Email::TYPE_LIST )
->send( $trackingMember );
}
/* Log */
if ( $member )
{
@@ -2290,4 +2314,31 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
return \IPS\Http\Url::internal( "app=nexus&module=payments&controller=invoices&do=view&id={$this->id}", 'admin' );
}
/**
* Is the member tracking this invoice?
*
* @param \IPS\Member|NULL $member The member to check for, or NULL for currently logged in member
* @return bool
*/
public function tracked( \IPS\Member $member = NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
if( !isset( $this->_tracked[ $member->member_id ] ) )
{
try
{
$tracked = \IPS\Db::i()->select( array( 'invoice_id' ), 'nexus_invoice_tracker', array( 'member_id=? and invoice_id=?', $member->member_id, $this->id ) )->first();
$this->_tracked[ $member->member_id ] = TRUE;
}
catch ( \UnderflowException $e )
{
$this->_tracked[ $member->member_id ] = FALSE;
}
}
return $this->_tracked[ $member->member_id ];
}
}
@@ -361,6 +361,33 @@ abstract class _Item
return NULL;
}
/**
* Is this item the same as another item in the cart?
* Used to decide when an item is added to the cart if we should just increase the quantity of this item instead of creating a new item.
*
* @param \IPS\nexus\Invoice\Item $item The other item
* @return bool
*/
public function isSameAsOtherItem( $item )
{
/* If one is set to be associated with something in the cart and the other is set to be associated
with an existing purchase, trying to compare those will throw an error */
if ( $item->parent or $this->parent )
{
if ( \gettype( $this->parent ) != \gettype( $item->parent ) )
{
return FALSE;
}
}
/* Assume the quantities are the same */
$cloned = clone $item;
$cloned->quantity = $this->quantity;
/* Compare */
return ( $cloned == $this );
}
/**
* Get output for API
*
@@ -296,6 +296,18 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
return TRUE;
}
/**
* Purchase can be reactivated in the ACP?
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param NULL $error Error to show, passed by reference
* @return bool
*/
public static function canAcpReactivate( \IPS\nexus\Purchase $purchase, &$error=NULL )
{
return TRUE;
}
/**
* Can Renew Until
*
@@ -475,4 +487,54 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
{
return TRUE;
}
/**
* Is this item the same as another item in the cart?
* Used to decide when an item is added to the cart if we should just increase the quantity of this item instead of creating a new item.
*
* @param \IPS\nexus\Invoice\Item $item The other item
* @return bool
*/
public function isSameAsOtherItem( $item )
{
// You can't compare DateInterval objects, it just throws an exception, so we have to
// manually figure out if the renewal terms have any differences
if ( $item instanceof \IPS\nexus\Invoice\Item\Purchase )
{
if ( $item->renewalTerm xor $this->renewalTerm )
{
return FALSE;
}
elseif ( !$item->renewalTerm and !$this->renewalTerm )
{
return parent::isSameAsOtherItem( $item );
}
else
{
if ( $item->renewalTerm and $this->renewalTerm )
{
if ( $item->renewalTerm->getTerm() != $this->renewalTerm->getTerm() )
{
return FALSE;
}
if ( $item->renewalTerm->cost != $item->renewalTerm->cost )
{
return FALSE;
}
}
$clonedThis = clone $this;
$clonedThis->renewalTerm = NULL;
$clonedOther = clone $item;
$clonedOther->renewalTerm = NULL;
return $clonedThis->isSameAsOtherItem( $clonedOther );
}
}
else
{
return FALSE;
}
}
}
+1 -1
View File
@@ -85,7 +85,7 @@ class _Ad extends \IPS\nexus\Package
$return['package_settings']['p_locations'] = new \IPS\Helpers\Form\CheckboxSet( 'p_locations', $locationValues, TRUE, array( 'options' => array_combine( array_keys( $locations ), array_keys( $locations ) ), 'toggles' => $locations, 'userSuppliedInput' => '_ad_custom_' ) );
$return['package_settings']['p_locations_custom'] = new \IPS\Helpers\Form\Stack( 'p_locations_custom', $customLocations, FALSE, array(), NULL, NULL, NULL, 'p_locations_custom' );
$return['package_settings']['p_exempt'] = new \IPS\Helpers\Form\Select( 'p_exempt', $package->id ? ( $package->exempt == '*' ? '*' : explode( ',', $package->exempt ) ) : '*', FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ) );
$return['package_settings']['p_exempt'] = new \IPS\Helpers\Form\CheckboxSet( 'p_exempt', $package->id ? ( $package->exempt == '*' ? '*' : explode( ',', $package->exempt ) ) : '*', FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ) );
$return['package_settings']['p_expire'] = new \IPS\Helpers\Form\Custom( 'p_expire', array( 'value' => ( $package->id ) ? $package->expire : -1, 'type' => ( $package->id ) ? $package->expire_unit : 'i' ), FALSE, array(
'getHtml' => function( $element )
@@ -220,9 +220,10 @@ class _CustomField extends \IPS\CustomField
*
* @param mixed $value The value
* @param bool $showSensitiveInformation If TRUE, potentially sensitive data (like passwords) will be displayed - otherwise will be blanked out
* @param string $separator Used to separate items when displaying a field with multiple values.
* @return string
*/
public function displayValue( $value=NULL, $showSensitiveInformation=FALSE )
public function displayValue( $value=NULL, $showSensitiveInformation=FALSE, $separator=NULL )
{
if ( $this->type === 'UserPass' )
{
@@ -51,6 +51,11 @@ class _Filter extends \IPS\Node\Model
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'nexus_product_filter_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_public';
/**
* @brief [Node] ACP Restrictions
+2 -2
View File
@@ -435,7 +435,7 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param array $reactions Reaction Data
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL, $reactions=array() )
public static function searchResult( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL, $reactions=array() )
{
$indexData['index_title'] = \IPS\Member::loggedIn()->language()->addToStack( 'nexus_package_' . $indexData['index_item_id'] );
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments, $template, $reactions );
@@ -489,7 +489,7 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param string $view 'expanded' or 'condensed'
* @return callable
*/
public static function searchResultSnippet( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $view )
public static function searchResultSnippet( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $view )
{
$url = static::urlFromIndexData( $indexData, $itemData );
+39 -17
View File
@@ -225,6 +225,12 @@ class _Package extends \IPS\Node\Model
$this->item()->delete();
parent::delete();
\IPS\Db::i()->delete( static::$packageDatabaseTable, array( 'p_id=?', $this->id ) );
/* Translatables */
foreach ( array( '_assoc', '_page', '_email_purchase_subject', '_email_expire_soon_subject', '_email_expire_subject' ) as $suffix )
{
\IPS\Lang::deleteCustom( 'nexus', "nexus_package_{$this->id}{$suffix}" );
}
}
/**
@@ -265,9 +271,14 @@ class _Package extends \IPS\Node\Model
$secondaryTable['p_id'] = $id;
\IPS\Db::i()->insert( static::$packageDatabaseTable, $secondaryTable );
\IPS\Lang::saveCustom( 'nexus', "nexus_package_{$id}", iterator_to_array( \IPS\Db::i()->select( 'CONCAT(word_custom, \' ' . \IPS\Member::loggedIn()->language()->get('copy_noun') . '\') as word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', static::$titleLangPrefix . $this->_id ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
/* Translatables */
\IPS\Lang::saveCustom( 'nexus', static::$titleLangPrefix . $id, iterator_to_array( \IPS\Db::i()->select( 'CONCAT(word_custom, \' ' . \IPS\Member::loggedIn()->language()->get('copy_noun') . '\') as word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', static::$titleLangPrefix . $this->_id ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
\IPS\Lang::saveCustom( 'nexus', static::$titleLangPrefix . $id . static::$descriptionLangSuffix, iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', static::$titleLangPrefix . $oldId . static::$descriptionLangSuffix ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
\IPS\Lang::saveCustom( 'nexus', "nexus_package_{$id}_assoc", iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', "nexus_package_{$oldId}_assoc" ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
foreach ( array( '_assoc', '_page', '_email_purchase_subject', '_email_expire_soon_subject', '_email_expire_subject' ) as $suffix )
{
\IPS\Lang::saveCustom( 'nexus', static::$titleLangPrefix . $id . $suffix, iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', static::$titleLangPrefix . $oldId . $suffix ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
}
\IPS\Db::i()->insert( 'nexus_package_base_prices', \IPS\Db::i()->select( "{$id} AS id, " . implode( ',', array_map( function( $val )
{
@@ -699,7 +710,7 @@ class _Package extends \IPS\Node\Model
* @return float
* @return string|NULL
*/
public static function fullPriceInfoFromData( \IPS\nexus\Customer $customer = NULL, $currency, $packageId, $prices, $discounts, $renewOptions, $stock, $tax, $initialTerm )
public static function fullPriceInfoFromData( ?\IPS\nexus\Customer $customer, $currency, $packageId, $prices, $discounts, $renewOptions, $stock, $tax, $initialTerm )
{
/* Get customer / currency */
$customer = $customer ?: \IPS\nexus\Customer::loggedIn();
@@ -1230,7 +1241,7 @@ class _Package extends \IPS\Node\Model
$form->addTab('package_store');
$form->addHeader('package_store_permissions');
$form->add( new \IPS\Helpers\Form\YesNo( 'p_store', $this->store, FALSE, array( 'togglesOn' => array( 'p_member_groups', 'p_featured', 'p_desc_editor', 'p_images', 'p_reviewable', "{$form->id}_header_package_store_display", "{$form->id}_header_package_reviews" ) ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'p_member_groups', $this->member_groups === '*' ? '*' : explode( ',', $this->member_groups ), FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'all' ), NULL, NULL, NULL, 'p_member_groups' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'p_member_groups', $this->member_groups === '*' ? '*' : explode( ',', $this->member_groups ), FALSE, array( 'options' => $groups, 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'all', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'p_member_groups' ) );
$form->add( new \IPS\Helpers\Form\Node( 'p_methods', ( !$this->methods or $this->methods === '*' ) ? 0 : explode( ',', $this->methods ), TRUE, array( 'class' => 'IPS\nexus\Gateway', 'multiple' => TRUE, 'zeroVal' => 'any' ) ) );
foreach ( $typeFields['store_permissions'] as $field )
{
@@ -1570,7 +1581,7 @@ class _Package extends \IPS\Node\Model
}
$values['p_renew_options'] = json_encode( $renewOptions );
if ( $values['p_initial_term'] and $values['p_initial_term']->interval )
if ( isset( $values['p_initial_term'] ) and $values['p_initial_term']->interval )
{
$term = $values['p_initial_term']->getTerm();
$values['p_initial_term'] = $term['term'] . mb_strtoupper( $term['unit'] );
@@ -2278,10 +2289,7 @@ class _Package extends \IPS\Node\Model
$added = FALSE;
foreach ( $_SESSION['cart'] as $_id => $_item )
{
$cloned = clone $_item;
$cloned->quantity = $quantity;
if ( $cloned == $item )
if ( $_item->isSameAsOtherItem( $item ) )
{
$_item->quantity += $quantity;
$added = TRUE;
@@ -2312,7 +2320,8 @@ class _Package extends \IPS\Node\Model
/* Turn off guest page caching */
if ( \IPS\CACHE_PAGE_TIMEOUT and !\IPS\Member::loggedIn()->member_id )
{
\IPS\Request::i()->setCookie( 'noCache', 1 );
/* Set the cookie for 20 minutes - if the guest hasn't checked out by then they may no longer see their cart on every page but as long as their session is still valid they will still be able to check out. */
\IPS\Request::i()->setCookie( 'noCache', 1, \IPS\DateTime::ts( time() + 1200 ) );
}
/* Return */
@@ -2524,10 +2533,11 @@ class _Package extends \IPS\Node\Model
}
$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() ) ) );
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
{
@@ -2559,10 +2569,11 @@ class _Package extends \IPS\Node\Model
}
$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() ) ) );
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
{
@@ -2764,6 +2775,17 @@ class _Package extends \IPS\Node\Model
{
// Blank for hooking
}
/**
* Warning to display to admin when cancelling a purchase
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return string
*/
public function onCancelWarning( \IPS\nexus\Purchase $purchase )
{
return NULL;
}
/**
* Check for member
@@ -2973,7 +2995,7 @@ class _Package extends \IPS\Node\Model
{
if ( $this->$typeKey == 'wysiwyg' )
{
$email = \IPS\Email::buildFromContent( $purchase->member->language()->get( "nexus_package_{$this->id}_email_{$type}_subject" ), $this->$valueKey, NULL, \IPS\Email::TYPE_TRANSACTIONAL, TRUE, 'package_customemail' );
$email = \IPS\Email::buildFromContent( $purchase->member->language()->get( "nexus_package_{$this->id}_email_{$type}_subject" ), \IPS\Text\Parser::removeLazyLoad( $this->$valueKey ), NULL, \IPS\Email::TYPE_TRANSACTIONAL, TRUE, 'package_customemail' );
}
else
{
@@ -63,7 +63,7 @@ class _Product extends \IPS\nexus\Package
{
$availableShippingMethods['easypost'] = 'enhancements__nexus_EasyPost';
}
$return['package_settings']['shipping'] = new \IPS\Helpers\Form\Select( 'p_shipping', ( $package->type === 'product' and $package->shipping !== '*' ) ? explode( ',', $package->shipping ) : '*', FALSE, array( 'options' => $availableShippingMethods, 'multiple' => TRUE, 'unlimited' => '*' ), NULL, NULL, NULL, 'p_shipping' );
$return['package_settings']['shipping'] = new \IPS\Helpers\Form\CheckboxSet( 'p_shipping', ( $package->type === 'product' and $package->shipping !== '*' ) ? explode( ',', $package->shipping ) : '*', FALSE, array( 'options' => $availableShippingMethods, 'multiple' => TRUE, 'unlimited' => '*', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'p_shipping' );
}
$return['package_settings']['show'] = new \IPS\Helpers\Form\YesNo( 'p_show', $package->type === 'product' ? $package->show : TRUE, FALSE, array( 'togglesOn' => array( "{$formId}_tab_package_client_area", "{$formId}_header_package_associations", "{$formId}_header_package_associations_desc", 'p_associate', "{$formId}_header_package_renewals", 'p_renews', 'p_support_severity', 'p_lkey' ) ) );
+1 -1
View File
@@ -97,7 +97,7 @@ abstract class _Payout extends \IPS\Patterns\ActiveRecord
* @param \IPS\Http\Url $ref URL to display table on
* @return \IPS\Helpers\Table\Db
*/
public static function table( $where = array(), \IPS\Http\Url $url )
public static function table( $where, \IPS\Http\Url $url )
{
$table = new \IPS\Helpers\Table\Db( 'nexus_payouts', $url, $where );
$table->include = array( 'po_status', 'po_id', 'po_gateway', 'po_member', 'po_amount', 'po_date' );
@@ -435,7 +435,7 @@ class _Purchase extends \IPS\Node\Model
*/
public function set_renewals( \IPS\nexus\Purchase\RenewalTerm $term = NULL )
{
if ( $term === NULL )
if ( $term === NULL OR $term->cost->amount->isZero() )
{
$this->_data['renewals'] = 0;
}
@@ -955,6 +955,19 @@ class _Purchase extends \IPS\Node\Model
throw new \BadMethodCallException;
}
/**
* Purchase can be reactivated in the ACP?
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param NULL $error Error to show, passed by reference
* @return bool
*/
public function canAcpReactivate( &$error=NULL )
{
$extension = $this->extension();
return $extension::canAcpReactivate( $this, $error );
}
/**
* Get output for API
*
@@ -1552,14 +1565,18 @@ class _Purchase extends \IPS\Node\Model
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and ( !$this->grouped_renewals or !$parent or !$parent->billing_agreement or $parent->billing_agreement->canceled ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_cancel' ) )
{
$extension = $this->extension();
if ( $this->cancelled )
{
$return['reactivate'] = array(
'icon' => 'check',
'title' => 'reactivate',
'link' => $url->setQueryString( 'do', 'reactivate' )->csrf(),
'data' => array( 'confirm' => '' )
);
if ( $extension::canAcpReactivate( $this ) )
{
$return['reactivate'] = array(
'icon' => 'check',
'title' => 'reactivate',
'link' => $url->setQueryString( 'do', 'reactivate' )->csrf(),
'data' => array( 'confirm' => '' )
);
}
}
else
{
@@ -556,4 +556,30 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
'name' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_shiprate_' . $this->id )
);
}
/**
* [ActiveRecord] Delete Record
*
* @return void
*/
public function delete()
{
parent::delete();
\IPS\Lang::deleteCustom( 'nexus', "nexus_shiprate_de_{$this->id}" );
}
/**
* [ActiveRecord] Duplicate
*
* @return void
*/
public function __clone()
{
$oldId = $this->_id;
parent::__clone();
\IPS\Lang::saveCustom( 'nexus', "nexus_shiprate_de_{$this->id}", iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', "nexus_shiprate_de_{$oldId}" ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
}
}
@@ -713,7 +713,7 @@ class _Package extends \IPS\Node\Model
return $diff;
}
else
{
{
return new \IPS\nexus\Money( $diff->amount->divide( $numberOfDaysInCurrentRenewalTerm )->multiply( $daysLeftUntilExpiry ), $currency );
}
}
@@ -67,6 +67,16 @@ class _Subscription extends \IPS\Patterns\ActiveRecord
return NULL;
}
/**
* Check if the subscription expired
*
* @return bool
*/
public function isExpired() : bool
{
return $this->expire AND $this->expire < \IPS\DateTime::create()->getTimestamp();
}
/**
* Get the package (ooh, very mysterious, sounds like something from Narcos)
*
@@ -192,7 +202,29 @@ class _Subscription extends \IPS\Patterns\ActiveRecord
{
$where[] = array( 'sub_active=1' );
}
return static::constructFromData( \IPS\Db::i()->select( '*', 'nexus_member_subscriptions', $where )->first() );
}
catch( \Exception $ex )
{
throw new \OutOfRangeException;
}
}
/**
* Load a subscription by member and purchase
*
* @param \IPS\Member $member Take a guess
* @param \IPS\nexus\Purchase $purchase I mean it's really writing itself
* @return \IPS\nexus\Subscription
* @throws \OutOfRangeException
*/
public static function loadByMemberAndPurchase( \IPS\Member $member, \IPS\nexus\Purchase $purchase )
{
try
{
$where = array( array( 'sub_purchase_id=? and sub_member_id=?', $purchase->id, $member->member_id ) );
return static::constructFromData( \IPS\Db::i()->select( '*', 'nexus_member_subscriptions', $where )->first() );
}
catch( \Exception $ex )
@@ -212,4 +244,14 @@ class _Subscription extends \IPS\Patterns\ActiveRecord
\IPS\Db::i()->update( 'nexus_member_subscriptions', array( 'sub_active' => 0 ), array( 'sub_member_id=?', $member->member_id ) );
}
/**
* Can we cancel this subscription?
*
* @return bool
*/
public function canCancel() : bool
{
return !$this->expire or $this->isExpired();
}
}
@@ -80,8 +80,16 @@ class _Table extends \IPS\Helpers\Table\Table
{
$where[] = \is_array( $this->filters[ $this->filter ] ) ? $this->filters[ $this->filter ] : array( $this->filters[ $this->filter ] );
}
$where[] = array( 'sp_enabled=1' );
// We want also the disabled but active subscription
if( !$this->activeSubscription )
{
$where[] = array( 'sp_enabled=1' );
}
else
{
$where[] = array( '(sp_enabled=1 OR sp_id=?)', $this->activeSubscription->package_id );
}
/* Get Count */
$count = \IPS\Db::i()->select( 'COUNT(*) as cnt', 'nexus_member_subscription_packages', $where )->first();
@@ -216,9 +216,10 @@ class _CustomField extends \IPS\CustomField
*
* @param mixed $value The value
* @param bool $showSensitiveInformation If TRUE, potentially sensitive data (like passwords) will be displayed - otherwise will be blanked out
* @param string $separator Used to separate items when displaying a field with multiple values.
* @return string
*/
public function displayValue( $value=NULL, $showSensitiveInformation=FALSE )
public function displayValue( $value=NULL, $showSensitiveInformation=FALSE, $separator=NULL )
{
if ( $this->type === 'UserPass' )
{
@@ -62,6 +62,11 @@ class _Department extends \IPS\Node\Model
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'nexus_department_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_desc';
/**
* @brief [Node] ACP Restrictions
@@ -1186,7 +1186,7 @@ class _Request extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
}
/* Mark it as read */
$this->markRead();
$this->markRead( NULL, NULL, NULL, TRUE );
/* Redirect */
$this->_staffFormRedirect( $pendingData, FALSE );
@@ -62,6 +62,11 @@ class _Severity extends \IPS\Node\Model
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'nexus_severity_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_desc';
/**
* @brief [Node] ACP Restrictions
+15 -1
View File
@@ -299,7 +299,7 @@ class _Status extends \IPS\Node\Model
public function getButtons( $url, $subnode=FALSE )
{
$buttons = parent::getButtons( $url, $subnode );
if ( isset( $buttons['delete'] ) and \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_requests', array( 'r_status=?', $this->id ) ) )
if ( isset( $buttons['delete'] ) and \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_requests', array( 'r_status=?', $this->id ) )->first() )
{
$buttons['delete']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('delete') );
}
@@ -322,6 +322,20 @@ class _Status extends \IPS\Node\Model
\IPS\Lang::saveCustom( 'nexus', "nexus_status_{$this->id}_front", iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', "nexus_status_{$oldId}_front" ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
\IPS\Lang::saveCustom( 'nexus', "nexus_status_{$this->id}_set", iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', "nexus_status_{$oldId}_set" ) )->setKeyField( 'lang_id' )->setValueField('word_custom') ) );
}
/**
* [ActiveRecord] Delete Record
*
* @return void
*/
public function delete()
{
parent::delete();
\IPS\Lang::deleteCustom( 'nexus', "nexus_status_{$this->id}_admin" );
\IPS\Lang::deleteCustom( 'nexus', "nexus_status_{$this->id}_front" );
\IPS\Lang::deleteCustom( 'nexus', "nexus_status_{$this->id}_set" );
}
/**
* Get output for API
@@ -217,10 +217,10 @@ class _Stream extends \IPS\Patterns\ActiveRecord
$this->staff = $values['r_staff'] ?: NULL;
$this->from_email = $values['r_email'] ?: NULL;
$this->search_term = $values['r_search'] ?: NULL;
$this->started = $values['r_started'][1] ? json_encode($values['r_started']): NULL;
$this->last_new_reply = $values['r_last_new_reply'][1] ? json_encode($values['r_last_new_reply']): NULL;
$this->last_reply = $values['r_last_reply'][1] ? json_encode($values['r_last_reply']): NULL;
$this->last_staff_reply = $values['r_last_staff_reply'][1] ? json_encode($values['r_last_staff_reply']): NULL;
$this->started = ( \is_array( $values['r_started'] ) AND $values['r_started'][1] ) ? json_encode($values['r_started']): NULL;
$this->last_new_reply = ( \is_array( $values['r_last_new_reply'] ) AND $values['r_last_new_reply'][1] ) ? json_encode($values['r_last_new_reply']): NULL;
$this->last_reply = ( \is_array( $values['r_last_new_reply'] ) AND $values['r_last_reply'][1] ) ? json_encode($values['r_last_reply']): NULL;
$this->last_staff_reply = ( \is_array( $values['r_last_staff_reply'] ) AND $values['r_last_staff_reply'][1] ) ? json_encode($values['r_last_staff_reply']): NULL;
$this->tracked = (bool) $values['r_tracked'];
}
@@ -267,7 +267,7 @@ class _Stream extends \IPS\Patterns\ActiveRecord
* @param \IPS\Http\Url $url The URL to submit to
* @return \IPS\Helpers\Form
*/
public function form( Stream $selectedStream = NULL, \IPS\Http\Url $url )
public function form( ?Stream $selectedStream, \IPS\Http\Url $url )
{
/* Init */
$form = new \IPS\Helpers\Form( 'form', 'show_results', $url );
+2 -2
View File
@@ -401,12 +401,12 @@ XML;
/* Cache and return */
if ( $responseArray and $responseArray['valid'] === 'true' )
{
\IPS\Data\Cache::i()->storeWithExpire( $cacheKey, $responseArray, \IPS\DateTime::create()->add( new\DateInterval( 'P7D' ) ), TRUE );
\IPS\Data\Cache::i()->storeWithExpire( $cacheKey, $responseArray, \IPS\DateTime::create()->add( new \DateInterval( 'P7D' ) ), TRUE );
return $responseArray;
}
else
{
\IPS\Data\Cache::i()->storeWithExpire( $cacheKey, NULL, \IPS\DateTime::create()->add( new\DateInterval( 'PT10S' ) ), TRUE );
\IPS\Data\Cache::i()->storeWithExpire( $cacheKey, NULL, \IPS\DateTime::create()->add( new \DateInterval( 'PT10S' ) ), TRUE );
return NULL;
}
}
@@ -93,7 +93,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
* @param string $ref Referrer
* @return \IPS\Helpers\Table\Db
*/
public static function table( $where = array(), \IPS\Http\Url $url, $ref = 't' )
public static function table( $where, \IPS\Http\Url $url, $ref = 't' )
{
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'nexus_transactions', $url, $where );
@@ -1083,7 +1083,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
'gateway' => $this->method ? $this->method->apiOutput( $authorizedMember ) : null,
'gatewayId' => $this->gw_id,
'date' => $this->date->rfc3339(),
'customer' => $this->member->apiOutput( $authorizedMember )
'customer' => $this->member ? $this->member->apiOutput( $authorizedMember ) : null
);
}