Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

@@ -12,71 +12,92 @@
namespace IPS\nexus\Purchase;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application;
use IPS\DateTime;
use IPS\Db;
use IPS\IPS;
use IPS\nexus\Purchase;
use IPS\Patterns\ActiveRecord;
use function count;
use function defined;
use function get_class;
use function strlen;
use function substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* License Key Model
*/
abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
abstract class LicenseKey extends ActiveRecord
{
/**
* License Key Types
*
* @return array
*/
public static function licenseKeyTypes()
public static function licenseKeyTypes() : array
{
return array(
$return = array(
'Standard' => 'IPS\nexus\Purchase\LicenseKey\Standard',
'Mdfive' => 'IPS\nexus\Purchase\LicenseKey\Mdfive',
);
/* Extensions */
foreach ( Application::allExtensions( 'nexus', 'LicenseKey', FALSE, 'core' ) as $key => $extension )
{
$return[$extension::$keyType] = $extension::class;
}
return $return;
}
/**
* @brief Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief Database Table
*/
public static $databaseTable = 'nexus_licensekeys';
public static ?string $databaseTable = 'nexus_licensekeys';
/**
* @brief Database Prefix
*/
public static $databasePrefix = 'lkey_';
public static string $databasePrefix = 'lkey_';
/**
* @brief [ActiveRecord] Database ID Column
*/
public static $databaseColumnId = 'key';
public static string $databaseColumnId = 'key';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array( 'lkey_purchase' );
protected static array $databaseIdFields = array( 'lkey_purchase' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* 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
* @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 )
public static function constructFromData( array $data, bool $updateMultitonStoreIfExists = TRUE ): static
{
$types = static::licenseKeyTypes();
$classname = $types[ mb_ucfirst( $data['lkey_type'] ) ];
$classname = $types[ IPS::mb_ucfirst( $data['lkey_type'] ) ];
/* Initiate an object */
$obj = new $classname;
@@ -87,7 +108,7 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
{
if( static::$databasePrefix AND mb_strpos( $k, static::$databasePrefix ) === 0 )
{
$k = \substr( $k, \strlen( static::$databasePrefix ) );
$k = substr( $k, strlen( static::$databasePrefix ) );
}
$obj->_data[ $k ] = $v;
@@ -109,23 +130,23 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
*
* @return void
*/
public function setDefaultValues()
public function setDefaultValues() : void
{
$exploded = explode( '\\', \get_class( $this ) );
$exploded = explode( '\\', get_class( $this ) );
$this->type = mb_strtolower( array_pop( $exploded ) );
$this->active = TRUE;
$this->uses = 0;
$this->activate_data = array();
$this->generated = new \IPS\DateTime;
$this->generated = new DateTime;
}
/**
* Set purchase
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param Purchase $purchase The purchase
* @return void
*/
public function set_purchase( \IPS\nexus\Purchase $purchase )
public function set_purchase( Purchase $purchase ) : void
{
$this->_data['purchase'] = $purchase->id;
$this->_data['member'] = $purchase->member->member_id;
@@ -134,11 +155,11 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
/**
* Set purchase
*
* @return \IPS\nexus\Purchase
* @return Purchase
*/
public function get_purchase()
public function get_purchase() : Purchase
{
return \IPS\nexus\Purchase::load( $this->_data['purchase'] );
return Purchase::load( $this->_data['purchase'] );
}
/**
@@ -147,7 +168,7 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
* @param array $data The data
* @return void
*/
public function set_activate_data( array $data )
public function set_activate_data( array $data ) : void
{
$this->_data['activate_data'] = json_encode( $data );
}
@@ -155,20 +176,20 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
/**
* Set activate data
*
* @return \IPS\DateTime
* @return array
*/
public function get_activate_data()
public function get_activate_data() : array
{
return json_decode( $this->_data['activate_data'], TRUE );
return json_decode( $this->_data['activate_data'], TRUE ) ?: array();
}
/**
* Set generated time
*
* @param \IPS\DateTime $generated Generated time
* @param DateTime $generated Generated time
* @return void
*/
public function set_generated( \IPS\DateTime $generated )
public function set_generated( DateTime $generated ) : void
{
$this->_data['generated'] = $generated->getTimestamp();
}
@@ -176,28 +197,28 @@ abstract class _LicenseKey extends \IPS\Patterns\ActiveRecord
/**
* Set generated time
*
* @return \IPS\DateTime
* @return DateTime
*/
public function get_generated()
public function get_generated() : DateTime
{
return \IPS\DateTime::ts( $this->_data['generated'] );
return DateTime::ts( $this->_data['generated'] );
}
/**
* Save Changed Columns
*
* @return void
* @return void
*/
public function save()
public function save(): void
{
if ( !$this->key )
{
do
{
$this->key = $this->generate();
$this->key = $this->generateKey();
}
while ( \count( \IPS\Db::i()->select( '*', 'nexus_licensekeys', array( 'lkey_key=?', $this->key ) ) ) );
while ( count( Db::i()->select( '*', 'nexus_licensekeys', array( 'lkey_key=?', $this->key ) ) ) );
}
return parent::save();
parent::save();
}
}
@@ -12,23 +12,27 @@
namespace IPS\nexus\Purchase\LicenseKey;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\nexus\Purchase\LicenseKey;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* License Key Model - MD5
*/
class _Mdfive extends \IPS\nexus\Purchase\LicenseKey
class Mdfive extends LicenseKey
{
/**
* Generates a License Key
*
* @return string
*/
public function generate()
public function generateKey() : string
{
return md5( mt_rand() );
}
@@ -12,53 +12,59 @@
namespace IPS\nexus\Purchase\LicenseKey;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\nexus\Purchase\LicenseKey;
use function chr;
use function defined;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* License Key Model - Standard
*/
class _Standard extends \IPS\nexus\Purchase\LicenseKey
class Standard extends LicenseKey
{
/**
* @brief Number of blocks
*/
protected static $blocks = 5;
protected static int $blocks = 5;
/**
* @brief Number of characters in a block
*/
protected static $characters = 4;
protected static int $characters = 4;
/**
* @brief Lowest allowed ASCII number
*/
protected static $low = 48; // 0
protected static int $low = 48; // 0
/**
* @brief Highest allowed ASCII number
*/
protected static $high = 90; // Z
protected static int $high = 90; // Z
/**
* @brief Disallowed ASCII numbers
*/
protected static $disallowed = array( 58, 59, 60, 61, 62, 63, 64 ); // Various non A-Z / 0-9 characters
protected static array $disallowed = array( 58, 59, 60, 61, 62, 63, 64 ); // Various non A-Z / 0-9 characters
/**
* @brief Seperator between blocks
*/
protected static $seperator = '-';
protected static string $seperator = '-';
/**
* Generates a License Key
*
* @return string
*/
public function generate()
public function generateKey() : string
{
$key = array();
foreach ( range( 1, static::$blocks ) as $i )
@@ -70,8 +76,8 @@ class _Standard extends \IPS\nexus\Purchase\LicenseKey
{
$chr = rand( static::$low, static::$high );
}
while ( \in_array( $chr, static::$disallowed ) );
$_k .= \chr( $chr );
while ( in_array( $chr, static::$disallowed ) );
$_k .= chr( $chr );
}
$key[] = $_k;
}
File diff suppressed because it is too large. Load diff
@@ -12,53 +12,65 @@
namespace IPS\nexus\Purchase;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use DomainException;
use IPS\DateTime;
use IPS\Math\Number;
use IPS\Member;
use IPS\nexus\Customer;
use IPS\nexus\Money;
use IPS\nexus\Tax;
use IPS\Settings;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Renewal Term Object
*/
class _RenewalTerm
class RenewalTerm
{
/**
* @brief Cost
*/
public $cost;
public Money|array|null $cost = null;
/**
* @brief Interval
*/
public $interval;
public ?DateInterval $interval = null;
/**
* @brief Tax
*/
public $tax;
public ?Tax $tax = null;
/**
* @brief Add to base price?
*/
public $addToBase = FALSE;
public bool $addToBase = FALSE;
/**
* @brief Grace period
*/
public $gracePeriod;
public ?DateInterval $gracePeriod = null;
/**
* Constructor
*
* @param \IPS\nexus\Money|array $cost Cost
* @param \DateInterval $interval Interval
* @param \IPS\nexus\Tax|NULL $tax Tax
* @param Money|array $cost Cost
* @param DateInterval|null $interval Interval
* @param Tax|NULL $tax Tax
* @param bool $addToBase Add to base?
* @param \DateInterval|NULL $gracePeriod Grace period
* @param DateInterval|NULL $gracePeriod Grace period
* @return void
*/
public function __construct( $cost, \DateInterval $interval = NULL, \IPS\nexus\Tax $tax = NULL, $addToBase = FALSE, \DateInterval $gracePeriod = NULL )
public function __construct( Money|array $cost, ?DateInterval $interval = NULL, Tax $tax = NULL, bool $addToBase = FALSE, ?DateInterval $gracePeriod = NULL )
{
$this->cost = $cost;
$this->interval = $interval;
@@ -72,7 +84,7 @@ class _RenewalTerm
*
* @return array
*/
public function getTerm()
public function getTerm() : array
{
if( $this->interval->y )
{
@@ -93,10 +105,10 @@ class _RenewalTerm
*
* @return string
*/
public function getTermUnit()
public function getTermUnit() : string
{
$term = $this->getTerm();
$lang = \IPS\Member::loggedIn()->language();
$lang = Member::loggedIn()->language();
switch( $term['unit'] )
{
case 'd':
@@ -113,6 +125,8 @@ class _RenewalTerm
case 'y':
return $lang->pluralize( $lang->get('renew_years'), array( $term['term'] ) );
}
return "";
}
/**
@@ -122,15 +136,15 @@ class _RenewalTerm
*/
public function days(): string
{
return number_format( \IPS\DateTime::intervalToDays( $this->interval ), 2, '.', '' );
return number_format( DateTime::intervalToDays( $this->interval ), 2, '.', '' );
}
/**
* Calculate cost per day
*
* @return \IPS\nexus\Money Cost per day
* @return Money|int Cost per day
*/
public function costPerDay()
public function costPerDay() : Money|int
{
$days = $this->days();
if ( !$days )
@@ -139,23 +153,23 @@ class _RenewalTerm
}
else
{
return new \IPS\nexus\Money( $this->cost->amount->divide( new \IPS\Math\Number("{$days}") ), $this->cost->currency );
return new Money( $this->cost->amount->divide( new 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
* @param RenewalTerm $term Term to add
* @return Money
* @throws DomainException
*/
public function add( RenewalTerm $term )
public function add( RenewalTerm $term ) : Money
{
/* They need to have the same currency */
if ( $this->cost->currency !== $term->cost->currency )
{
throw new \DomainException('currencies_dont_match');
throw new DomainException('currencies_dont_match');
}
/* Get some details */
@@ -210,23 +224,23 @@ class _RenewalTerm
/* 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']}") ) ) );
$adjustedCost = $adjustedCost->multiply( ( ( new Number("{$thisTerm['term']}") )->divide( new Number("{$otherTerm['term']}") ) ) );
}
/* And return */
return new \IPS\nexus\Money( $this->cost->amount->add( $adjustedCost ), $this->cost->currency );
return new 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
* @param RenewalTerm $term Term to subtract
* @return Money
* @throws DomainException
*/
public function subtract( RenewalTerm $term )
public function subtract( RenewalTerm $term ) : Money
{
$term->cost->amount = $term->cost->amount->multiply( new \IPS\Math\Number( '-1' ) );
$term->cost->amount = $term->cost->amount->multiply( new Number( '-1' ) );
return $this->add( $term );
}
@@ -236,25 +250,26 @@ class _RenewalTerm
* @param int $n The number to times by
* @return string
*/
public function times( $n )
public function times( int $n ) : string
{
$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() );
$cost = new Money( $this->cost->amount->multiply( new Number("{$n}") ), $this->cost->currency );
return sprintf( Member::loggedIn()->language()->get( 'renew_option'), $cost, $this->getTermUnit() );
}
/**
* Get difference between this term and another
*
* @param \IPS\nexus\Purchase\RenewalTerm $otherTerm The other term
* @param RenewalTerm $otherTerm The other term
* @param bool $returnAsPercentage If TRUE, will return percentage difference rather than money amount
* @return \IPS\nexus\Money|\IPS\Math\Number
* @return Money|Number
* @throws DomainException
*/
public function diff( $otherTerm, $returnAsPercentage = FALSE )
public function diff( RenewalTerm $otherTerm, bool $returnAsPercentage = FALSE ) : Money|Number
{
/* Sanity check */
if ( $this->cost->currency !== $otherTerm->cost->currency )
{
throw new \DomainException('currencies_dont_match');
throw new DomainException('currencies_dont_match');
}
/* Try to normalise the terms so we can work off multiplying - we want to either be dealing in days or months */
@@ -277,14 +292,14 @@ class _RenewalTerm
/* If the two terms are now based on the same unit of time, we can just multiply */
if ( $thisTermDetails['unit'] === $otherTermDetails['unit'] )
{
$factor = ( new \IPS\Math\Number( (string) $thisTermDetails['term'] ) )->divide( ( new \IPS\Math\Number( (string) $otherTermDetails['term'] ) ) );
$factor = ( new Number( (string) $thisTermDetails['term'] ) )->divide( ( new Number( (string) $otherTermDetails['term'] ) ) );
$thisCostPerDivision = $this->cost->amount;
$otherCostPerDivision = $otherTerm->cost->amount->multiply( $factor );
}
/* Otherwise we have to work it out based on the cost per day */
else
{
$days = new \IPS\Math\Number( (string) $this->days() );
$days = new Number( $this->days() );
$thisCostPerDivision = $this->costPerDay()->amount->multiply( $days );
$otherCostPerDivision = $otherTerm->costPerDay()->amount->multiply( $days );
}
@@ -295,11 +310,11 @@ class _RenewalTerm
/* Return in desired format */
if ( $returnAsPercentage )
{
return ( new \IPS\Math\Number('100') )->divide( $otherCostPerDivision, 4 )->multiply( $saving );
return ( new Number('100') )->divide( $otherCostPerDivision, 4 )->multiply( $saving );
}
else
{
return new \IPS\nexus\Money( $saving, $this->cost->currency );
return new Money( $saving, $this->cost->currency );
}
}
@@ -308,54 +323,54 @@ class _RenewalTerm
*
* @return string
*/
public function __toString()
public function __toString() : string
{
return sprintf( \IPS\Member::loggedIn()->language()->get( 'renew_option'), $this->cost, $this->getTermUnit() ) ;
return sprintf( 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 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 )
public function toDisplay( ?Customer $customer = NULL, int $quantity = 1 ) : string
{
$cost = $this->cost;
if ( \IPS\Settings::i()->nexus_show_tax and $this->tax )
if ( 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 );
$customer = $customer ?: Customer::loggedIn();
$taxRate = new Number( $this->tax->rate( $customer->estimatedLocation() ) );
$cost = new Money( $cost->amount->add( $cost->amount->multiply( $taxRate ) ), $cost->currency );
}
if ( $quantity != 1 )
{
$cost = new \IPS\nexus\Money( $cost->amount->multiply( new \IPS\Math\Number( "$quantity" ) ), $cost->currency );
$cost = new Money( $cost->amount->multiply( new Number( "$quantity" ) ), $cost->currency );
}
return sprintf( \IPS\Member::loggedIn()->language()->get( 'renew_option'), $cost, $this->getTermUnit() );
return sprintf( Member::loggedIn()->language()->get( 'renew_option'), $cost, $this->getTermUnit() );
}
/**
* Get output for API
*
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
* @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( \IPS\Member $authorizedMember = NULL )
public function apiOutput( Member $authorizedMember = NULL ): array
{
$term = $this->getTerm();
return array(
'term' => $term['term'],
'unit' => $term['unit'],
'price' => $this->cost->apiOutput( $authorizedMember ),
'taxClass' => $this->tax ? $this->tax->apiOutput( $authorizedMember ) : null,
'taxClass' => $this->tax?->apiOutput($authorizedMember),
);
}
}