Version 4.1.19

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

No files matched your search

@@ -2,9 +2,9 @@
/**
* @brief Commission Rule Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 15 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -240,7 +240,7 @@ class _CommissionRule extends \IPS\Node\Model
if( isset( $values['rrule_commission_limit'] ) )
{
$values['rrule_commission_limit'] = json_encode( $values['rrule_commission_limit'] );
$values['rrule_commission_limit'] = ( !$values['rrule_commission_limit'] or $values['rrule_commission_limit'] == '*' ) ? '*' : json_encode( $values['rrule_commission_limit'] );
}
return $values;
@@ -2,9 +2,9 @@
/**
* @brief Commission Rule Filter Iterator
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 18 Aug 2014
* @version SVN_VERSION_NUMBER
+30 -14
View File
@@ -2,9 +2,9 @@
/**
* @brief Coupon Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -49,6 +49,11 @@ class _Coupon extends \IPS\Node\Model
*/
protected static $databaseIdFields = array( 'c_code' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] ACP Restrictions
* @code
@@ -148,11 +153,11 @@ class _Coupon extends \IPS\Node\Model
{
return !( $node instanceof \IPS\nexus\Package\Group );
} ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'c_renewals', $this->renewals, FALSE, array(), NULL, NULL, NULL, 'c_renewals' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'c_renewals', $this->renewals ?: FALSE, FALSE, array(), NULL, NULL, NULL, 'c_renewals' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'c_limit_discount', $this->limit_discount, FALSE, array( 'options' => array( 0 => 'c_limit_discount_no', 1 => 'c_limit_discount_yes' ) ), NULL, NULL, NULL, 'c_limit_discount' ) );
$form->addHeader( 'coupon_dates' );
$form->add( new \IPS\Helpers\Form\Date( 'c_start', $this->id ? $this->start : \IPS\DateTime::ts( time() ), FALSE, array( 'time' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\Date( 'c_start', $this->id ? $this->start : \IPS\DateTime::ts( time() ), TRUE, array( 'time' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\Date( 'c_end', $this->id ? $this->end : 0, FALSE, array( 'time' => TRUE, 'unlimited' => 0, 'unlimitedLang' => 'no_end_date' ) ) );
$form->addHeader( 'coupon_restrictions' );
@@ -331,18 +336,23 @@ class _Coupon extends \IPS\Node\Model
}
/* Restricted to products? */
$items = NULL;
if ( $this->products !== '*' )
{
$allowedProductIds = explode( ',', $this->products );
$hasAllowedProduct = FALSE;
$productTotal = 0;
$productTotal = new \IPS\Math\Number('0');
foreach ( $invoice->items as $item )
$items = array();
foreach ( $invoice->items as $k => $item )
{
if ( $item instanceof \IPS\nexus\extensions\nexus\Item\Package and in_array( $item->id, $allowedProductIds ) )
{
$hasAllowedProduct = TRUE;
$productTotal += $item->price->amount;
$productTotal = $productTotal->add( $item->price->amount );
$items[] = $k;
}
elseif ( $this->renewals and $item instanceof \IPS\nexus\Invoice\Item\Renewal )
{
@@ -352,7 +362,9 @@ class _Coupon extends \IPS\Node\Model
if ( $purchase->app == 'nexus' and $purchase->type == 'package' and in_array( $purchase->item_id, $allowedProductIds ) )
{
$hasAllowedProduct = TRUE;
$productTotal += $item->price->amount;
$productTotal = $productTotal->add( $item->price->amount );
$items[] = $k;
}
}
catch ( \OutOfRangeException $e ) { }
@@ -377,22 +389,23 @@ class _Coupon extends \IPS\Node\Model
}
else
{
if ( $this->limit_discount )
if ( $this->limit_discount and isset( $productTotal ) )
{
$base = $productTotal;
}
else
{
$base = $invoice->total->amount;
$summary = $invoice->summary();
$base = $summary['subtotal']->amount;
}
$discount = new \IPS\nexus\Money( -( $base / 100 * $this->discount ), $invoice->currency );
$discount = new \IPS\nexus\Money( $base->percentage( new \IPS\Math\Number( $this->discount ) )->multiply( new \IPS\Math\Number( '-1' ) ), $invoice->currency );
}
/* Never allow a discount greater than the invoice total */
if ( $discount->amount < -$invoice->total->amount )
if ( $discount->amount->multiply( new \IPS\Math\Number( '-1' ) )->compare( $invoice->total->amount ) === 1 )
{
$discount = new \IPS\nexus\Money( -$invoice->total->amount, $invoice->currency );
$discount = new \IPS\nexus\Money( $invoice->total->amount->multiply( new \IPS\Math\Number( '-1' ) ), $invoice->currency );
}
/* Save that we've used it */
@@ -411,6 +424,9 @@ class _Coupon extends \IPS\Node\Model
$item = new \IPS\nexus\extensions\nexus\Item\CouponDiscount( $this->code, $discount );
$item->id = $this->id;
$item->extra['usedBy'] = $customer->member_id;
$item->extra['type'] = $this->unit;
$item->extra['value'] = $this->unit === 'v' ? $discount->amount : $this->discount;
$item->extra['items'] = $items;
return $item;
}
}
@@ -2,9 +2,9 @@
/**
* @brief Credit card object
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -109,11 +109,11 @@ class _CreditCard
}
elseif ( preg_match( '/^6(?:011|5[0-9]{2})[0-9]{12}$/', $cardNumber ) )
{
$obj->type = static::DISCOVER;
$obj->type = static::TYPE_DISCOVER;
}
elseif ( preg_match( '/^(?:2131|1800|35\d{3})\d{11}$/', $cardNumber ) )
{
$obj->type = static::JCB;
$obj->type = static::TYPE_JCB;
}
else
{
@@ -128,7 +128,8 @@ class _CreditCard
);
$sum = 0;
$flip = 0;
for ($i = \strlen( $cardNumber ) - 1; $i >= 0; $i--)
$cardNumberLength = \strlen( $cardNumber );
for ($i = $cardNumberLength - 1; $i >= 0; $i--)
{
$sum += $sumTable[ $flip++ & 0x1 ][ $cardNumber[ $i ] ];
}
@@ -2,9 +2,9 @@
/**
* @brief Customer Address Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Alternative Contact Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 09 May 2014
* @version SVN_VERSION_NUMBER
@@ -0,0 +1,263 @@
<?php
/**
* @brief Billing Agreement Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 16 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\Customer;
/* 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;
}
/**
* Billing Agreement Model
*/
class _BillingAgreement extends \IPS\Patterns\ActiveRecord
{
const STATUS_ACTIVE = 'active'; // Billing Agreement is active and will charge automatically
const STATUS_SUSPENDED = 'suspended'; // Billing Agreement has been suspended but can be reactivated
const STATUS_CANCELED = 'canceled'; // Billing Agreement has been canceled
/**
* @brief Multiton Store
*/
protected static $multitons;
/**
* @brief Database Table
*/
public static $databaseTable = 'nexus_billing_agreements';
/**
* @brief Database Prefix
*/
public static $databasePrefix = 'ba_';
/**
* Construct ActiveRecord from database row
*
* @param array $data Row from database table
* @param bool $updateMultitonStoreIfExists Replace current object in multiton store if it already exists there?
* @return static
*/
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
{
$gateway = \IPS\nexus\Gateway::load( $data['ba_method'] );
$classname = 'IPS\nexus\Gateway\\' . $gateway->gateway . '\\BillingAgreement';
/* Initiate an object */
$obj = new $classname;
$obj->_new = FALSE;
/* Import data */
foreach ( $data as $k => $v )
{
if( static::$databasePrefix AND mb_strpos( $k, static::$databasePrefix ) === 0 )
{
$k = \substr( $k, \strlen( static::$databasePrefix ) );
}
$obj->_data[ $k ] = $v;
}
$obj->changed = array();
/* Init */
if ( method_exists( $obj, 'init' ) )
{
$obj->init();
}
/* Return */
return $obj;
}
/**
* Load and check permissions
*
* @return \IPS\Content\Item
* @throws \OutOfRangeException
*/
public static function loadAndCheckPerms( $id )
{
$obj = static::load( $id );
if ( !$obj->canView() )
{
throw new \OutOfRangeException;
}
return $obj;
}
/**
* Member can view?
*
* @param \IPS\Member|NULL $member The member to check for, or NULL for currently logged in member
* @return bool
*/
public function canView( \IPS\Member $member = NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
return $this->member->member_id === $member->member_id or array_key_exists( $member->member_id, iterator_to_array( $this->member->alternativeContacts( array( 'billing=1' ) ) ) );
}
/**
* Get member
*
* @return \IPS\Member
*/
public function get_member()
{
return \IPS\nexus\Customer::load( $this->_data['member'] );
}
/**
* Set member
*
* @param \IPS\Member
* @return void
*/
public function set_member( \IPS\Member $member )
{
$this->_data['member'] = $member->member_id;
}
/**
* Get payment gateway
*
* @return \IPS\nexus\Gateway
*/
public function get_method()
{
return \IPS\nexus\Gateway::load( $this->_data['method'] );
}
/**
* Set payment gateway
*
* @param \IPS\nexus\Gateway
* @return void
*/
public function set_method( \IPS\nexus\Gateway $gateway )
{
$this->_data['method'] = $gateway->id;
}
/**
* Get start date
*
* @return \IPS\DateTime
*/
public function get_started()
{
return \IPS\DateTime::ts( $this->_data['started'] );
}
/**
* Set next payment date
*
* @param \IPS\DateTime $date The invoice date
* @return void
*/
public function set_next_cycle( \IPS\DateTime $date = NULL )
{
$this->_data['next_cycle'] = $date ? $date->getTimestamp() : NULL;
}
/**
* Get next payment date
*
* @return \IPS\DateTime
*/
public function get_next_cycle()
{
return $this->_data['next_cycle'] ? \IPS\DateTime::ts( $this->_data['next_cycle'] ) : NULL;
}
/**
* Set start date
*
* @param \IPS\DateTime $date The invoice date
* @return void
*/
public function set_started( \IPS\DateTime $date )
{
$this->_data['started'] = $date->getTimestamp();
}
/**
* Suspend
*
* @return void
* @throws \DomainException
*/
public function suspend()
{
$this->doSuspend();
$this->member->log( 'billingagreement', array( 'type' => 'suspend', 'id' => $this->id, 'gw_id' => $this->gw_id ) );
$this->next_cycle = NULL;
$this->save();
}
/**
* Reactivate
*
* @return void
* @throws \DomainException
*/
public function reactivate()
{
$this->doReactivate();
$this->member->log( 'billingagreement', array( 'type' => 'reactivate', 'id' => $this->id, 'gw_id' => $this->gw_id ) );
$this->next_cycle = $this->nextPaymentDate();
$this->save();
}
/**
* Cancel
*
* @return void
* @throws \DomainException
*/
public function cancel()
{
$this->doCancel();
$this->member->log( 'billingagreement', array( 'type' => 'cancel', 'id' => $this->id, 'gw_id' => $this->gw_id ) );
$this->next_cycle = NULL;
$this->canceled = TRUE;
}
/**
* Front-End URL
*
* @return \IPS\Http\Url
*/
public function url()
{
return \IPS\Http\Url::internal( "app=nexus&module=clients&controller=billingagreements&do=view&id={$this->id}", 'front', 'clientsbillingagreement' );
}
/**
* ACP URL
*
* @return \IPS\Http\Url
*/
public function acpUrl()
{
return \IPS\Http\Url::internal( "app=nexus&module=payments&controller=billingagreements&id={$this->id}", 'admin' );
}
}
@@ -2,9 +2,9 @@
/**
* @brief Customer Stored Card Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 12 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -56,11 +56,15 @@ class _CreditCard extends \IPS\Patterns\ActiveRecord
$obj->_new = FALSE;
/* Import data */
if ( static::$databasePrefix )
{
$databasePrefixLength = \strlen( static::$databasePrefix );
}
foreach ( $data as $k => $v )
{
if( static::$databasePrefix AND mb_strpos( $k, static::$databasePrefix ) === 0 )
{
$k = \substr( $k, \strlen( static::$databasePrefix ) );
$k = \substr( $k, $databasePrefixLength );
}
$obj->_data[ $k ] = $v;
@@ -93,7 +97,7 @@ class _CreditCard extends \IPS\Patterns\ActiveRecord
foreach ( $gateways as $gateway )
{
$invoice = new \IPS\nexus\Invoice;
foreach ( $gateway->paymentScreen( $invoice, $invoice->total ) as $element )
foreach ( $gateway->paymentScreen( $invoice, $invoice->total, $customer ) as $element )
{
if ( !$element->htmlId )
{
@@ -2,9 +2,10 @@
/**
* @brief Custom Customer Field Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 16 Apr 2013
* @version SVN_VERSION_NUMBER
*/
@@ -118,13 +119,14 @@ class _CustomField extends \IPS\CustomField
$form->addHeader( 'customer_field_purchase' );
$form->add( new \IPS\Helpers\Form\YesNo( 'f_purchase_show', $this->purchase_show ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'f_purchase_require', $this->purchase_require ) );
unset( $form->elements[''][1] );
unset( $form->elements['']['pf_not_null'] );
unset( $form->elements['']['pf_max_input'] );
unset( $form->elements['']['pf_input_format'] );
unset( $form->elements[''][2] );
unset( $form->elements['']['pf_search_type'] );
unset( $form->elements['']['pf_search_type_on_off'] );
unset( $form->elements['']['pf_format'] );
}
@@ -2,9 +2,9 @@
/**
* @brief Customer Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -32,13 +32,29 @@ class _Customer extends \IPS\Member
/**
* @brief Cached logged in member
*/
protected static $loggedInMember = NULL;
public static $loggedInMember = NULL;
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'member_id';
/**
* Get logged in member
*
* @return \IPS\Member
*/
public static function loggedIn()
{
/* If we haven't loaded the member yet, or if the session member has changed since we last loaded the member, reload and cache */
if( static::$loggedInMember === NULL )
{
static::$loggedInMember = static::load( parent::loggedIn()->member_id );
}
return static::$loggedInMember;
}
/**
* Construct Load Query
*
@@ -204,7 +220,7 @@ class _Customer extends \IPS\Member
$save = array();
foreach ( $amounts as $amount )
{
$save[ $amount->currency ] = $amount->amount;
$save[ $amount->currency ] = $amount->amountAsString();
}
$this->_data['cm_credits'] = json_encode( $save );
}
@@ -499,6 +515,6 @@ class _Customer extends \IPS\Member
*/
public function acpUrl()
{
return \IPS\Http\Url::internal("app=nexus&module=customers&controller=view&id={$this->member_id}");
return \IPS\Http\Url::internal( "app=nexus&module=customers&controller=view&id={$this->member_id}", 'admin' );
}
}
+112 -7
View File
@@ -2,9 +2,9 @@
/**
* @brief Customer History Table
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 18 Sep 2014
* @version SVN_VERSION_NUMBER
@@ -48,8 +48,8 @@ class _History extends \IPS\Helpers\Table\Db
if ( $showCustomer )
{
$this->include[] = 'log_member';
}
}
$this->sortBy = $this->sortBy ?: 'log_date';
$this->noSort = array( 'log_type', 'log_ip_address', 'log_data' );
$this->parsers = array(
@@ -292,9 +292,28 @@ class _History extends \IPS\Helpers\Table\Db
return \IPS\Member::loggedIn()->language()->addToStack( 'history_purchase_edited', FALSE, array( 'htmlsprintf' => array( $purchase, ( isset( $val['system'] ) and $val['system'] ) ? '' : $byStaff ) ) );
case 'transfer_from':
return \IPS\Member::loggedIn()->language()->addToStack( 'history_purchase_transfer_from', FALSE, array( 'htmlsprintf' => array( $purchase, \IPS\Theme::i()->getTemplate('global', 'nexus')->userLink( \IPS\nexus\Customer::load( $val['to'] ) ), $byStaff ) ) );
try
{
$to = \IPS\Theme::i()->getTemplate('global', 'nexus')->userLink( \IPS\nexus\Customer::load( $val['to'] ) );
}
catch( \OutOfRangeException $e )
{
$to = \IPS\Member::loggedIn()->language()->addToStack( 'deleted_member' );
}
return \IPS\Member::loggedIn()->language()->addToStack( 'history_purchase_transfer_from', FALSE, array( 'htmlsprintf' => array( $purchase, $to, $byStaff ) ) );
case 'transfer_to':
return \IPS\Member::loggedIn()->language()->addToStack( 'history_purchase_transfer_to', FALSE, array( 'htmlsprintf' => array( $purchase, \IPS\Theme::i()->getTemplate('global', 'nexus')->userLink( \IPS\nexus\Customer::load( $val['from'] ) ), $byStaff ) ) );
try
{
$from = \IPS\Theme::i()->getTemplate('global', 'nexus')->userLink( \IPS\nexus\Customer::load( $val['from'] ) );
}
catch( \OutOfRangeException $e )
{
$from = \IPS\Member::loggedIn()->language()->addToStack( 'deleted_member' );
}
return \IPS\Member::loggedIn()->language()->addToStack( 'history_purchase_transfer_to', FALSE, array( 'htmlsprintf' => array( $purchase, $from, $byStaff ) ) );
case 'cancel':
return \IPS\Member::loggedIn()->language()->addToStack( 'history_purchase_canceled', FALSE, array( 'htmlsprintf' => array( $purchase, $byStaff ) ) );
@@ -336,6 +355,7 @@ class _History extends \IPS\Helpers\Table\Db
return \IPS\Member::loggedIn()->language()->addToStack( "history_commission_{$val['type']}", FALSE, array( 'htmlsprintf' => array( new \IPS\nexus\Money( $val['amount'], $val['currency'] ), $purchase ) ) );
case 'invoice':
case 'invoice_refund':
try
{
$invoice = \IPS\Theme::i()->getTemplate('invoices', 'nexus')->link( \IPS\nexus\Invoice::load( $val['invoice_id'] ), TRUE );
@@ -345,7 +365,7 @@ class _History extends \IPS\Helpers\Table\Db
$invoice = \IPS\Member::loggedIn()->language()->addToStack('invoice_number', FALSE, array( 'sprintf' => array( $val['invoice_id'] ) ) );
}
return \IPS\Member::loggedIn()->language()->addToStack( 'history_commission_invoice', FALSE, array( 'htmlsprintf' => array( new \IPS\nexus\Money( $val['amount'], $val['currency'] ), $invoice ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( "history_commission_{$val['type']}", FALSE, array( 'htmlsprintf' => array( new \IPS\nexus\Money( $val['amount'], $val['currency'] ), $invoice ) ) );
case 'bought':
try
@@ -432,6 +452,49 @@ class _History extends \IPS\Helpers\Table\Db
{
foreach ( $val['other'] as $change )
{
/* Older versions may not have stored the display value, so we need to account for that */
if( mb_strpos( $change['name'], 'nexus_ccfield_' ) !== FALSE AND is_array( $change['value'] ) )
{
try
{
/* If it's an array, we will start first by assuming it is probably an address */
$value = \IPS\GeoLocation::buildFromJson( json_encode( $change['value'] ) )->toString( ', ' );
/* A bad address will return an empty string */
if( !$value )
{
throw new \BadFunctionCallException;
}
$change['value'] = $value;
}
/* Maybe it wasn't an address or geoip support is disabled */
catch( \BadFunctionCallException $e )
{
$_value = array();
/* We will just loop and implode so we have a string */
foreach( $change['value'] as $k => $v )
{
if( is_array( $v ) )
{
foreach( $v as $_k => $_v )
{
$_value[] = $_k . ': ' . $_v;
}
}
else
{
$_value[] = $k . ': ' . $v;
}
}
$value = implode( ', ', $_value );
$change['value'] = htmlspecialchars( $value, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
}
}
if ( isset( $change['old'] ) )
{
$changes[] = \IPS\Member::loggedIn()->language()->addToStack('history_field_changed_from', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $change['name'] ), $change['old'], $change['value'] ) ) );
@@ -554,11 +617,18 @@ class _History extends \IPS\Helpers\Table\Db
break;
case 'download':
switch ( $val['type'] )
{
case 'idm':
try
{
if ( \IPS\Application::appIsEnabled( 'downloads' ) === FALSE )
{
/* If the app is disabled, bubble up to avoid an \IPS\Db\Exception for missing tables - we still want to show the log */
throw new \OutOfRangeException;
}
$options = array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'customers', 'nexus' )->downloadsLink( \IPS\downloads\File::load( $val['id'] ) ) ) );
}
catch ( \OutOfRangeException $e )
@@ -585,6 +655,41 @@ class _History extends \IPS\Helpers\Table\Db
return \IPS\Member::loggedIn()->language()->addToStack( 'history_download', FALSE, array( 'htmlsprintf' => $file ) );
}
break;
case 'support':
if ( isset( $val['type'] ) and $val['type'] == 'email' )
{
$by = \IPS\Member::loggedIn()->language()->addToStack( 'history_support_by_email' );
}
else
{
$by = $byCustomer ?: $byStaff;
}
try
{
$options = array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'support', 'nexus' )->link( \IPS\nexus\Support\Request::load( $val['id'] ), FALSE ), $by ) );
}
catch ( \OutOfRangeException $e )
{
$options = array( 'sprintf' => array( $val['title'] ), 'htmlsprintf' => array( $by ) );
}
return \IPS\Member::loggedIn()->language()->addToStack( 'history_support', FALSE, $options );
case 'billingagreement':
return \IPS\Member::loggedIn()->language()->addToStack( 'history_billingagreement_' . $val['type'], FALSE, array( 'sprintf' => array( $val['id'], $val['gw_id'], $byCustomer ?: $byStaff ) ) );
break;
case 'note':
return \IPS\Member::loggedIn()->language()->addToStack( 'history_' . $val . '_note', FALSE, array( 'sprintf' => $byStaff ) );
break;
case 'custom':
return $val['message'];
break;
}
return '';
@@ -0,0 +1,112 @@
<?php
/**
* @brief Domain Name Lookup
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Oct 2016
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus;
/* 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;
}
/**
* Domain Name Lookup
*/
class _DomainLookup
{
const DOMAIN_TYPE_DOMAIN = 1;
const DOMAIN_TYPE_SUBDOMAIN = 2;
/**
* Domain Name
*/
public $domain;
/**
* Domain Name Type
*/
public $type;
/**
* TLD
*/
public $tld;
/**
* SLD
*/
public $sld;
/**
* WHOIS
*/
public $whois = array();
/**
* Constructor
*
* @param string $domain The domain name
* @param bool $performWhoisLookup if TRUE, a WHOIS lookup will be attempted
* @throws \InvalidArgumentException
*/
public function __construct( $domain, $performWhoisLookup=TRUE )
{
$this->domain = $domain;
$domainData = $this->_findDomainData( NULL, $domain, json_decode( file_get_contents( \IPS\ROOT_PATH . '/applications/nexus/data/domains.json' ), TRUE ) );
if ( $domainData === NULL )
{
throw new \InvalidArgumentException;
}
$this->tld = $domainData['name'];
$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 ( $resource = @fsockopen( $whoisServer, 43 ) )
{
\fwrite( $resource, $domain . "\r\n" );
$response = '';
while( !feof( $resource ) )
{
$response .= \fgets( $resource, 8192 );
}
fclose( $resource );
$this->whois[] = $response;
}
}
}
/**
* Find the data for the deepest domain level
*
* @param array|null $currentVal The value to return if no deeper value exists
* @param array $domain Domain being examined
* @return array|null
*/
protected function _findDomainData( $currentVal, $domain, $jsonData )
{
foreach ( $jsonData['domains'] as $data )
{
if ( mb_substr( $domain, - ( mb_strlen( $data['name'] ) + 1 ) ) === ".{$data['name']}" )
{
return $this->_findDomainData( $data, $domain, $data );
}
}
return $currentVal;
}
}
@@ -2,10 +2,11 @@
/**
* @brief eNom Domain Registrar
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage 08 Aug 2014
* @package IPS Community Suite
* @subpackage Nexus
* @since 08 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -83,15 +84,15 @@ class _Enom
/**
* Register Domain
*
* @param string $sld SLD
* @param string $tld TLD
* @param array $nameservers Nameservers
* @param \IPS\nexus\Customer $customer The customer
* @param \IPS\GeoLocation $billingAddress Billing Address
* @param string $sld SLD
* @param string $tld TLD
* @param array $nameservers Nameservers
* @param \IPS\nexus\Customer $customer The customer
* @param \IPS\GeoLocation|NULL $billingAddress Billing Address
* @return bool
* @throws \RuntimeException
*/
public function register( $sld, $tld, array $nameservers, \IPS\nexus\Customer $customer, \IPS\GeoLocation $billingAddress )
public function register( $sld, $tld, array $nameservers, \IPS\nexus\Customer $customer, \IPS\GeoLocation $billingAddress = NULL )
{
$send = array(
'command' => 'purchase',
@@ -111,7 +112,7 @@ class _Enom
$send['UseWireTransfer'] = 'Yes';
$send['responsetype'] = 'xml';
$response = $this->url->request( 30 )->post( $send )->decodeXml();
$response = $this->url->request( \IPS\LONG_REQUEST_TIMEOUT )->post( $send )->decodeXml();
if ( $response->errors )
{
@@ -132,7 +133,7 @@ class _Enom
*/
public function renew( $sld, $tld, $years )
{
$response = $this->url->request( 30 )->post( array(
$response = $this->url->request( \IPS\LONG_REQUEST_TIMEOUT )->post( array(
'command' => 'extend',
'UID' => $this->username,
'PW' => $this->password,
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Donation Goal Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Jun 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Credit card input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Bulk discount input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 2 May 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Loyalty discount input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 2 May 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Usergroup discount input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 2 May 2014
* @version SVN_VERSION_NUMBER
+29
View File
@@ -0,0 +1,29 @@
<?php
/**
* @brief Ftp input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Apr 2014
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\Form;
/* 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;
}
/**
* Ftp input class for Form Builder
* @note This class is necessary, because it is defined as an additional field type for Commerce Custom Fields, which will attempt to load this class.
*/
class _Ftp extends \IPS\Helpers\Form\Ftp
{
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Length input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 19 Jun 2014
* @version SVN_VERSION_NUMBER
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Money input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 12 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Renewal Term input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 25 Mar 2014
* @version SVN_VERSION_NUMBER
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Shipping method input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 19 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Country/State input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 12 Feb 2014
* @version SVN_VERSION_NUMBER
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Username & Password input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Apr 2014
* @version SVN_VERSION_NUMBER
+4 -4
View File
@@ -2,9 +2,9 @@
/**
* @brief Weight input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 14 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -46,7 +46,7 @@ class _Weight extends \IPS\Helpers\Form\FormAbstract
public function html()
{
$unit = \IPS\nexus\Shipping\Weight::bestUnit();
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->weight( $this->name, $this->value ? round( $this->value->float( $unit ), $this->options['round'] ) : 0, $unit, $this->options['unlimitedLang'], $this->value === '*' );
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->weight( $this->name, ( $this->value and $this->value !== '*' ) ? round( $this->value->float( $unit ), $this->options['round'] ) : 0, $unit, $this->options['unlimitedLang'], $this->value === '*' );
}
/**
@@ -62,7 +62,7 @@ class _Weight extends \IPS\Helpers\Form\FormAbstract
return $val;
}
if ( $this->options['unlimitedLang'] and isset( $val['unlimited'] ) )
if ( $this->options['unlimitedLang'] and ( $val === '*' or isset( $val['unlimited'] ) ) )
{
return '*';
}
@@ -2,9 +2,9 @@
/**
* @brief MaxMind Request
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 07 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -42,15 +42,7 @@ class _Request
if ( $session )
{
$this->setIpAddress( $_SERVER['REMOTE_ADDR'] );
if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
{
$this->data['forwardedIP'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
elseif( isset( $_SERVER['HTTP_CLIENT_IP'] ) )
{
$this->data['forwardedIP'] = $_SERVER['HTTP_CLIENT_IP'];
}
$this->setIpAddress( \IPS\Request::i()->ipAddress() );
$this->data['sessionID'] = session_id();
@@ -85,7 +77,7 @@ class _Request
*/
public function setTransaction( \IPS\nexus\Transaction $transaction )
{
$this->setMember( $transaction->member );
$this->setMember( $transaction->member->member_id ? $transaction->member : $transaction->invoice->member );
if ( $billingAddress = $transaction->invoice->billaddress )
{
@@ -96,7 +88,7 @@ class _Request
$this->setShippingAddress( $transaction->invoice->shipaddress );
}
$this->data['order_amount'] = $transaction->amount->amount;
$this->data['order_amount'] = (string) $transaction->amount->amount;
$this->data['order_currency'] = $transaction->amount->currency;
}
@@ -117,7 +109,7 @@ class _Request
/**
* Set Shipping Address
*
* @param \IPS\GeoLocation $billingAddress
* @param \IPS\GeoLocation $shippingAddress
* @return void
*/
public function setShippingAddress( \IPS\GeoLocation $shippingAddress )
@@ -156,7 +148,7 @@ class _Request
/**
* Set Credit Card
*
* @param string $cardNumber The card number
* @param string $card The card number
* @return void
*/
public function setCard( \IPS\nexus\CreditCard $card )
@@ -194,7 +186,14 @@ class _Request
* @throws \IPS\Http\Request\Exception
*/
public function request()
{
return new \IPS\nexus\Fraud\MaxMind\Response( \IPS\Http\Url::external( 'https://minfraud.maxmind.com/app/ccv2r' )->request()->post( $this->data ) );
{
$response = \IPS\Http\Url::external( 'https://minfraud.maxmind.com/app/ccv2r' )->request()->post( $this->data );
if ( isset( $response->httpHeaders['Content-Type'] ) and preg_match( '/; charset=(.+?)$/', $response->httpHeaders['Content-Type'], $matches ) )
{
$response = mb_convert_encoding( $response, 'UTF-8', $matches[1] );
}
return new \IPS\nexus\Fraud\MaxMind\Response( (string) $response );
}
}
@@ -2,9 +2,9 @@
/**
* @brief MaxMind Response
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 07 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -32,10 +32,10 @@ class _Response
/**
* Constructor
*
* @param \IPS\Http\Response
* @param string|NULL $data Data from MaxMind
* @return void
*/
public function __construct( \IPS\Http\Response $data = NULL )
public function __construct( $data = NULL )
{
if ( $data )
{
@@ -94,4 +94,41 @@ class _Response
{
return ( 100 - 10 ) / 3 * $this->proxyScore + ( $this->proxyScore > 3 ? ( 10 * ( $this->proxyScore - 3 ) ) : 0 );
}
/**
* minFraud error codes
*
* @see https://dev.maxmind.com/minfraud/#Error_Reporting
*
* @var array
*/
protected static $responseErrors = array( 'INVALID_LICENSE_KEY', 'IP_REQUIRED', 'IP_NOT_FOUND ', 'MAX_REQUESTS_REACHED', 'LICENSE_REQUIRED ');
/**
* Does the response include an error?
*
* @return bool
*/
public function error()
{
if ( $this->err AND in_array($this->err, static::$responseErrors ) )
{
return TRUE;
}
return FALSE;
}
/**
* Does the response include a warning?
*
* @return bool
*/
public function warning()
{
if ( $this->err AND !in_array($this->err, static::$responseErrors ) )
{
return TRUE;
}
return FALSE;
}
}
+39 -9
View File
@@ -2,9 +2,9 @@
/**
* @brief Fraud Rule Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -428,7 +428,7 @@ class _Rule extends \IPS\Node\Model
}
/* Country */
if ( $this->country != '*' )
if ( $this->country != '*' and $transaction->invoice->billaddress )
{
if ( !in_array( $transaction->invoice->billaddress->country, explode(',', $this->country ) ) )
{
@@ -468,15 +468,15 @@ class _Rule extends \IPS\Node\Model
{
/* If there was an error, we cannot check any of these */
$maxMind = $transaction->fraud;
if ( $maxMind->err )
if ( $maxMind->error() )
{
return FALSE;
}
/* Score */
if ( $this->maxmind )
{
if ( !$this->_checkCondition( $maxMind->score, $this->maxmind, $this->maxmind_unit ) )
{
if ( !$this->_checkCondition( $maxMind->riskScore !== NULL ? $maxMind->riskScore : round( $maxMind->score * 10 ), $this->maxmind, $this->maxmind_unit ) )
{
return FALSE;
}
@@ -551,14 +551,44 @@ class _Rule extends \IPS\Node\Model
*/
protected function _checkCondition( $a, $operator, $b )
{
if ( $a instanceof \IPS\Math\Number and !( $b instanceof \IPS\Math\Number ) )
{
$b = new \IPS\Math\Number( "{$b}" );
}
if ( !( $a instanceof \IPS\Math\Number ) and $b instanceof \IPS\Math\Number )
{
$a = new \IPS\Math\Number( "{$a}" );
}
switch ( $operator )
{
case 'g':
return $a > $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 1;
}
else
{
return $a > $b;
}
case 'e':
return $a == $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 0;
}
else
{
return $a == $b;
}
case 'l':
return $a < $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === -1;
}
else
{
return $a < $b;
}
case 'c':
return mb_strpos( $a, $b ) !== FALSE;
case 'mm':
@@ -2,9 +2,9 @@
/**
* @brief Authorize.Net Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -32,56 +32,72 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
/**
* Check the gateway can process this...
*
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation The billing address
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation|NULL The billing address, which may be NULL if one if not provided
* @param $customer \IPS\nexus\Customer The customer (Default NULL value is for backwards compatibility - it should always be provided.)
* @param array $recurrings Details about recurring costs
* @return bool
*/
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress )
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress = NULL, \IPS\nexus\Customer $customer = NULL, $recurrings = array() )
{
$settings = json_decode( $this->settings, TRUE );
/* If we don't know the processor, we can only go off the default checks */
if ( !$settings['processor'] )
{
return parent::checkValidity( $amount, $billingAddress );
return parent::checkValidity( $amount, $billingAddress, $customer, $recurrings );
}
$currencies = array();
/* European processors require a full name and billing address */
if ( in_array( $settings['processor'], array( 8, 9, 10, 11, 12, 13 ) ) )
{
if ( !$customer->cm_first_name or !$customer->cm_last_name or !$billingAddress )
{
return FALSE;
}
}
/* GPN Canada and WorldPay Streamline Processing Platform also require a billing address */
if ( in_array( $settings['processor'], array( 4, 13 ) ) )
{
if ( !$billingAddress )
{
return FALSE;
}
}
/* For everything else, check the currencies they can handle... */
$currencies = array();
if ( in_array( $settings['processor'], array( 1, 2, 4, 5, 6, 7, 8, 13, 14 ) ) )
{
$currencies[] = 'USD';
}
if ( in_array( $settings['processor'], array( 1, 2, 4 ) ) )
{
$currencies[] = 'CAD';
}
if ( in_array( $settings['processor'], array( 8, 9, 10, 11, 12, 13 ) ) )
{
$currencies[] = 'GBP';
}
if ( in_array( $settings['processor'], array( 8, 9, 11, 13 ) ) )
{
$currencies[] = 'EUR';
}
if ( in_array( $settings['processor'], array( 14, 15 ) ) )
{
$currencies[] = 'AUD';
}
if ( in_array( $settings['processor'], array( 14 ) ) )
{
$currencies[] = 'NZD';
}
if ( !in_array( $amount->currency, $currencies ) )
{
return FALSE;
}
return parent::checkValidity( $amount, $billingAddress );
/* Still here? We're good - pass to parent for general checks */
return parent::checkValidity( $amount, $billingAddress, $customer, $recurrings );
}
/**
@@ -113,9 +129,11 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
*
* @param \IPS\nexus\Invoice $invoice Invoice
* @param \IPS\nexus\Money $amount The amount to pay now
* @param \IPS\Member $member The member the payment screen is for (if in the ACP charging to a member's card) or NULL for currently logged in member
* @param array $recurrings Details about recurring costs
* @return array
*/
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount )
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount, \IPS\Member $member = NULL, $recurrings = array() )
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_gateways.js', 'nexus', 'global' ) );
@@ -146,7 +164,7 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
{
$types = array( \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS, \IPS\nexus\CreditCard::TYPE_DINERS_CLUB, \IPS\nexus\CreditCard::TYPE_DISCOVER, \IPS\nexus\CreditCard::TYPE_JCB, \IPS\nexus\CreditCard::TYPE_MASTERCARD, \IPS\nexus\CreditCard::TYPE_VISA );
}
$options = array( 'types' => $types );
$options = array( 'types' => $types, 'member' => $member );
/* If we're using DPM, we include all that data in here */
if ( $settings['method'] == 'DPM' )
@@ -165,7 +183,7 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
'x_version' => '3.1',
'x_type' => 'AUTH_ONLY',
'x_method' => 'CC',
'x_amount' => $amount->amount,
'x_amount' => (string) $amount->amount,
'x_currency_code' => $amount->currency,
'x_fp_hash' => hash_hmac( 'md5', "{$settings['login']}^{$invoice->id}^{$timestamp}^{$amount->amount}^{$amount->currency}", $settings['tran_key'] ),
'x_fp_sequence' => $invoice->id,
@@ -208,10 +226,11 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
* @param \IPS\nexus\Transaction $transaction Transaction
* @param array|\IPS\nexus\Customer\CreditCard $values Values from form OR a stored card object if this gateway supports them
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
* @param array $recurrings Details about recurring costs
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, $recurrings = array() )
{
$card = is_array( $values ) ? $values[ $this->id . '_card' ] : $values;
@@ -226,15 +245,18 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
/* Authorize */
$profiles = $card->member->cm_profiles;
$xml = $card->create( 'createCustomerProfileTransactionRequest' );
$xml = $card->createAuthenticationXml( 'createCustomerProfileTransactionRequest' );
$xml->addChild( 'transaction', array(
'profileTransAuthOnly' => array(
'amount' => $transaction->amount->amount,
'amount' => (string) $transaction->amount->amount,
'customerProfileId' => $profiles[ $card->method->id ],
'customerPaymentProfileId' => $card->data,
),
) );
$response = $this->_handleResponse( explode( ',', $card->api( $xml )->directResponse ) );
/* Change $card to the actual \IPS\nexus\CreditCard object so we get the last 4 digits correctly */
$card = $card->card;
}
/* New Card */
@@ -250,18 +272,18 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
$response = $this->api( array(
'x_type' => 'AUTH_ONLY',
'x_method' => 'CC',
'x_amount' => $transaction->amount->amount,
'x_amount' => (string) $transaction->amount->amount,
'x_currency_code' => $transaction->amount->currency,
'x_card_num' => $card->number,
'x_exp_date' => str_pad( $card->expMonth, 2, '0', STR_PAD_LEFT ) . '-' . $card->expYear,
'x_card_code' => $card->ccv,
'x_first_name' => $transaction->member->cm_first_name,
'x_last_name' => $transaction->member->cm_last_name,
'x_address' => implode( ', ', $transaction->invoice->billaddress->addressLines ),
'x_city' => $transaction->invoice->billaddress->city,
'x_state' => $transaction->invoice->billaddress->region,
'x_zip' => $transaction->invoice->billaddress->postalCode,
'x_country' => $transaction->invoice->billaddress->country,
'x_address' => $transaction->invoice->billaddress ? implode( ', ', $transaction->invoice->billaddress->addressLines ) : '',
'x_city' => $transaction->invoice->billaddress ? $transaction->invoice->billaddress->city : '',
'x_state' => $transaction->invoice->billaddress ? $transaction->invoice->billaddress->region : '',
'x_zip' => $transaction->invoice->billaddress ? $transaction->invoice->billaddress->postalCode : '',
'x_country' => $transaction->invoice->billaddress ? $transaction->invoice->billaddress->country : '',
'x_phone' => $transaction->member->cm_phone,
'x_email' => $transaction->member->email,
'x_email_customer' => 'FALSE',
@@ -339,15 +361,24 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
* @throws \Exception
*/
public function refund( \IPS\nexus\Transaction $transaction, $amount = NULL )
{
$extra = json_decode( $transaction->extra, TRUE );
$this->api( array(
'x_type' => 'CREDIT',
'x_trans_id' => $transaction->gw_id,
'x_amount' => $amount ?: $transaction->amount->amount,
'x_card_num' => $extra['lastFour']
) );
{
try
{
$this->api( array(
'x_type' => 'CREDIT',
'x_trans_id' => $transaction->gw_id,
'x_amount' => (string) ( $amount ?: $transaction->amount->amount ),
'x_card_num' => $transaction->extra['lastFour']
) );
}
catch( \IPS\nexus\Gateway\AuthorizeNet\Exception $e )
{
/* If the transaction is unsettled (error code 54 from the API), then we need to void it instead */
if ( $e->getCode() == 54 )
{
$this->void( $transaction );
}
}
}
/* !ACP Configuration */
@@ -376,7 +407,7 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'authorizenet_cim', $settings['cim'], FALSE, array(), NULL, NULL, NULL, 'authorizenet_cim' ) );
$form->add( new \IPS\Helpers\Form\Text( 'authorizenet_hash', $settings['hash'], FALSE, array(), NULL, NULL, NULL, 'authorizenet_hash' ) );
$form->add( new \IPS\Helpers\Form\Select( 'authorizenet_processor', $settings['processor'], FALSE, array( 'options' => array(
$form->add( new \IPS\Helpers\Form\Select( 'authorizenet_processor', isset( $settings['processor'] ) ? $settings['processor'] : '0', FALSE, array( 'options' => array(
0 => 'dont_know',
'North American Payment Processors' => array(
1 => 'Chase Paymentech Tampa Processing Platform',
@@ -2,9 +2,9 @@
/**
* @brief Authorize.Net CIM Stored Card
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -44,17 +44,50 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
throw new \UnexpectedValueException;
}
$xml = $this->create('getCustomerPaymentProfileRequest');
$xml = $this->createAuthenticationXml('getCustomerPaymentProfileRequest');
$xml->addChild( 'customerProfileId', $profiles[ $this->method->id ] );
$xml->addChild( 'customerPaymentProfileId', $this->data );
$response = $this->api( $xml );
$this->_card = new \IPS\nexus\CreditCard;
$this->_card->lastFour = mb_substr( $response->paymentProfile->payment->creditCard->cardNumber, -4 );
$this->_card->lastFour = mb_substr( $response->paymentProfile->payment->creditCard->cardNumber, -4 );
if ( isset( $response->paymentProfile->payment->creditCard->cardType ) )
{
switch ( $response->paymentProfile->payment->creditCard->cardType )
{
case 'Visa':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_VISA;
break;
case 'AmericanExpress':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS;
break;
case 'MasterCard':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_MASTERCARD;
break;
case 'Discover':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DISCOVER;
break;
case 'JCB':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_JCB;
break;
case 'DinersClub':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DINERS_CLUB;
break;
default:
$this->_card->type = NULL;
break;
}
}
else
{
$this->_card->type = NULL;
}
$this->_card->expYear = NULL;
$this->_card->expMonth = NULL;
}
return $this->_card;
}
/**
* Set card
*
@@ -67,7 +100,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
$profiles = $this->member->cm_profiles;
if ( !isset( $profiles[ $this->method->id ] ) )
{
$xml = $this->create( 'createCustomerProfileRequest' );
$xml = $this->createAuthenticationXml( 'createCustomerProfileRequest' );
$xml->addChild( 'profile', array(
'merchantCustomerId' => $this->member->member_id
) );
@@ -83,7 +116,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
}
/* Add the card */
$xml = $this->create( 'createCustomerPaymentProfileRequest' );
$xml = $this->createAuthenticationXml( 'createCustomerPaymentProfileRequest' );
$xml->addChild( 'customerProfileId', $profiles[ $this->method->id ] );
$xml->addChild( 'paymentProfile', array(
'payment' => array(
@@ -116,7 +149,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
$profiles = $this->member->cm_profiles;
if ( isset( $profiles[ $this->method->id ] ) )
{
$xml = $this->create( 'deleteCustomerPaymentProfileRequest' );
$xml = $this->createAuthenticationXml( 'deleteCustomerPaymentProfileRequest' );
$xml->addChild( 'customerProfileId', $profiles[ $this->method->id ] );
$xml->addChild( 'customerPaymentProfileId', $this->data );
$response = $this->api( $xml );
@@ -146,7 +179,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
* @param string $method The API method we will be calling
* @return \IPS\Xml\SimpleXML
*/
public function create( $method )
public function createAuthenticationXml( $method )
{
$settings = json_decode( $this->method->settings, TRUE );
$xml = \IPS\Xml\SimpleXML::create( $method, 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' );
@@ -2,9 +2,9 @@
/**
* @brief Authorize.Net Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Mar 2014
* @version SVN_VERSION_NUMBER
+89 -14
View File
@@ -2,9 +2,9 @@
/**
* @brief Gateway Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -227,6 +227,24 @@ class _Gateway extends \IPS\Node\Model
return $return;
}
/**
* Get gateways that support billing agreements
*
* @return array
*/
public static function billingAgreementGateways()
{
$return = array();
foreach ( static::roots() as $gateway )
{
if ( $gateway->billingAgreements() )
{
$return[ $gateway->id ] = $gateway;
}
}
return $return;
}
/**
* [ActiveRecord] Save Changed Columns
*
@@ -245,7 +263,14 @@ class _Gateway extends \IPS\Node\Model
*/
public function delete()
{
/* Delete cards and billing agreements */
\IPS\Db::i()->delete( 'nexus_customer_cards', array( 'card_method=?', $this->id ) );
\IPS\Db::i()->delete( 'nexus_billing_agreements', array( 'ba_method=?', $this->id ) );
/* Delete */
parent::delete();
/* Recount how many gateways support cards */
static::recountCardStorageGateways();
}
@@ -256,9 +281,11 @@ class _Gateway extends \IPS\Node\Model
*/
protected static function recountCardStorageGateways()
{
$count = count( static::cardStorageGateways() );
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $count ), array( 'conf_key=?', 'card_storage_gateways' ) );
\IPS\Settings::i()->card_storage_gateways = $count;
foreach ( array( 'card_storage_gateways' => count( static::cardStorageGateways() ), 'billing_agreement_gateways' => count( static::billingAgreementGateways() ) ) as $k => $v )
{
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $v ), array( 'conf_key=?', $k ) );
\IPS\Settings::i()->$k = $v;
}
unset( \IPS\Data\Store::i()->settings );
}
@@ -270,15 +297,24 @@ class _Gateway extends \IPS\Node\Model
/**
* Check the gateway can process this...
*
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation The billing address
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation|NULL The billing address, which may be NULL if one if not provided
* @param $customer \IPS\nexus\Customer The customer (Default NULL value is for backwards compatibility - it should always be provided.)
* @param array $recurrings Details about recurring costs
* @return bool
*/
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress )
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress = NULL, \IPS\nexus\Customer $customer = NULL, $recurrings = array() )
{
if ( $this->countries and $this->countries !== '*' )
{
return in_array( $billingAddress->country, explode( ',', $this->countries ) );
if ( $billingAddress )
{
return in_array( $billingAddress->country, explode( ',', $this->countries ) );
}
else
{
return FALSE;
}
}
return TRUE;
@@ -304,6 +340,16 @@ class _Gateway extends \IPS\Node\Model
return FALSE;
}
/**
* Supports billing agreements?
*
* @return bool
*/
public function billingAgreements()
{
return FALSE;
}
/* !Payment Gateway */
/**
@@ -311,9 +357,11 @@ class _Gateway extends \IPS\Node\Model
*
* @param \IPS\nexus\Invoice $invoice Invoice
* @param \IPS\nexus\Money $amount The amount to pay now
* @param \IPS\Member $member The member the payment screen is for (if in the ACP charging to a member's card) or NULL for currently logged in member
* @param array $recurrings Details about recurring costs
* @return array
*/
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount )
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount, \IPS\Member $member = NULL, $recurrings = array() )
{
return array();
}
@@ -322,12 +370,13 @@ class _Gateway extends \IPS\Node\Model
* Authorize
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @param array|\IPS\nexus\Customer\StoredCard $values Values from form OR a stored card object if this gateway supports them
* @param array|\IPS\nexus\Customer\CreditCard $values Values from form OR a stored card object if this gateway supports them
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
* @param array $recurrings Details about recurring costs
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, $recurrings = array() )
{
return NULL;
}
@@ -368,4 +417,30 @@ class _Gateway extends \IPS\Node\Model
{
throw new \Exception;
}
/**
* Extra data to show on the ACP transaction page
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @return string
*/
public function extraData( \IPS\nexus\Transaction $transaction )
{
return '';
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
*/
public function apiOutput()
{
return array(
'id' => $this->id,
'name' => $this->_title
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Manual Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 18 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -32,16 +32,21 @@ class _Manual extends \IPS\nexus\Gateway
* @param \IPS\nexus\Transaction $transaction Transaction
* @param array|\IPS\nexus\Customer\CreditCard $values Values from form OR a stored card object if this gateway supports them
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
* @param array $recurrings Details about recurring costs
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, $recurrings = array() )
{
$transaction->status = \IPS\nexus\Transaction::STATUS_WAITING;
$extra = $transaction->extra;
$extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_WAITING );
$transaction->extra = $extra;
$transaction->save();
/* Send Notification */
$transaction->sendNotification();
\IPS\Output::i()->redirect( $transaction->url() );
}
@@ -2,9 +2,9 @@
/**
* @brief Manual Pay Out Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 7 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -0,0 +1,176 @@
<?php
/**
* @brief PayPal Billing Agreement
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 16 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\Gateway\PayPal;
/* 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;
}
/**
* PayPal Billing Agreement
*/
class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
{
/**
* Get status
*
* @return string See STATUS_* constants
* @throws \IPS\nexus\Gateway\PayPal\Exception
*/
public function status()
{
$data = $this->_getData();
switch ( $data['state'] )
{
case 'Active':
case 'Pending':
case 'Reactivate':
return static::STATUS_ACTIVE;
break;
case 'Suspend':
case 'Suspended':
return static::STATUS_SUSPENDED;
break;
case 'Expired':
case 'Cancel':
case 'Cancelled':
return static::STATUS_CANCELED;
break;
}
}
/**
* Get term
*
* @return \IPS\nexus\Purchase\RenewalTerm
* @throws \IPS\nexus\Gateway\PayPal\Exception
*/
public function term()
{
$data = $this->_getData();
$amount = $data['plan']['payment_definitions'][0]['amount']['value'];
if ( isset( $data['plan']['payment_definitions'][0]['charge_models'][0] ) )
{
$amount += $data['plan']['payment_definitions'][0]['charge_models'][0]['amount']['value'];
}
return new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $amount, $data['plan']['payment_definitions'][0]['amount']['currency'] ), new \DateInterval( 'P' . $data['plan']['payment_definitions'][0]['frequency_interval'] . mb_substr( $data['plan']['payment_definitions'][0]['frequency'], 0, 1 ) ) );
}
/**
* Get next payment date
*
* @return \IPS\DateTime
* @throws \IPS\nexus\Gateway\PayPal\Exception
*/
public function nextPaymentDate()
{
$data = $this->_getData();
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
*/
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' );
$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;
}
}
}
throw new \OutOfRangeException( "{$this->id} ({$this->gw_id})\n{$url}\n\n" . print_r( $transactions, TRUE ) );
}
/**
* Suspend
*
* @return void
* @throws \DomainException
*/
public function doSuspend()
{
$this->method->api( "payments/billing-agreements/{$this->gw_id}/suspend", array( 'note' => 'Suspend' ) );
}
/**
* Reactivate
*
* @return void
* @throws \DomainException
*/
public function doReactivate()
{
$this->method->api( "payments/billing-agreements/{$this->gw_id}/re-activate", array( 'note' => 'Reactivate' ) );
}
/**
* Cancel
*
* @return void
* @throws \DomainException
*/
public function doCancel()
{
$this->method->api( "payments/billing-agreements/{$this->gw_id}/cancel", array( 'note' => 'Cancel' ) );
}
/**
* @brief Cached data
*/
protected $_payPalData = NULL;
/**
* Get data
*
* @return array
* @throws \IPS\nexus\Gateway\PayPal\Exception
*/
protected function _getData()
{
if ( $this->_payPalData === NULL )
{
$this->_payPalData = $this->method->api( "payments/billing-agreements/{$this->gw_id}", NULL, 'get' );
}
return $this->_payPalData;
}
}
@@ -2,9 +2,9 @@
/**
* @brief PayPal Stored Card
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief PayPal Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -43,7 +43,7 @@ class _Exception extends \DomainException
*/
public function __construct( \IPS\Http\Response $response, $refund=FALSE )
{
\IPS\Log::i( \LOG_DEBUG )->write( (string) $response );
\IPS\Log::debug( (string) $response, 'paypal' );
$details = $response->decodeJson();
$this->name = $details['name'];
@@ -80,9 +80,14 @@ class _Exception extends \DomainException
break;
case 'ADDRESS_INVALID':
case 'VALIDATION_ERROR':
$message = \IPS\Member::loggedIn()->language()->get( 'address_invalid' );
break;
case 'INSTRUMENT_DECLINED':
$message = \IPS\Member::loggedIn()->language()->get( 'payment_refused' );
break;
default:
if ( $refund )
{
File diff suppressed because it is too large. Load diff
@@ -2,9 +2,9 @@
/**
* @brief PayPal Pay Out Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 7 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -1,391 +0,0 @@
<?php
/**
* @brief SagePay Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Nexus
* @since 17 Mar 2014
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\Gateway;
/* 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;
}
/**
* SagePay Gateway
*/
class _SagePay extends \IPS\nexus\Gateway
{
/* !Features (Each gateway will override) */
const SUPPORTS_REFUNDS = TRUE;
const SUPPORTS_PARTIAL_REFUNDS = TRUE;
/**
* Can store cards?
*
* @return bool
*/
public function canStoreCards()
{
return FALSE;
}
/**
* Admin can manually charge using this gateway?
*
* @return bool
*/
public function canAdminCharge()
{
$settings = json_decode( $this->settings, TRUE );
return ( $settings['method'] === 'direct' );
}
/* !Payment Gateway */
/**
* Payment Screen Fields
*
* @param \IPS\nexus\Invoice $invoice Invoice
* @param \IPS\nexus\Money $amount The amount to pay now
* @return array
*/
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount )
{
$return = array();
$settings = json_decode( $this->settings, TRUE );
if ( $settings['method'] === 'direct' )
{
$options = array();
$options['types'] = array( \IPS\nexus\CreditCard::TYPE_VISA, \IPS\nexus\CreditCard::TYPE_MASTERCARD, \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS, \IPS\nexus\CreditCard::TYPE_DINERS_CLUB, \IPS\nexus\CreditCard::TYPE_DISCOVER, \IPS\nexus\CreditCard::TYPE_JCB );
$return['card'] = new \IPS\nexus\Form\CreditCard( $this->id . '_card', NULL, FALSE, $options );
}
return $return;
}
/**
* Authorize
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @param array|\IPS\nexus\Customer\CreditCard $values Values from form OR a stored card object if this gateway supports them
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
{
/* Init */
$transaction->save();
$settings = json_decode( $this->settings, TRUE );
/* Build basic data */
$shipAddress = $transaction->invoice->shipaddress ?: $transaction->invoice->billaddress;
$data = array(
'TxType' => 'PAYMENT',
'VendorTxCode' => $transaction->id,
'Amount' => $transaction->amount->amount,
'Currency' => $transaction->amount->currency,
'Description' => mb_substr( $transaction->invoice->title, 0, 100 ),
'BillingSurname' => $transaction->member->cm_last_name,
'BillingFirstnames' => $transaction->member->cm_first_name,
'BillingAddress1' => $transaction->invoice->billaddress->addressLines[0],
'BillingAddress2' => isset( $transaction->invoice->billaddress->addressLines[1] ) ? $transaction->invoice->billaddress->addressLines[0] : NULL,
'BillingCity' => $transaction->invoice->billaddress->city,
'BillingPostCode' => $transaction->invoice->billaddress->postalCode,
'BillingCountry' => $transaction->invoice->billaddress->country,
'BillingState' => ( $transaction->invoice->billaddress->country === 'US' ) ? $transaction->invoice->billaddress->region : NULL,
'BillingPhone' => $transaction->member->cm_phone,
'DeliverySurname' => $transaction->member->cm_last_name,
'DeliveryFirstnames'=> $transaction->member->cm_first_name,
'DeliveryAddress1' => $shipAddress->addressLines[0],
'DeliveryAddress2' => isset( $shipAddress->addressLines[1] ) ? $shipAddress->addressLines[0] : NULL,
'DeliveryCity' => $shipAddress->city,
'DeliveryPostCode' => $shipAddress->postalCode,
'DeliveryCountry' => $shipAddress->country,
'DeliveryState' => ( $shipAddress->country === 'US' ) ? $shipAddress->region : NULL,
'DeliveryPhone' => $transaction->member->cm_phone,
'CustomerEMail' => $transaction->member->email,
);
/* Add method-specific data */
if ( $settings['method'] === 'direct' )
{
$data['ClientIPAddress'] = \IPS\Request::i()->ipAddress();
$card = $values[ $this->id . '_card' ];
$data['CardHolder'] = $transaction->member->cm_first_name . ' ' . $transaction->member->cm_last_name;
$data['CardNumber'] = $card->number;
$data['ExpiryDate'] = $card->expMonth . mb_substr( $card->expYear, 2 );
$data['CV2'] = $card->ccv;
$data['CardType'] = static::cardType( $card );
}
else
{
$data['NotificationURL'] = \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/sagepay.php';
}
/* Add Basket XML */
if ( $transaction->amount->amount == $transaction->invoice->total->amount )
{
$summary = $transaction->invoice->summary();
$basketXml = \IPS\Xml\SimpleXml::create( 'basket' );
$discounts = array();
foreach ( $summary['items'] as $item )
{
if ( $item->price->amount < 0 )
{
$discounts[] = array(
'fixed' => abs( $item->price->amount * $item->quantity )
);
}
else
{
$basketXml->addChild( 'item', array(
'description' => $item->name,
'quantity' => $item->quantity,
'unitNetAmount' => $item->price->amount,
'unitTaxAmount' => $item->price->amount * $item->taxRate(),
'unitGrossAmount' => $item->price->amount + ( $item->price->amount * $item->taxRate() ),
'totalGrossAmount' => $item->grossLinePrice()->amount,
) );
}
}
$shippingNet = 0;
$shippingTax = 0;
foreach ( $summary['shipping'] as $shipping )
{
$shippingNet += $shipping->price->amount;
$shippingTax += ( $shipping->price->amount * $shipping->taxRate() );
}
if ( $shippingNet )
{
$basketXml->addChild( 'deliveryNetAmount', $shippingNet );
$basketXml->addChild( 'deliveryTaxAmount', $shippingTax );
$basketXml->addChild( 'deliveryGrossAmount', $shippingNet + $shippingTax );
}
if ( count( $discounts ) )
{
$basketXml->addChild( 'discounts', $discounts );
}
$data['BasketXML'] = trim( str_replace( '<?xml version="1.0" encoding="UTF-8"?>', '', $basketXml->asXml() ) );
}
/* Send */
try
{
if ( $settings['method'] === 'direct' )
{
$response = $this->api( 'vspdirect-register.vsp', $data );
if ( isset( $data['CreateToken'] ) and isset( $response['Token'] ) and $response['Token'] )
{
echo 'xx<pre>';
print_r( $response );
exit;
}
echo 'zz<pre>';
print_r( $response );
exit;
}
else
{
$response = $this->api( 'vspserver-register.vsp', $data );
}
}
catch ( \DomainException $e )
{
echo '<pre>';
print_r( $e );
exit;
throw new \DomainException( $transaction->member->language()->addToStack('gateway_err') );
}
/* Save details */
$transaction->gw_id = $response['VPSTxId'];
$extra = $transaction->extra;
$extra['sagepaySecurityKey'] = $response['SecurityKey'];
$transaction->extra = $extra;
$transaction->save();
/* Continue */
if ( $settings['method'] === 'direct' )
{
return NULL;
}
else
{
\IPS\Output::i()->redirect( $response['NextURL'] . '=' . $response['VPSTxId'] );
}
}
/**
* Refund
*
* @param \IPS\nexus\Transaction $transaction Transaction to be refunded
* @param float|NULL $amount Amount to refund (NULL for full amount - always in same currency as transaction)
* @return mixed Gateway reference ID for refund, if applicable
* @throws \Exception
*/
public function refund( \IPS\nexus\Transaction $transaction, $amount = NULL )
{
$refundId = $transaction->id . 'R' . rand( 1, 999 );
$extra = $transaction->extra;
$response = $this->api( 'refund.vsp', array(
'TxType' => 'REFUND',
'VendorTxCode' => $refundId,
'Amount' => $amount ?: $transaction->amount->amount,
'Currency' => $transaction->currency,
'Description' => $transaction->invoice->title,
'RelatedVPSTxId' => $transaction->gw_id,
'RelatedVendorTxCode' => $transaction->id,
'RelatedSecurityKey' => $extra['sagepaySecurityKey'],
'RelatedTxAuthNo' => $extra['sagepayAuth'],
) );
return $response['VPSTxId'];
}
/* !ACP Configuration */
/**
* Settings
*
* @param \IPS\Helpers\Form $form The form
* @return void
*/
public function settings( &$form )
{
$settings = json_decode( $this->settings, TRUE );
$form->add( new \IPS\Helpers\Form\Text( 'sagepay_vendor', $settings ? $settings['vendor'] : '', TRUE ) );
\IPS\Member::loggedIn()->language()->words['sagepay_vendor_desc'] = sprintf( \IPS\Member::loggedIn()->language()->get( 'sagepay_vendor_desc' ), gethostbyname( gethostname() ) );
$form->add( new \IPS\Helpers\Form\Radio( 'sagepay_method', $settings ? $settings['method'] : 'server', TRUE, array( 'options' => array( 'server' => 'sagepay_method_server', 'direct' => 'sagepay_method_direct' ) ) ) );
}
/**
* Test Settings
*
* @param array $settings Settings
* @return array
* @throws \InvalidArgumentException
*/
public function testSettings( $settings )
{
return $settings;
}
/* !Utility Methods */
/**
* Get card type
*
* @param \IPS\nexus\CreditCard $card The card
* @return string
*/
public static function cardType( \IPS\nexus\CreditCard $card )
{
switch ( $card->type )
{
case \IPS\nexus\CreditCard::TYPE_VISA:
if ( preg_match( '/^(400626|40854749|40940002|41228586|41373337|41378788|418760|41917679|419772|420672|42159294|422793|423769|431072|444001|44400508|44620011|44621354|44625772|44627483|446286|446294|450875|45397|454313|45443235|454742|45672545|46583079|46590150|47511059|47571059|47622069|47634089|48440910|484427|49096079|49218182)/', $card->number ) )
{
return 'DELTA';
}
elseif ( preg_match( '/^(400(115|121|83[7-9])|401773|4026|405670|410654|41292[1-3]|4140(49|99)|415045|416(039|451|896)|417(7|935)|4197(4[0-1]|7[3-6])|420796|429158|431262|4329(19|37)|433445|435225|44(0(626|753)|615[578]|8360)|4508|453904|479(056|731)|4844(0[6-9]|1[013489]|2[0-68-9]|[3-5][0-9])|491(3|7([3-5][0-9])|859)|4935|494114)
/', $card->number ) )
{
return 'UKE';
}
else
{
return 'VISA';
}
break;
case \IPS\nexus\CreditCard::TYPE_MASTERCARD:
if ( preg_match( '/^(?:5[0678]\d\d|6304|6390|67\d\d)\d{8,15}$/', $card->number ) )
{
return 'MAESTRO';
}
elseif ( in_array( mb_substr( $card->number, 0, 6 ), array( '516730', '516979', '517000', '517049', '535110', '535309', '535420', '535819', '537210', '537609', '557347', '557496', '557498', '557547' ) ) )
{
return 'MCDEBIT';
}
else
{
return 'MC';
}
break;
case \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS:
return 'AMEX';
break;
case \IPS\nexus\CreditCard::TYPE_DINERS_CLUB:
case \IPS\nexus\CreditCard::TYPE_DISCOVER:
return 'DC';
break;
case \IPS\nexus\CreditCard::TYPE_JCB:
return 'JCB';
break;
}
throw new \UnexpectedValueException;
}
/**
* Send API Request
*
* @param string $uri The API to request (e.g. "payments/payment")
* @param array $data The data to send
* @return array
* @throws \IPS\Http|Exception
*/
public function api( $uri, $data=NULL )
{
$settings = json_decode( $this->settings, TRUE );
$data = array_merge( array(
'VPSProtocol' => '3.00',
'Vendor' => $settings['vendor'],
), $data );
$_response = \IPS\Http\Url::external( \IPS\NEXUS_TEST_GATEWAYS ? 'https://test.sagepay.com/gateway/service/' . $uri : 'https://live.sagepay.com/gateway/service/' . $uri )->request()->post( $data );
$response = array();
foreach ( explode( "\n", $_response ) as $line )
{
$line = trim( $line );
if ( $line )
{
$ex = explode( '=', $line );
$response[ $ex[0] ] = $ex[1];
}
}
if ( !isset( $response['Status'] ) or $response['Status'] !== 'OK' )
{
throw new \DomainException( $response['StatusDetail'] );
}
return $response;
}
}
Whitespace-only changes.
@@ -2,9 +2,9 @@
/**
* @brief PayPal Stored Card
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -94,7 +94,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
}
$response = $this->method->api( "customers/{$profiles[ $this->method->id ]}/cards", array(
'card' => $card->token
'card' => isset( $card->token ) ? $card->token : NULL
) );
$this->data = $response['id'];
@@ -2,9 +2,9 @@
/**
* @brief Stripe Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -42,6 +42,10 @@ class _Exception extends \DomainException
{
parent::__construct( $response['message'] );
}
elseif ( $response['type'] == 'invalid_request_error' )
{
parent::__construct( \IPS\Member::loggedIn()->language()->get( 'card_number_invalid' ) );
}
else
{
parent::__construct( \IPS\Member::loggedIn()->language()->get( 'gateway_err' ) );
@@ -2,9 +2,9 @@
/**
* @brief Stripe Pay Out Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 7 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -31,19 +31,7 @@ class _Payout extends \IPS\nexus\Payout
*/
public static function settings()
{
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
if ( !isset( $settings['Stripe'] ) or !count( $settings['Stripe'] ) )
{
try
{
$method = \IPS\Db::i()->select( '*', 'nexus_paymethods', array( 'm_gateway=? AND m_active=1', 'Stripe' ) )->first();
$_settings = json_decode( $method['m_settings'], TRUE );
$settings['Stripe']['secret_key'] = $_settings['secret_key'];
}
catch ( \UnderflowException $e ) { }
}
return array( new \IPS\Helpers\Form\Text( 'stripe_secret_key', ( isset( $settings['Stripe'] ) and isset( $settings['Stripe']['secret_key'] ) ) ? $settings['Stripe']['secret_key'] : NULL ) );
return array();
}
/**
@@ -53,64 +41,7 @@ class _Payout extends \IPS\nexus\Payout
*/
public static function form()
{
$return = array();
/* Get any details we've used before */
$previous = \IPS\Db::i()->select( 'po_data', 'nexus_payouts', array( 'po_member=? AND po_gateway=?', \IPS\Member::loggedIn()->member_id, 'Stripe' ) );
if ( count( $previous ) )
{
$options = array();
foreach ( $previous as $id )
{
$recipient = static::api( "recipients/{$id}", NULL, 'get' );
$options[ $id ] = "{$recipient['active_account']['bank_name']} - XXXX{$recipient['active_account']['last4']}";
}
$options[0] = 'other';
$return[] = new \IPS\Helpers\Form\Radio( 'stripe_recipient', NULL, NULL, array( 'options' => $options, 'toggles' => array( 0 => array( 'stripe_country', 'stripe_routing_number', 'stripe_account_number', 'stripe_account_type' ) ) ) );
}
/* Required Validation */
$requiredValidation = function( $val )
{
if ( !$val and \IPS\Request::i()->withdraw_method === 'Stripe' and isset( \IPS\Request::i()->stripe_recipient ) and !\IPS\Request::i()->stripe_recipient )
{
throw new \DomainException('form_required');
}
};
/* Build form for new */
return array_merge( $return, array(
'country' => new \IPS\Helpers\Form\Select( 'stripe_country', 'US', NULL, array( 'options' => array_map( function( $val )
{
return 'country-' . $val;
}, array_combine( \IPS\GeoLocation::$countries, \IPS\GeoLocation::$countries ) ) ), NULL, NULL, NULL, 'stripe_country' ),
'routing_number' => new \IPS\Helpers\Form\Text( 'stripe_routing_number', NULL, NULL, array(), $requiredValidation, NULL, NULL, 'stripe_routing_number' ),
'account_number' => new \IPS\Helpers\Form\Text( 'stripe_account_number', NULL, NULL, array(), $requiredValidation, NULL, NULL, 'stripe_account_number' ),
'account_type' => new \IPS\Helpers\Form\Radio( 'stripe_account_type', 'individual', NULL, array(
'options' => array(
'individual' => 'stripe_account_type_individual',
'corporation' => 'stripe_account_type_corporation'
),
'toggles' => array(
'individual' => array( 'stripe_individual_name' ),
'corporation' => array( 'stripe_corporation_name' )
)
), NULL, NULL, NULL, 'stripe_account_type' ),
'individual_name' => new \IPS\Helpers\Form\Text( 'stripe_individual_name', \IPS\nexus\Customer::loggedIn()->cm_name, NULL, array(), function( $val )
{
if ( !$val and \IPS\Request::i()->withdraw_method === 'Stripe' and isset( \IPS\Request::i()->stripe_recipient ) and !\IPS\Request::i()->stripe_recipient and \IPS\Request::i()->stripe_account_type === 'individual' )
{
throw new \DomainException('form_required');
}
}, NULL, NULL, 'stripe_individual_name' ),
'corporation_name' => new \IPS\Helpers\Form\Text( 'stripe_corporation_name', NULL, NULL, array(), function( $val )
{
if ( !$val and \IPS\Request::i()->withdraw_method === 'Stripe' and isset( \IPS\Request::i()->stripe_recipient ) and !\IPS\Request::i()->stripe_recipient and \IPS\Request::i()->stripe_account_type === 'corporation' )
{
throw new \DomainException('form_required');
}
}, NULL, NULL, 'stripe_corporation_name' ),
) );
return array();
}
/**
@@ -122,25 +53,7 @@ class _Payout extends \IPS\nexus\Payout
*/
public function getData( array $values )
{
if ( isset( $values['stripe_recipient'] ) and $values['stripe_recipient'] )
{
return $values['stripe_recipient'];
}
$recipient = static::api( 'recipients', array(
'name' => $values['stripe_account_type'] === 'individual' ? $values['stripe_individual_name'] : $values['stripe_corporation_name'],
'type' => $values['stripe_account_type'],
'bank_account' => array(
'country' => $values['stripe_country'],
'routing_number' => $values['stripe_routing_number'],
'account_number' => $values['stripe_account_number']
),
'metadata' => array(
'id' => \IPS\Member::loggedIn()->member_id
),
) );
return $recipient['id'];
return NULL;
}
/**
@@ -151,54 +64,6 @@ class _Payout extends \IPS\nexus\Payout
*/
public function process()
{
$response = static::api( 'transfers', array(
'amount' => $this->amount->amount,
'currency' => $this->amount->currency,
'recipient' => $this->data
) );
$this->status = static::STATUS_COMPLETE;
$this->completed = new \IPS\DateTime;
$this->gw_id = $response['id'];
$this->save();
}
/**
* Get recipient
*
* @return array
*/
public function recipient()
{
return static::api( "recipients/{$this->data}", NULL, 'get' );
}
/**
* Send API Request
*
* @param string $uri The API to request (e.g. "charges")
* @param array $data The data to send
* @param string $method Method (get/post)
* @return array
* @throws \IPS\Http|Exception
* @throws \IPS\nexus\Gateway\PayPal\Exception
*/
protected static function api( $uri, $data=NULL, $method='post' )
{
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$response = \IPS\Http\Url::external( 'https://api.stripe.com/v1/' . $uri )
->request()
->setHeaders( array( 'Stripe-Version' => '2014-01-31' ) )
->login( $settings['Stripe']['secret_key'], '' )
->$method( $data )
->decodeJson();
if ( isset( $response['error'] ) )
{
throw new \DomainException( $response['error']['message'] );
}
return $response;
throw new \DomainException('stripe_payout_deprecated');
}
}
@@ -2,9 +2,9 @@
/**
* @brief Stripe Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 13 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -32,17 +32,23 @@ class _Stripe extends \IPS\nexus\Gateway
/**
* Check the gateway can process this...
*
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation The billing address
* @return bool
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation|NULL The billing address, which may be NULL if one if not provided
* @param $customer \IPS\nexus\Customer The customer (Default NULL value is for backwards compatibility - it should always be provided.)
* @param array $recurrings Details about recurring costs
* @see <a href="https://support.stripe.com/questions/which-currencies-does-stripe-support/">Which currencies does Stripe support?</a>
* @return bool
*/
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress )
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress = NULL, \IPS\nexus\Customer $customer = NULL, $recurrings = array() )
{
if ( static::_amountAsCents( $amount ) < 50 )
{
return FALSE;
}
/* See https://support.stripe.com/questions/which-currencies-does-stripe-support#currencygroup1 */
$group1 = array( 'AED', 'ALL', 'ANG', 'ARS', 'AUD', 'AWG', 'BBD', 'BDT', 'BIF', 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CAD', 'CHF', 'CLP', 'CNY', 'COP', 'CRC', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EEK', 'EGP', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'JMD', 'JPY', 'KES', 'KHR', 'KMF', 'KRW', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LTL', 'LVL', 'MAD', 'MDL', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR', 'MWK', 'MXN', 'MYR', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RUB', 'SAR', 'SBD', 'SCR', 'SEK', 'SGD', 'SHP', 'SLL', 'SOS', 'STD', 'SVC', 'SZL', 'THB', 'TOP', 'TTD', 'TWD', 'TZS', 'UAH', 'UGX', 'USD', 'UYI', 'UZS', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XOF', 'XPF', 'YER', 'ZAR' );
$group2 = array( 'AFN', 'AMD', 'AOA', 'AZN', 'BAM', 'BGN', 'CDF', 'GEL', 'LSL', 'MKD', 'RON', 'RWF', 'TJS', 'XCD', 'KGS', 'MGA', 'MZN', 'RSD', 'SRD', 'TRY', 'ZMW' );
$settings = json_decode( $this->settings, TRUE );
switch ( $settings['country'] )
@@ -53,23 +59,23 @@ class _Stripe extends \IPS\nexus\Gateway
return FALSE;
}
break;
case 'AU':
if( $amount->currency != 'AUD' )
if ( !in_array( $amount->currency, $group1 ) )
{
return FALSE;
}
break;
default:
if ( !in_array( $amount->currency, array( 'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN', 'BIF', 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CAD', 'CDF', 'CHF', 'CLP', 'CNY', 'COP', 'CRC', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EEK', 'EGP', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'JMD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KRW', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL', 'LVL', 'MAD', 'MDL', 'MGA', 'MKD', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR', 'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RON', 'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SEK', 'SGD', 'SHP', 'SLL', 'SOS', 'SRD', 'STD', 'SVC', 'SZL', 'THB', 'TJS', 'TOP', 'TRY', 'TTD', 'TWD', 'TZS', 'UAH', 'UGX', 'USD', 'UYI', 'UZS', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XCD', 'XOF', 'XPF', 'YER', 'ZAR', 'ZMW' ) ) )
if ( !in_array( $amount->currency, array_merge( $group1, $group2 ) ) )
{
return FALSE;
}
break;
}
return parent::checkValidity( $amount, $billingAddress );
return parent::checkValidity( $amount, $billingAddress, $customer, $recurrings );
}
/**
@@ -100,16 +106,18 @@ class _Stripe extends \IPS\nexus\Gateway
*
* @param \IPS\nexus\Invoice $invoice Invoice
* @param \IPS\nexus\Money $amount The amount to pay now
* @param \IPS\Member $member The member the payment screen is for (if in the ACP charging to a member's card) or NULL for currently logged in member
* @param array $recurrings Details about recurring costs
* @return array
*/
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount )
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount, \IPS\Member $member = NULL, $recurrings = array() )
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_gateways.js', 'nexus', 'global' ) );
$settings = json_decode( $this->settings, TRUE );
$supportedCards = array( \IPS\nexus\CreditCard::TYPE_VISA, \IPS\nexus\CreditCard::TYPE_MASTERCARD );
if ( !in_array( $invoice->currency, array( 'AFN', 'AOA', 'ARS', 'AUD', 'BOB', 'BRL', 'CAD', 'CLP', 'COP', 'CRC', 'CVE', 'CZK', 'DJF', 'EEK', 'FKP', 'GNF', 'GTQ', 'HNL', 'INR', 'LAK', 'MUR', 'MXN', 'NIO', 'PAB', 'PEN', 'PYG', 'SHP', 'SRD', 'SVC', 'UYI', 'VEF', 'XOF', 'XPF' ) ) )
if ( !in_array( $invoice->currency, array( 'AFN', 'AOA', 'ARS', 'BOB', 'BRL', 'CAD', 'CLP', 'COP', 'CRC', 'CVE', 'CZK', 'DJF', 'EEK', 'FKP', 'GNF', 'GTQ', 'HNL', 'INR', 'LAK', 'MUR', 'MXN', 'NIO', 'PAB', 'PEN', 'PYG', 'SHP', 'SRD', 'SVC', 'UYI', 'VEF', 'XOF', 'XPF' ) ) )
{
$supportedCards[] = \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS;
}
@@ -131,7 +139,7 @@ class _Stripe extends \IPS\nexus\Gateway
'jsRequired' => TRUE,
'names' => FALSE,
'save' => ( $settings['cards'] and \IPS\Member::loggedIn()->member_id ) ? $this : NULL,
'member' => $invoice->member,
'member' => $member,
) ) );
}
@@ -141,10 +149,11 @@ class _Stripe extends \IPS\nexus\Gateway
* @param \IPS\nexus\Transaction $transaction Transaction
* @param array|\IPS\nexus\Customer\CreditCard $values Values from form OR a stored card object if this gateway supports them
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
* @param array $recurrings Details about recurring costs
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, $recurrings = array() )
{
/* Set MaxMind type */
if ( $maxMind )
@@ -302,8 +311,9 @@ class _Stripe extends \IPS\nexus\Gateway
$settings = $settings ?: json_decode( $this->settings, TRUE );
$response = \IPS\Http\Url::external( 'https://api.stripe.com/v1/' . $uri )
->request()
->request( \IPS\LONG_REQUEST_TIMEOUT )
->setHeaders( array( 'Stripe-Version' => '2014-01-31' ) )
->forceTls()
->login( $settings['secret_key'], '' )
->$method( $data )
->decodeJson();
@@ -326,11 +336,11 @@ class _Stripe extends \IPS\nexus\Gateway
{
if ( in_array( $amount->currency, array( 'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VUV', 'XAF', 'XOF', 'XPF' ) ) )
{
return intval( $amount->amount );
return intval( (string) $amount->amount );
}
else
{
return intval( $amount->amount * 100 );
return intval( (string) $amount->amount->multiply( new \IPS\Math\Number( '100' ) ) );
}
}
}
@@ -2,9 +2,9 @@
/**
* @brief Test Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -26,8 +26,8 @@ class _Test extends \IPS\nexus\Gateway
{
/* !Features */
const SUPPORTS_REFUNDS = FALSE;
const SUPPORTS_PARTIAL_REFUNDS = FALSE;
const SUPPORTS_REFUNDS = TRUE;
const SUPPORTS_PARTIAL_REFUNDS = TRUE;
/* !Payment Gateway */
@@ -36,9 +36,11 @@ class _Test extends \IPS\nexus\Gateway
*
* @param \IPS\nexus\Invoice $invoice Invoice
* @param \IPS\nexus\Money $amount The amount to pay now
* @param \IPS\Member $member The member the payment screen is for (if in the ACP charging to a member's card) or NULL for currently logged in member
* @param array $recurrings Details about recurring costs
* @return array
*/
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount )
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount, \IPS\Member $member = NULL, $recurrings = array() )
{
return array();
}
@@ -55,6 +57,19 @@ class _Test extends \IPS\nexus\Gateway
}
/**
* Refund
*
* @param \IPS\nexus\Transaction $transaction Transaction to be refunded
* @param float|NULL $amount Amount to refund (NULL for full amount - always in same currency as transaction)
* @return mixed Gateway reference ID for refund, if applicable
* @throws \Exception
*/
public function refund( \IPS\nexus\Transaction $transaction, $amount = NULL )
{
}
/* !ACP Configuration */
/**
@@ -2,9 +2,9 @@
/**
* @brief 2Checkout Gateway
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 17 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -28,6 +28,27 @@ class _TwoCheckout extends \IPS\nexus\Gateway
const SUPPORTS_REFUNDS = TRUE;
const SUPPORTS_PARTIAL_REFUNDS = TRUE;
/**
* Check the gateway can process this...
*
* @param $amount \IPS\nexus\Money The amount
* @param $billingAddress \IPS\GeoLocation|NULL The billing address, which may be NULL if one if not provided
* @param $customer \IPS\nexus\Customer The customer (Default NULL value is for backwards compatibility - it should always be provided.)
* @param array $recurrings Details about recurring costs
* @return bool
*/
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress = NULL, \IPS\nexus\Customer $customer = NULL, $recurrings = array() )
{
/* We require both a full name and a billing address */
if ( !$customer->cm_first_name or !$customer->cm_last_name or !$billingAddress )
{
return FALSE;
}
/* Still here? We're good - pass to parent for general checks */
return parent::checkValidity( $amount, $billingAddress, $customer, $recurrings );
}
/* !Payment Gateway */
@@ -37,10 +58,11 @@ class _TwoCheckout extends \IPS\nexus\Gateway
* @param \IPS\nexus\Transaction $transaction Transaction
* @param array|\IPS\nexus\Customer\CreditCard $values Values from form OR a stored card object if this gateway supports them
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
* @param array $recurrings Details about recurring costs
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, $recurrings = array() )
{
$settings = json_decode( $this->settings, TRUE );
$transaction->save();
@@ -80,18 +102,18 @@ class _TwoCheckout extends \IPS\nexus\Gateway
}
/* Items */
if ( $transaction->amount->amount == $transaction->invoice->total->amount )
if ( $transaction->amount->amount->compare( $transaction->invoice->total->amount ) === 0 )
{
$summary = $transaction->invoice->summary();
$k = 0;
foreach ( $summary['items'] as $item )
{
$data["li_{$k}_type"] = $item->price->amount >= 0 ? 'product' : 'coupon';
$data["li_{$k}_type"] = $item->price->amount->isGreaterThanZero() ? 'product' : 'coupon';
$data["li_{$k}_name"] = $item->name;
$data["li_{$k}_quantity"] = $item->quantity;
$data["li_{$k}_price"] = abs( $item->price->amount );
$data["li_{$k}_tangible"] = $item->physical ?'Y' : 'N';
$data["li_{$k}_price"] = (string) ( $item->price->amount->isPositive() ? $item->price->amount : $item->price->amount->multiply( new \IPS\Math\Number( '-1' ) ) );
$data["li_{$k}_tangible"] = $item->physical ? 'Y' : 'N';
$d = 0;
foreach ( $item->details as $label => $value )
@@ -109,7 +131,7 @@ class _TwoCheckout extends \IPS\nexus\Gateway
$data["li_{$k}_type"] = 'shipping';
$data["li_{$k}_name"] = $shipping->name;
$data["li_{$k}_quantity"] = 1;
$data["li_{$k}_price"] = $shipping->price->amount;
$data["li_{$k}_price"] = (string) $shipping->price->amount;
$data["li_{$k}_tangible"] = 'Y';
$k++;
@@ -117,20 +139,13 @@ class _TwoCheckout extends \IPS\nexus\Gateway
foreach ( $summary['tax'] as $tax )
{
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $tax['name'] );
$data["li_{$k}_type"] = 'tax';
$data["li_{$k}_name"] = $tax['name'];
$data["li_{$k}_quantity"] = 1;
$data["li_{$k}_price"] = $tax['amount']->amount;
$data["li_{$k}_price"] = (string) $tax['amount']->amount;
$data["li_{$k}_tangible"] = 'N';
$d = 0;
foreach ( $item['details'] as $label => $value )
{
$data["li_{$k}_option_{$d}_name"] = $label;
$data["li_{$k}_option_{$d}_value"] = $value;
$d++;
}
$k++;
}
}
@@ -139,7 +154,7 @@ class _TwoCheckout extends \IPS\nexus\Gateway
$data["li_0_type"] = 'product';
$data["li_0_name"] = $transaction->member->language()->addToStack( 'partial_payment_desc', FALSE, array( 'sprintf' => array( $transaction->invoice->id ) ) );
$data["li_0_quantity"] = 1;
$data["li_0_price"] = $transaction->amount->amount;
$data["li_0_price"] = (string) $transaction->amount->amount;
$data["li_0_tangible"] = 'N';
}
@@ -175,7 +190,7 @@ class _TwoCheckout extends \IPS\nexus\Gateway
}
$settings = json_decode( $this->settings, TRUE );
$response = \IPS\Http\Url::external( 'https://www.2checkout.com/api/sales/refund_invoice' )->request()->login( $settings['api_username'], $settings['api_password'] )->post( $data )->decodeJson();
$response = \IPS\Http\Url::external( 'https://www.2checkout.com/api/sales/refund_invoice' )->request()->forceTls()->login( $settings['api_username'], $settings['api_password'] )->post( $data )->decodeJson();
if ( $response['response_code'] != 'OK' )
{
@@ -207,15 +222,29 @@ class _TwoCheckout extends \IPS\nexus\Gateway
* @throws \InvalidArgumentException
*/
public function testSettings( $settings )
{
$response = \IPS\Http\Url::external( 'https://www.2checkout.com/api/acct/detail_company_info' )->request()->setHeaders( array( 'Accept' => 'application/json' ) )->login( $settings['api_username'], $settings['api_password'] )->get()->decodeJson();
if ( isset( $response['errors'] ) )
{
try
{
throw new \LogicException( $response['errors'][0]['message'] );
$response = \IPS\Http\Url::external( 'https://www.2checkout.com/api/acct/detail_company_info' )->request()->forceTls()->setHeaders( array( 'Accept' => 'application/json' ) )->login( $settings['api_username'], $settings['api_password'] )->get()->decodeJson();
if ( isset( $response['errors'] ) )
{
throw new \InvalidArgumentException( $response['errors'][0]['message'] );
}
if ( $response['response_code'] != 'OK' )
{
throw new \InvalidArgumentException( $response['response_message'] );
}
}
if ( $response['response_code'] != 'OK' )
catch ( \IPS\Http\Request\Exception $e )
{
throw new \LogicException( $response['response_message'] );
if ( \OPENSSL_VERSION_NUMBER < 268439647 )
{
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'twocheckout_openssl_req' ) );
}
else
{
throw $e;
}
}
$settings['sid'] = $response['vendor_company_info']['vendor_id'];
+28 -5
View File
@@ -2,9 +2,9 @@
/**
* @brief Hosting Account Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 6 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -80,13 +80,13 @@ class _Account extends \IPS\Patterns\ActiveRecord
{
/* Set a random password */
$password = '';
foreach ( range( 1, 12 ) as $j )
foreach ( range( 1, 20 ) as $j )
{
do
{
$chr = rand( 33, 122 );
}
while ( in_array( $chr, array( 34, 37, 38, 39, 44, 46, 47, 58, 59, 60, 61, 62, 64, 91, 92, 93, 94, 96 ) ) );
while ( in_array( $chr, array( 38, 43, 47, 64, 92, 93, 94 ) ) );
$password .= chr( $chr );
}
@@ -125,7 +125,7 @@ class _Account extends \IPS\Patterns\ActiveRecord
*/
public function set_domain( $domain )
{
$this->_data['account_domain'] = $domain;
$this->_data['account_domain'] = mb_strtolower( $domain );
}
/**
@@ -367,6 +367,18 @@ class _Account extends \IPS\Patterns\ActiveRecord
throw new \IPS\nexus\Hosting\Exception( $this, 'DUMMY_SERVER' );
}
/**
* Change Domain
*
* @param string $domain New Domain
* @param bool $keepOld If true, will park this domain, keeping the old domain as the main domain (rather than just changing the main domain)
* @return void
*/
public function changeDomain( $domain, $park=FALSE )
{
throw new \IPS\nexus\Hosting\Exception( $this, 'DUMMY_SERVER' );
}
/**
* Edit Priviledges
*
@@ -402,6 +414,17 @@ class _Account extends \IPS\Patterns\ActiveRecord
throw new \IPS\nexus\Hosting\Exception( $this, 'DUMMY_SERVER' );
}
/**
* Park a domain
*
* @param string $domain Domain to be parked
* @return void
*/
public function parkDomain( $domain )
{
throw new \IPS\nexus\Hosting\Exception( $this, 'DUMMY_SERVER' );
}
/**
* Suspend
*
@@ -2,9 +2,9 @@
/**
* @brief Hosting Account Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 6 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -36,7 +36,7 @@ class _Account extends \IPS\nexus\Hosting\Account
$response = $this->server->api( 'createacct', array(
'username' => $this->username,
'domain' => $this->domain,
'quota' => ( $package->quota == -1 ) ? 'unlimited' : $package->quota,
'quota' => ( $package->quota == -1 ) ? 'unlimited' : floor( $package->quota * 1.024 ),
'password' => $this->password,
'ip' => $package->ip ? 'y' : 'n',
'cgi' => $package->cgi,
@@ -50,7 +50,7 @@ class _Account extends \IPS\nexus\Hosting\Account
'maxsub' => ( $package->maxsub == -1 ) ? 'unlimited' : $package->maxsub,
'maxpark' => ( $package->maxpark == -1 ) ? 'unlimited' : $package->maxpark,
'maxaddon' => ( $package->maxaddon == -1 ) ? 'unlimited' : $package->maxaddon,
'bwlimit' => ( $package->bwlimit == -1 ) ? 'unlimited' : $package->bwlimit,
'bwlimit' => ( $package->bwlimit == -1 ) ? 'unlimited' : floor( $package->bwlimit * 1.024 ),
) );
if ( !$response['result'][0]['status'] )
@@ -95,7 +95,7 @@ class _Account extends \IPS\nexus\Hosting\Account
{
$information = $this->summary();
return ( $information['disklimit'] === 'unlimited' ) ? NULL : ( intval( $information['disklimit'] ) * 1000000 );
return ( $information['disklimit'] === 'unlimited' ) ? NULL : ( intval( $information['disklimit'] ) * 1048576 );
}
/**
@@ -107,7 +107,7 @@ class _Account extends \IPS\nexus\Hosting\Account
{
$information = $this->summary();
return intval( $information['diskused'] ) * 1000000;
return intval( $information['diskused'] ) * 1048576;
}
/**
@@ -132,8 +132,8 @@ class _Account extends \IPS\nexus\Hosting\Account
{
if ( $data['user'] == $this->username )
{
$this->monthlyBandwidthAllowance = $data['limit'] / 1048576 * 1000000;
$this->bandwidthUsedThisMonth = $data['totalbytes'] / 1048576 * 1000000;
$this->monthlyBandwidthAllowance = $data['limit'];
$this->bandwidthUsedThisMonth = $data['totalbytes'];
break;
}
}
@@ -265,6 +265,29 @@ class _Account extends \IPS\nexus\Hosting\Account
return \IPS\Http\Url::external( 'https://' . $this->server->hostname . ':2083' );
}
/**
* Change Domain
*
* @param string $domain New Domain
* @param bool $keepOld If true, will park this domain, keeping the old domain as the main domain (rather than just changing the main domain)
* @return void
*/
public function changeDomain( $domain, $park=FALSE )
{
if ( $park )
{
return $this->parkDomain( $domain );
}
else
{
$response = $this->server->api( 'modifyacct', array( 'user' => $this->username, 'DNS' => $domain ) );
if( !$response['result'][0]['status'] )
{
throw new \IPS\nexus\Hosting\Exception( $this->server, $response['result'][0]['statusmsg'] );
}
}
}
/**
* Edit Priviledges
*
@@ -274,63 +297,52 @@ class _Account extends \IPS\nexus\Hosting\Account
public function edit( $values )
{
/* Init */
$data = array(
'user' => $this->username,
'DNS' => $values['account_domain'],
'QUOTA' => ( $values['p_quota'] != -1 ) ? ( $values['p_quota'] * 1000000 ) : 'unlimited',
'MAXFTP' => ( $values['p_maxftp'] != -1 ) ? ( $values['p_maxftp'] ) : 'unlimited',
'MAXSQL' => ( $values['p_maxsql'] != -1 ) ? ( $values['p_maxsql'] ) : 'unlimited',
'MAXPOP' => ( $values['p_maxpop'] != -1 ) ? ( $values['p_maxpop'] ) : 'unlimited',
'MAXLST' => ( $values['p_maxlst'] != -1 ) ? ( $values['p_maxlst'] ) : 'unlimited',
'MAXSUB' => ( $values['p_maxsub'] != -1 ) ? ( $values['p_maxsub'] ) : 'unlimited',
'MAXPARK' => ( $values['p_maxpark'] != -1 ) ? ( $values['p_maxpark'] ) : 'unlimited',
'MAXADDON' => ( $values['p_maxaddon'] != -1 ) ? ( $values['p_maxaddon'] ) : 'unlimited',
'HASSHELL' => intval( $values['p_hasshell'] )
);
$data = array( 'user' => $this->username );
/* Making quota or bandwidth unlimited? */
if ( $values['p_quota'] == -1 )
/* Set data */
foreach ( array( 'DNS' => 'account_domain', 'QUOTA' => 'p_quota', 'MAXFTP' => 'p_maxftp', 'MAXSQL' => 'p_maxsql', 'MAXPOP' => 'p_maxpop', 'MAXLST' => 'p_maxlst', 'MAXSUB' => 'p_maxsub', 'MAXPARK' => 'p_maxpark', 'MAXADDON' => 'p_maxaddon', 'HASSHELL' => 'p_hasshell' ) as $cpanelKey => $formKey )
{
$data['QUOTA'] = 'unlimited';
}
if ( $values['p_bwlimit'] == -1 )
{
$data['BWLIMIT'] = 'unlimited';
}
if ( isset( $values[ $formKey ] ) )
{
if ( $values[ $formKey ] == -1 )
{
$values[ $formKey ] = 'unlimited';
}
elseif ( $cpanelKey === 'QUOTA' )
{
$values[ $formKey ] = $values[ $formKey ] * 1048576 * 1.024;
}
$data[ $cpanelKey ] = $values[ $formKey ];
}
}
/* Has the username changed? */
if ( $values['account_username'] != $this->username )
if ( isset( $values['account_username'] ) AND $values['account_username'] != $this->username )
{
$data['newuser'] = $values['account_username'];
}
/* Send */
$response = $this->server->api( 'modifyacct', $data );
if( !$response['result'][0]['status'] )
{
throw new \IPS\nexus\Hosting\Exception( $this->server, $response['result'][0]['statusmsg'] );
}
/* Change quota? */
if ( $values['p_quota'] != -1 and ( $this->diskspaceAllowance() === NULL or $values['p_quota'] * 1000000 != $this->diskspaceAllowance() ) )
{
$response = $this->server->api( 'editquota', array( 'user' => $values['account_username'], 'quota' => $values['p_quota'] ) );
if( !$response['result'][0]['status'] )
{
throw new \IPS\nexus\Hosting\Exception( $this->server, $response['result'][0]['statusmsg'] );
}
}
/* Change bandwidth limit? */
if ( ( $values['p_bwlimit'] != -1 and ( $this->monthlyBandwidthAllowance() === NULL or $values['p_bwlimit'] * 1000000 != $this->monthlyBandwidthAllowance() ) ) or ( $values['p_bwlimit'] != -1 and $this->monthlyBandwidthAllowance() !== NULL ) )
if ( isset( $values['p_bwlimit'] ) )
{
$this->changeBandwidthLimit( $values['p_bwlimit'] * 1000000, $values['account_username'] );
if ( ( $values['p_bwlimit'] == -1 and $this->monthlyBandwidthAllowance() !== NULL ) or ( $values['p_bwlimit'] != -1 and $values['p_bwlimit'] != ( $this->monthlyBandwidthAllowance() / 1048576 / 1.024 ) ) )
{
$this->changeBandwidthLimit( $values['p_bwlimit'] * 1048576 / 1.024, $values['account_username'] );
}
}
/* Change password? */
if ( $values['account_password'] != (string) $this->password )
if ( isset( $values['account_password'] ) and $values['account_password'] != (string) $this->password )
{
$this->changePassword( $values['account_password'], $values['account_username'] );
$this->changePassword( $values['account_password'], $this->username );
}
}
@@ -366,6 +378,27 @@ class _Account extends \IPS\nexus\Hosting\Account
}
}
/**
* Park a domain
*
* @param string $domain Domain to be parked
* @return void
*/
public function parkDomain( $domain )
{
$response = $this->server->api( 'cpanel', array(
'cpanel_jsonapi_user' => $this->username,
'cpanel_jsonapi_apiversion' => 2,
'cpanel_jsonapi_module' => 'Park',
'cpanel_jsonapi_func' => 'park',
'domain' => $domain
) );
if( !$response['cpanelresult']['data'][0]['result'] )
{
throw new \IPS\nexus\Hosting\Exception( $this->server, $response['cpanelresult']['data'][0]['reason'] );
}
}
/**
* Suspend
*
@@ -2,9 +2,9 @@
/**
* @brief CPanel/WHM Server Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 6 Aug 2014
* @version SVN_VERSION_NUMBER
+4 -4
View File
@@ -2,9 +2,9 @@
/**
* @brief Expected Output Monitoring Rule
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 12 Sep 2014
* @version SVN_VERSION_NUMBER
@@ -73,7 +73,7 @@ class _EOM extends \IPS\Node\Model
*/
public function get__title()
{
return $this->url;
return (string) $this->url;
}
/**
@@ -115,6 +115,6 @@ class _EOM extends \IPS\Node\Model
*/
public function postSaveForm( $values )
{
\IPS\DB::i()->update( 'core_tasks', array( 'enabled' => 1 ), "`key`='expectedOutputMonitoring'" );
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), "`key`='expectedOutputMonitoring'" );
}
}
@@ -2,9 +2,9 @@
/**
* @brief Hosting Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -64,7 +64,7 @@ class _Exception extends \RuntimeException
{
if ( \IPS\Settings::i()->nexus_hosting_error_emails )
{
\IPS\Email::buildFromTemplate( 'nexus', 'hostingError', array( $this->server, $this->getMessage(), \IPS\Http\Url::internal( '&app=nexus&module=hosting&controller=errors', 'admin' )->stripQueryString('adsess') ) )->send( explode( ',', \IPS\Settings::i()->nexus_hosting_error_emails ) );
\IPS\Email::buildFromTemplate( 'nexus', 'hostingError', array( $this->server, $this->getMessage(), \IPS\Http\Url::internal( '&app=nexus&module=hosting&controller=errors', 'admin' )->stripQueryString('adsess') ), \IPS\Email::TYPE_LIST )->send( explode( ',', \IPS\Settings::i()->nexus_hosting_error_emails ) );
}
\IPS\Db::i()->insert( 'nexus_hosting_errors', array(
@@ -2,9 +2,9 @@
/**
* @brief Dummy Server Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 6 Aug 2014
* @version SVN_VERSION_NUMBER
+22 -5
View File
@@ -2,9 +2,9 @@
/**
* @brief Hosting Queue Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 6 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -122,9 +122,26 @@ class _Queue extends \IPS\Node\Model
$where[] = array( 'server_type<>?', 'none' );
}
$serverData = \IPS\Db::i()->select( 'nexus_hosting_servers.*, COUNT(nexus_hosting_accounts.ps_id) AS accounts', 'nexus_hosting_servers', $where, 'accounts ASC', NULL, 'server_id' )->join( 'nexus_hosting_accounts', 'account_server=server_id AND account_exists=1' )->first();
unset( $serverData['accounts'] );
$serverLimits = iterator_to_array( \IPS\Db::i()->select( array( 'server_id', 'server_max_accounts' ), 'nexus_hosting_servers', $where )->setKeyField('server_id')->setValueField('server_max_accounts') );
$serverWithLeastAccounts = NULL;
foreach ( \IPS\Db::i()->select( 'nexus_hosting_servers.server_id, COUNT(nexus_hosting_accounts.ps_id) AS accounts', 'nexus_hosting_servers', $where, 'accounts ASC', NULL, 'server_id' )->join( 'nexus_hosting_accounts', 'account_server=server_id AND account_exists=1' ) as $serverData )
{
if ( !$serverWithLeastAccounts )
{
$serverWithLeastAccounts = $serverData['server_id'];
}
if ( !isset( $serverLimits[ $serverData['server_id'] ] ) or !$serverLimits[ $serverData['server_id'] ] or $serverLimits[ $serverData['server_id'] ] > $serverData['accounts'] )
{
return \IPS\nexus\Hosting\Server::load( $serverData['server_id'] );
}
}
return \IPS\nexus\Hosting\Server::constructFromData( $serverData );
if ( $serverWithLeastAccounts )
{
return \IPS\nexus\Hosting\Server::load( $serverWithLeastAccounts );
}
throw new \UnderflowException;
}
}
+13 -8
View File
@@ -2,9 +2,9 @@
/**
* @brief Hosting Server Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 6 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -46,6 +46,11 @@ class _Server extends \IPS\Node\Model
*/
protected static $databaseIdFields = array( 'server_hostname', 'server_dedicated' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* Construct ActiveRecord from database row
*
@@ -185,7 +190,7 @@ class _Server extends \IPS\Node\Model
$form->add( new \IPS\Helpers\Form\Stack( 'server_nameservers', $nameservers, NULL, array( 'placeholder' => 'ns.example.net' ), NULL, NULL, NULL, 'server_nameservers' ) );
$form->addHeader( 'server_reporting' );
$form->add( new \IPS\Helpers\Form\YesNo( 'server_monitor_on', \IPS\Settings::i()->monitoring_script and $this->monitor, FALSE, array( 'disabled' => !\IPS\Settings::i()->monitoring_script, 'togglesOn' => array( 'server_monitor' ) ) ) );
$form->add( new \IPS\Helpers\Form\Url( 'server_monitor', $this->monitor, NULL, array( 'placeholder' => 'http://server.example.net/monitor_remote.php' ), function( $val )
$form->add( new \IPS\Helpers\Form\Url( 'server_monitor', $this->monitor ?: '', NULL, array( 'placeholder' => 'http://server.example.net/monitor_remote.php' ), function( $val )
{
if ( !$val and \IPS\Request::i()->sever_monitor_on )
{
@@ -272,7 +277,7 @@ class _Server extends \IPS\Node\Model
{
foreach ( explode( ',', \IPS\Settings::i()->monitoring_alert ) as $address )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_online', array( $this, urlencode( $address ) ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_online', array( $this, urlencode( $address ) ), \IPS\Email::TYPE_LIST );
$email->send( $address );
}
}
@@ -286,7 +291,7 @@ class _Server extends \IPS\Node\Model
{
foreach ( explode( ',', \IPS\Settings::i()->monitoring_alert ) as $address )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_offline', array( $this, urlencode( $address ) ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_offline', array( $this, urlencode( $address ) ), \IPS\Email::TYPE_LIST );
$email->send( $address );
}
}
@@ -300,7 +305,7 @@ class _Server extends \IPS\Node\Model
{
foreach ( explode( ',', \IPS\Settings::i()->monitoring_alert ) as $address )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_panic', array( $this, urlencode( $address ) ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_panic', array( $this, urlencode( $address ) ), \IPS\Email::TYPE_LIST );
$email->send( $address );
}
}
@@ -315,7 +320,7 @@ class _Server extends \IPS\Node\Model
{
foreach ( explode( ',', \IPS\Settings::i()->monitoring_alert ) as $address )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_acknowledged', array( $this, urlencode( $address ), $by ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_acknowledged', array( $this, urlencode( $address ), $by ), \IPS\Email::TYPE_LIST );
$email->send( $address );
}
}
@@ -330,7 +335,7 @@ class _Server extends \IPS\Node\Model
{
foreach ( explode( ',', \IPS\Settings::i()->monitoring_alert ) as $address )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_reset', array( $this, urlencode( $address ), $by ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'monitoring_reset', array( $this, urlencode( $address ), $by ), \IPS\Email::TYPE_LIST );
$email->send( $address );
}
}
+406 -115
View File
@@ -2,9 +2,9 @@
/**
* @brief Invoice Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -211,6 +211,12 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
try
{
$this->_member = \IPS\nexus\Customer::load( $this->_data['member'] );
/* \IPS\nexus\Customer does not necessarily throw an OutOfRangeException if passed a value of 0 (a guest instance is valid) - throw one here so we can bubble up to get the correct information for guests. */
if ( !$this->_member->member_id )
{
throw new \OutOfRangeException;
}
}
catch ( \OutOfRangeException $e )
{
@@ -274,13 +280,13 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
return is_array( $val ) and isset( $val['act'] );
} );
if ( !$itemsJson and $this->total->amount )
if ( !$itemsJson and $this->total->amount->isGreaterThanZero() )
{
$itemsJson = array( array(
'act' => 'charge',
'app' => 'nexus',
'type' => 'charge',
'cost' => $this->total->amount,
'cost' => $this->total->amountAsString(),
'tax' => NULL,
'quantity' => 1,
'itemName' => $this->title,
@@ -291,7 +297,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'extra' => NULL,
) );
}
/* Return it */
$this->_itemsIterator = new \IPS\nexus\Invoice\ItemsIterator( $itemsJson );
$this->_itemsIterator->currency = $this->currency;
@@ -470,14 +476,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
else
{
try
{
return \IPS\GeoLocation::buildFromJson( \IPS\Db::i()->select( 'address', 'nexus_customer_addresses', array( 'member=? AND primary_billing=1', $this->member->member_id ) )->first() );
}
catch ( \UnderflowException $e )
{
return NULL;
}
return NULL;
}
}
@@ -514,6 +513,28 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$this->_data['guest_data'] = $data ? json_encode( $data ) : NULL;
}
/**
* Set a title automatically based on item names
*
* @return void
*/
public function setDefaultTitle()
{
$titles = array();
foreach ( $this->items as $item )
{
$titles[] = ( $item->name . ( $item->quantity > 1 ? " x{$item->quantity}" : '' ) );
}
$thisTitle = implode( ', ', $titles );
if ( mb_strlen( $thisTitle ) > 128 )
{
$thisTitle = mb_substr( $thisTitle, 0, 125 ) . '...';
}
$this->title = $thisTitle;
}
/**
* Save Changed Columns
*
@@ -523,19 +544,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
if ( !$this->title )
{
$titles = array();
foreach ( $this->items as $item )
{
$titles[] = ( $item->name . ( $item->quantity > 1 ? " x{$item->quantity}" : '' ) );
}
$thisTitle = implode( ', ', $titles );
if ( mb_strlen( $thisTitle ) > 128 )
{
$thisTitle = mb_substr( $thisTitle, 0, 125 ) . '...';
}
$this->title = $thisTitle;
$this->setDefaultTitle();
}
if ( $this->_new )
@@ -556,9 +565,11 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
public function delete()
{
\IPS\File::unclaimAttachments( 'nexus_Purchases', NULL, NULL, "invoice-{$this->id}" );
parent::delete();
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW ) ) as $transaction )
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ) ) as $transaction )
{
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PENDING )
{
@@ -596,7 +607,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'act' => $item::$act,
'app' => isset( $item::$application ) ? $item::$application : $item->application,
'type' => isset( $item::$type ) ? $item::$type : $item->type,
'cost' => $item->price->amount,
'cost' => $item->price->amountAsString(),
'tax' => $item->tax ? $item->tax->id : NULL,
'quantity' => $item->quantity,
'itemName' => $item->name,
@@ -629,7 +640,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$renewTermData = $item->renewalTerm->getTerm();
$data['renew_term'] = $renewTermData['term'];
$data['renew_units'] = $renewTermData['unit'];
$data['renew_cost'] = $item->renewalTerm->cost->amount;
$data['renew_cost'] = $item->renewalTerm->cost->amountAsString();
if ( $item->renewalTerm->gracePeriod )
{
$data['grace_period'] = \IPS\DateTime::create()->add( $item->renewalTerm->gracePeriod )->getTimestamp() - time();
@@ -748,10 +759,61 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$return = array();
$return['items'] = array();
$shippingItems = array();
$subtotal = 0;
$subtotal = new \IPS\Math\Number('0');
$taxableAmounts = array();
$taxClasses = \IPS\nexus\Tax::roots();
/* Reduce taxable amount from coupons */
$couponTaxReductions = array();
foreach ( $this->items as $itemId => $item )
{
if ( $item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount )
{
if ( isset( $item->extra['type'] ) )
{
if ( isset( $item->extra['items'] ) )
{
$itemCount = count( $item->extra['items'] );
}
else
{
$itemCount = 0;
foreach ( clone $this->items as $k => $_item ) // We have to clone because $items is an iterator, and we're already looping them, but we need a separate loop
{
if ( $_item != $item and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\ShippingCharge ) and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount ) )
{
$itemCount++;
}
}
}
foreach ( clone $this->items as $k => $_item ) // We have to clone because $items is an iterator, and we're already looping them, but we need a separate loop
{
if ( $_item != $item and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount ) and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\ShippingCharge ) and ( !isset( $item->extra['items'] ) or in_array( $k, $item->extra['items'] ) ) )
{
if ( $item->extra['type'] == 'p' )
{
$reductionAmount = $_item->linePrice()->amount->multiply( new \IPS\Math\Number("{$item->extra['value']}") )->divide( new \IPS\Math\Number('100') );
}
else
{
$reductionAmount = ( new \IPS\Math\Number( "{$item->extra['value']}" ) )->divide( new \IPS\Math\Number( "{$itemCount}" ) )->multiply( new \IPS\Math\Number( '-1' ) );
}
if ( isset( $couponTaxReductions[ $k ] ) )
{
$couponTaxReductions[ $k ] = $couponTaxReductions[ $k ]->add( $reductionAmount );
}
else
{
$couponTaxReductions[ $k ] = $reductionAmount;
}
}
}
}
}
}
/* Work out the line totals */
foreach ( $this->items as $k => $item )
{
@@ -761,16 +823,24 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
else
{
$subtotal += $item->linePrice()->amount;
$subtotal = $subtotal->add( $item->linePrice()->amount );
if ( isset( $item->tax ) )
{
$taxAmount = $item->linePrice()->amount;
if ( isset( $couponTaxReductions[ $k ] ) )
{
$taxAmount = $taxAmount->subtract( $couponTaxReductions[ $k ] );
}
if ( isset( $taxableAmounts[ $item->tax->id ] ) )
{
$taxableAmounts[ $item->tax->id ] += $item->linePrice()->amount;
$taxableAmounts[ $item->tax->id ] = $taxableAmounts[ $item->tax->id ]->add( $taxAmount );
}
else
{
$taxableAmounts[ $item->tax->id ] = $item->linePrice()->amount;
$taxableAmounts[ $item->tax->id ] = $taxAmount;
}
}
@@ -783,17 +853,17 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
/* Add shipping */
$return['shipping'] = array();
$shipping = 0;
$shipping = new \IPS\Math\Number('0');
foreach ( $shippingItems as $k => $item )
{
$linePrice = $item->linePrice()->amount;
$shipping += $linePrice;
$shipping = $shipping->add( $linePrice );
if ( isset( $item->tax ) )
{
if ( isset( $taxableAmounts[ $item->tax->id ] ) )
{
$taxableAmounts[ $item->tax->id ] += $linePrice;
$taxableAmounts[ $item->tax->id ] = $taxableAmounts[ $item->tax->id ]->add( $linePrice );
}
else
{
@@ -807,29 +877,45 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
/* Now work out the tax */
$return['tax'] = array();
$tax = 0;
$tax = new \IPS\Math\Number('0');
foreach ( $taxableAmounts as $taxId => $amount )
{
$rate = $taxClasses[ $taxId ]->rate( $this->billaddress );
$amount = $amount * $rate;
$amount = $amount->multiply( new \IPS\Math\Number( $rate ) );
$amount = new \IPS\nexus\Money( $amount, $this->currency );
$amount = $amount->round(); // Tax value may be something like $1.005, but needs to be a valid currency amount. Always rounds up.
$return['tax'][ $taxId ] = array( 'name' => $taxClasses[ $taxId ]->_title, 'rate' => $rate, 'amount' => $amount );
$tax += $amount->amount;
$tax = $tax->add( $amount->amount );
}
$return['taxTotal'] = new \IPS\nexus\Money( $tax, $this->currency );
/* Add it all together */
$return['total'] = new \IPS\nexus\Money( $subtotal + $shipping + $tax, $this->currency );
$return['total'] = $return['total']->round(); // Total value must also be a valid currency amount
$return['total'] = new \IPS\nexus\Money( $subtotal->add( $shipping )->add( $tax ), $this->currency );
return $return;
}
/* !Information */
/**
* Does the invoice contain items that require a billing address?
*
* @return bool
*/
public function hasItemsRequiringBillingAddress()
{
foreach ( $this->items as $item )
{
if ( $item->requiresBillingAddress() )
{
return TRUE;
}
}
return FALSE;
}
/**
* Does the invoice contain physical items that need shipping?
*
@@ -881,23 +967,23 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
if ( !isset( $recipients[ $item->payTo->member_id ] ) )
{
$recipients[ $item->payTo->member_id ] = 0;
$recipients[ $item->payTo->member_id ] = new \IPS\Math\Number('0');
}
$itemRecipientAmounts = $item->recipientAmounts();
$recipients[ $item->payTo->member_id ] += $itemRecipientAmounts['recipient_final']->amount;
$recipients[ $item->payTo->member_id ] = $recipients[ $item->payTo->member_id ]->add( $itemRecipientAmounts['recipient_final']->amount );
}
}
return $recipients;
}
/**
* Get the commission information
*
* @param float $amountReceived The amount the invoice is for, minus amounts being given to other recipients
* @param \IPS\Math\Number $amountReceived The amount the invoice is for, minus amounts being given to other recipients
* @return array( 'rule' => \IPS\nexus\CommissionRule, 'referrer' => \IPS\nexus\Customer, 'amount' => \IPS\nexus\Money )
*/
public function commission( $amountReceived )
public function commission( \IPS\Math\Number $amountReceived )
{
if ( !isset( $this->status_extra['commissionrule'] ) )
{
@@ -917,19 +1003,19 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$forAmounts = iterator_to_array( \IPS\Db::i()->select( 'i_currency, SUM( i_total ) as value', 'nexus_invoices', array( "i_member=? AND i_status=?", $this->member->member_id, static::STATUS_PAID ), NULL, NULL, 'i_currency' )->setKeyField( 'i_currency' )->setValueField( 'value' ) );
/* Loop the rules to find out the best one */
$maxAmount = 0;
$maxAmount = new \IPS\Math\Number('0');
$actingRule = NULL;
foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_referral_rules', array( array( "rrule_by_group='*' OR " . \IPS\Db::i()->findInSet( 'rrule_by_group', $referrer->groups ) ), array( "rrule_for_group='*' OR " . \IPS\Db::i()->findInSet( 'rrule_for_group', $this->member->groups ) ) ) ), 'IPS\nexus\CommissionRule' ) as $commissionRule )
{
/* Before we go nuts, don't bother if it's not going to give us more commission */
if ( round( ( ( $amountReceived / 100 ) * $commissionRule->commission ), 2 ) <= $maxAmount )
if ( $amountReceived->percentage( $commissionRule->commission )->compare( $maxAmount ) === -1 )
{
continue;
}
if ( $commissionRule->commission_limit )
{
$commisionLimit = json_decode( $commissionRule->commission_limit, TRUE );
if ( isset( $commisionLimit[ $this->currency ] ) and $commisionLimit[ $this->currency ]['amount'] <= $maxAmount )
if ( isset( $commisionLimit[ $this->currency ] ) and ( new \IPS\Math\Number( number_format( $commisionLimit[ $this->currency ]['amount'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) ) )->compare( $maxAmount ) === -1 )
{
continue;
}
@@ -984,7 +1070,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
if ( $commissionRule->purchase_package_limit )
{
$amountToApplyCommissionOn = 0;
$amountToApplyCommissionOn = new \IPS\Math\Number('0');
}
$itemsToCount = array();
@@ -995,7 +1081,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
$packageId = $item->id;
}
elseif ( $commissionRule->purchase_renewal and $item instanceof \IPS\nexus\Invoice\Item\Renewal and $item->application == 'nexus' and $item->type == 'package' )
elseif ( $commissionRule->purchase_renewal and $item instanceof \IPS\nexus\Invoice\Item\Renewal and $item::$application == 'nexus' and $item::$type == 'package' )
{
try
{
@@ -1017,14 +1103,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
if ( $commissionRule->purchase_package_limit )
{
if ( isset( $amountReceivedForItem[ $k ] ) )
{
$amountToApplyCommissionOn += $amountReceivedForItem[ $k ];
}
else
{
$amountToApplyCommissionOn += ( $item->price->amount * $item->quantity );
}
$amountToApplyCommissionOn = $amountToApplyCommissionOn->add( $item->price->amount->multiply( new \IPS\Math\Number( "{$item->quantity}" ) ) );
}
}
@@ -1049,18 +1128,18 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
/* If we're still here, we're applying commission */
$amountToGive = round( ( ( $amountToApplyCommissionOn / 100 ) * $commissionRule->commission ), 2 );
$amountToGive = $amountToApplyCommissionOn->percentage( $commissionRule->commission );
if ( $commissionRule->commission_limit and $commissionRule->commission_limit != '*' )
{
$limit = json_decode( $commissionRule->commission_limit, TRUE );
if ( isset( $limit[ $this->currency ] ) and $amountToGive > $limit[ $this->currency ]['amount'] )
if ( isset( $limit[ $this->currency ] ) and $amountToGive->compare( new \IPS\Math\Number( number_format( $limit[ $this->currency ]['amount'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) ) ) === 1 )
{
$amountToGive = $limit[ $this->currency ]['amount'];
$amountToGive = new \IPS\Math\Number( number_format( $limit[ $this->currency ]['amount'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) );
}
}
/* Is this more than what we have? */
if ( ( $amountToGive > $maxAmount ) or ( $amountToGive and !$maxAmount ) )
if ( ( $amountToGive->compare( $maxAmount ) === 1 ) or ( $amountToGive->isGreaterThanZero() and !$maxAmount->isGreaterThanZero() ) )
{
$maxAmount = $amountToGive;
$actingRule = $commissionRule;
@@ -1086,11 +1165,25 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$this->save();
}
return array(
'rule' => $this->status_extra['commissionrule'] ? \IPS\nexus\CommissionRule::load( $this->status_extra['commissionrule'] ) : NULL,
'referrer' => $this->status_extra['commissionref'] ? \IPS\nexus\Customer::load( $this->status_extra['commissionref'] ) : NULL,
'amount' => $this->status_extra['commissionamount'] ? new \IPS\nexus\Money( $this->status_extra['commissionamount'], $this->currency ) : NULL
);
try
{
return array(
'rule' => $this->status_extra['commissionrule'] ? \IPS\nexus\CommissionRule::load( $this->status_extra['commissionrule'] ) : NULL,
'referrer' => $this->status_extra['commissionref'] ? \IPS\nexus\Customer::load( $this->status_extra['commissionref'] ) : NULL,
'amount' => $this->status_extra['commissionamount'] ? new \IPS\nexus\Money( $this->status_extra['commissionamount'], $this->currency ) : NULL
);
}
catch( \OutOfRangeException $e )
{
/* If either the commission rule, or the commission ref, are no longer valid, then let's try again and see if it matches up with a different one. */
$extra = $this->status_extra;
unset( $extra['commissionrule'], $extra['commissionref'], $extra['commissionamount'] );
$this->status_extra = $extra;
$this->save();
return $this->commission( $amountReceived );
}
}
/**
@@ -1103,17 +1196,44 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
protected function _checkCondition( $a, $operator, $b )
{
$a = floatval( $a );
$b = floatval( $b );
if ( $a instanceof \IPS\Math\Number and !( $b instanceof \IPS\Math\Number ) )
{
$b = new \IPS\Math\Number( "{$b}" );
}
if ( !( $a instanceof \IPS\Math\Number ) and $b instanceof \IPS\Math\Number )
{
$a = new \IPS\Math\Number( "{$a}" );
}
switch ( $operator )
{
case 'g':
return $a > $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 1;
}
else
{
return $a > $b;
}
case 'e':
return $a == $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 0;
}
else
{
return $a == $b;
}
case 'l':
return $a < $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 11;
}
else
{
return $a < $b;
}
}
return FALSE;
}
@@ -1123,19 +1243,33 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*
* @return \IPS\nexus\Money
*/
public function amountToPay()
public function amountToPay( $deductHeldTransactions=FALSE )
{
$amountToPay = $this->total;
$amountToPay = $this->total;
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ) ) as $transaction )
$statuses = array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED );
if ( $deductHeldTransactions )
{
$statuses[] = \IPS\nexus\Transaction::STATUS_HELD;
$statuses[] = \IPS\nexus\Transaction::STATUS_REVIEW;
$statuses[] = \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING;
}
foreach ( $this->transactions( $statuses ) as $transaction )
{
if ( $transaction->currency === $this->currency )
{
$amountToPay->amount -= ( $transaction->amount->amount - ( $transaction->status === \IPS\nexus\Transaction::STATUS_PART_REFUNDED ? $transaction->partial_refund->amount : 0 ) );
$amountToSubtract = $transaction->amount->amount;
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PART_REFUNDED )
{
$amountToSubtract = $amountToSubtract->subtract( $transaction->partial_refund->amount );
}
$amountToPay->amount = $amountToPay->amount->subtract( $amountToSubtract );
}
}
return $amountToPay->round();
return $amountToPay;
}
/**
@@ -1149,6 +1283,15 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$url = $this->acpUrl()->setQueryString( 'r', $ref );
$return = array();
if ( $this->status !== \IPS\nexus\Invoice::STATUS_PAID and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
{
$return['edit'] = array(
'title' => 'edit',
'icon' => 'pencil',
'link' => $url->setQueryString( array( 'do' => 'generate', '_new' => 1 ) )
);
}
if ( $this->status === \IPS\nexus\Invoice::STATUS_PENDING )
{
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
@@ -1157,7 +1300,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'title' => 'invoice_mark_paid',
'icon' => 'check',
'link' => $url->setQueryString( 'do', 'paid' ),
'data' => count( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW ) ) ) ? array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('invoice_mark_paid') ) : array( 'confirm' => '' )
'data' => count( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ) ) ) ? array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('invoice_mark_paid') ) : array( 'confirm' => '' )
);
}
if ( count( \IPS\nexus\Gateway::manualChargeGateways() ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'chargetocard' ) )
@@ -1168,7 +1311,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'link' => $url->setQueryString( array( 'do' => 'card', '_new' => 1 ) ),
);
}
if ( isset( $this->member->cm_credits[ $this->currency ] ) and $this->member->cm_credits[ $this->currency ]->amount and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
if ( isset( $this->member->cm_credits[ $this->currency ] ) and $this->member->cm_credits[ $this->currency ]->amount->isGreaterThanZero() and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
{
$return['credit'] = array(
'title' => 'invoice_charge_to_credit',
@@ -1200,6 +1343,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$return['print'] = array(
'title' => 'print',
'icon' => 'print',
'target' => '_blank',
'link' => $url->setQueryString( 'do', 'printout' ),
);
if ( $this->status !== \IPS\nexus\Invoice::STATUS_CANCELED and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
@@ -1253,6 +1397,81 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
return 0.01;
}
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string title Title
* @apiresponse string status 'paid' = Paid; 'pend' = Pending; 'expd' = Expired; 'canc' = Cancelled
* @apiresponse datetime issueDate The date the invoice was issued
* @apiresponse datetime paidDate The date the invoice was paid
* @apiresponse \IPS\nexus\Customer customer Customer
* @apiresponse [\IPS\nexus\Invoice\Item] items Items
* @apiresponse \IPS\nexus\Money subTotal Subtotal of item costs
* @apiresponse [\IPS\nexus\Invoice\Item] shippingCharges Shipping charges
* @apiresponse \IPS\nexus\Money shippingTotal Total cost of shipping charges
* @apiresponse [\IPS\nexus\Invoice\Item\TaxItem] tax Tax charges
* @apiresponse \IPS\nexus\Money taxTotal Total cost of tax charges
* @apiresponse \IPS\nexus\Money total Total
* @apiresponse \IPS\nexus\Money outstanding The outstanding amount to be paid
* @apiresponse \IPS\GeoLocation billingAddress Billing address
* @apiresponse \IPS\GeoLocation shippingAddress Shipping Address
* @apiresponse string poNumber PO Number (set by customer)
* @apiresponse string notes Notes (set by customer)
* @apiresponse [\IPS\nexus\Transaction] transactions Transactions against this invoice
* @apiresponse [\IPS\nexus\Shipping\Order] shipments Shipments generated from this invoice (if paid)
* @apiresponse [\IPS\nexus\Purchase] purchases Purchases generated from this invoice (if paid)
* @apiresponse string viewUrl The URL to where the customer can view this invoice
* @apiresponse string checkoutUrl The URL to where the customer can pay this invoice
*/
public function apiOutput()
{
$summary = $this->summary();
$tax = array();
foreach ( $summary['tax'] as $classId => $data )
{
$tax[] = ( new \IPS\nexus\Invoice\Item\TaxItem( $classId, $data ) )->apiOutput();
}
return array(
'id' => $this->id,
'title' => $this->title,
'status' => $this->status,
'issueDate' => $this->date->rfc3339(),
'paidDate' => $this->paid ? $this->paid->rfc3339() : null,
'customer' => $this->member->apiOutput(),
'items' => array_map( function( $item ){
return $item->apiOutput();
}, $summary['items'] ),
'subTotal' => $summary['subtotal']->apiOutput(),
'shippingCharges' => array_map( function( $item ){
return $item->apiOutput();
}, $summary['shipping'] ),
'shippingTotal' => $summary['shippingTotal']->apiOutput(),
'tax' => $tax,
'taxTotal' => $summary['taxTotal']->apiOutput(),
'total' => $summary['total']->apiOutput(),
'outstanding' => $this->amountToPay()->apiOutput(),
'billingAddress' => $this->billaddress ? $this->billaddress->apiOutput() : null,
'shippingAddress' => $this->shipaddress ? $this->shipaddress->apiOutput() : null,
'poNumber' => $this->po,
'notes' => $this->notes,
'transactions' => array_map( function( $transaction ){
return $transaction->apiOutput();
}, iterator_to_array( $this->transactions() ) ),
'shipments' => array_map( function( $shipment ){
return $shipment->apiOutput();
}, iterator_to_array( $this->shipments() ) ),
'purchases' => array_map( function( $purchase ){
return $purchase->apiOutput();
}, iterator_to_array( $this->purchasesCreated() ) ),
'viewUrl' => (string) $this->url(),
'checkoutUrl' => (string) $this->checkoutUrl(),
);
}
/* !Related Resources */
@@ -1260,11 +1479,11 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
* Get transactions
*
* @param array|null $statuses Statuses to get, or NULL for all
* @param array $where Initial where clause
* @return \IPS\Patterns\ActiveRecordIteator
*/
public function transactions( $statuses=NULL )
public function transactions( $statuses=NULL, $where = array() )
{
$where = array();
$where[] = array( 't_invoice=?', $this->id );
if ( $statuses )
{
@@ -1312,25 +1531,56 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
* @note It is the responsibility of the code calling this to log the action on the customer's account *before* calling this method
*/
public function markPaid( \IPS\Member $member = NULL )
{
{
if ( $this->status !== static::STATUS_PAID )
{
/* Create the member account if this was a guest */
if ( !$this->member->member_id and $this->guest_data )
if ( !$this->member->member_id and $this->guest_data and $this->requiresLogin() )
{
$profileFields = $this->guest_data['profileFields'];
$securityAnswers = isset( $this->guest_data['securityAnswers'] ) ? $this->guest_data['securityAnswers'] : array();
$memberToSave = new \IPS\nexus\Customer;
foreach ( $this->guest_data['member'] as $k => $v )
/* If the member has already been created (the user made two separate purchases with the same data), use that, otherwise create one */
try
{
$memberToSave->_data[ $k ] = $v;
$memberToSave->changed[ $k ] = $v;
$memberToSave = \IPS\nexus\Customer::load( $this->guest_data['member']['email'], 'email' );
}
catch ( \OutOfRangeException $e )
{
$memberToSave = new \IPS\nexus\Customer;
foreach ( $this->guest_data['member'] as $k => $v )
{
$memberToSave->_data[ $k ] = $v;
$memberToSave->changed[ $k ] = $v;
}
$memberToSave->save();
}
$memberToSave->save();
$this->member = $memberToSave;
$this->guest_data = NULL;
$this->save();
/* If we provided answers to security questions in check out, save those */
if ( \IPS\Settings::i()->security_questions_enabled and in_array( \IPS\Settings::i()->security_questions_prompt, array( 'register', 'optional' ) ) )
{
if ( $securityAnswers )
{
foreach ( $securityAnswers as $k => $v )
{
\IPS\Db::i()->insert( 'core_security_answers', array(
'answer_question_id' => $k,
'answer_member_id' => $this->member->member_id,
'answer_answer' => $v
) );
}
$this->member->members_bitoptions['has_security_answers'] = TRUE;
}
else
{
$this->member->members_bitoptions['security_questions_opt_out'] = TRUE;
}
$this->member->save();
}
/* If we've entered an address during checkout, save it */
if ( $this->billaddress !== NULL )
{
@@ -1353,13 +1603,18 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$profileFields['member_id'] = $memberToSave->member_id;
\IPS\Db::i()->replace( 'core_pfields_content', $profileFields );
/* Notify the incoming mail address */
if( \IPS\Settings::i()->new_reg_notify )
/* Validation */
if ( \IPS\Settings::i()->nexus_checkreg_validate or !$this->total->amount->isGreaterThanZero() )
{
\IPS\Email::buildFromTemplate( 'core', 'registration_notify', array( $memberToSave, $profileFields ) )->send( \IPS\Settings::i()->email_in );
$this->member->postRegistration( FALSE, TRUE );
}
else if( \IPS\Settings::i()->new_reg_notify )
{
\IPS\Email::buildFromTemplate( 'core', 'registration_notify', array( $memberToSave, $profileFields ), \IPS\Email::TYPE_LIST )->send( \IPS\Settings::i()->email_in );
}
/* Update associated transactions */
/* Update associated transactions */
\IPS\Db::i()->update( 'nexus_billing_agreements', array( 'ba_member' => $this->member->member_id ), array( 'ba_id IN(?)', \IPS\Db::i()->select( 't_billing_agreement', 'nexus_transactions', array( 't_billing_agreement>0 AND t_invoice=? AND t_member=0', $this->id ) ) ) );
\IPS\Db::i()->update( 'nexus_transactions', array( 't_member' => $this->member->member_id ), array( 't_invoice=? AND t_member=0', $this->id ) );
/* Did we use any coupons? */
@@ -1388,6 +1643,13 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$extra = $this->status_extra;
$shipOrders = array();
/* Find any billing agreements */
$billingAgreement = NULL;
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ), array( array( 't_billing_agreement IS NOT NULL' ) ) ) as $transaction )
{
$billingAgreement = $transaction->billing_agreement;
}
/* Loop items */
$purchases = array();
$setParentIds = array();
@@ -1418,6 +1680,10 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
$purchase->grace_period = \IPS\DateTime::create()->add( $item->renewalTerm->gracePeriod )->getTimestamp() - time();
}
if ( $billingAgreement )
{
$purchase->billing_agreement = $billingAgreement;
}
}
if ( $item->expireDate )
{
@@ -1463,6 +1729,14 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
$purchases[ $k ][] = $purchase;
/* Claim attachments */
foreach ( \IPS\Db::i()->select( '*', 'core_attachments_map', array( 'location_key=? AND id1=? AND id3=?', 'nexus_Purchases', $k, "invoice-{$this->id}" ) ) as $attachMap )
{
$attachMap['id1'] = $purchase->id;
$attachMap['id3'] = 'purchase';
\IPS\Db::i()->insert( 'core_attachments_map', $attachMap );
}
/* Log */
if ( $showPurchaseRecord )
{
@@ -1493,16 +1767,16 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$recipientAmounts = $item->recipientAmounts();
$credits = $item->payTo->cm_credits;
$credits[ $this->currency ]->amount += $recipientAmounts['recipient_final']->amount;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->add( $recipientAmounts['recipient_final']->amount );
$item->payTo->cm_credits = $credits;
$item->payTo->save();
$amountReceived -= $recipientAmounts['recipient_final']->amount;
$amountReceived = $amountReceived->subtract( $recipientAmounts['recipient_final']->amount );
if ( !isset( $extra['commission'][ $item->payTo->member_id ] ) )
{
$extra['commission'][ $item->payTo->member_id ] = array( 'amount' => 0 );
$extra['commission'][ $item->payTo->member_id ] = array( 'amount' => new \IPS\Math\Number('0') );
}
$extra['commission'][ $item->payTo->member_id ]['amount'] += $recipientAmounts['recipient_final']->amount;
$extra['commission'][ $item->payTo->member_id ]['amount'] = $extra['commission'][ $item->payTo->member_id ]['amount']->add( $recipientAmounts['recipient_final']->amount );
$item->payTo->log( 'comission', array(
'type' => 'purchase',
@@ -1514,7 +1788,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'name' => $item->name
), FALSE );
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, $item, $recipientAmounts['recipient_final'] ) )
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, $item, $recipientAmounts['recipient_final'] ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$item->payTo,
array_map(
@@ -1533,15 +1807,16 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
if ( \IPS\Settings::i()->cm_ref_on and $commission = $this->commission( $amountReceived ) and $commission['amount'] )
{
$credits = $commission['referrer']->cm_credits;
$credits[ $this->currency ]->amount += $commission['amount']->amount;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->add( $commission['amount']->amount );
$commission['referrer']->cm_credits = $credits;
$commission['referrer']->save();
if ( !isset( $extra['commission'][ $commission['referrer']->member_id ] ) )
{
$extra['commission'][ $commission['referrer']->member_id ] = array( 'amount' => 0 );
$extra['commission'][ $commission['referrer']->member_id ] = array( 'amount' => new \IPS\Math\Number('0') );
}
$extra['commission'][ $commission['referrer']->member_id ]['amount'] += $commission['amount']->amount;
$extra['commission'][ $commission['referrer']->member_id ]['amount'] = $extra['commission'][ $commission['referrer']->member_id ]['amount']->add( $commission['amount']->amount );
$commission['referrer']->log( 'comission', array(
'type' => 'invoice',
@@ -1557,12 +1832,16 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$amounts = $record['amount'] ? json_decode( $record['amount'], TRUE ) : array();
if ( !isset( $amounts[ $this->currency ] ) )
{
$amounts[ $this->currency ] = 0;
$amounts[ $this->currency ] = new \IPS\Math\Number('0');
}
$amounts[ $this->currency ] += $commission['amount']->amount;
elseif ( !( $amounts[ $this->currency ] instanceof \IPS\Math\Number ) )
{
$amounts[ $this->currency ] = new \IPS\Math\Number( number_format( $amounts[ $this->currency ], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) );
}
$amounts[ $this->currency ] = $amounts[ $this->currency ]->add( $commission['amount']->amount );
\IPS\Db::i()->update( 'nexus_referrals', array( 'amount' => json_encode( $amounts ) ), array( 'member_id=? AND referred_by=?', $this->member->member_id, $commission['referrer']->member_id ) );
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, NULL, $commission['amount'] ) )
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, NULL, $commission['amount'] ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$commission['referrer'],
array_map(
@@ -1576,6 +1855,18 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
);
}
/* Change commission values back to numbers */
if ( isset( $extra['commission'] ) )
{
foreach ( $extra['commission'] as $memberId => $data )
{
if ( $data['amount'] and $data['amount'] instanceof \IPS\Math\Number )
{
$extra['commission'][ $memberId ]['amount'] = (string) $data['amount'];
}
}
}
/* Set parent IDs */
foreach ( $setParentIds as $k => $v )
{
@@ -1629,7 +1920,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
/* Refuse any pending transactions */
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW ) ) as $transaction )
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ) ) as $transaction )
{
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PENDING )
{
@@ -1643,9 +1934,9 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
catch ( \Exception $e ){}
$extra = $transaction->extra;
$extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'note' => 'invoicePaid' );
$transaction->extra = $extra;
$textra = $transaction->extra;
$textra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'note' => 'invoicePaid' );
$transaction->extra = $textra;
$transaction->status = \IPS\nexus\Transaction::STATUS_REFUSED;
$transaction->save();
}
@@ -1693,7 +1984,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$recipientAmounts = $item->recipientAmounts();
$credits = $item->payTo->cm_credits;
$credits[ $this->currency ]->amount -= $recipientAmounts['recipient_final']->amount;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->subtract( $recipientAmounts['recipient_final']->amount );
$item->payTo->cm_credits = $credits;
$item->payTo->save();
@@ -1707,7 +1998,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'invoice_title' => $this->title
), FALSE );
\IPS\Email::buildFromTemplate( 'nexus', 'commissionRevoked', array( $this, $item, $recipientAmounts['recipient_final'] ) )
\IPS\Email::buildFromTemplate( 'nexus', 'commissionRevoked', array( $this, $item, $recipientAmounts['recipient_final'] ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$item->payTo,
array_map(
@@ -1807,7 +2098,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$message = \IPS\Member::loggedIn()->language()->addToStack('account_credit_remove', FALSE, array( 'sprintf' => array( $amount, $item->payTo->cm_name ) ) );
$credits = $item->payTo->cm_credits;
if ( ( $credits[ $amount->currency ]->amount - $amount->amount ) < 0 )
if ( !$credits[ $amount->currency ]->amount->subtract( $amount->amount )->isPositive() )
{
$return[] = array( 'message' => $message, 'warning' => \IPS\Member::loggedIn()->language()->addToStack('account_credit_remove_neg') );
}
@@ -1836,7 +2127,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
public function sendNotification()
{
\IPS\Email::buildFromTemplate( 'nexus', 'newInvoice', array( $this, $this->summary() ) )
\IPS\Email::buildFromTemplate( 'nexus', 'newInvoice', array( $this, $this->summary() ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$this->member,
array_map(
@@ -2,9 +2,9 @@
/**
* @brief Invoice Item Class for Charges
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
+131 -24
View File
@@ -2,9 +2,9 @@
/**
* @brief Invoice Abstract Item Class
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -109,6 +109,11 @@ abstract class _Item
*/
public $payTo;
/**
* @brief Application
*/
public static $application;
/**
* @brief Commission percentage
*/
@@ -124,6 +129,11 @@ abstract class _Item
*/
public $extra;
/**
* @brief Group With Parent
*/
public $groupWithParent = FALSE;
/**
* Constructor
*
@@ -134,7 +144,7 @@ abstract class _Item
public function __construct( $name, \IPS\nexus\Money $price )
{
$this->name = $name;
$this->price = $price->round();
$this->price = $price;
}
/**
@@ -156,27 +166,40 @@ abstract class _Item
*/
public function linePrice()
{
return new \IPS\nexus\Money( $this->price->amount * $this->quantity, $this->price->currency );
return new \IPS\nexus\Money( $this->price->amount->multiply( new \IPS\Math\Number("{$this->quantity}") ), $this->price->currency );
}
/**
* Get tax rate
*
* @return float
* @param \IPS\GeoLocation|NULL $location Location to use for tax rate or NULL to use specified billing address
* @return \IPS\Math\Number
*/
public function taxRate()
public function taxRate( \IPS\GeoLocation $location = NULL )
{
return ( $this->tax ? $this->tax->rate( $this->billaddress ) : 0 );
return new \IPS\Math\Number( $this->tax ? $this->tax->rate( $location ?: $this->billaddress ) : '0' );
}
/**
* Get item price with tax
*
* @param \IPS\GeoLocation|NULL $location Location to use for tax rate or NULL to use specified billing address
* @return \IPS\nexus\Money
*/
public function grossPrice( \IPS\GeoLocation $location = NULL )
{
return new \IPS\nexus\Money( $this->price->amount->add( $this->price->amount->multiply( $this->taxRate( $location ) ) ), $this->price->currency );
}
/**
* Get line price with tax
*
* @param \IPS\GeoLocation|NULL $location Location to use for tax rate or NULL to use specified billing address
* @return \IPS\nexus\Money
*/
public function grossLinePrice()
public function grossLinePrice( \IPS\GeoLocation $location = NULL )
{
return new \IPS\nexus\Money( $this->linePrice()->amount + ( $this->linePrice()->amount * $this->taxRate() ), $this->price->currency );
return new \IPS\nexus\Money( $this->linePrice()->amount->add( $this->linePrice()->amount->multiply( $this->taxRate( $location ) ) ), $this->price->currency );
}
/**
@@ -192,20 +215,21 @@ abstract class _Item
{
$linePrice = $this->linePrice();
$currency = $linePrice->currency;
$commission = $this->price->amount->percentage( $this->commission );
$lineComission = $commission->multiply( new \IPS\Math\Number("{$this->quantity}") );
$commission = ( new \IPS\nexus\Money( ( $this->price->amount / 100 ) * $this->commission, $currency ) );
$return['site_commission_unit'] = $commission->round();
$return['site_commission_line'] = new \IPS\nexus\Money( $return['site_commission_unit']->amount * $this->quantity, $currency );
$return['site_commission_unit'] = new \IPS\nexus\Money( $commission, $currency );
$return['site_commission_line'] = new \IPS\nexus\Money( $lineComission, $currency );
$return['recipient_unit'] = new \IPS\nexus\Money( $this->price->amount - $return['site_commission_unit']->amount, $currency );
$return['recipient_line'] = new \IPS\nexus\Money( $linePrice->amount - $return['site_commission_line']->amount, $currency );
$return['recipient_unit'] = new \IPS\nexus\Money( $this->price->amount->subtract( $return['site_commission_unit']->amount ), $currency );
$return['recipient_line'] = new \IPS\nexus\Money( $linePrice->amount->subtract( $return['site_commission_line']->amount ), $currency );
$return['site_total'] = new \IPS\nexus\Money( $return['site_commission_line']->amount + ( $this->fee ? $this->fee->amount : 0 ), $currency );
$return['recipient_final'] = new \IPS\nexus\Money( $linePrice->amount - $return['site_total']->amount, $currency );
if ( $return['recipient_final']->amount < 0 )
{
$return['recipient_final']->amount = 0;
}
$fee = $this->fee ? $this->fee->amount : new \IPS\Math\Number('0');
$siteTotal = $return['site_commission_line']->amount->add( $fee );
$recipientTotal = $linePrice->amount->subtract( $siteTotal );
$return['site_total'] = new \IPS\nexus\Money( $siteTotal, $currency );
$return['recipient_final'] = new \IPS\nexus\Money( $recipientTotal->isGreaterThanZero() ? $recipientTotal : 0, $currency );
}
else
{
@@ -228,10 +252,8 @@ abstract class _Item
throw new \BadMethodCallException;
}
$linePrice = $this->linePrice();
$amountForSite = ( $linePrice->amount / 100 ) * $this->commission;
$amountForRecipient = ( new \IPS\nexus\Money( $linePrice->amount - $amountForSite, $linePrice->currency ) );
return $amountForRecipient->round();
$recipientAmount = $this->recipientAmounts();
return $recipientAmount['recipient_final'];
}
/**
@@ -289,6 +311,32 @@ abstract class _Item
}
/**
* Check for member
* If a user initially checks out as a guest and then logs in during checkout, this method
* is ran to check the items they are purchasing can be bought.
* Is expected to throw a DomainException with an error message to display to the user if not valid
*
* @param \IPS\Member $member The new member
* @return void
* @throws \DomainException
*/
public function memberCanPurchase( \IPS\Member $member )
{
}
/**
* Requires Billing Address
*
* @return bool
* @throws \DomainException
*/
public function requiresBillingAddress()
{
return in_array( 'other', explode( ',', \IPS\Settings::i()->nexus_require_billing ) );
}
/**
* Client Area URL
*
@@ -308,4 +356,63 @@ abstract class _Item
{
return NULL;
}
/**
* Get output for API
*
* @return array
* @apiresponse string name Item name
* @apiresponse string itemApp Key for application. For example, 'nexus' for products and renewals; 'downloads' for Downloads files
* @apiresponse string itemType Key for item type. For example, 'package' for products; 'file' for Downloads files.
* @apiresponse int itemId The ID for the item. For example, the product ID or the file ID.
* @apiresponse string itemUrl Any relevant URL (for example, for Downloads files, this will be the URL to view the file)
* @apiresponse string itemImage If the item has a relevant image (for exmaple, product image, Downloads file screenshot), the URL to it
* @apiresponse int quantity The quantity being purchased
* @apiresponse \IPS\nexus\Money itemPrice Item price, before tax
* @apiresponse \IPS\nexus\Money linePrice Line price, before tax
* @apiresponse \IPS\nexus\Tax taxClassId If the item should be taxed, the Tax Class that applies
* @apiresponse bool physical If the item is physical
* @apiresponse float weight If the item is physical, the weight in kilograms
* @apiresponse float length If the item is physical, the length in metres
* @apiresponse float width If the item is physical, the width in metres
* @apiresponse float height If the item is physical, the height in metres
* @apiresponse \IPS\nexus\Purchase\RenewalTerm renewalTerm If the item renews, the renewal term
* @apiresponse datetime expireDate If the item has been set to expire at a certain date but not automatically renew, the dare it will expire
* @apiresponse object details The values for any custom package fields
* @apiresponse \IPS\nexus\Purchase parentPurchase If when the item has been purchased it will be a child of an existing purchase, the parent purchase
* @apiresponse int parentItem If when the item has been purchased it will be a child of another item on the same invoice, the ID number of the item that will be the parent
* @apiresponse bool groupWithParent If when the item has been purchased it will have its renewals grouped with its parent
* @apiresponse \IPS\nexus\Customer payTo If the payment for this item goes to another user (for example for Downloads files), the user who will receive the payment
* @apiresonse float commission If the payment for this item goes to another user (for example for Downloads files), the percentage of the price that will be retained by the site (in addition to fee)
* @apiresonse \IPS\nexus\Money fee If the payment for this item goes to another user (for example for Downloads files), the fee that will be deducted by the site (in addition to commission)
*/
public function apiOutput()
{
return array(
'name' => $this->name,
'itemApp' => $this->appKey,
'itemType' => $this->typeKey,
'itemId' => $this->id,
'itemUrl' => $this->url() ? ( (string) $this->url() ) : null,
'itemImage' => $this->image() ? ( (string) $this->image()->url ) : null,
'quantity' => $this->quantity,
'itemPrice' => $this->price->apiOutput(),
'linePrice' => $this->linePrice()->apiOutput(),
'taxClass' => $this->tax ? $this->tax->apiOutput() : null,
'physical' => $this->physical,
'weight' => isset( $this->weight ) ? $this->weight->kilograms : null,
'length' => isset( $this->length ) ? $this->length->metres : null,
'width' => isset( $this->width ) ? $this->width->metres : null,
'height' => isset( $this->height ) ? $this->height->metres : null,
'renewalTerm' => isset( $this->renewalTerm ) ? $this->renewalTerm->apiOutput() : null,
'expireDate' => isset( $this->expireDate ) ? $this->expireDate->rfc3339() : null,
'details' => $this->details,
'parentPurchase' => ( isset( $this->parent ) and $this->parent instanceof \IPS\nexus\Purchase ) ? $this->parent->apiOutput() : null,
'parentItem' => ( isset( $this->parent ) and is_int( $this->parent ) ) ? $this->parent : null,
'groupWithParent' => isset( $this->groupWithParent ) ? $this->groupWithParent : false,
'payTo' => $this->payTo ? $this->payTo->apiOutput() : null,
'commission' => $this->commission,
'fee' => $this->fee ? $this->fee->apiOutput() : null,
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Invoice Item Class for Purchases
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -179,16 +179,20 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
{
$form->add( new \IPS\Helpers\Form\Text( 'ps_name', $purchase->_name, TRUE, array( 'maxLength' => 128 ) ) );
if ( !$purchase->grouped_renewals )
if ( !$purchase->grouped_renewals and ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled ) )
{
$form->add( new \IPS\Helpers\Form\Date( 'ps_expire', $purchase->expire ?: 0, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'does_not_expire', 'disabled' => !$purchase->canChangeExpireDate() ) ) );
}
$form->add( new \IPS\nexus\Form\RenewalTerm( 'ps_renewals', $renewals, FALSE, array( 'lockTerm' => !$purchase->canChangeExpireDate() ) ) );
if ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled )
{
$form->add( new \IPS\nexus\Form\RenewalTerm( 'ps_renewals', $renewals, FALSE, array( 'lockTerm' => !$purchase->canChangeExpireDate() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'ps_grace_period', $purchase->grace_period / 86400, FALSE, array( 'max' => \IPS\Settings::i()->cm_invoice_expireafter ?: NULL ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days') ) );
}
if ( !$purchase->grouped_renewals )
{
$form->add( new \IPS\Helpers\Form\Node( 'ps_parent', $purchase->parent(), FALSE, array( 'class' => 'IPS\nexus\Purchase', 'forceOwner' => $purchase->member, 'zeroVal' => 'no_parent' ) ) );
$form->add( new \IPS\Helpers\Form\Node( 'ps_parent', $purchase->parent(), FALSE, array( 'class' => 'IPS\nexus\Purchase', 'forceOwner' => $purchase->member, 'zeroVal' => 'no_parent', 'disabledIds' => array( $purchase->id ) ) ) );
}
}
@@ -205,14 +209,21 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
if ( $purchase->grouped_renewals )
{
$purchase->ungroupFromParent();
$purchase->renewals = $values['ps_renewals'];
if ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled )
{
$purchase->renewals = $values['ps_renewals'];
}
$purchase->save();
$purchase->groupWithParent();
}
else
{
$purchase->expire = ( $values['ps_expire'] ?: NULL );
$purchase->renewals = $values['ps_renewals'];
if ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled )
{
$purchase->expire = ( $values['ps_expire'] ?: NULL );
$purchase->renewals = $values['ps_renewals'];
$purchase->grace_period = $values['ps_grace_period'] ? ( $values['ps_grace_period'] * 86400 ) : 0;
}
$purchase->parent = $values['ps_parent'] ?: NULL;
$purchase->save();
}
@@ -263,6 +274,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
return TRUE;
}
/**
* Get ACP Support View HTML
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return string
*/
public static function acpSupportView( \IPS\nexus\Purchase $purchase )
{
return '';
}
/**
* Support Severity
*
@@ -274,6 +296,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
return NULL;
}
/**
* Get renewal payment methods IDs
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return array|NULL
*/
public static function renewalPaymentMethodIds( \IPS\nexus\Purchase $purchase )
{
return NULL;
}
/**
* On Purchase Generated
*
@@ -331,6 +364,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
}
/**
* Warning to display to admin when cancelling a purchase
*
* @param \IPS\nexus\Invoice $invoice The invoice
* @return string
*/
public static function onCancelWarning( \IPS\nexus\Purchase $purchase )
{
return NULL;
}
/**
* On Purchase Deleted
*
@@ -365,6 +409,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
}
/**
* Requires Billing Address
*
* @return bool
* @throws \DomainException
*/
public function requiresBillingAddress()
{
return in_array( 'product', explode( ',', \IPS\Settings::i()->nexus_require_billing ) );
}
/**
* Show Purchase Record?
*
@@ -2,9 +2,9 @@
/**
* @brief Invoice Item Class for Renewals
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 01 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -71,6 +71,11 @@ class _Renewal extends \IPS\nexus\Invoice\Item
$obj->commission = $purchase->commission;
}
if ( $renewalPaymentMethodIds = $purchase->renewalPaymentMethodIds() )
{
$obj->paymentMethodIds = $renewalPaymentMethodIds;
}
return $obj;
}
@@ -98,11 +103,22 @@ class _Renewal extends \IPS\nexus\Invoice\Item
$purchase->expire = $expire;
$purchase->invoice_pending = NULL;
$billingAgreement = NULL;
foreach ( $invoice->transactions( array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ), array( array( 't_billing_agreement IS NOT NULL' ) ) ) as $transaction )
{
$billingAgreement = $transaction->billing_agreement;
}
if ( $billingAgreement )
{
$purchase->billing_agreement = $billingAgreement;
}
$purchase->save();
$purchase->onRenew( $this->quantity );
$purchase->member->log( 'purchase', array( 'type' => 'renew', 'id' => $purchase->id, 'name' => $purchase->name, 'invoice_id' => $invoice->id, 'invoice_title' => $invoice->title ) );
}
}
}
/**
@@ -115,7 +131,14 @@ class _Renewal extends \IPS\nexus\Invoice\Item
{
$return = parent::onUnpaidDescription( $invoice );
$purchase = \IPS\nexus\Purchase::load( $this->id );
try
{
$purchase = \IPS\nexus\Purchase::load( $this->id );
}
catch ( \OutOfRangeException $e )
{
return $return;
}
if ( $purchase->renewals and $interval = $purchase->renewals->interval and !$purchase->cancelled and $expire = $purchase->expire )
{
@@ -139,7 +162,14 @@ class _Renewal extends \IPS\nexus\Invoice\Item
*/
public function onUnpaid( \IPS\nexus\Invoice $invoice, $status )
{
$purchase = \IPS\nexus\Purchase::load( $this->id );
try
{
$purchase = \IPS\nexus\Purchase::load( $this->id );
}
catch ( \OutOfRangeException $e )
{
return;
}
if ( $purchase->renewals and $interval = $purchase->renewals->interval and !$purchase->cancelled and $expire = $purchase->expire )
{
@@ -184,6 +214,30 @@ class _Renewal extends \IPS\nexus\Invoice\Item
*/
public function acpUrl()
{
return \IPS\nexus\Purchase::load( $this->id )->acpUrl();
try
{
return \IPS\nexus\Purchase::load( $this->id )->acpUrl();
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
/**
* Image
*
* @return |IPS\File|NULL
*/
public function image()
{
try
{
return \IPS\nexus\Purchase::load( $this->id )->image();
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
}
@@ -0,0 +1,66 @@
<?php
/**
* @brief Tax Item
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 9 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\Invoice\Item;
/* 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;
}
/**
* Tax Item
*/
class _TaxItem
{
/**
* @brief Class ID
*/
protected $class;
/**
* @brief Data
*/
protected $data;
/**
* Constructor
*
* @param int $class Class ID
* @param array $data Data
* @return void
*/
public function __construct( $class, $data )
{
$this->class = $class;
$this->data = $data;
}
/**
* Get output for API
*
* @return array
* @apiresponse \IPS\nexus\Tax class The tax class
* @apiresponse float rate The rate (for example 0.2 means 20%)
* @apiresponse \IPS\nexus\Money amount Amount to pay
*/
public function apiOutput()
{
return array(
'class' => \IPS\nexus\Tax::load( $this->class )->apiOutput(),
'rate' => $this->data['rate'],
'amount' => $this->data['amount']
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Invoice Items Iterator
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 24 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -72,18 +72,68 @@ class _ItemsIterator extends \ArrayIterator
{
$class = 'IPS\nexus\extensions\nexus\Item\MiscellaneousCharge';
}
$obj = new $class( $data['itemName'], new \IPS\nexus\Money( $data['cost'], $this->currency ) );
$obj = new $class( $data['itemName'], new \IPS\nexus\Money( number_format( $data['cost'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ), $this->currency ) );
}
/* Basic information */
$obj->quantity = $data['quantity'];
$obj->id = $data['itemID'];
$obj->appKey = $data['app'];
$obj->typeKey = $data['type'];
/* Details */
$details = array();
if ( isset( $data['cfields'] ) and $data['cfields'] )
{
$obj->details = $data['cfields'];
foreach( $data['cfields'] AS $key => $value )
{
try
{
$field = \IPS\nexus\Package\CustomField::load( $key );
if ( $field->invoice )
{
switch( $field->type )
{
case 'UserPass':
$value = json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
$display = array();
if ( $value['un'] )
{
$display[] = $value['un'];
}
if ( $value['pw'] )
{
$display[] = $value['pw'];
}
$details[ $key ] = implode( ' &sdot; ', $display );
break;
case 'Ftp':
$value = json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
$details[ $key ] = (string) \IPS\Http\Url::createFromComponents(
$value['server'],
( isset( $value['protocol'] ) ) ? "{$value['protocol']}://" : 'ftp://',
( isset( $value['path'] ) ) ? $value['path'] : NULL,
NULL,
( isset( $value['port'] ) ) ? $value['port'] : NULL,
( isset( $value['un'] ) ) ? $value['un'] : NULL,
( isset( $value['pw'] ) ) ? $value['pw'] : NULL
);
break;
default:
$details[ $key ] = $value;
break;
}
}
}
catch( \OutOfRangeException $e ) {}
}
}
$obj->details = $details;
/* Tax */
if ( isset( $data['tax'] ) AND $data['tax'] )
+102 -79
View File
@@ -2,9 +2,9 @@
/**
* @brief Money Object
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -40,6 +40,56 @@ class _Money
return array( \IPS\Settings::i()->nexus_currency );
}
}
/**
* Get the number of decimal points used for a currency
*
* @param string $currency Currency code
* @return int
*/
public static function numberOfDecimalsForCurrency( $currency )
{
switch ( $currency )
{
case 'CLF':
return 4;
case 'BHD':
case 'IQD':
case 'JOD':
case 'KWD':
case 'LYD':
case 'OMR':
case 'TND':
return 3;
case 'MGA':
case 'MRO':
return 1;
case 'BIF':
case 'BYR':
case 'CLP':
case 'CVE':
case 'DJF':
case 'GNF':
case 'ISK':
case 'JPY':
case 'KMF':
case 'KRW':
case 'PYG':
case 'RWF':
case 'UGX':
case 'UYI':
case 'VND':
case 'VUV':
case 'XAF':
return 0;
default:
return 2;
}
}
/**
* @brief Amount
@@ -59,40 +109,27 @@ class _Money
/**
* Contructor
*
* @param float $amount Amount
* @param mixed $amount Amount
* @param string $currency Currency code
* @return void
* @note If you specify $1.005, this will be deliberately accepted. Call ->round() to round
*/
public function __construct( $amount, $currency )
{
$this->amount = floatval( $amount );
$this->currency = $currency;
}
/**
* Round to the correct number of decimal places for the currency
*
* @param bool $alwaysUp If TRUE, 0.051 would be rounded to 0.06 rather than 0.05
* @return \IPS\nexus\Money
*/
public function round( $alwaysUp = TRUE )
{
if ( mb_strlen( mb_substr( mb_strrchr( $this->amount, '.' ), 1 ) ) > $this->numberOfDecimalPlaces )
$this->numberOfDecimalPlaces = static::numberOfDecimalsForCurrency( $currency );
if ( !( $amount instanceof \IPS\Math\Number ) )
{
if ( $alwaysUp )
if ( is_string( $amount ) )
{
$mult = pow( 10, $this->numberOfDecimalPlaces );
$this->amount = ceil( $this->amount * $mult ) / $mult;
}
else
{
$this->amount = round( $this->amount, $this->numberOfDecimalPlaces );
$amount = floatval( $amount );
}
$amount = new \IPS\Math\Number( number_format( $amount, $this->numberOfDecimalPlaces, '.', '' ) );
}
return $this;
$this->amount = $amount->round( $this->numberOfDecimalPlaces, \IPS\Math\Number::ROUND_UP );
}
/**
* Amount as string (not formatted, not locale aware)
* Used for gateways where, for example, (float) 10.5 is not acceptable, and (string) "10.50" is required
@@ -101,7 +138,7 @@ class _Money
*/
public function amountAsString()
{
return sprintf( "%01." . $this->numberOfDecimalPlaces . "F", $this->amount );
return sprintf( "%01." . $this->numberOfDecimalPlaces . "F", (string) $this->amount );
}
/**
@@ -117,56 +154,26 @@ class _Money
/**
* To string for language
*
* @param \IPS\Lang $language The language to use
* @return string
*/
public function toString( \IPS\Lang $language )
{
/* If the server has intl installed, use that */
if ( function_exists( 'numfmt_format_currency' ) )
{
/* Unfortunately a bug in intl means that locales which use commas for decimal separation are broken.
@link https://community.invisionpower.com/4bugtrack/active-reports/nexus-money-formatting-r6911/ .
What we'll do is store the current locale, reset to English, then reset back to current locale afterwards. */
$currentLocale = setlocale( LC_ALL, '0' );
if( mb_strtoupper( mb_substr( PHP_OS, 0, 3 ) ) === 'WIN' )
{
setlocale( LC_ALL, 'english' );
}
else
{
setlocale( LC_ALL, 'en_US' );
}
$format = numfmt_create( $language->short, \NumberFormatter::CURRENCY );
if ( $return = numfmt_format_currency( $format, $this->amount, $this->currency ) and $return !== 'NaN' )
{
/* Now we have to reset locale back to what it was */
foreach( explode( ";", $currentLocale ) as $locale )
{
if( mb_strpos( $locale, '=' ) !== FALSE )
{
$parts = explode( "=", $locale );
if( in_array( $parts[0], array( 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME' ) ) )
{
setlocale( constant( $parts[0] ), $parts[1] );
}
}
else
{
setLocale( LC_ALL, $locale );
}
}
return $return;
}
}
{
/* If this currency matches the locale the user is using, and money_format is supported (Windows doesn't support it), use that */
if ( function_exists( 'money_format' ) and trim( $language->locale['int_curr_symbol'] ) === $this->currency )
{
return money_format( '%n', $this->amount );
/* We need to make sure the locale is correct */
$currentLocale = setlocale( LC_ALL, '0' );
$language->setLocale();
/* Get the value to use */
$return = money_format( '%n', (string) $this->amount );
/* Then put the locale back */
\IPS\Lang::restoreLocale( $currentLocale );
/* And return */
return $return;
}
/* If it matches any of the installed languages, we can do something with the locale data */
@@ -174,16 +181,17 @@ class _Money
{
if ( isset( $lang->locale['int_curr_symbol'] ) AND trim( $lang->locale['int_curr_symbol'] ) === $this->currency )
{
$currencySymbol = $lang->locale['currency_symbol'];
$currencySymbolPreceeds = ( $this->amount >= 0 ) ? $lang->locale['p_cs_precedes'] : $lang->locale['n_cs_precedes'];
$spaceBetweenCurrencySymbolAndAmount = ( ( $this->amount >= 0 ) ? $lang->locale['p_sep_by_space'] : $lang->locale['n_sep_by_space'] ) ? ' ' : '';
$amount = number_format( $this->amount, $lang->locale['frac_digits'] );
$currencySymbol = $lang->convertString( $lang->locale['currency_symbol'] );
$currencySymbolPreceeds = $this->amount->isPositive() ? $lang->locale['p_cs_precedes'] : $lang->locale['n_cs_precedes'];
$spaceBetweenCurrencySymbolAndAmount = ( $this->amount->isPositive() ? $lang->locale['p_sep_by_space'] : $lang->locale['n_sep_by_space'] ) ? ' ' : '';
$amount = number_format( (string) $this->amount, $lang->locale['frac_digits'], $lang->locale['mon_decimal_point'], $lang->locale['mon_thousands_sep'] );
$positiveNegativeSymbol = ( $this->amount >= 0 ) ? $lang->locale['positive_sign'] : $lang->locale['negative_sign'];
$positiveNegativeSymbol = $this->amount->isPositive() ? $lang->locale['positive_sign'] : $lang->locale['negative_sign'];
if ( $positiveNegativeSymbol )
{
$positiveNegativeSymbolFormat = ( $this->amount >= 0 ) ? $lang->locale['p_sign_posn'] : $lang->locale['n_sign_posn'];
for ( $i=0; $i < \strlen( $positiveNegativeSymbolFormat ); $i++ )
$positiveNegativeSymbolFormat = $this->amount->isPositive() ? $lang->locale['p_sign_posn'] : $lang->locale['n_sign_posn'];
$positiveNegativeSymbolFormatLength = \strlen( $positiveNegativeSymbolFormat );
for ( $i=0; $i < $positiveNegativeSymbolFormatLength; $i++ )
{
switch ( $positiveNegativeSymbolFormat[ $i ] )
{
@@ -229,11 +237,26 @@ class _Money
}
}
return ( $currencySymbolPreceeds ? ( $currencySymbol . $spaceBetweenCurrencySymbolAndAmount . $amount ) : ( $amount . $space . $currencySymbol ) );
return ( $currencySymbolPreceeds ? ( $currencySymbol . $spaceBetweenCurrencySymbolAndAmount . $amount ) : ( $amount . $spaceBetweenCurrencySymbolAndAmount . $currencySymbol ) );
}
}
/* And if all else fails, just use the currency code */
return number_format( $this->amount, 2 ) . " {$this->currency}";
return number_format( (string) $this->amount, static::numberOfDecimalsForCurrency( $this->currency ) ) . " {$this->currency}";
}
/**
* Get output for API
*
* @return array
* @apiresponse string currency The currency code (e.g. 'USD')
* @apiresponse string amount The amount
*/
public function apiOutput()
{
return array(
'currency' => $this->currency,
'amount' => $this->amountAsString(),
);
}
}
+29 -9
View File
@@ -2,9 +2,9 @@
/**
* @brief Advertisement Package
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 29 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -174,11 +174,12 @@ class _Ad extends \IPS\nexus\Package
/**
* Update existing purchases
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param array $changes The old values
* @param \IPS\nexus\Purchase $purchase The purchase
* @param array $changes The old values
* @param bool $cancelBillingAgreementIfNecessary If making changes to renewal terms, TRUE will cancel associated billing agreements. FALSE will skip that change
* @return void
*/
public function updatePurchase( \IPS\nexus\Purchase $purchase, $changes )
public function updatePurchase( \IPS\nexus\Purchase $purchase, $changes, $cancelBillingAgreementIfNecessary=FALSE )
{
if ( $purchase->extra['ad'] )
{
@@ -188,7 +189,7 @@ class _Ad extends \IPS\nexus\Package
}
catch ( \UnderflowException $e )
{
return parent::updatePurchase( $purchase, $changes );
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
$update = array();
@@ -224,7 +225,7 @@ class _Ad extends \IPS\nexus\Package
}
}
return parent::updatePurchase( $purchase, $changes );
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
/* !Store */
@@ -253,7 +254,12 @@ class _Ad extends \IPS\nexus\Package
$maxDims['maxHeight'] = $this->max_height;
}
}
$form->add( new \IPS\Helpers\Form\Upload( 'advertisement_image', NULL, TRUE, array( 'storageExtension' => 'nexus_Ads', 'image' => $maxDims ) ) );
if ( !isset( \IPS\Request::i()->stockCheck ) ) // The stock check will attempt to save the upload which we don't want to do until the form is actually submitted
{
$form->add( new \IPS\Helpers\Form\Upload( 'advertisement_image', NULL, TRUE, array( 'storageExtension' => 'nexus_Ads', 'image' => $maxDims ) ) );
}
if ( $this->max_height and $this->max_width )
{
\IPS\Member::loggedIn()->language()->words['advertisement_image_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'advertisement_image_max_wh', FALSE, array( 'sprintf' => array( $this->max_width, $this->max_height ) ) );
@@ -444,6 +450,8 @@ class _Ad extends \IPS\nexus\Package
$purchase->save();
unset( \IPS\Data\Cache::i()->advertisements );
parent::onPurchaseGenerated( $purchase, $invoice );
}
/**
@@ -461,7 +469,7 @@ class _Ad extends \IPS\nexus\Package
if ( ( !$ad->end or $ad->end > time() ) )
{
if ( $ad->maximum_value )
if ( $ad->maximum_value > -1 AND $ad->maximum_value )
{
if ( $ad->maximum_unit == 'i' )
{
@@ -499,6 +507,8 @@ class _Ad extends \IPS\nexus\Package
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
parent::onExpirationDateChange( $purchase );
}
/**
@@ -516,6 +526,8 @@ class _Ad extends \IPS\nexus\Package
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
parent::onExpire( $purchase );
}
/**
@@ -533,6 +545,8 @@ class _Ad extends \IPS\nexus\Package
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
parent::onCancel( $purchase );
}
/**
@@ -548,6 +562,8 @@ class _Ad extends \IPS\nexus\Package
\IPS\core\Advertisement::load( $purchase->extra['ad'] )->delete();
}
catch ( \OutOfRangeException $e ) { }
parent::onDelete( $purchase );
}
/**
@@ -559,6 +575,8 @@ class _Ad extends \IPS\nexus\Package
public function onReactivate( \IPS\nexus\Purchase $purchase )
{
$this->onExpirationDateChange( $purchase );
parent::onReactivate( $purchase );
}
/**
@@ -577,6 +595,8 @@ class _Ad extends \IPS\nexus\Package
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
parent::onTransfer( $purchase, $newCustomer );
}
/**
@@ -2,9 +2,10 @@
/**
* @brief Custom Package Field Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 1 May 2013
* @version SVN_VERSION_NUMBER
*/
@@ -99,7 +100,7 @@ class _CustomField extends \IPS\CustomField
/**
* @brief [CustomField] Upload Field Storage Extension
*/
public static $uploadStorageExtension = 'nexus_PackageFields';
public static $uploadStorageExtension = 'nexus_PurchaseFields';
/**
* @brief [CustomField] Editor Options
@@ -125,23 +126,25 @@ class _CustomField extends \IPS\CustomField
}
catch ( \OutOfRangeException $e ) { }
}
$form->add( new \IPS\Helpers\Form\Node( 'cf_packages', $packages, TRUE, array( 'class' => 'IPS\nexus\Package\Group', 'noParentNodes' => 'custom_packages', 'multiple' => TRUE, 'permissionCheck' => function( $node )
$form->add( new \IPS\Helpers\Form\Node( 'cf_packages', $packages, FALSE, array( 'class' => 'IPS\nexus\Package\Group', 'noParentNodes' => 'custom_packages', 'multiple' => TRUE, 'permissionCheck' => function( $node )
{
return !( $node instanceof \IPS\nexus\Package\Group );
} ) ), 'pf_desc' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_sticky', $this->sticky, FALSE ), 'pf_type' );
$form->addHeader( 'display_settings' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_purchase', $this->purchase, FALSE, array( 'togglesOn' => array( 'cf_required' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_editable', $this->editable, FALSE, array( 'togglesOn' => array( 'cf_required' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_required', $this->required, FALSE, array(), NULL, NULL, NULL, 'cf_required' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_purchase', $this->purchase, FALSE ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_editable', $this->editable, FALSE ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_required', $this->required ?: FALSE, FALSE, array(), NULL, NULL, NULL, 'pf_not_null' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_invoice', $this->invoice ) );
unset( $form->elements[''][1] );
unset( $form->elements['']['pf_not_null'] );
unset( $form->elements['']['pf_max_input'] );
unset( $form->elements['']['pf_input_format'] );
unset( $form->elements[''][2] );
unset( $form->elements['']['pf_search_type'] );
unset( $form->elements['']['pf_search_type_on_off'] );
unset( $form->elements['']['pf_format'] );
}
@@ -153,12 +156,21 @@ class _CustomField extends \IPS\CustomField
*/
public function formatFormValues( $values )
{
if( isset( $values['cf_packages'] ) )
if( isset( $values['cf_packages'] ) AND is_array( $values['cf_packages'] ) )
{
$values['packages'] = implode( ',', array_map( function( $node ){ return $node->id; }, $values['cf_packages'] ) );
unset( $values['cf_packages'] );
}
/*
* Make sure that the multiple field is set to 0 for the Radio field, otherwise the package package_stock_adjustments will fail..
* it seems that people changed a select field often to a radio field, which caused that the field was missing in the stock adjustment
*/
if( isset( $values['pf_type'] ) and $values['pf_type'] == 'Radio' )
{
$values['pf_multiple'] = 0;
}
return parent::formatFormValues( $values );
}
@@ -190,7 +202,14 @@ class _CustomField extends \IPS\CustomField
{
if ( $this->type === 'UserPass' )
{
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->usernamePasswordDisplay( json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE ) );
if ( is_array( $value ) )
{
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->usernamePasswordDisplay( $value );
}
else
{
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->usernamePasswordDisplay( json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE ) );
}
}
return parent::displayValue( $value );
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Dedicated Server Package
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 29 Apr 2014
* @version SVN_VERSION_NUMBER
+39 -4
View File
@@ -2,9 +2,9 @@
/**
* @brief Package Group Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 29 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -101,7 +101,7 @@ class _Group extends \IPS\Node\Model
*/
public static function loadFromUrl( \IPS\Http\Url $url )
{
$qs = array_merge( $url->queryString, $url->getFriendlyUrlData() );
$qs = array_merge( $url->queryString, $url->hiddenQueryString );
if ( isset( $qs['cat'] ) )
{
@@ -202,7 +202,7 @@ class _Group extends \IPS\Node\Model
{
if( $this->_url === NULL )
{
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=store&controller=store&cat={$this->id}", 'front', 'store_group', \IPS\Http\Url::seoTitle( \IPS\Member::loggedIn()->language()->get( 'nexus_pgroup_' . $this->id ) ) );
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=store&controller=store&cat={$this->id}", 'front', 'store_group', \IPS\Http\Url\Friendly::seoTitle( \IPS\Member::loggedIn()->language()->get( 'nexus_pgroup_' . $this->id ) ) );
}
return $this->_url;
@@ -240,4 +240,39 @@ class _Group extends \IPS\Node\Model
{
return ( $this->childrenCount( $member === FALSE ? FALSE : 'view', $member, NULL, $_where ) > 0 );
}
/**
* [ActiveRecord] Duplicate
*
* @return void
*/
public function __clone()
{
if ( $this->skipCloneDuplication === TRUE )
{
return;
}
$oldImage = $this->image;
parent::__clone();
if ( $oldImage )
{
try
{
$icon = \IPS\File::get( 'nexus_PackageGroups', $oldImage );
$newIcon = \IPS\File::create( 'nexus_PackageGroups', $icon->originalFilename, $icon->contents() );
$this->image = (string) $newIcon;
}
catch ( \Exception $e )
{
$this->pg_image = NULL;
}
$this->save();
}
}
}
+412 -120
View File
@@ -2,9 +2,9 @@
/**
* @brief Hosting Package
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 29 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -52,14 +52,21 @@ class _Hosting extends \IPS\nexus\Package
*/
public function getPurchaseNameInfo( \IPS\nexus\Purchase $purchase )
{
try
$stickyCustomFields = array_filter( parent::getPurchaseNameInfo( $purchase ) );
if ( !count( $stickyCustomFields ) )
{
return array_merge( array( \IPS\nexus\Hosting\Account::load( $purchase->id )->domain ), parent::getPurchaseNameInfo( $purchase ) );
}
catch ( \Exception $e )
{
return parent::getPurchaseNameInfo( $purchase );
try
{
return array( \IPS\nexus\Hosting\Account::load( $purchase->id )->domain );
}
catch ( \Exception $e )
{
return parent::getPurchaseNameInfo( $purchase );
}
}
return $stickyCustomFields;
}
/* !ACP Package Form */
@@ -144,83 +151,59 @@ class _Hosting extends \IPS\nexus\Package
/**
* Update existing purchases
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param array $changes The old values
* @param \IPS\nexus\Purchase $purchase The purchase
* @param array $changes The old values
* @param bool $cancelBillingAgreementIfNecessary If making changes to renewal terms, TRUE will cancel associated billing agreements. FALSE will skip that change
* @return void
*/
public function updatePurchase( \IPS\nexus\Purchase $purchase, $changes )
public function updatePurchase( \IPS\nexus\Purchase $purchase, $changes, $cancelBillingAgreementIfNecessary=FALSE )
{
/* Get current values */
try
{
$account = \IPS\nexus\Hosting\Account::load( $purchase->id );
$current = array(
'account_username' => $account->username,
'account_domain' => $account->domain,
'account_password' => (string) $account->password,
'p_quota' => $account->diskspaceAllowance() !== NULL ? $account->diskspaceAllowance() / 1000000 : -1,
'p_maxftp' => $account->maxFtpAccounts() !== NULL ? $account->maxFtpAccounts() : -1,
'p_maxsql' => $account->maxDatabases() !== NULL ? $account->maxDatabases() : -1,
'p_maxpop' => $account->maxEmailAccounts() !== NULL ? $account->maxEmailAccounts() : -1,
'p_maxlst' => $account->maxMailingLists() !== NULL ? $account->maxMailingLists() : -1,
'p_maxsub' => $account->maxSubdomains() !== NULL ? $account->maxSubdomains() : -1,
'p_maxpark' => $account->maxParkedDomains() !== NULL ? $account->maxParkedDomains() : -1,
'p_maxaddon' => $account->maxAddonDomains() !== NULL ? $account->maxAddonDomains() : -1,
'p_hasshell' => $account->hasSSHAccess(),
'p_bwlimit' => $account->monthlyBandwidthAllowance() !== NULL ? $account->monthlyBandwidthAllowance() / 1000000 : -1,
);
$update = array();
foreach ( array( 'quota' => 'diskspaceAllowance', 'bwlimit' => 'monthlyBandwidthAllowance', 'maxftp' => 'maxFtpAccounts', 'maxsql' => 'maxDatabases', 'maxpop' => 'maxEmailAccounts', 'maxlst' => 'maxMailingLists', 'maxsub' => 'maxSubdomains', 'maxpark' => 'maxParkedDomains', 'maxaddon' => 'maxAddonDomains', 'hasshell' => 'hasSSHAccess' ) as $k => $method )
{
if ( isset( $changes[ $k ] ) )
{
/* What is the current value? */
$currentValue = call_user_func( array( $account, $method ) );
if ( $currentValue === NULL )
{
$currentValue = -1;
}
elseif ( $k == 'quota' or $k == 'bwlimit' )
{
$currentValue = $currentValue / 1048576 / 1.024;
}
/* If the current value matches the old value (i.e. it hasn't been manually modified), set it to the new value */
if ( $currentValue == $changes[ $k ] )
{
$update[ 'p_' . $k ] = $this->$k;
}
}
}
if ( !empty( $update ) )
{
$account->edit( $update );
}
}
catch ( \IPS\nexus\Hosting\Exception $e )
{
$e->log();
return parent::updatePurchase( $purchase, $changes );
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
catch ( \Exception $e )
{
return parent::updatePurchase( $purchase, $changes );
}
/* Set new values */
$update = array();
foreach ( array(
'quota',
'bwlimit',
'hasshell',
'maxftp',
'maxsql',
'maxpop',
'maxlst',
'maxsub',
'maxpark',
'maxaddon',
) as $k )
{
if ( array_key_exists( $k, $changes ) )
{
if ( $current["p_{$k}"] == $changes[ $k ] )
{
$update["p_{$k}"] = $this->$k;
}
}
}
/* Send API call */
if ( !empty( $update ) )
{
try
{
$account->edit( array_merge( $current, $update ) );
}
catch ( \IPS\nexus\Hosting\Exception $e )
{
$e->log();
}
catch ( \Exception $e ) {}
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
/* Call parent */
return parent::updatePurchase( $purchase, $changes );
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
/* !Store */
@@ -239,7 +222,6 @@ class _Hosting extends \IPS\nexus\Package
{
$queue = \IPS\nexus\Hosting\Queue::load( $this->queue );
$server = $queue->activeServer();
$form->hiddenValues['server'] = $server->id;
}
catch ( \OutOfRangeException $e )
{
@@ -250,6 +232,23 @@ class _Hosting extends \IPS\nexus\Package
\IPS\Output::i()->error( 'hosting_err_public', '4X245/3', 500, 'hosting_err_no_server' );
}
/* Add the options to the form */
$this->_domainForm( $form, $memberCurrency, $server, $this->maxpark === -1 or $this->maxpark > 0 );
}
/**
* Domain Form
*
* @param \IPS\Helpers\Form $form The form
* @param string $memberCurrency The currency being used
* @param \IPS\nexus\Hosting\Server $server The server the account will be used on
* @param bool $canParkDomains If the account can park domains (which controls if CNAME is an option)
* @return
*/
protected function _domainForm( \IPS\Helpers\Form $form, $memberCurrency, \IPS\nexus\Hosting\Server $server, $canParkDomains )
{
$form->hiddenValues['server'] = $server->id;
/* What options are available? */
$domainTypeOptions = array();
$domainTypeToggles = array();
@@ -266,14 +265,26 @@ class _Hosting extends \IPS\nexus\Package
}
if ( \IPS\Settings::i()->nexus_hosting_allow_own_domain )
{
$domainTypeOptions['own'] = 'ha_domain_own';
$domainTypeToggles['own'] = array( 'ha_domain_to_use' );
$ownDomainOptions = explode( ',', \IPS\Settings::i()->nexus_hosting_own_domains );
if ( in_array( 'nameservers', $ownDomainOptions ) or in_array( 'aname', $ownDomainOptions ) or ( in_array( 'cname', $ownDomainOptions ) and $canParkDomains ) )
{
$domainTypeOptions['own'] = 'ha_domain_own';
$domainTypeToggles['own'] = array( 'ha_domain_to_use' );
}
}
if ( !count( $domainTypeOptions ) )
{
\IPS\Output::i()->error( 'hosting_err_public', '4X245/4', 500, 'hosting_err_no_domains' );
}
$form->add( new \IPS\Helpers\Form\Radio( 'ha_domain_type', NULL, TRUE, array( 'options' => $domainTypeOptions, 'toggles' => $domainTypeToggles ) ) );
if ( count( $domainTypeOptions ) > 1 )
{
$form->add( new \IPS\Helpers\Form\Radio( 'ha_domain_type', NULL, TRUE, array( 'options' => $domainTypeOptions, 'toggles' => $domainTypeToggles ) ) );
}
else
{
$_domainTypeOptions = array_keys( $domainTypeOptions );
$form->hiddenValues['ha_domain_type'] = array_pop( $_domainTypeOptions );
}
/* Buy Domain */
if ( array_key_exists( 'buy', $domainTypeOptions ) )
@@ -316,7 +327,7 @@ class _Hosting extends \IPS\nexus\Package
$form->add( new \IPS\Helpers\Form\Custom( 'ha_subdomain_to_use', NULL, NULL, array(
'getHtml' => function( $field )
{
return \IPS\Theme::i()->getTemplate('store')->domainSub( $field );
return \IPS\Theme::i()->getTemplate( 'store', 'nexus' )->domainSub( $field );
},
'validate' => function( $field )
{
@@ -328,10 +339,24 @@ class _Hosting extends \IPS\nexus\Package
}
else
{
/* Subdomain cannot contain any dots */
if ( mb_strpos( $field->value['subdomain'], '.' ) !== FALSE )
{
throw new \DomainException('subdomain_cannot_contain_dot');
}
/* Subdomain cannot contain any spaces */
if ( mb_strpos( $field->value['subdomain'], ' ' ) !== FALSE )
{
throw new \DomainException('subdomain_cannot_contain_space');
}
/* Subdomain cannot contain any of these characters: ~!$&'()*+,;= */
if ( (bool) preg_match( '/([~!\$&\'\"\(\)\*\+,;=]+)/', $field->value['subdomain'] ) )
{
throw new \DomainException('subdomain_invalid_characters');
}
return static::_validateDomain( $field->value['subdomain'] . '.' . $field->value['domain'] );
}
}
@@ -352,15 +377,134 @@ class _Hosting extends \IPS\nexus\Package
}
else
{
return static::_validateDomain( $value );
static::_validateDomain( $value );
try
{
$domain = new \IPS\nexus\DomainLookup( $value, FALSE );
$options = explode( ',', \IPS\Settings::i()->nexus_hosting_own_domains );
if ( $domain->type === \IPS\nexus\DomainLookup::DOMAIN_TYPE_DOMAIN )
{
if ( !in_array( 'nameservers', $options ) )
{
throw new \DomainException('domain_must_be_subdomain');
}
}
else
{
if ( !in_array( 'aname', $options ) and ( !in_array( 'cname', $options ) or !$this->maxpark ) )
{
throw new \DomainException('domain_cannot_be_subdomain');
}
}
}
catch ( \Exception $e )
{
throw new \DomainException('domain_not_valid');
}
}
}
}, 'http://', NULL, 'ha_domain_to_use' ) );
\IPS\Member::loggedIn()->language()->words['ha_domain_to_use_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'ha_domain_to_use_ns', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $server->nameservers() ) ) ) );
}, 'http://', NULL, 'ha_domain_to_use' ) );
}
}
/**
* Additional Secondary Page to show on checkout
*
* @param array $values Values from store form
* @param string|NULL $cnameTarget If a there is known target for a CNAME, otherwise NULL to assign a domain
* @return string|NULL
*/
public function storeAdditionalPage( array $values, $cname=NULL )
{
if ( $values['ha_domain_type'] === 'own' )
{
$domain = new \IPS\nexus\DomainLookup( $values['ha_domain_to_use'] );
$options = explode( ',', \IPS\Settings::i()->nexus_hosting_own_domains );
if ( $domain->type === \IPS\nexus\DomainLookup::DOMAIN_TYPE_DOMAIN )
{
if ( !in_array( 'nameservers', $options ) )
{
throw new \DomainException('domain_must_be_subdomain');
}
$nameservers = explode( ',', mb_strtolower( \IPS\Settings::i()->nexus_hosting_nameservers ) );
$isValid = FALSE;
foreach ( $domain->whois as $whoisData )
{
$whoisData = mb_strtolower( $whoisData );
foreach( $nameservers as $nameserver )
{
if ( mb_strpos( $whoisData, $nameserver ) !== FALSE )
{
$isValid = TRUE;
break;
}
}
}
if ( !$isValid )
{
return \IPS\Theme::i()->getTemplate( 'hosting', 'nexus', 'front' )->ownDomainName( $values, $nameservers );
}
}
else
{
$aname = NULL;
if ( in_array( 'aname', $options ) )
{
$aname = \IPS\nexus\Hosting\Server::load( $values['server'] )->ip;
}
if ( in_array( 'cname', $options ) and $this->maxpark )
{
if ( !$cname )
{
$cname = isset( $values['cname'] ) ? $values['cname'] : str_replace( '.', '', mb_strpos( $domain->sld, '.' ) ? mb_substr( $domain->sld, mb_strpos( $domain->sld, '.' ) ) : $domain->sld );
while ( \IPS\Db::i()->select( 'COUNT(*)', 'nexus_hosting_accounts', array( 'account_domain=? AND account_exists=1', $cname . '.' . \IPS\Settings::i()->nexus_hosting_own_domain_sub ) )->first() )
{
$cname .= rand( 0, 9 );
}
$values['cname'] = $cname;
$cname .= '.' . \IPS\Settings::i()->nexus_hosting_own_domain_sub;
}
}
$isValid = FALSE;
$records = @dns_get_record( $values['ha_domain_to_use'], 0 + ( $aname ? DNS_A : 0 ) + ( $cname ? DNS_CNAME : 0 ) );
if ( is_array( $records ) )
{
foreach ( $records as $record )
{
if ( $aname and $record['type'] === 'A' and $record['ip'] === $aname )
{
$isValid = TRUE;
break;
}
if ( $cname and $record['type'] === 'CNAME' and $record['target'] === $cname )
{
$isValid = TRUE;
break;
}
}
}
if ( !$isValid )
{
return \IPS\Theme::i()->getTemplate( 'hosting', 'nexus', 'front' )->ownDomainName( $values, NULL, $cname, $aname );
}
}
}
return NULL;
}
/**
* Add To Cart
*
@@ -381,7 +525,7 @@ class _Hosting extends \IPS\nexus\Package
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'generic_error', '3X245/5', 403, '' );
\IPS\Output::i()->error( 'generic_error', '3X245/5', 403, 'hosting_no_server' );
}
$item->extra['server'] = $server->id;
@@ -414,7 +558,15 @@ class _Hosting extends \IPS\nexus\Package
return array();
case 'own':
$item->extra['domain'] = $values['ha_domain_to_use'];
if ( isset( \IPS\Request::i()->cname ) )
{
$item->extra['domain'] = \IPS\Request::i()->cname . '.' . \IPS\Settings::i()->nexus_hosting_own_domain_sub;
$item->extra['parkDomain'] = $values['ha_domain_to_use'];
}
else
{
$item->extra['domain'] = $values['ha_domain_to_use'];
}
return array();
}
}
@@ -430,7 +582,7 @@ class _Hosting extends \IPS\nexus\Package
{
$domain = mb_strtolower( $domain );
$data = @parse_url( 'http://' . $domain );
if ( $data === FALSE or $data['host'] != $domain or !preg_match( '/^[a-z][a-z0-9-]*\.[a-z0-9-\.]+$/', $domain ) )
if ( $data === FALSE or $data['host'] != $domain or !\IPS\Http\Url::validateComponent( \IPS\Http\Url::COMPONENT_HOST, $domain ) )
{
throw new \DomainException('domain_not_valid');
}
@@ -466,7 +618,7 @@ class _Hosting extends \IPS\nexus\Package
return $class::_validateDomain( $value );
}
}, 'http://', NULL, 'ha_domain_to_use' );
$field->label = 'ha_domain_to_use';
$field->label = \IPS\Member::loggedIn()->language()->addToStack( 'ha_domain_to_use' );
$form->add( $field );
}
@@ -486,8 +638,8 @@ class _Hosting extends \IPS\nexus\Package
$item->extra['server'] = \IPS\nexus\Hosting\Queue::load( $this->queue )->activeServer()->id;
}
catch ( \UnderflowException $e )
{
\IPS\Output::i()->error( 'generic_error', '3X245/6', 403, '' );
{
\IPS\Output::i()->error( 'hosting_no_server', '3X245/6', 403, '' );
}
$item->extra['domain'] = $values[ 'ha_domain_to_use' . $k ];
}
@@ -527,6 +679,8 @@ class _Hosting extends \IPS\nexus\Package
*/
public function acpEdit( \IPS\nexus\Purchase $purchase, \IPS\Helpers\Form $form, $renewals )
{
parent::acpEdit( $purchase, $form, $renewals );
try
{
$account = \IPS\nexus\Hosting\Account::load( $purchase->id );
@@ -540,15 +694,15 @@ class _Hosting extends \IPS\nexus\Package
$form->add( new \IPS\Helpers\Form\Text( 'account_username', $account->username, TRUE, array(), NULL, NULL, \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) );
$form->add( new \IPS\Helpers\Form\Text( 'account_password', $account->password, TRUE, array(), NULL, NULL, \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) );
$form->add( new \IPS\Helpers\Form\Text( 'account_domain', $account->domain, TRUE, array(), NULL, NULL, \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_quota', ( $account->diskspaceAllowance() !== NULL ? $account->diskspaceAllowance() / 1000000 : -1 ), TRUE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ), NULL, NULL, 'MB' ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_bwlimit', ( $account->monthlyBandwidthAllowance() !== NULL ? $account->monthlyBandwidthAllowance() / 1000000 : -1 ), TRUE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ), NULL, NULL, \IPS\Member::loggedIn()->language()->get('mb_per_month') ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxftp', ( $account->maxFtpAccounts() !== NULL ? $account->maxFtpAccounts() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxsql', ( $account->maxDatabases() !== NULL ? $account->maxDatabases() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxpop', ( $account->maxEmailAccounts() !== NULL ? $account->maxEmailAccounts() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxlst', ( $account->maxMailingLists() !== NULL ? $account->maxMailingLists() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxsub', ( $account->maxSubdomains() !== NULL ? $account->maxSubdomains() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxpark', ( $account->maxParkedDomains() !== NULL ? $account->maxParkedDomains() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxaddon', ( $account->maxAddonDomains() !== NULL ? $account->maxAddonDomains() : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_quota', ( $account->diskspaceAllowance() !== NULL ? intval( $account->diskspaceAllowance() ) / 1048576 / 1.024 : -1 ), TRUE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ), NULL, NULL, 'MB' ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_bwlimit', ( $account->monthlyBandwidthAllowance() !== NULL ? intval( $account->monthlyBandwidthAllowance() ) / 1048576 / 1.024 : -1 ), TRUE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ), NULL, NULL, \IPS\Member::loggedIn()->language()->get('mb_per_month') ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxftp', ( $account->maxFtpAccounts() !== NULL ? intval( $account->maxFtpAccounts() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxsql', ( $account->maxDatabases() !== NULL ? intval( $account->maxDatabases() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxpop', ( $account->maxEmailAccounts() !== NULL ? intval( $account->maxEmailAccounts() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxlst', ( $account->maxMailingLists() !== NULL ? intval( $account->maxMailingLists() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxsub', ( $account->maxSubdomains() !== NULL ? intval( $account->maxSubdomains() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxpark', ( $account->maxParkedDomains() !== NULL ? intval( $account->maxParkedDomains() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'p_maxaddon', ( $account->maxAddonDomains() !== NULL ? intval( $account->maxAddonDomains() ) : -1 ), FALSE, array( 'unlimited' => -1, 'endSuffix' => \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'p_hasshell', $account->hasSSHAccess(), FALSE, array(), NULL, NULL, \IPS\Theme::i()->getTemplate('hosting')->accountEditWarning() ) );
$form->add( new \IPS\Helpers\Form\Custom( 'do_not_update_server', FALSE, FALSE, array(
'rowHtml' => function()
@@ -577,36 +731,39 @@ class _Hosting extends \IPS\nexus\Package
{
try
{
/* Update server if necessary */
$account = \IPS\nexus\Hosting\Account::load( $purchase->id );
if ( $values['account_server']->id != $account->server->id )
if ( $account->exists )
{
$account->server = $values['account_server'];
/* Update server if necessary */
if ( $values['account_server']->id != $account->server->id )
{
$account->server = $values['account_server'];
$account->save();
}
/* Send API calls */
if ( !$values['do_not_update_server'] )
{
try
{
$account->edit( $values );
}
catch ( \IPS\nexus\Hosting\Exception $e )
{
\IPS\Output::i()->error( $e->getMessage(), '3X245/1', 503, '' );
}
}
/* Update local database */
$account->username = $values['account_username'];
$account->password = $values['account_password'];
$account->domain = $values['account_domain'];
$account->save();
}
/* Send API calls */
if ( $account->exists and !$values['do_not_update_server'] )
{
try
{
$account->edit( $values );
}
catch ( \IPS\nexus\Hosting\Exception $e )
{
\IPS\Output::i()->error( '', '3X245/1', 503, '' );
}
}
/* Update local database */
$account->username = $values['account_username'];
$account->password = $values['account_password'];
$account->domain = $values['account_domain'];
$account->save();
}
catch ( \Exception $e ) { }
parent::save( $purchase, $values );
parent::acpEditSave( $purchase, $values );
}
/**
@@ -676,7 +833,7 @@ class _Hosting extends \IPS\nexus\Package
}
$item = new \IPS\nexus\extensions\nexus\Item\Bandwidth(
sprintf( $purchase->member->language()->get('bandwidth_purchase_name'), \IPS\Output\Plugin\Filesize::humanReadableFilesize( $values['bandwidth_to_buy'] * 1000000, TRUE ) ),
sprintf( $purchase->member->language()->get('bandwidth_purchase_name'), \IPS\Output\Plugin\Filesize::humanReadableFilesize( $values['bandwidth_to_buy'] * 1000000, TRUE, FALSE, TRUE ) ),
new \IPS\nexus\Money( $bandwidthOptions[ $values['bandwidth_to_buy'] ][ $currency ]['amount'], $currency )
);
$item->parent = $purchase;
@@ -703,6 +860,25 @@ class _Hosting extends \IPS\nexus\Package
}
}
/**
* Get ACP Support View HTML
*
* @return string
*/
public function acpSupportView( \IPS\nexus\Purchase $purchase )
{
try
{
$account = \IPS\nexus\Hosting\Account::load( $purchase->id );
return \IPS\Theme::i()->getTemplate('purchases')->hostingSupport( $purchase, $account );
}
catch ( \Exception $e )
{
return '';
}
}
/* !Client Area */
/**
@@ -819,6 +995,80 @@ class _Hosting extends \IPS\nexus\Package
return $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'forms', 'core' ) ), 'popupTemplate' ) );
case 'changedomain':
$account = \IPS\nexus\Hosting\Account::load( $purchase->id );
$form = new \IPS\Helpers\Form( 'change_domain', 'change_domain' );
$currency = $purchase->renewals ? $purchase->renewals->cost->currency : $purchase->defaultCurrency();
try
{
$activeDomainPurchase = \IPS\nexus\Purchase::constructFromData( \IPS\Db::i()->select( '*', 'nexus_purchases', array( 'ps_parent=? AND ps_app=? AND ps_type=? AND ps_active=1', $purchase->id, 'nexus', 'domain' ) )->first() );
}
catch ( \UnderflowException $e )
{
$activeDomainPurchase = NULL;
}
$this->_domainForm( $form, $currency, $account->server, $account->maxParkedDomains() === NULL or $this->maxpark === -1 or $this->maxpark > $account->maxParkedDomains() );
if ( $values = $form->values() )
{
if ( $additionalPage = $this->storeAdditionalPage( $_POST, $account->domain ) )
{
return \IPS\Theme::i()->getTemplate( 'purchases', 'nexus', 'front' )->hostingChangeDomain( $purchase, $additionalPage, $activeDomainPurchase );
}
else
{
switch ( $values['ha_domain_type'] )
{
case 'buy':
$domainPrices = json_decode( \IPS\Settings::i()->nexus_domain_prices, TRUE );
$cost = new \IPS\nexus\Money( $domainPrices[ $values['ha_domain_to_buy']['tld'] ][ $currency ]['amount'], $currency );
$tax = NULL;
if ( \IPS\Settings::i()->nexus_domain_tax )
{
try
{
$tax = \IPS\nexus\Tax::load( \IPS\Settings::i()->nexus_domain_tax );
}
catch ( \OutOfRangeException $e ) { }
}
$domain = new \IPS\nexus\extensions\nexus\Item\Domain( $values['ha_domain_to_buy']['sld'] . '.' . $values['ha_domain_to_buy']['tld'], $cost );
$domain->renewalTerm = new \IPS\nexus\Purchase\RenewalTerm( $cost, new \DateInterval('P1Y'), $tax );
$domain->paymentMethodIds = $item->paymentMethodIds;
$domain->tax = $tax;
$domain->extra = array_merge( $values['ha_domain_to_buy'], array( 'nameservers' => $server->nameservers(), 'registerTo' => $purchase->id ) );
$invoice = new \IPS\nexus\Invoice;
$invoice->currency = $currency;
$invoice->member = \IPS\Member::loggedIn();
$invoice->addItem( $domain );
$invoice->save();
\IPS\Output::i()->redirect( $invoice->checkoutUrl() );
case 'sub':
$account->domain = $values['ha_subdomain_to_use']['subdomain'] . '.' . $values['ha_subdomain_to_use']['domain'];
$account->save();
$account->changeDomain( $account->domain );
return array();
case 'own':
if ( isset( \IPS\Request::i()->cname ) )
{
$account->changeDomain( $values['ha_domain_to_use'], TRUE );
}
else
{
$account->domain = $values['ha_domain_to_use'];
$account->save();
$account->changeDomain( $account->domain );
}
return array();
}
}
}
return \IPS\Theme::i()->getTemplate( 'purchases', 'nexus', 'front' )->hostingChangeDomain( $purchase, $form, $activeDomainPurchase );
default:
return parent::clientAreaAction( $purchase );
}
@@ -887,6 +1137,12 @@ class _Hosting extends \IPS\nexus\Package
$account->username = $username;
$account->create( $this, $invoice->member );
$account->save();
/* Park a domain if necessary */
if ( isset( $purchase->extra['parkDomain'] ) )
{
$account->parkDomain( $purchase->extra['parkDomain'] );
}
}
catch ( \IPS\nexus\Hosting\Exception $e )
{
@@ -969,6 +1225,31 @@ class _Hosting extends \IPS\nexus\Package
$e->log();
}
}
/**
* 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 )
{
if ( \IPS\Settings::i()->nexus_hosting_terminate )
{
if ( \IPS\Settings::i()->nexus_hosting_terminate == -1 )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'hosting_cancel_warning_nt' );
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack( 'hosting_cancel_warning', FALSE, array( 'pluralize' => array( \IPS\Settings::i()->nexus_hosting_terminate ) ) );
}
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack( 'hosting_cancel_warning_at' );
}
}
/**
* On Purchase Deleted
@@ -1010,13 +1291,24 @@ class _Hosting extends \IPS\nexus\Package
$update = array();
foreach ( array( 'quota' => 'diskspaceAllowance', 'bwlimit' => 'monthlyBandwidthAllowance', 'maxftp' => 'maxFtpAccounts', 'maxsql' => 'maxDatabases', 'maxpop' => 'maxEmailAccounts', 'maxlst' => 'maxMailingLists', 'maxsub' => 'maxSubdomains', 'maxpark' => 'maxParkedDomains', 'maxaddon' => 'maxAddonDomains', 'hasshell' => 'hasSSHAccess' ) as $k => $method )
{
$val = call_user_func( array( $account, $method ) );
if ( ( $val === NULL and $this->$k == -1 ) or $val == $this->$k )
/* What is the current value? */
$currentValue = call_user_func( array( $account, $method ) );
if ( $currentValue === NULL )
{
$update[ $k ] = $newPackage->$k;
$currentValue = -1;
}
elseif ( $k == 'quota' or $k == 'bwlimit' )
{
$currentValue = $currentValue / 1048576 / 1.024;
}
/* If the current value matches the old package (i.e. it hasn't been manually modified), set it to the new package */
if ( $currentValue == $this->$k )
{
$update[ 'p_' . $k ] = $newPackage->$k;
}
}
if ( !empty( $update ) )
{
$account->edit( $update );
+8 -7
View File
@@ -2,9 +2,9 @@
/**
* @brief Nexus Package Content Item Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 29 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -126,7 +126,7 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param string $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array $limit Limit clause
* @param string|NULL $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index or NULL to ignore permissions
* @param bool|NULL $includeHiddenItems Include hidden files? Boolean or NULL to detect if currently logged member has permission
* @param mixed $includeHiddenItems Include hidden items? NULL to detect if currently logged in member has permission, -1 to return public content only, TRUE to return unapproved content and FALSE to only return unapproved content the viewing member submitted
* @param int $queryFlags Select bitwise flags
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
@@ -138,16 +138,17 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param bool $joinTags If true, will join the tags table
* @param bool $joinAuthor If true, will join the members table for the author
* @param bool $joinLastCommenter If true, will join the members table for the last commenter
* @param bool $showMovedLinks If true, moved item links are included in the results
* @return \IPS\Patterns\ActiveRecordIterator|int
*/
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=NULL, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL, $skipPermission=FALSE, $joinTags=TRUE, $joinAuthor=TRUE, $joinLastCommenter=TRUE )
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=\IPS\Content\Hideable::FILTER_AUTOMATIC, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL, $skipPermission=FALSE, $joinTags=TRUE, $joinAuthor=TRUE, $joinLastCommenter=TRUE, $showMovedLinks=FALSE )
{
$member = $member ?: \IPS\Member::loggedIn();
$where[] = "( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', $member->groups ) . ' )';
$where[] = array( 'p_store=?', 1 );
$return = parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenItems, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins, $skipPermission, $joinTags, $joinAuthor, $joinLastCommenter );
$return = parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenItems, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins, $skipPermission, $joinTags, $joinAuthor, $joinLastCommenter, $showMovedLinks );
$return->classname = 'IPS\nexus\Package';
return $return;
}
@@ -169,7 +170,7 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
if( !isset( $this->_url[ $_key ] ) )
{
$this->_url[ $_key ] = \IPS\Http\Url::internal( "app=nexus&module=store&controller=product&id={$this->id}", 'front', 'store_product', \IPS\Http\Url::seoTitle( \IPS\Member::loggedIn()->language()->get( 'nexus_package_' . $this->id ) ) );
$this->_url[ $_key ] = \IPS\Http\Url::internal( "app=nexus&module=store&controller=product&id={$this->id}", 'front', 'store_product', \IPS\Http\Url\Friendly::seoTitle( \IPS\Member::loggedIn()->language()->get( 'nexus_package_' . $this->id ) ) );
if ( $action )
{
@@ -231,7 +232,7 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
*/
public function images()
{
return new \IPS\File\Iterator( \IPS\Db::i()->select( 'image_location', 'nexus_package_images', array( 'image_product=?', $this->id ) ), 'nexus_Products', NULL, TRUE );
return new \IPS\File\Iterator( \IPS\Db::i()->select( 'image_location', 'nexus_package_images', array( 'image_product=?', $this->id ),'image_primary desc' ), 'nexus_Products', NULL, TRUE );
}
/* !Embeddable */
File diff suppressed because it is too large. Load diff
+72 -9
View File
@@ -2,9 +2,9 @@
/**
* @brief Product Package
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 29 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -107,7 +107,7 @@ class _Product extends \IPS\nexus\Package
}
}
$return['package_benefits']['lkey_identifier'] = new \IPS\Helpers\Form\Select( 'p_lkey_identifier', $package->type === 'product' ? $package->lkey_identifier : 0, FALSE, array( 'options' => $identifierOptions, 'unlimited' => 0, 'unlimitedLang' => 'lkey_identifier_none' ), NULL, NULL, NULL, 'p_lkey_identifier' );
$return['package_benefits']['lkey_identifier'] = new \IPS\Helpers\Form\Select( 'p_lkey_identifier', $package->type === 'product' ? $package->lkey_identifier : '0', FALSE, array( 'options' => $identifierOptions, 'unlimited' => '0', 'unlimitedLang' => 'lkey_identifier_none' ), NULL, NULL, NULL, 'p_lkey_identifier' );
return $return;
}
@@ -129,21 +129,37 @@ class _Product extends \IPS\nexus\Package
{
$values['p_weight'] = is_object( $values['p_weight'] ) ? $values['p_weight']->kilograms : 0;
}
else
{
$values['p_weight'] = 0;
}
if( isset( $values['p_length'] ) )
{
$values['p_length'] = is_object( $values['p_length'] ) ? $values['p_length']->metres : 0;
}
else
{
$values['p_length'] = 0;
}
if( isset( $values['p_width'] ) )
{
$values['p_width'] = is_object( $values['p_width'] ) ? $values['p_width']->metres : 0;
}
else
{
$values['p_width'] = 0;
}
if( isset( $values['p_height'] ) )
{
$values['p_height'] = is_object( $values['p_height'] ) ? $values['p_height']->metres : 0;
}
else
{
$values['p_height'] = 0;
}
if( isset( $values['p_shipping'] ) )
{
@@ -171,11 +187,12 @@ class _Product extends \IPS\nexus\Package
/**
* Update existing purchases
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param array $changes The old values
* @param \IPS\nexus\Purchase $purchase The purchase
* @param array $changes The old values
* @param bool $cancelBillingAgreementIfNecessary If making changes to renewal terms, TRUE will cancel associated billing agreements. FALSE will skip that change
* @return void
*/
public function updatePurchase( \IPS\nexus\Purchase $purchase, $changes )
public function updatePurchase( \IPS\nexus\Purchase $purchase, $changes, $cancelBillingAgreementIfNecessary=FALSE )
{
if ( array_key_exists( 'lkey', $changes ) )
{
@@ -211,7 +228,7 @@ class _Product extends \IPS\nexus\Package
$purchase->save();
}
return parent::updatePurchase( $purchase, $changes );
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
/* !Actions */
@@ -233,8 +250,7 @@ class _Product extends \IPS\nexus\Package
\IPS\Output::i()->error( 'err_subscription_qty', '1X247/2', 403, '' );
}
$purchase = \IPS\Db::i()->select( '*', 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_item_id=? AND ps_cancelled=0 AND ps_member=?', 'nexus', 'package', $this->id, \IPS\Member::loggedIn()->member_id ) );
if ( count( $purchase ) )
if ( $this->_memberHasPurchasedSubscription( \IPS\Member::loggedIn() ) )
{
\IPS\Output::i()->error( 'err_subscription_bought', '1X247/1', 403, '' );
}
@@ -254,6 +270,39 @@ class _Product extends \IPS\nexus\Package
return parent::addToCart( $item, $values, $memberCurrency );
}
/**
* Check for member
* If a user initially checks out as a guest and then logs in during checkout, this method
* is ran to check the items they are purchasing can be bought.
* Is expected to throw a DomainException with an error message to display to the user if not valid
*
* @param \IPS\Member $member The new member
* @return void
* @throws \DomainException
*/
public function memberCanPurchase( \IPS\Member $member )
{
if ( $this->subscription and $this->_memberHasPurchasedSubscription( $member ) )
{
throw new \DomainException( $member->language()->addToStack( 'err_subscription_bought_login', FALSE, array( 'sprintf' => array( $member->language()->addToStack( "nexus_package_{$this->id}" ) ) ) ) );
}
if ( ! ( $this->member_groups == "*" or !empty( ( array_intersect( explode( ",", $this->member_groups ), $member->groups ) ) ) ) )
{
throw new \DomainException( $member->language()->addToStack( 'err_group_cant_purchase', FALSE, array( 'sprintf' => array( $member->language()->addToStack( "nexus_package_{$this->id}" ) ) ) ) );
}
}
/**
* Check if a member has purchased this subscription product
*
* @param \IPS\Member $member The new member
* @return bool
*/
protected function _memberHasPurchasedSubscription( \IPS\Member $member )
{
return (bool) \IPS\Db::i()->select( 'COUNT(*)', 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_item_id=? AND ps_cancelled=0 AND ps_member=?', 'nexus', 'package', $this->id, $member->member_id ) )->first();
}
/**
* Show Purchase Record?
*
@@ -308,6 +357,9 @@ class _Product extends \IPS\nexus\Package
}
catch ( \OutOfRangeException $e ) { }
/* Invalidate License Key Cache so old data is not loaded */
$purchase->licenseKey = NULL;
$class = 'IPS\nexus\Purchase\LicenseKey\\' . ucfirst( $this->lkey );
$licenseKey = new $class;
$licenseKey->identifier = $this->lkey_identifier;
@@ -344,4 +396,15 @@ class _Product extends \IPS\nexus\Package
parent::onPurchaseGenerated( $purchase, $invoice );
}
/**
* 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;
}
}
@@ -2,9 +2,9 @@
/**
* @brief Product Review
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 5 May 2014
* @version SVN_VERSION_NUMBER
+31 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Payout Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -319,4 +319,33 @@ abstract class _Payout extends \IPS\Patterns\ActiveRecord
{
return \IPS\Http\Url::internal( "app=nexus&module=payments&controller=payouts&do=view&id={$this->id}", 'admin' );
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string status Status: 'done' = Payment sent; 'pend' = Pending; 'canc' = Canceled
* @apiresponse \IPS\nexus\Money amount Amount
* @apiresponse string gateway The gateway that will process the withdrawal
* @apiresponse string data The data provided by the member for the process. For example, if the gateway is PayPal, this will be their PayPal email address
* @apiresponse datetime requestedDate Date withdrawal was requested
* @apiresponse datetime completedDate Date withdrawal was completed
* @apiresponse string gatewayId Any ID number provided by the gateway to identify the transaction on their end
* @apiresponse \IPS\nexus\Customer customer Customer
*/
public function apiOutput()
{
return array(
'id' => $this->id,
'status' => $this->status,
'amount' => $this->amount->apiOutput(),
'member' => $this->member->apiOutput(),
'gateway' => $this->gateway,
'data' => $this->data,
'requestedDate' => $this->date->rfc3339(),
'completedDate' => $this->completed ? $this->completed->rfc3339() : null,
'gatewayId' => $this->gw_id,
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief License Key Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 30 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -49,6 +49,11 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
*/
protected static $databaseIdFields = array( 'lkey_purchase' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* Construct ActiveRecord from database row
*
@@ -2,9 +2,9 @@
/**
* @brief License Key Model - MD5
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 30 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief License Key Model - Standard
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 30 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -62,21 +62,21 @@ class _Standard extends \IPS\nexus\Purchase\LicenseKey
public function generate()
{
$key = array();
foreach ( range( 1, self::$blocks ) as $i )
foreach ( range( 1, static::$blocks ) as $i )
{
$_k = '';
foreach ( range( 1, self::$characters ) as $j )
foreach ( range( 1, static::$characters ) as $j )
{
do
{
$chr = rand( self::$low, self::$high );
$chr = rand( static::$low, static::$high );
}
while ( in_array( $chr, self::$disallowed ) );
while ( in_array( $chr, static::$disallowed ) );
$_k .= chr( $chr );
}
$key[] = $_k;
}
return implode( self::$seperator, $key );
return implode( static::$seperator, $key );
}
}
+246 -67
View File
@@ -2,9 +2,9 @@
/**
* @brief Purchase Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -107,11 +107,34 @@ class _Purchase extends \IPS\Node\Model
{
$childCount = $this->childrenCount( NULL, NULL, TRUE, array( array( 'ps_show=1' ) ) );
$hasCustomFields = count( $this->custom_fields );
try
{
$description = call_user_func( array( $this->extension(), 'getPurchaseNameInfo' ), $this );
}
catch ( \OutOfRangeException $e )
{
$description = array();
}
if ( $this->grouped_renewals )
{
$description[] = \IPS\Member::loggedIn()->language()->addToStack('purchase_grouped');
}
elseif ( $this->renewals )
{
$renewals = (string) $this->renewals;
if ( $this->renewals->tax )
{
$renewals .= \IPS\Member::loggedIn()->language()->addToStack( 'plus_tax_rate', NULL, array( 'sprintf' => array( $this->renewals->tax->_title ) ) );
}
$description[] = $renewals;
}
$description = implode( ' &middot; ', $description );
return \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row(
$url,
$this->id,
$hasCustomFields ? \IPS\Theme::i()->getTemplate( 'purchases', 'nexus' )->link( $this ) : $this->name,
( $hasCustomFields and !$childCount ) ? \IPS\Theme::i()->getTemplate( 'purchases', 'nexus' )->link( $this, FALSE, FALSE, TRUE ) : $this->_name,
$childCount,
array_merge( array(
'view' => array(
@@ -120,13 +143,13 @@ class _Purchase extends \IPS\Node\Model
'icon' => 'search',
)
), $this->buttons( $ref ) ),
$this->grouped_renewals ? \IPS\Member::loggedIn()->language()->addToStack('purchase_grouped') : ( (string) ( $this->renewals ?: '' ) ),
$description,
$this->getIcon(),
NULL,
$this->id == \IPS\Request::i()->root,
NULL,
NULL,
!$this->active ? ( $this->cancelled ? array( 'style5', 'canceled' ) : array( 'style6', 'expired' ) ) : NULL,
!$this->active ? ( $this->cancelled ? array( 'style5', 'purchase_canceled' ) : array( 'style6', 'purchase_expired' ) ) : NULL,
$hasCustomFields
);
}
@@ -293,6 +316,10 @@ class _Purchase extends \IPS\Node\Model
if ( !$this->expire or $this->expire->getTimestamp() > time() )
{
$this->active = TRUE;
if ( $this->_data['cancelled'] )
{
$this->onReactivate();
}
}
}
@@ -361,7 +388,7 @@ class _Purchase extends \IPS\Node\Model
*/
public function get_renewals()
{
if( $this->_data['renewals'] )
if( isset( $this->_data['renewals'] ) and $this->_data['renewals'] )
{
$tax = NULL;
if ( $this->_data['tax'] )
@@ -558,6 +585,38 @@ class _Purchase extends \IPS\Node\Model
$this->_data['grouped_renewals'] = !is_null( $data ) ? json_encode( $data ) : NULL;
}
/**
* Get billing agreement
*
* @return \IPS\nexus\Customer\BillingAgreement|NULL
*/
public function get_billing_agreement()
{
if ( $this->_data['billing_agreement'] )
{
try
{
return \IPS\nexus\Customer\BillingAgreement::load( $this->_data['billing_agreement'] );
}
catch ( \OutOfRangeException $e )
{
$this->_data['billing_agreement'] = NULL;
}
}
return NULL;
}
/**
* Set billing agreement
*
* @param \IPS\nexus\Customer\BillingAgreement|NULL $billingAgreement The billing agreement
* @return void
*/
public function set_billing_agreement( \IPS\nexus\Customer\BillingAgreement $billingAgreement = NULL )
{
$this->_data['billing_agreement'] = $billingAgreement === NULL ? NULL : $billingAgreement->id;
}
/* !Properties and syncing */
/**
@@ -569,12 +628,27 @@ class _Purchase extends \IPS\Node\Model
* Get extension
*
* @return \IPS\nexus\Invoice\Item\Purchase
* @throws \OutOfRangeException
*/
protected function extension()
{
if ( $this->extension === NULL )
{
foreach ( \IPS\Application::load( $this->app )->extensions( 'nexus', 'Item', FALSE ) as $ext )
/* Load the app. \IPS\Application::load() throws UnexpectedValueException
if the Application.php does not exist, which we'll standardize to
OutOfRangeException so areas calling this method only need to check
for that */
try
{
$app = \IPS\Application::load( $this->app );
}
catch ( \UnexpectedValueException $e )
{
throw new \OutOfRangeException;
}
/* Find the extension */
foreach ( $app->extensions( 'nexus', 'Item', FALSE ) as $ext )
{
if ( $ext::$type == $this->type )
{
@@ -582,6 +656,12 @@ class _Purchase extends \IPS\Node\Model
break;
}
}
/* Don't have it? */
if ( !$this->extension )
{
throw new \OutOfRangeException;
}
}
return $this->extension;
}
@@ -742,6 +822,23 @@ class _Purchase extends \IPS\Node\Model
}
}
/**
* Get ACP Support View HTML
*
* @return string
*/
public function acpSupportView()
{
try
{
return call_user_func( array( $this->extension(), 'acpSupportView' ), $this );
}
catch ( \OutOfRangeException $e )
{
return '';
}
}
/**
* Support Severity
*
@@ -758,6 +855,23 @@ class _Purchase extends \IPS\Node\Model
return NULL;
}
}
/**
* Get renewal payment methods IDs
*
* @return array|NULL
*/
public function renewalPaymentMethodIds()
{
try
{
return call_user_func( array( $this->extension(), 'renewalPaymentMethodIds' ), $this );
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
/**
* Call on*
@@ -779,12 +893,67 @@ class _Purchase extends \IPS\Node\Model
throw new \BadMethodCallException;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
* @apiresponse string itemApp Key for application. For example, 'nexus' for products and renewals; 'downloads' for Downloads files
* @apiresponse string itemType Key for item type. For example, 'package' for products; 'file' for Downloads files.
* @apiresponse int itemId The ID for the item. For example, the product ID or the file ID.
* @apiresponse \IPS\nexus\Customer customer Customer
* @apiresponse datetime purchased Purchased date
* @apiresponse datetime expires Expiration date
* @apiresponse bool active If purchase is currently active (not expired)
* @apiresponse bool canceled If purchase has been canceled
* @apiresponse \IPS\nexus\Purchase\RenewalTerm renewalTerm Renewal term
* @apiresponse object customFields Values for custom fields
* @apiresponse \IPS\nexus\Purchase parent Parent purchase
* @apiresponse bool show If this purchase shows in the client area and AdminCP
* @apiresponse string licenseKey License key
* @apiresponse string image If the item has a relevant image (for exmaple, product image, Downloads file screenshot), the URL to it
* @apiresponse string url The URL for the customer to view this purchase in the client area
*/
public function apiOutput()
{
$parent = NULL;
if ( $this->parent )
{
try
{
$parent = static::load( $this->parent )->apiOutput();
}
catch ( \OutOfRangeException $e ) {}
}
return array(
'id' => $this->id,
'name' => $this->name,
'itemApp' => $this->app,
'itemType' => $this->type,
'itemId' => $this->item_id,
'customer' => $this->member->apiOutput(),
'purchased' => $this->start->rfc3339(),
'expires' => $this->expire ? $this->expire->rfc3339() : null,
'active' => (bool) $this->active,
'canceled' => (bool) $this->cancelled,
'renewalTerm' => $this->renewals ? $this->renewals->apiOutput() : null,
'customFields' => $this->custom_fields,
'parent' => $parent,
'show' => (bool) $this->show,
'licenseKey' => $this->licenseKey() ? $this->licenseKey()->key : null,
'image' => $this->image() ? ( (string) $this->image()->url ) : null,
'url' => (string) $this->url(),
);
}
/* !License Keys */
/**
* @brief License key
*/
protected $licenseKey;
public $licenseKey;
/**
* Get license key
@@ -897,6 +1066,11 @@ class _Purchase extends \IPS\Node\Model
*/
public function canChangeExpireDate()
{
if ( $this->billing_agreement and !$this->billing_agreement->canceled )
{
return FALSE;
}
try
{
return call_user_func( array( $this->extension(), 'canChangeExpireDate' ), $this );
@@ -924,14 +1098,20 @@ class _Purchase extends \IPS\Node\Model
return FALSE;
}
if ( !$admin and $this->cancelled or !$this->expire or !$this->renewals )
if ( $this->cancelled or !$this->renewals or ( $this->billing_agreement and !$this->billing_agreement->canceled ) )
{
return FALSE;
}
if ( !$admin and $pendingInvoice = $this->invoice_pending and $pendingInvoice->status === $pendingInvoice::STATUS_PENDING )
{
return FALSE;
}
try
{
$date = call_user_func( array( $this->extension(), 'canRenewUntil' ), $this, $admin );
if ( $inCycles and $date instanceof \IPS\DateTime )
{
$now = \IPS\DateTime::create();
@@ -967,7 +1147,7 @@ class _Purchase extends \IPS\Node\Model
return FALSE;
}
return !$this->grouped_renewals and $this->renewals and $this->active;
return !$this->grouped_renewals and ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->renewals and $this->active;
}
/**
@@ -984,7 +1164,7 @@ class _Purchase extends \IPS\Node\Model
{
if( $this->_url === NULL )
{
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=clients&controller=purchases&do=view&id={$this->id}", 'front', 'clientspurchase', array( \IPS\Http\Url::seoTitle( $this->name ) ), \IPS\Settings::i()->nexus_https );
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=clients&controller=purchases&do=view&id={$this->id}", 'front', 'clientspurchase', array( \IPS\Http\Url\Friendly::seoTitle( $this->name ) ), \IPS\Settings::i()->nexus_https );
}
return $this->_url;
@@ -1017,12 +1197,19 @@ class _Purchase extends \IPS\Node\Model
*/
public function delete()
{
\IPS\File::unclaimAttachments( 'nexus_Purchases', $this->id, NULL, 'purchase' );
foreach ( $this->children() as $child )
{
$child->delete();
}
$this->onDelete();
try
{
$this->onDelete();
}
catch ( \BadMethodCallException $e ) {}
parent::delete();
}
@@ -1049,33 +1236,27 @@ class _Purchase extends \IPS\Node\Model
/* Remember what our term is */
$term = $this->renewals->getTerm();
$this->grouped_renewals = array( 'term' => $term['term'], 'unit' => $term['unit'], 'price' => $this->renewals->cost->amount, 'currency' => $this->renewals->cost->currency, 'tax' => $this->renewals->tax ? $this->renewals->tax->id : 0 );
$this->save();
/* If the parent also has a renewal term, merge them */
if ( $parent->renewals )
/* Add our term to the parent... */
if ( !$this->cancelled )
{
$parentCostPerDay = $parent->renewals->costPerDay();
$itemCostPerDay = $this->renewals->costPerDay();
if ( $parentCostPerDay->currency !== $itemCostPerDay->currency )
/* If the parent also has a renewal term, merge them */
if ( $parent->renewals )
{
throw new \DomainException('currencies_dont_match');
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $parent->renewals->add( $this->renewals ), $parent->renewals->interval, $parent->renewals->tax );
$parent->save();
}
$sum = $parentCostPerDay->amount + $itemCostPerDay->amount;
$increase = 1 / ( $parentCostPerDay->amount / $sum );
$newRenewalPrice = new \IPS\nexus\Money( $parent->renewals->cost->amount * $increase, $itemCostPerDay->currency );
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $newRenewalPrice->round(), $parent->renewals->interval, $parent->renewals->tax );
$parent->save();
}
/* Otherwise just set the parent to this */
else
{
$parent->renewals = $this->renewals;
if ( !$parent->expire )
/* Otherwise just set the parent to this */
else
{
$parent->expire = $this->expire;
$parent->renewals = $this->renewals;
if ( !$parent->expire )
{
$parent->expire = $this->expire;
}
$parent->save();
}
$parent->save();
}
/* Cancel any pending invoices as they're no longer valid */
@@ -1119,49 +1300,45 @@ class _Purchase extends \IPS\Node\Model
if ( $this->renewals )
{
/* Restore parent renewal term */
if ( $parent->renewals )
if ( $parent->renewals and !$this->cancelled )
{
$parentCostPerDay = $parent->renewals->costPerDay();
$itemCostPerDay = $this->renewals->costPerDay();
if ( $parentCostPerDay->currency !== $itemCostPerDay->currency )
$newRenewalPrice = $parent->renewals->subtract( $this->renewals );
if ( $newRenewalPrice->amount->isZero() )
{
throw new \DomainException('currencies_dont_match');
}
$sum = $parentCostPerDay->amount - $itemCostPerDay->amount;
if ( $sum and ( $parentCostPerDay->amount / $sum ) )
{
$increase = 1 / ( $parentCostPerDay->amount / $sum );
$newRenewalPrice = new \IPS\nexus\Money( $parent->renewals->cost->amount * $increase, $parent->renewals->cost->currency );
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $newRenewalPrice->round(), $parent->renewals->interval, $parent->renewals->tax );
$parent->renewals = NULL;
}
else
{
$parent->renewals = NULL;
$parent->renewals = new \IPS\nexus\Purchase\RenewalTerm( $newRenewalPrice, $parent->renewals->interval, $parent->renewals->tax );
}
$parent->save();
}
/* Restore this purchase renewal term */
$groupedRenewals = $this->grouped_renewals;
try
if ( $groupedRenewals['term'] and $groupedRenewals['unit'] )
{
$tax = ( isset( $groupedRenewals['tax'] ) and $groupedRenewals['tax'] ) ? \IPS\nexus\Tax::load( $groupedRenewals['tax'] ) : NULL;
try
{
$tax = ( isset( $groupedRenewals['tax'] ) and $groupedRenewals['tax'] ) ? \IPS\nexus\Tax::load( $groupedRenewals['tax'] ) : NULL;
}
catch ( \OutOfRangeException $e )
{
$tax = NULL;
}
$this->renewals = new \IPS\nexus\Purchase\RenewalTerm(
new \IPS\nexus\Money( $groupedRenewals['price'], isset( $groupedRenewals['currency'] ) ? $groupedRenewals['currency'] : $this->member->defaultCurrency() ),
new \DateInterval( 'P' . $groupedRenewals['term'] . mb_strtoupper( $groupedRenewals['unit'] ) ),
$tax
);
}
catch ( \OutOfRangeException $e )
{
$tax = NULL;
}
$this->renewals = new \IPS\nexus\Purchase\RenewalTerm(
new \IPS\nexus\Money( $groupedRenewals['price'], isset( $groupedRenewals['currency'] ) ? $groupedRenewals['currency'] : $this->member->defaultCurrency() ),
new \DateInterval( 'P' . $groupedRenewals['term'] . mb_strtoupper( $groupedRenewals['unit'] ) ),
$tax
);
$this->expire = $parent->expire;
$this->grouped_renewals = NULL;
$this->save();
}
/* Ungroup */
$this->grouped_renewals = NULL;
$this->save();
/* Cancel any pending invoices as they're no longer valid */
if ( $invoice = $parent->invoice_pending )
{
@@ -1214,8 +1391,10 @@ class _Purchase extends \IPS\Node\Model
$return = array_merge( $return, call_user_func( array( $this->extension(), 'acpButtons' ), $this, $url ) );
}
catch ( \OutOfRangeException $e ) { }
$parent = $this->parent();
if ( !$this->grouped_renewals and $this->renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
if ( !$this->grouped_renewals and ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
{
$return['renew'] = array(
'icon' => 'refresh',
@@ -1225,7 +1404,7 @@ class _Purchase extends \IPS\Node\Model
);
}
if ( $this->parent() and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_edit' ) )
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and $this->parent() and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_edit' ) )
{
if ( !$this->grouped_renewals )
{
@@ -1247,7 +1426,7 @@ class _Purchase extends \IPS\Node\Model
}
}
if( !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_transfer' ) )
if( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_transfer' ) )
{
$return['transfer'] = array(
'icon' => 'user',
@@ -1257,7 +1436,7 @@ class _Purchase extends \IPS\Node\Model
);
}
if ( !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_cancel' ) )
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and ( !$this->grouped_renewals or !$parent or !$parent->billing_agreement or $parent->billing_agreement->canceled ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_cancel' ) )
{
if ( $this->cancelled )
{
@@ -1279,7 +1458,7 @@ class _Purchase extends \IPS\Node\Model
}
}
if ( !$this->grouped_renewals and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_delete' ) )
if ( ( !$this->billing_agreement or $this->billing_agreement->canceled ) and ( !$this->grouped_renewals or !$parent or !$parent->billing_agreement or $parent->billing_agreement->canceled ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'purchases_delete' ) )
{
$return['delete'] = array(
'icon' => 'times-circle',
@@ -2,9 +2,9 @@
/**
* @brief Renewal Term Object
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 13 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -129,7 +129,7 @@ class _RenewalTerm
{
$days += $this->interval->d;
}
return $days;
return number_format( $days, 2, '.', '' );
}
/**
@@ -146,18 +146,161 @@ class _RenewalTerm
}
else
{
return new \IPS\nexus\Money( $this->cost->amount / $days, $this->cost->currency );
return new \IPS\nexus\Money( $this->cost->amount->divide( new \IPS\Math\Number("{$days}") ), $this->cost->currency );
}
}
/**
* Get the combined cost of this term and another term (used for grouping)
*
* @param \IPS\nexus\Purchase\RenewalTerm $term Term to add
* @return \IPS\nexus\Money
* @throws \DomainException
*/
public function add( RenewalTerm $term )
{
/* They need to have the same currency */
if ( $this->cost->currency !== $term->cost->currency )
{
throw new \DomainException('currencies_dont_match');
}
/* Get some details */
$thisTerm = $this->getTerm();
$otherTerm = $term->getTerm();
$adjustedCost = $term->cost->amount;
/* If they're not based on the same term, try to normalise as best we can */
if ( $thisTerm['unit'] != $otherTerm['unit'] )
{
switch ( $thisTerm['unit'] )
{
case 'd':
switch ( $otherTerm['unit'] )
{
case 'm':
$otherTerm['term'] *= ( 365 / 12 );
break;
case 'y':
$otherTerm['term'] *= 365;
break;
}
break;
case 'm':
switch ( $otherTerm['unit'] )
{
case 'd':
$thisTerm['term'] *= ( 365 / 12 );
break;
case 'y':
$otherTerm['term'] *= 12;
break;
}
break;
case 'y':
switch ( $otherTerm['unit'] )
{
case 'd':
$thisTerm['term'] *= 365;
break;
case 'm':
$thisTerm['term'] *= 12;
break;
}
break;
}
}
/* If they're not the same term, adjust */
if ( $thisTerm['term'] != $otherTerm['term'] )
{
$adjustedCost = $adjustedCost->multiply( ( ( new \IPS\Math\Number("{$thisTerm['term']}") )->divide( new \IPS\Math\Number("{$otherTerm['term']}") ) ) );
}
/* And return */
return new \IPS\nexus\Money( $this->cost->amount->add( $adjustedCost ), $this->cost->currency );
}
/**
* Get the cost of this term subtract another term (used for grouping)
*
* @param \IPS\nexus\Purchase\RenewalTerm $term Term to subtract
* @return \IPS\nexus\Money
* @throws \DomainException
*/
public function subtract( RenewalTerm $term )
{
$term->cost->amount = $term->cost->amount->multiply( new \IPS\Math\Number( '-1' ) );
return $this->add( $term );
}
/**
* Times quantity (used to describe a combined renewal cost)
*
* @param int $n The number to times by
* @return string
*/
public function times( $n )
{
$cost = new \IPS\nexus\Money( $this->cost->amount->multiply( new \IPS\Math\Number("{$n}") ), $this->cost->currency );
return sprintf( \IPS\Member::loggedIn()->language()->get( 'renew_option'), $cost, $this->getTermUnit() );
}
/**
* To String
*
* @return string
*/
public function __toString()
{
//return \IPS\Member::loggedIn()->language()->addToStack( 'renew_option', FALSE, array( 'sprintf' => array( $this->cost, $this->getTermUnit() ) ) );
{
return sprintf( \IPS\Member::loggedIn()->language()->get( 'renew_option'), $this->cost, $this->getTermUnit() ) ;
}
/**
* To String incl. tax
*
* @param \IPS\nexus\Customer|NULL $customer The customer (NULL for currently logged in member)
* @param int $quantity The quantity to times amount by
* @return string
*/
public function toDisplay( \IPS\nexus\Customer $customer = NULL, $quantity = 1 )
{
$cost = $this->cost;
if ( \IPS\Settings::i()->nexus_show_tax and $this->tax )
{
$customer = $customer ?: \IPS\nexus\Customer::loggedIn();
$taxRate = new \IPS\Math\Number( $this->tax->rate( $customer->estimatedLocation() ) );
$cost = new \IPS\nexus\Money( $cost->amount->add( $cost->amount->multiply( $taxRate ) ), $cost->currency );
}
if ( $quantity != 1 )
{
$cost = new \IPS\nexus\Money( $cost->amount->multiply( $quantity ), $cost->currency );
}
return sprintf( \IPS\Member::loggedIn()->language()->get( 'renew_option'), $cost, $this->getTermUnit() ) ;
}
/**
* Get output for API
*
* @return array
* @apiresponse string term 'd' for days; 'w' for weeks; 'm' for months; 'y' for years
* @apiresponse int unit The number for term. For example, if the renewal term is every 6 months, term will be 'm' and unit will be 6
* @apiresponse \IPS\nexus\Money price The renewal price
* @apiresponse \IPS\nexus\Tax taxClass If the renewal price is taxed, the tax class that applies
*/
public function apiOutput()
{
$term = $this->getTerm();
return array(
'term' => $term['term'],
'unit' => $term['unit'],
'price' => $this->cost->apiOutput(),
'taxClass' => $this->tax ? $this->tax->apiOutput() : null,
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Referral Banner Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 18 Aug 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief EasyPost Shipping Rate
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 19 Jun 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Flat Shipping Rate Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 13 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -234,9 +234,9 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
{
$rates = array();
/* Populate */
foreach ( $values[ "s_rates_{$values['s_type']}" ] as $rate )
{
if ( $rate["{$values['s_type']}_rate_min"] == '*' )
foreach ( $values[ "s_rates_{$values['s_type']}" ] as $k => $rate )
{
if ( $rate["{$values['s_type']}_rate_min"] === '*' )
{
$min = '*';
}
@@ -260,7 +260,7 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
}
}
if ( $rate["{$values['s_type']}_rate_max"] == '*' )
if ( $rate["{$values['s_type']}_rate_max"] === '*' )
{
$max = '*';
}
@@ -283,12 +283,12 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
break;
}
}
if ( !$min and !$max )
{
continue;
}
$price = array();
foreach ( $rate["{$values['s_type']}_rate_price"] as $money )
{
@@ -320,7 +320,7 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
{
unset( $rates[$k] );
}
/* Save */
$values['rates'] = json_encode( $rates );
}
@@ -406,8 +406,8 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
{
$max = $max[ $currency ];
}
if ( ( $min === '*' or $criteria >= $min ) and ( $max === '*' or $criteria <= $max ) )
if ( ( $min === '*' or $criteria->compare( new \IPS\Math\Number( number_format( $min, 5, '.', '' ) ) ) !== -1 ) and ( $max === '*' or $criteria->compare( new \IPS\Math\Number( number_format( $max, 5, '.', '' ) ) ) !== 1 ) )
{
return TRUE;
}
@@ -430,7 +430,7 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
*
* @param array $items Items
* @param \IPS\nexus\Invoice|NULL $invoice The invoice
* @return The criteria the rased is based on (e.g. if the rate is based on invoice total, will return the invoice total)
* @return \IPS\Math\Number The criteria the rased is based on (e.g. if the rate is based on invoice total, will return the invoice total)
*/
protected function _getCriteria( array $items, \IPS\nexus\Invoice $invoice = NULL )
{
@@ -443,29 +443,29 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
}
else
{
$criteria = 0;
$criteria = new \IPS\Math\Number('0');
foreach ( $items as $item )
{
$criteria += ( $item->price->amount * $item->quantity );
$criteria = $criteria->add( $item->linePrice()->amount );
}
}
break;
case 'q':
$criteria = 0;
$criteria = new \IPS\Math\Number('0');
foreach ( $items as $item )
{
$criteria += $item->quantity;
$criteria = $criteria->add( new \IPS\Math\Number( "{$item->quantity}" ) );
}
break;
case 'w':
$criteria = 0;
$criteria = new \IPS\Math\Number('0');
foreach ( $items as $item )
{
if ( isset( $item->weight ) )
{
$criteria += ( $item->weight->kilograms * $item->quantity );
$criteria = $criteria->add( ( new \IPS\Math\Number( number_format( $item->weight->kilograms, 5, '.', '' ) ) )->multiply( new \IPS\Math\Number( "$item->quantity" ) ) );
}
}
}
@@ -484,7 +484,7 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
public function getPrice( array $items, $currency, \IPS\nexus\Invoice $invoice = NULL )
{
$criteria = $this->_getCriteria( $items, $invoice );
$rates = json_decode( $this->rates, TRUE );
foreach ( $rates as $rate )
{
@@ -498,8 +498,8 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
{
$max = $max[ $currency ];
}
if ( ( $min === '*' or $criteria >= $min ) and ( $max === '*' or $criteria <= $max ) )
if ( ( $min === '*' or $criteria->compare( new \IPS\Math\Number( number_format( $min, 5, '.', '' ) ) ) !== -1 ) and ( $max === '*' or $criteria->compare( new \IPS\Math\Number( number_format( $max, 5, '.', '' ) ) ) !== 1 ) )
{
return new \IPS\nexus\Money( $rate['price'][ $currency ], $currency );
}
@@ -541,4 +541,19 @@ class _FlatRate extends \IPS\Node\Model implements \IPS\nexus\Shipping\Rate
{
return \IPS\Member::loggedIn()->language()->addToStack( "nexus_shiprate_de_{$this->id}" );
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
*/
public function apiOutput()
{
return array(
'id' => $this->id,
'name' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_shiprate_' . $this->id )
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Length Object
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 19 Jun 2014
* @version SVN_VERSION_NUMBER
@@ -34,7 +34,7 @@ class _Length
);
/**
* @brief Length in centimetres (naturally we use metres as per le Système international d'unités like good boys)
* @brief Length in metres (naturally we use metres as per le Système international d'unités like good boys)
*/
public $metres = 0;
@@ -96,6 +96,6 @@ class _Length
$unit = static::bestUnit();
}
return round( $this->float( $unit ), $round ) . $unit;
return \IPS\Member::loggedIn()->language()->addToStack( 'length_short_' . $unit, FALSE, array( 'sprintf' => array( round( $this->float( $unit ), $round ) ) ) );
}
}
+45 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief Shipping Order Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 19 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -228,7 +228,7 @@ class _Order extends \IPS\Patterns\ActiveRecord
*/
public function sendNotification()
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'shipment', array( $this ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'shipment', array( $this ), \IPS\Email::TYPE_TRANSACTIONAL );
$email->send( $this->invoice->member );
}
@@ -344,4 +344,46 @@ class _Order extends \IPS\Patterns\ActiveRecord
return $return;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string status Status: 'done' = Shipped; 'pend' = Waiting to be shipped; 'canc' = Canceled
* @apiresponse int invoiceId Invoice ID Number
* @apiresponse \IPS\nexus\Shipping\FlatRate method The shipment method (may be null if using EasyPost)
* @apiresponse object items The items in the shipment and their quantities
* @apiresponse datetime requestDate When the shipment was requested
* @apiresponse datetime shipDate When the shipment was shipped
* @apiresponse \IPS\GeoLocation address The delivery address
* @apiresponse string trackingUrl The URL to view tracking information, if available
*/
public function apiOutput()
{
$items = array();
foreach ( $this->items as $item )
{
if ( isset( $items[ $item['name'] ] ) )
{
$items[ $item['name'] ] += $item['quantity'];
}
else
{
$items[ $item['name'] ] = $item['quantity'];
}
}
return array(
'id' => $this->id,
'status' => $this->status,
'invoiceId' => $this->invoice->id,
'method' => $this->method ? $this->method->apiOutput() : null,
'items' => $items,
'requestDate' => $this->date->rfc3339(),
'shipDate' => $this->shipped_date ? $this->shipped_date->rfc3339() : null,
'address' => $this->address()->apiOutput(),
'trackingUrl' => $this->trackingUrl() ? ( (string) $this->trackingUrl() ) : null
);
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Shipping Rate Interface
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 19 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -2,9 +2,9 @@
/**
* @brief Weight Object
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 13 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -34,7 +34,7 @@ class _Weight
);
/**
* @brief Weight in kilograms (naturally we use kilograms as per le Syst�me international d'unitŽs like good boys)
* @brief Weight in kilograms (naturally we use kilograms as per le système international d'unités like good boys)
*/
public $kilograms = 0;
@@ -95,7 +95,7 @@ class _Weight
{
$unit = static::bestUnit();
}
return round( $this->float( $unit ), $round ) . $unit;
return \IPS\Member::loggedIn()->language()->addToStack( 'weight_short_' . $unit, FALSE, array( 'sprintf' => array( round( $this->float( $unit ), $round ) ) ) );
}
}
@@ -2,9 +2,9 @@
/**
* @brief Support Author Model - Email
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 15 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -61,7 +61,7 @@ class _Email
{
return \IPS\Http\Url::external( "https://secure.gravatar.com/avatar/" . md5( trim( mb_strtolower( $this->email ) ) ) )->setQueryString( 'd', (string) \IPS\Theme::i()->resource( 'default_photo.png', 'core', 'global' ) )->makeSafeForAcp( TRUE );
}
return \IPS\Theme::i()->resource( 'default_photo.png', 'core', 'global' );
return NULL;
}
/**
@@ -91,7 +91,7 @@ class _Email
*/
public function meta()
{
return array( 'email' => $this->email );
return array( );
}
/**
@@ -120,6 +120,6 @@ class _Email
$where[] = array( 'r_id<>?', $exclude->id );
}
return \IPS\nexus\Support\Request::getItemsWithPermission( $where, $order, $limit, 'read', NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
return \IPS\nexus\Support\Request::getItemsWithPermission( $where, $order, $limit, 'read', \IPS\Content\Hideable::FILTER_AUTOMATIC, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
}
}
@@ -2,9 +2,9 @@
/**
* @brief Support Author Model - Member
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -67,7 +67,7 @@ class _Member
*/
public function photo()
{
return $this->customer->photo;
return \IPS\Member::photoUrl( $this->customer->_data, TRUE, FALSE, FALSE );
}
/**
@@ -130,6 +130,6 @@ class _Member
$where[] = array( 'r_id<>?', $exclude->id );
}
return \IPS\nexus\Support\Request::getItemsWithPermission( $where, $order, $limit, 'read', NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
return \IPS\nexus\Support\Request::getItemsWithPermission( $where, $order, $limit, 'read', \IPS\Content\Hideable::FILTER_AUTOMATIC, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
}
}
@@ -2,9 +2,10 @@
/**
* @brief Custom Support Field Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 16 Apr 2013
* @version SVN_VERSION_NUMBER
*/
@@ -165,15 +166,18 @@ class _CustomField extends \IPS\CustomField
*/
public function formatFormValues( $values )
{
if( isset( $values['sf_validate_ftp'] ) )
if ( array_key_exists( 'sf_departments', $values ) )
{
$values['departments'] = $values['sf_departments'] ? implode( ',', array_keys( $values['sf_departments'] ) ) : '*';
unset( $values['sf_departments'] );
}
if( isset( $values['sf_validate_ftp'] ) )
if( array_key_exists( 'sf_validate_ftp', $values ) )
{
$values['validate'] = $values['sf_validate_ftp'];
if ( $values['sf_validate_ftp'] !== NULL )
{
$values['validate'] = $values['sf_validate_ftp'];
}
unset( $values['sf_validate_ftp'] );
}
@@ -2,9 +2,9 @@
/**
* @brief Support Department Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 8 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -54,6 +54,11 @@ class _Department extends \IPS\Node\Model
*/
protected static $databaseIdFields = array( 'dpt_email' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
@@ -88,7 +93,7 @@ class _Department extends \IPS\Node\Model
*/
protected function setDefaultValues()
{
$this->ppi = json_encode( array() );
$this->ppi = '*';
}
/**
@@ -216,41 +221,46 @@ class _Department extends \IPS\Node\Model
\IPS\File::claimAttachments( 'nexus-new-dpt', $this->id, NULL, 'dpt', TRUE );
}
if( isset( $values['dpt_name'] ) )
if ( array_key_exists( 'dpt_name', $values ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_department_{$this->id}", $values['dpt_name'] );
unset( $values['dpt_name'] );
}
if( isset( $values['dpt_desc'] ) )
if ( array_key_exists( 'dpt_desc', $values ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_department_{$this->id}_desc", $values['dpt_desc'] );
unset( $values['dpt_desc'] );
}
if( isset( $values['dpt_ppi'] ) )
unset( $values['dpt_desc'] );
if ( array_key_exists( 'dpt_ppi', $values ) )
{
$values['dpt_ppi'] = $values['dpt_ppi'] === '*' ? '*' : json_encode( $values['dpt_ppi'] );
}
if( isset( $values['dpt_staff'] ) )
if ( array_key_exists( 'dpt_staff', $values ) )
{
$values['dpt_staff'] = $values['dpt_staff'] === '*' ? '*' : implode( ',', $values['dpt_staff'] );
}
if( isset( $values['dpt_ppi_tax'] ) )
if ( array_key_exists( 'dpt_ppi_tax', $values ) )
{
$values['dpt_ppi_tax'] = $values['dpt_ppi_tax'] ? $values['dpt_ppi_tax']->id : NULL;
}
if( isset( $values['dpt_packages'] ) )
if ( array_key_exists( 'dpt_packages', $values ) )
{
$values['dpt_packages'] = is_array( $values['dpt_packages'] ) ? implode( ',', array_map( function( $val )
{
return ltrim( $val, 's' );
}, array_keys( $values['dpt_packages'] ) ) ) : 0;
}
if ( ( array_key_exists( 'dpt_require_package', $values ) AND is_null( $values['dpt_require_package'] ) ) or $values['dpt_packages'] == 0 )
{
$values['dpt_require_package'] = 0;
}
return $values;
}
@@ -342,7 +352,7 @@ class _Department extends \IPS\Node\Model
{
$costs = json_decode( $this->ppi, TRUE );
if( isset( $costs[ $currency ?: \IPS\nexus\Customer::loggedIn()->defaultCurrency() ] ) )
if( isset( $costs[ $currency ?: \IPS\nexus\Customer::loggedIn()->defaultCurrency() ] ) AND $costs[ $currency ?: \IPS\nexus\Customer::loggedIn()->defaultCurrency() ]['amount'] )
{
$cost = $costs[ $currency ?: \IPS\nexus\Customer::loggedIn()->defaultCurrency() ];
return new \IPS\nexus\Money( $cost['amount'], $cost['currency'] );
@@ -380,4 +390,19 @@ class _Department extends \IPS\Node\Model
return $buttons;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
*/
public function apiOutput()
{
return array(
'id' => $this->_id,
'name' => $this->_title
);
}
}
+12 -7
View File
@@ -2,9 +2,9 @@
/**
* @brief Support Log Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 14 Nov 2014
* @version SVN_VERSION_NUMBER
@@ -63,22 +63,22 @@ class _Log extends \IPS\Patterns\ActiveRecord
/**
* Set member performing the action
*
* @param \IPS\nexus\Support\Request $member The member performing the action
* @param \IPS\Member $member The member performing the action
* @return void
*/
public function set_member( \IPS\Member $member )
public function set_member( \IPS\Member $member = NULL )
{
$this->_data['member'] = $member->member_id;
$this->_data['member'] = $member ? $member->member_id : 0;
}
/**
* Get member performing the action
*
* @return \IPS\nexus\Customer
* @return \IPS\nexus\Customer|NULL
*/
public function get_member()
{
return \IPS\nexus\Customer::load( $this->_data['member'] );
return $this->_data['member'] ? \IPS\nexus\Customer::load( $this->_data['member'] ) : NULL;
}
/**
@@ -176,6 +176,7 @@ class _Log extends \IPS\Patterns\ActiveRecord
switch ( $this->action )
{
case 'status':
case 'autoresolve':
return \IPS\nexus\Support\Status::load( $id );
case 'department':
return \IPS\nexus\Support\Department::load( $id );
@@ -185,6 +186,10 @@ class _Log extends \IPS\Patterns\ActiveRecord
return \IPS\nexus\Support\Severity::load( $id );
case 'staff':
return \IPS\Member::load( $id );
case 'split_new':
case 'split_away':
case 'previous_request':
return \IPS\nexus\Support\Request::load( $id );
}
}
catch ( \OutOfRangeException $e )
+66 -49
View File
@@ -2,9 +2,9 @@
/**
* @brief Support Reply Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 9 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -125,9 +125,11 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\Hideable
* @param bool|NULL $incrementPostCount Increment post count? If NULL, will use static::incrementPostCount()
* @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member.
* @param \IPS\DateTime|NULL $time The time
* @return \IPS\Content\Comment
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @return static
*/
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL )
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
{
$obj = call_user_func_array( 'parent::create', func_get_args() );
@@ -287,31 +289,47 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\Hideable
$type[] = $assignedTo->member_id;
}
$sentTo = array( $this->member );
foreach ( \IPS\Db::i()->select( 'staff_id', 'nexus_support_notify', array( $type, array( "departments='*' OR " . \IPS\Db::i()->findInSet( 'departments', array( $this->item()->department->id ) ) ) ) ) as $staffId )
$staffToSendTo = array();
foreach ( \IPS\Db::i()->select( 'staff_id', 'nexus_support_notify', array( $type, array( "( departments='*' OR " . \IPS\Db::i()->findInSet( 'departments', array( $this->item()->department->id ) ) . ' )' ) ) ) as $staffId )
{
$staffToSendTo[ $staffId ] = $staffId;
}
foreach ( \IPS\Db::i()->select( 'member_id', 'nexus_support_tracker', array( 'request_id=? AND notify=1', $this->item()->id ) ) as $staffId )
{
$staffToSendTo[ $staffId ] = $staffId;
}
foreach ( $staffToSendTo as $staffId )
{
if ( in_array( $staffId, $staffIds ) )
{
if ( !in_array( $staffId, $sentTo ) and ( $this->item()->department->staff === '*' or in_array( $staffId, explode( ',', $this->item()->department->staff ) ) ) )
/* The department may only be available to specific members OR groups - we need to load an \IPS\Member object here so that we can check both and send the notification as appropriate. */
$staff = \IPS\Member::load( $staffId );
$departmentStaff = $this->item()->department->staff;
if ( !in_array( $staffId, $sentTo ) and ( $this->item()->department->staff === '*' or count( array_intersect( explode( ',', $departmentStaff ), Department::staffDepartmentPerms( $staff ) ) ) ) )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', $this->type === static::REPLY_HIDDEN ? 'staffNotifyNote' : 'staffNotifyReply', array( $this ) );
$email->from = ( $this->item()->department->email ) ? $this->item()->department->email : \IPS\Settings::i()->email_out;
$fromEmail = ( $this->item()->department->email ) ? $this->item()->department->email : \IPS\Settings::i()->email_out;
switch ( \IPS\Settings::i()->nexus_sout_from )
{
case 'staff':
if ( $this->member )
{
$email->fromName = $this->author()->name;
}
$fromName = $this->member ? $this->author()->name : $fromEmail;
break;
case 'dpt':
$email->fromName = $email->language->get( 'nexus_department_' . $this->item()->department->_id );
$member = is_int( $this->member ) ? \IPS\Member::load( $this->member ) : $this->member;
$fromName = $this->member
? $member->language()->get( 'nexus_department_' . $this->item()->department->_id )
: \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'nexus_department_' . $this->item()->department->_id );
break;
default:
$email->fromName = \IPS\Settings::i()->nexus_sout_from;
$fromName = \IPS\Settings::i()->nexus_sout_from;
break;
}
$email->unsubscribe = array( 'text' => 'manage_notification_settings', 'link' => \IPS\Http\Url::internal( 'app=nexus&module=support&controller=requests&do=notifications', 'admin' ) );
$email->send( \IPS\Member::load( $staffId ) );
\IPS\Email::buildFromTemplate( 'nexus', $this->type === static::REPLY_HIDDEN ? 'staffNotifyNote' : 'staffNotifyReply', array( $this ), \IPS\Email::TYPE_LIST )
->setUnsubscribe( 'nexus', 'unsubscribeStaffNotify' )
->send( $staff, array(), array(), $fromEmail, $fromName );
$sentTo[] = $staffId;
}
@@ -323,6 +341,17 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\Hideable
}
}
/**
* Send Unapproved Notification
*
* @return void
*/
public function sendUnapprovedNotification()
{
// If a hidden note is added, we don't want to send the normal "User has posted something which
// requires approval" email - so this method is overloaded and intentionally left blank.
}
/**
* Send Customer Notifications
*
@@ -335,27 +364,40 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\Hideable
{
if ( \IPS\Settings::i()->nexus_sout_chrome )
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'staffReply', array( $this ) );
$email = \IPS\Email::buildFromTemplate( 'nexus', 'staffReply', array( $this ), \IPS\Email::TYPE_TRANSACTIONAL );
}
else
{
$email = \IPS\Email::buildFromTemplate( 'nexus', 'staffReplyNoChrome', array( $this ) );
$email->useWrapper = FALSE;
$email = \IPS\Email::buildFromTemplate( 'nexus', 'staffReplyNoChrome', array( $this ), \IPS\Email::TYPE_TRANSACTIONAL, FALSE );
}
$email->from = $this->item()->department->email ?: \IPS\Settings::i()->email_out;
/* We need to figure out which language we will use */
try
{
$member = \IPS\Member::load( $to, 'email' );
}
catch( \OutOfRangeException $e )
{
$member = NULL;
}
$language = $member ? $member->language() : \IPS\Lang::load( \IPS\Lang::defaultLanguage() );
$fromEmail = $this->item()->department->email ?: \IPS\Settings::i()->email_out;
switch ( \IPS\Settings::i()->nexus_sout_from )
{
case 'staff':
$email->fromName = $this->author()->name;
$fromName = $this->author()->name;
break;
case 'dpt':
$email->fromName = $email->language->get( 'nexus_department_' . $this->item()->department->_id );
$fromName = $language->get( 'nexus_department_' . $this->item()->department->_id );
break;
default:
$email->fromName = \IPS\Settings::i()->nexus_sout_from;
$fromName = \IPS\Settings::i()->nexus_sout_from;
break;
}
$email->send( $to, $cc, $bcc );
$email->send( $to, $cc, $bcc, $fromEmail, $fromName );
}
/**
@@ -374,29 +416,4 @@ class _Reply extends \IPS\Content\Comment implements \IPS\Content\Hideable
return parent::postCreate();
}
/**
* Get ACP URL
*
* @param string|NULL $action Action
* @return \IPS\Http\Url
*/
public function acpUrl( $action=NULL )
{
$url = \IPS\Http\Url::internal( "app=nexus&module=support&controller=request&id={$this->id}", 'admin' );
if( $action )
{
return $url->setQueryString( 'do', $action . 'Comment' )->setQueryString( 'comment', $this->id );
}
else
{
$commentPosition = \IPS\Db::i()->select( 'COUNT(*) AS position', static::$databaseTable, array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=? AND ' . static::$databasePrefix . static::$databaseColumnId . '<=?', $this->request, $this->id ), static::$databasePrefix . static::$databaseColumnMap['date'] . ' asc' )->first();
$page = ceil( $commentPosition / \IPS\nexus\Support\Request::getCommentsPerPage() );
if ( $page != 1 )
{
$url = $url->setQueryString( 'page', $page );
}
return $url->setFragment( 'comment-' . $this->id );
}
}
}
File diff suppressed because it is too large. Load diff
@@ -2,9 +2,9 @@
/**
* @brief Support Severity Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 9 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -54,6 +54,11 @@ class _Severity extends \IPS\Node\Model
*/
protected static $databaseIdFields = array( 'sev_default' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
@@ -80,6 +85,51 @@ class _Severity extends \IPS\Node\Model
'module' => 'support',
'all' => 'severities_manage'
);
/**
* Load Record
*
* @see \IPS\Db::build
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
{
try
{
return parent::load( $id, $idField, $extraWhereClause );
}
catch ( \OutOfRangeException $e )
{
/* Severity is required. If none are set as the fault (for example, bad data from upgrade), find or create one */
if ( $id === TRUE and $idField === 'sev_default' and $extraWhereClause === NULL )
{
try
{
$return = parent::constructFromData( \IPS\Db::i()->select( '*', 'nexus_support_severities' )->first() );
}
catch ( \UnderflowException $e )
{
$return = new static;
$return->id = 1;
$return->color = '000';
$return->public = TRUE;
$return->position = 1;
$return->departments = '*';
}
$return->default = TRUE;
$return->save();
return $return;
}
throw $e;
}
}
/**
* [Node] Add/Edit Form
@@ -91,7 +141,7 @@ class _Severity extends \IPS\Node\Model
{
$form->addHeader( 'severity_settings' );
$form->add( new \IPS\Helpers\Form\Translatable( 'sev_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => ( $this->id ? "nexus_severity_{$this->id}" : NULL ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'sev_default', $this->default ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'sev_default', $this->default, FALSE, array( 'disabled' => $this->default ) ) );
$form->addHeader( 'severity_submissions' );
$form->add( new \IPS\Helpers\Form\YesNo( 'sev_public', $this->public, FALSE, array( 'togglesOn' => array( 'sev_departments', 'sev_desc' ) ) ) );
$form->add( new \IPS\Helpers\Form\Node( 'sev_departments', ( $this->departments and $this->departments !== '*' ) ? explode( ',', $this->departments ) : 0, FALSE, array( 'class' => 'IPS\nexus\Support\Department', 'multiple' => TRUE, 'zeroVal' => 'all' ), NULL, NULL, NULL, 'sev_departments' ) );
@@ -101,7 +151,6 @@ class _Severity extends \IPS\Node\Model
'textArea' => TRUE
), NULL, NULL, NULL, 'sev_desc' ) );
$form->addHeader( 'severity_acp_list' );
$form->add( new \IPS\Helpers\Form\Upload( 'sev_icon', $this->icon ? \IPS\File::get( 'nexus_Support', $this->icon ) : NULL, FALSE, array( 'storageExtension' => 'nexus_Support', 'image' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\Color( 'sev_color', $this->color ?: '000' ) );
}
@@ -124,9 +173,17 @@ class _Severity extends \IPS\Node\Model
unset( $values['sev_name'] );
}
if( isset( $values['sev_desc'] ) )
if( array_key_exists( 'sev_desc', $values ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_severity_{$this->id}_desc", $values['sev_desc'] );
if ( $values['sev_desc'] )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_severity_{$this->id}_desc", $values['sev_desc'] );
}
else
{
\IPS\Lang::deleteCustom( 'nexus', "nexus_severity_{$this->id}_desc" );
}
unset( $values['sev_desc'] );
}
@@ -177,4 +234,19 @@ class _Severity extends \IPS\Node\Model
return $buttons;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
*/
public function apiOutput()
{
return array(
'id' => $this->_id,
'name' => $this->_title
);
}
}
+68 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief Support Status Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 8 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -53,6 +53,11 @@ class _Status extends \IPS\Node\Model
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array( 'status_default_member', 'status_default_staff' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] ACP Restrictions
@@ -75,7 +80,51 @@ class _Status extends \IPS\Node\Model
'module' => 'support',
'all' => 'statuses_manage'
);
/**
* Load Record
*
* @see \IPS\Db::build
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
{
try
{
return parent::load( $id, $idField, $extraWhereClause );
}
catch ( \OutOfRangeException $e )
{
/* A default status (both user and staff) is required. If none are set as the fault (for example, bad data from upgrade), find or create one */
if ( $id === TRUE and ( $idField === 'status_default_member' OR $idField === 'status_default_staff' ) and $extraWhereClause === NULL )
{
try
{
$return = parent::constructFromData( \IPS\Db::i()->select( '*', 'nexus_support_statuses' )->first() );
}
catch ( \UnderflowException $e )
{
$return = new static;
$return->open = 1;
$return->log = 1;
$return->position = 0;
}
$col = str_replace( 'status_', '', $idField );
$return->$col = TRUE;
$return->save();
return $return;
}
throw $e;
}
}
/**
* Statuses that the member can set
*
@@ -274,4 +323,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') ) );
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
*/
public function apiOutput()
{
return array(
'id' => $this->_id,
'publicName' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_status_' . $this->id . '_front' ),
'internalName' => \IPS\Member::loggedIn()->language()->addToStack( 'nexus_status_' . $this->id . '_admin' )
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Support Stock Action Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 15 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -0,0 +1,475 @@
<?php
/**
* @brief Support Requests Stream
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 11 Sep 2016
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\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;
}
/**
* Support Requests Stream
*/
class _Stream extends \IPS\Patterns\ActiveRecord
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'nexus_support_streams';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'stream_';
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'position';
/**
* Get streams for a member
*
* @param \IPS\Member $member The member
* @return array
*/
public static function myStreams( \IPS\Member $member )
{
$return = array();
foreach ( array( 'open', 'assigned', 'tracked' ) as $k )
{
$return[] = static::load( $k );
}
foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_support_streams', array( 'stream_owner=?', $member->member_id ) ), 'IPS\nexus\Support\Stream' ) as $stream )
{
$return[] = $stream;
}
return $return;
}
/**
* Get my stream
*
* @return \IPS\nexus\Support\Stream
*/
public static function myStream()
{
$streamId = isset( \IPS\Request::i()->cookie['support_stream'] ) ? \IPS\Request::i()->cookie['support_stream'] : 'open';
try
{
$stream = \IPS\nexus\Support\Stream::load( $streamId );
}
catch ( \OutOfRangeException $e )
{
$stream = \IPS\nexus\Support\Stream::load( 'open' );
}
return $stream;
}
/**
* Load
*
* @param mixed $keyOrId The key or ID number
* @return \IPS\nexus\Support\Stream
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
{
switch ( $id )
{
case 'open':
$return = new static;
$return->id = 'open';
$return->title = \IPS\Member::loggedIn()->language()->addToStack('support_stream_open');
$return->departments = NULL;
$return->statuses = iterator_to_array( \IPS\Db::i()->select( 'status_id', 'nexus_support_statuses', array( 'status_open=1' ) ) );
$return->severities = NULL;
$return->staff = NULL;
$return->from_email = NULL;
$return->search_term = NULL;
$return->started = NULL;
$return->last_new_reply = NULL;
$return->last_reply = NULL;
$return->last_staff_reply = NULL;
$return->tracked = FALSE;
return $return;
case 'assigned':
$return = new static;
$return->id = 'assigned';
$return->title = \IPS\Member::loggedIn()->language()->addToStack('support_stream_assigned');
$return->departments = NULL;
$return->statuses = iterator_to_array( \IPS\Db::i()->select( 'status_id', 'nexus_support_statuses', array( 'status_open=1' ) ) );
$return->severities = NULL;
$return->staff = 'me';
$return->from_email = NULL;
$return->search_term = NULL;
$return->started = NULL;
$return->last_new_reply = NULL;
$return->last_reply = NULL;
$return->last_staff_reply = NULL;
$return->tracked = FALSE;
return $return;
case 'tracked':
$return = new static;
$return->id = 'tracked';
$return->title = \IPS\Member::loggedIn()->language()->addToStack('support_stream_tracked');
$return->departments = NULL;
$return->statuses = NULL;
$return->severities = NULL;
$return->staff = NULL;
$return->from_email = NULL;
$return->search_term = NULL;
$return->started = NULL;
$return->last_new_reply = NULL;
$return->last_reply = NULL;
$return->last_staff_reply = NULL;
$return->tracked = TRUE;
return $return;
default:
return parent::load( $id, $idField, $extraWhereClause );
}
}
/**
* Customer Stream
*
* @param \IPS\nexus\Customer $customer The customer
* @return \IPS\nexus\Support\Stream
*/
public static function customer( \IPS\nexus\Customer $customer )
{
$return = new static;
$return->title = \IPS\Member::loggedIn()->language()->addToStack( 'members_support_requests', FALSE, array( 'sprintf' => array( $customer->cm_name ) ) );
$return->departments = NULL;
$return->statuses = NULL;
$return->severities = NULL;
$return->staff = NULL;
$return->from_email = $customer->email;
$return->search_term = NULL;
$return->started = NULL;
$return->last_new_reply = NULL;
$return->last_reply = NULL;
$return->last_staff_reply = NULL;
$return->tracked = FALSE;
return $return;
}
/**
* Create from form
*
* @param array $values Values from the form
* @return \IPS\nexus\Support\Stream
*/
public static function createFromForm( $values )
{
$return = new static;
$return->updateFromForm( $values );
$return->temporary = TRUE;
$return->owner = \IPS\Member::loggedIn()->member_id;
$return->title = \IPS\Member::loggedIn()->language()->get('stream_default_title');
$return->save();
return $return;
}
/**
* Update from form
*
* @param array $values Values from the form
* @return void
*/
public function updateFromForm( $values )
{
$this->departments = $values['r_department'] ?: NULL;
$this->statuses = $values['r_status'] ?: NULL;
$this->severities = $values['r_severity'] ?: NULL;
$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'] ?: NULL;
$this->last_new_reply = $values['r_last_new_reply'] ?: NULL;
$this->last_reply = $values['r_last_reply'] ?: NULL;
$this->last_staff_reply = $values['r_last_staff_reply'] ?: NULL;
$this->tracked = (bool) $values['r_tracked'];
}
/**
* Convert arrays
*
* @param mixed $key Key
* @return mixed Value from the datastore
*/
public function __get( $key )
{
$return = parent::__get( $key );
if ( $return !== NULL and in_array( $key, array( 'departments', 'statuses', 'severities', 'staff' ) ) and $return !== 'me' )
{
$return = explode( ',', $return );
}
return $return;
}
/**
* Set value in data store
*
* @see \IPS\Patterns\ActiveRecord::save
* @param mixed $key Key
* @param mixed $value Value
* @return void
*/
public function __set( $key, $value )
{
if ( is_array( $value ) and in_array( $key, array( 'departments', 'statuses', 'severities', 'staff' ) ) )
{
$value = implode( ',', $value );
}
return parent::__set( $key, $value );
}
/**
* Get form
*
* @param \IPS\nexus\Support\Stream $selectedStream The stream to show as selected
* @param \IPS\Http\Url $url The URL to submit to
* @return \IPS\Helpers\Form
*/
public function form( Stream $selectedStream = NULL, \IPS\Http\Url $url )
{
/* Init */
$form = new \IPS\Helpers\Form( 'form', 'show_results', $url );
$form->class = 'ipsForm_vertical';
/* Streams */
$form->addTab('streams');
$form->add( new \IPS\Helpers\Form\Custom( 'stream', $selectedStream ? $selectedStream->id : 'custom', FALSE, array(
'getHtml' => function( $input ) {
return \IPS\Theme::i()->getTemplate('support')->filterFormStream( $input, \IPS\nexus\Support\Stream::myStreams( \IPS\Member::loggedIn() ) );
}
) ) );
/* Departments */
$form->addTab('first');
$departments = array();
foreach ( \IPS\nexus\Support\Department::departmentsWithPermission() as $department )
{
$departments[ $department->_id ] = $department->_title;
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'r_department', $this->departments ?: array_keys( $departments ), FALSE, array( 'options' => $departments ) ) );
/* Statuses */
$statuses = array();
$openStatuses = array();
foreach ( \IPS\nexus\Support\Status::roots() as $status )
{
$statuses[ $status->_id ] = $status->_title;
if ( $status->open )
{
$openStatuses[] = $status->_id;
}
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'r_status', $this->statuses ?: $openStatuses, FALSE, array( 'options' => $statuses ) ) );
/* Severities */
$form->addTab('second');
$severities = array();
foreach ( \IPS\nexus\Support\Severity::roots() as $severity )
{
$severities[ $severity->_id ] = $severity->_title;
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'r_severity', $this->severities ?: array_keys( $severities ), FALSE, array( 'options' => $severities ) ) );
/* Assigned To */
$staff = \IPS\nexus\Support\Request::staff();
$staff[0] = 'unassigned';
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'r_staff', $this->staff ?: array_keys( $staff ), FALSE, array( 'options' => $staff ) ) );
/* Tracked */
$form->add( new \IPS\Helpers\Form\YesNo( 'r_tracked', $this->tracked, FALSE ) );
/* Customer email */
$form->addTab('third');
$form->add( new \IPS\Helpers\Form\Email( 'r_email', $this->from_email ) );
/* Search text */
$form->add( new \IPS\Helpers\Form\Text( 'r_search', $this->search_term ) );
/* Time frame */
$timeFrameOptions = array(
'getHtml' => function( $input ) {
return \IPS\Theme::i()->getTemplate('support')->filterFormTime( $input );
}
);
$form->add( new \IPS\Helpers\Form\Custom( 'r_started', $this->started, FALSE, $timeFrameOptions ) );
$form->add( new \IPS\Helpers\Form\Custom( 'r_last_new_reply', $this->last_new_reply, FALSE, $timeFrameOptions ) );
$form->add( new \IPS\Helpers\Form\Custom( 'r_last_reply', $this->last_reply, FALSE, $timeFrameOptions ) );
$form->add( new \IPS\Helpers\Form\Custom( 'r_last_staff_reply', $this->last_staff_reply, FALSE, $timeFrameOptions ) );
/* Return */
return $form;
}
/**
* Get where clause for results
*
* @param $member Staff member, or NULL for no permission restriction
* @return array
*/
public function _whereClause( $member )
{
/* Init where clause */
$where = array();
/* Departments */
$departments = NULL;
if ( $this->departments )
{
if ( $member )
{
$departments = array_intersect( $this->departments, array_keys( iterator_to_array( \IPS\nexus\Support\Department::departmentsWithPermission( $member ) ) ) );
}
else
{
$departments = $this->departments;
}
}
elseif ( $member )
{
$departments = array_keys( iterator_to_array( \IPS\nexus\Support\Department::departmentsWithPermission( $member ) ) );
}
if ( $departments )
{
$where[] = array( \IPS\Db::i()->in( 'r_department', $departments ) );
}
/* Statuses */
if ( $this->statuses )
{
$where[] = array( \IPS\Db::i()->in( 'r_status', $this->statuses ) );
}
/* Severities */
if ( $this->severities )
{
$where[] = array( \IPS\Db::i()->in( 'r_severity', $this->severities ) );
}
/* Staff */
if ( $this->staff )
{
if ( $this->staff === 'me' )
{
$where[] = array( 'r_staff=?', $member->member_id );
}
else
{
$where[] = array( \IPS\Db::i()->in( 'r_staff', $this->staff ) );
}
}
/* From */
if ( $this->from_email )
{
$fromMember = \IPS\Member::load( $this->from_email, 'email' );
if ( $fromMember->member_id )
{
$where[] = array( '( r_member=? OR r_email=? )', $fromMember->member_id, $fromMember->email );
}
else
{
$where[] = array( '( r_email=? )', $this->from_email );
}
}
/* Search term */
if ( $this->search_term )
{
$where[] = array( 'r_id IN(?)', \IPS\Db::i()->select( 'reply_request', 'nexus_support_replies', array( "MATCH(reply_post) AGAINST (? IN BOOLEAN MODE)", $this->search_term ) ) );
}
/* Tracked? */
if ( $this->tracked and $member )
{
$where[] = array( 'r_id IN(?)', \IPS\Db::i()->select( 'request_id', 'nexus_support_tracker', array( 'member_id=?', $member->member_id ) ) );
}
/* Time limits */
foreach ( array( 'started', 'last_new_reply', 'last_reply', 'last_staff_reply' ) as $k )
{
if ( $this->$k )
{
$where[] = array( "r_{$k}<?", time() - ( 3600 * $this->$k ) );
}
}
/* Return */
return $where;
}
/**
* Get count
*
* @param $member Staff member, or NULL for no permission restriction
* @return \IPS\Patterns\ActiveRecordIterator
*/
public function count( $member = NULL )
{
return \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_requests', $this->_whereClause( $member ) )->first();
}
/**
* Get results
*
* @param $member Staff member, or NULL for no permission restriction
* @return \IPS\Patterns\ActiveRecordIterator
*/
public function results( $member = NULL, $order='r_id DESC', $page = 1, $perPage = 25 )
{
$query = \IPS\Db::i()->select( '*', 'nexus_support_requests', $this->_whereClause( $member ), $order, array( ( $page - 1 ) * $perPage, $perPage ) );
if ( mb_strpos( $order, 'dpt_position' ) !== FALSE )
{
try
{
\IPS\Db::i()->select( 'staff_id', 'nexus_support_staff_dpt_order', array( 'staff_id=?', $member->member_id ), NULL, 1 )->first();
$query->join( 'nexus_support_staff_dpt_order', 'nexus_support_staff_dpt_order.department_id=r_department AND nexus_support_staff_dpt_order.staff_id=' . $member->member_id );
}
catch ( \UnderflowException $e )
{
$query->join( 'nexus_support_departments', 'dpt_id=r_department' );
}
}
if ( mb_strpos( $order, 'sev_position' ) !== FALSE )
{
$query->join( 'nexus_support_severities', 'sev_id=r_severity' );
}
$query->setKeyField('r_id');
$results = new \IPS\Patterns\ActiveRecordIterator( $query, 'IPS\nexus\Support\Request' );
return $results;
}
}
+44 -13
View File
@@ -2,9 +2,9 @@
/**
* @brief Tax Rate Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -102,10 +102,11 @@ class _Tax extends \IPS\Node\Model
$form->addTab( 'tax_rates_tab' );
$matrix = new \IPS\Helpers\Form\Matrix;
$matrix->squashFields = FALSE;
$matrix->columns = array(
'tax_rate' => function( $key, $value, $data )
{
return new \IPS\Helpers\Form\Number( $key, $value, FALSE, array(), NULL, NULL, '%' );
return new \IPS\Helpers\Form\Number( $key, $value, FALSE, array( 'decimals' => 2 ), NULL, NULL, '%' );
},
'tax_locations' => function( $key, $value, $data )
{
@@ -140,15 +141,18 @@ class _Tax extends \IPS\Node\Model
public function formatFormValues( $values )
{
$rates = array();
if( isset( $values['rates'] ) )
{
foreach ( $values['rates'] as $rate )
{
$rates[] = array(
'locations' => $rate['tax_locations'],
'rate' => $rate['tax_rate']
);
if ( $rate['tax_locations'] )
{
$rates[] = array(
'locations' => $rate['tax_locations'],
'rate' => $rate['tax_rate']
);
}
}
}
@@ -159,7 +163,7 @@ class _Tax extends \IPS\Node\Model
'rate' => $values['tax_default']
);
}
$values = array_merge( array( 'rate' => json_encode( $rates ) ), $values );
unset( $values['tax_default'] );
@@ -176,13 +180,25 @@ class _Tax extends \IPS\Node\Model
return $values;
}
/**
* [Node] Delete
*
* @return void
*/
public function delete()
{
\IPS\Db::i()->update( 'nexus_packages', array( 'p_tax' => 0 ), array( "p_tax=?", $this->_id ) );
parent::delete();
}
/**
* Get rate
*
* For example, if the rate for the location is 10%, will return 0.1
*
* @param \IPS\GeoLocation|NULL $billingAddress The billing address
* @return float
* @return string Result after running through number_format, which is a float represented as a string
*/
public function rate( \IPS\GeoLocation $billingAddress = NULL )
{
@@ -197,11 +213,26 @@ class _Tax extends \IPS\Node\Model
{
if ( $rate['locations'][ $billingAddress->country ] === '*' or in_array( $billingAddress->region, $rate['locations'][ $billingAddress->country ] ) )
{
return $rate['rate'] / 100;
return number_format( ( $rate['rate'] / 100 ), 5, '.', '' );
}
}
}
return ( $defaultVal / 100 );
return number_format( ( $defaultVal / 100 ), 5, '.', '' );
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID Number
* @apiresponse string name Name
*/
public function apiOutput()
{
return array(
'id' => $this->id,
'name' => $this->_title
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Transaction Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -30,8 +30,9 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
const STATUS_HELD = 'hold'; // Transaction is being held for approval
const STATUS_REVIEW = 'revw'; // Transaction, after being held for approval, has been flagged for review by staff
const STATUS_REFUSED = 'fail'; // Transaction was refused
const STATUS_REFUNDED = 'rfnd'; // Transaction has been refunded in fulll
const STATUS_REFUNDED = 'rfnd'; // Transaction has been refunded in full
const STATUS_PART_REFUNDED = 'prfd'; // Transaction has been partially refunded
const STATUS_GATEWAY_PENDING= 'gwpd'; // The gateway is processing the transaction
/**
* @brief Multiton Store
@@ -94,8 +95,6 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
*/
public static function table( $where = array(), \IPS\Http\Url $url, $ref = 't' )
{
$where[] = array( 't_status<>?', static::STATUS_PENDING );
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'nexus_transactions', $url, $where );
$table->sortBy = $table->sortBy ?: 't_date';
@@ -185,7 +184,14 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
*/
public function get_member()
{
return \IPS\nexus\Customer::load( $this->_data['member'] );
try
{
return \IPS\nexus\Customer::load( $this->_data['member'] );
}
catch( \OutOfRangeException $e )
{
return NULL;
}
}
/**
@@ -202,11 +208,19 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
/**
* Get invoice
*
* @return \IPS\nexus\Invoice
* @return \IPS\nexus\Invoice|NULL
*/
public function get_invoice()
{
return \IPS\nexus\Invoice::load( $this->_data['invoice'] );
/* If an invoice is deleted, then the transaction will remain present, which then can result in uncaught exception errors. */
try
{
return \IPS\nexus\Invoice::load( $this->_data['invoice'] );
}
catch( \OutOfRangeException $e )
{
return NULL;
}
}
/**
@@ -261,7 +275,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
public function get_amount()
{
$amount = new \IPS\nexus\Money( $this->_data['amount'], $this->_data['currency'] );
return $amount->round(); // A transaction has to be a valid monetary amount, eg not $1.005
return $amount;
}
/**
@@ -272,7 +286,6 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
*/
public function set_amount( \IPS\nexus\Money $amount )
{
$amount = $amount->round(); // A transaction has to be a valid monetary amount, eg not $1.005
$this->_data['amount'] = $amount->amount;
$this->_data['currency'] = $amount->currency;
}
@@ -318,7 +331,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
{
$this->_data['extra'] = json_encode( $extra );
}
/**
* Get MaxMind data
*
@@ -411,13 +424,33 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
}
/**
* Check fraud rules and capture
* Get billing agreement
*
* @return \IPS\nexus\Customer\BillingAgreement|NULL
*/
public function get_billing_agreement()
{
return $this->_data['billing_agreement'] ? \IPS\nexus\Customer\BillingAgreement::load( $this->_data['billing_agreement'] ) : NULL;
}
/**
* Set billing agreement
*
* @param \IPS\nexus\Customer\BillingAgreement|NULL $billingAgreement The billing agreement
* @return void
*/
public function set_billing_agreement( \IPS\nexus\Customer\BillingAgreement $billingAgreement = NULL )
{
$this->_data['billing_agreement'] = $billingAgreement === NULL ? NULL : $billingAgreement->id;
}
/**
* Run Anti-Fraud Checks and return status for transaction
*
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return void
* @throws \LogicException
* @return string
*/
public function checkFraudRulesAndCapture( $maxMind=NULL )
public function runFraudCheck( $maxMind=NULL )
{
/* Run MaxMind */
if ( \IPS\Settings::i()->maxmind_key )
@@ -428,26 +461,27 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$maxMind->setTransaction( $this );
}
$this->fraud = $maxMind->request();
/* If MaxMind fails, stop here */
if ( $this->fraud->err and \IPS\Settings::i()->maxmind_error == 'hold' )
try
{
$this->status = static::STATUS_HELD;
$extra = $this->extra;
$extra['history'][] = array( 's' => static::STATUS_HELD );
$this->extra = $extra;
$this->save();
$maxMindResponse = $maxMind->request();
$this->member->log( 'transaction', array(
'type' => 'paid',
'status' => $this->status,
'id' => $this->id,
'invoice_id' => $this->invoice->id,
'invoice_title' => $this->invoice->title,
) );
$this->fraud = $maxMindResponse;
$this->save();
return;
/* If MaxMind fails, stop here */
if ( $this->fraud->error() and \IPS\Settings::i()->maxmind_error == 'hold' )
{
return static::STATUS_HELD;
}
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'maxmind_error' );
if ( \IPS\Settings::i()->maxmind_error == 'hold' )
{
return static::STATUS_HELD;
}
}
}
@@ -457,28 +491,86 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
if ( $rule->matches( $this ) )
{
$this->fraud_blocked = $rule;
$this->save();
return $rule->action;
}
}
if ( $this->fraud_blocked and $this->fraud_blocked->action !== static::STATUS_PAID )
/* Still here? No fraud rule matches so we can proceed with the transaction */
return static::STATUS_PAID;
}
/**
* Check fraud rules and capture
*
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
* @return void
* @throws \LogicException
*/
public function checkFraudRulesAndCapture( $maxMind=NULL )
{
/* Check fraud rules */
$fraudResult = $this->runFraudCheck( $maxMind );
if ( $fraudResult )
{
$this->status = $this->fraud_blocked->action;
$extra = $this->extra;
$extra['history'][] = array( 's' => $this->fraud_blocked->action );
$this->extra = $extra;
$this->save();
$this->executeFraudAction( $fraudResult, TRUE );
}
/* If we're not being fraud blocked, we can capture and approve */
if ( $fraudResult === static::STATUS_PAID )
{
$this->captureAndApprove();
}
}
/**
* Perform fraud rule action
*
* @param string $fraudResult Status as returned by runFraudCheck()
* @param bool $isApproved Has the payment already been approved? If so and the fraus rule wants to refuse, we will void
* @return void
* @throws \LogicException
*/
public function executeFraudAction( $fraudResult, $isApproved=TRUE )
{
/* If the fraud rule wants to hold or refuse... */
if ( $fraudResult !== static::STATUS_PAID )
{
/* If it wants to refuse, void the payment */
if ( $isApproved and $fraudResult === static::STATUS_REFUSED )
{
$this->method->void( $this );
}
/* Set the status */
$this->status = $fraudResult;
$extra = $this->extra;
$extra['history'][] = array( 's' => $fraudResult );
$this->extra = $extra;
/* Log */
$this->member->log( 'transaction', array(
'type' => 'paid',
'status' => $this->status,
'status' => $fraudResult,
'id' => $this->id,
'invoice_id' => $this->invoice->id,
'invoice_title' => $this->invoice->title,
) );
return;
}
/* Save */
$this->save();
}
/**
* Capture and approve
*
* @return void
* @throws \LogicException
*/
public function captureAndApprove()
{
$this->capture();
$this->member->log( 'transaction', array(
@@ -528,12 +620,41 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$this->save();
/* Mark the invoice paid if necessary */
if ( $this->invoice->amountToPay()->amount <= 0 )
if ( !$this->invoice->amountToPay()->amount->isGreaterThanZero() )
{
$this->invoice->markPaid();
}
}
/**
* Void
*
* @return void
* @throws \Exception
*/
public function void()
{
/* Void it */
$this->method->void( $this );
/* Update transaction */
$extra = $this->extra;
$extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'by' => \IPS\Member::loggedIn()->member_id );
$this->extra = $extra;
$this->status = \IPS\nexus\Transaction::STATUS_REFUSED;
$this->save();
/* Log it */
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => \IPS\nexus\Transaction::STATUS_REFUSED,
'id' => $this->id
) );
}
}
/**
* Refund
*
@@ -545,7 +666,14 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
public function refund( $refundMethod='gateway', $amount=NULL )
{
/* What's the amount? */
if ( !$amount or $amount == $this->amount->amount )
if ( $amount )
{
if ( !( $amount instanceof \IPS\Math\Number ) )
{
$amount = new \IPS\Math\Number( number_format( $amount, \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->amount->currency ), '.', '' ) );
}
}
if ( !$amount or $this->amount->amount->compare( $amount ) === 0 )
{
$amount = NULL;
}
@@ -559,7 +687,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
elseif ( $refundMethod === 'credit' )
{
$credits = $this->member->cm_credits;
$credits[ $this->amount->currency ]->amount += ( $amount ?: $this->amount->amount );
$credits[ $this->amount->currency ]->amount = $credits[ $this->amount->currency ]->amount->add( $amount ?: $this->amount->amount );
$this->member->cm_credits = $credits;
$this->member->save();
}
@@ -571,11 +699,14 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$this->status = static::STATUS_REFUSED;
$extra['history'][] = array( 's' => static::STATUS_REFUSED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time() );
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_REFUSED,
'id' => $this->id
) );
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_REFUSED,
'id' => $this->id
) );
}
}
else
{
@@ -584,16 +715,19 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$this->status = static::STATUS_REFUNDED;
$extra['history'][] = array( 's' => static::STATUS_REFUNDED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'to' => $refundMethod, 'ref' => $refundReference );
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_REFUNDED,
'id' => $this->id,
'refund' => $refundMethod
) );
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_REFUNDED,
'id' => $this->id,
'refund' => $refundMethod
) );
}
}
else
{
$this->partial_refund->amount += $amount;
$this->partial_refund->amount = $this->partial_refund->amount->add( $amount );
$extra['history'][] = array( 's' => static::STATUS_PART_REFUNDED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'to' => $refundMethod, 'amount' => $amount, 'ref' => $refundReference );
if ( $amount >= $this->amount->amount )
{
@@ -605,14 +739,17 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$this->status = static::STATUS_PART_REFUNDED;
}
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_PART_REFUNDED,
'id' => $this->id,
'refund' => $refundMethod,
'amount' => $amount,
'currency' => $this->currency
) );
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_PART_REFUNDED,
'id' => $this->id,
'refund' => $refundMethod,
'amount' => $amount,
'currency' => $this->currency
) );
}
}
}
$this->extra = $extra;
@@ -653,13 +790,18 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$key = 'transactionRefunded';
$emailKey = 'payment_refunded';
break;
case static::STATUS_GATEWAY_PENDING:
$key = 'transactionGatewayPending';
$emailKey = 'payment_gateway_pending';
break;
default:
throw new \RuntimeException;
break;
}
\IPS\Email::buildFromTemplate( 'nexus', $key, array( $this, $this->invoice, $this->invoice->summary() ) )
\IPS\Email::buildFromTemplate( 'nexus', $key, array( $this, $this->invoice, $this->invoice->summary() ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$this->invoice->member,
array_map(
@@ -687,7 +829,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
{
if( $this->_url === NULL )
{
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=payments&controller=checkout&do=transaction&id={$this->invoice->id}&t={$this->id}", 'front', 'nexus_checkout', \IPS\Settings::i()->nexus_https );
$this->_url = \IPS\Http\Url::internal( "app=nexus&module=checkout&controller=checkout&do=transaction&id={$this->invoice->id}&t={$this->id}", 'front', 'nexus_checkout', \IPS\Settings::i()->nexus_https );
}
return $this->_url;
@@ -715,7 +857,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$return = array();
/* Approve button */
if ( in_array( $this->status, array( static::STATUS_PENDING, static::STATUS_WAITING, static::STATUS_HELD, static::STATUS_REVIEW ) ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'transactions_edit' ) )
if ( in_array( $this->status, array( static::STATUS_PENDING, static::STATUS_WAITING, static::STATUS_HELD, static::STATUS_REVIEW, static::STATUS_GATEWAY_PENDING ) ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'transactions_edit' ) )
{
$return['approve'] = array(
'title' => $this->auth ? 'transaction_capture' : 'transaction_approve',
@@ -736,7 +878,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
}
/* Void button */
if ( ( $this->status === static::STATUS_PENDING or ( $this->auth and in_array( $this->status, array( static::STATUS_HELD, static::STATUS_REVIEW ) ) ) ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'transactions_edit' ) )
if ( ( in_array( $this->status, array( static::STATUS_PENDING, static::STATUS_GATEWAY_PENDING ) ) or ( $this->auth and in_array( $this->status, array( static::STATUS_HELD, static::STATUS_REVIEW ) ) ) ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'transactions_edit' ) )
{
$return['void'] = array(
'title' => 'transaction_void',
@@ -798,7 +940,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
}
else
{
if ( !in_array( $this->status, array( static::STATUS_PENDING, static::STATUS_WAITING ) ) )
if ( !in_array( $this->status, array( static::STATUS_PENDING, static::STATUS_WAITING, static::STATUS_GATEWAY_PENDING ) ) )
{
$return[] = array(
's' => $this->status,
@@ -810,4 +952,34 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
return $return;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string status Status: 'okay' = Paid; 'pend' = Pending, waiting for gateway; 'wait' = Pending, manual approval required; 'hold' = Held for manual approval; 'revw' = Flagged for review; 'fail' = Failed; 'rfnd' = Refunded; 'prfd' = Partially refunded
* @apiresponse int invoiceId Invoice ID Number
* @apiresponse \IPS\nexus\Money amount Amount
* @apiresponse \IPS\nexus\Money refundAmount If partially refunded, the amount that has been refunded
* @apiresponse \IPS\nexus\Gateway gateway The gateway
* @apiresponse string gatewayId Any ID number provided by the gateway to identify the transaction on their end
* @apiresponse datetime date Date
* @apiresponse \IPS\nexus\Customer customer Customer
*/
public function apiOutput()
{
return array(
'id' => $this->id,
'status' => $this->status,
'invoiceId' => $this->invoice->id,
'amount' => $this->amount->apiOutput(),
'refundAmount' => $this->partial_refund ? $this->partial_refund->apiOutput() : null,
'gateway' => $this->method->apiOutput(),
'gatewayId' => $this->gw_id,
'date' => $this->date->rfc3339(),
'customer' => $this->member->apiOutput()
);
}
}