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

+275 -158
View File
@@ -12,89 +12,168 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application;
use IPS\core\AdminNotification;
use IPS\core\Advertisement;
use IPS\Db;
use IPS\Extensions\AdvertisementLocationsAbstract;
use IPS\Helpers\Form;
use IPS\Helpers\Form\CheckboxSet;
use IPS\Helpers\Form\Custom;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Number;
use IPS\Helpers\Form\Upload;
use IPS\Helpers\Form\WidthHeight;
use IPS\Helpers\Form\YesNo;
use IPS\Http\Url;
use IPS\Member;
use IPS\Member\Group;
use IPS\nexus\Invoice;
use IPS\nexus\Package;
use IPS\nexus\Purchase;
use IPS\nexus\Purchase\RenewalTerm;
use IPS\Request;
use IPS\Theme;
use OutOfRangeException;
use UnderflowException;
use function defined;
use function intval;
use function is_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;
}
/**
* Advertisement Package
*/
class _Ad extends \IPS\nexus\Package
class Ad extends Package
{
/**
* @brief Database Table
*/
protected static $packageDatabaseTable = 'nexus_packages_ads';
protected static string $packageDatabaseTable = 'nexus_packages_ads';
/**
* @brief Which columns belong to the local table
*/
protected static $packageDatabaseColumns = array( 'p_locations', 'p_exempt', 'p_expire', 'p_expire_unit', 'p_max_height', 'p_max_width' );
protected static array $packageDatabaseColumns = array( 'p_locations', 'p_exempt', 'p_expire', 'p_expire_unit', 'p_max_height', 'p_max_width', 'p_settings' );
/**
* @brief Icon
*/
public static $icon = 'newspaper-o';
public static string $icon = 'newspaper';
/**
* @brief Title
*/
public static $title = 'advertisement';
public static string $title = 'advertisement';
/* !ACP Package Form */
/**
* ACP Fields
*
* @param \IPS\nexus\Package $package The package
* @param bool $custom If TRUE, is for a custom package
* @param bool $customEdit If TRUE, is editing a custom package
* @return array
* @param Package $package The package
* @param bool $custom If TRUE, is for a custom package
* @param bool $customEdit If TRUE, is editing a custom package
* @return array
*/
public static function acpFormFields( \IPS\nexus\Package $package, $custom=FALSE, $customEdit=FALSE )
public static function acpFormFields( Package $package, bool $custom=FALSE, bool $customEdit=FALSE ): array
{
$return = array();
if ( !$customEdit ) // If we're editing a custom package, they can get to this by editing the advertisement
{
$locations = array(
'ad_global_header' => array(),
'ad_global_footer' => array(),
'ad_sidebar' => array(),
$currentValues = ( $package->id AND $package->settings ) ? json_decode( $package->settings, true ) : array();
/* Add the location fields, remember to call extensions for additional locations.
Array format: location => array of toggle fields to show */
$defaultLocations = array(
'ad_global_header' => array( 'ad_apps' ),
'ad_global_footer' => array( 'ad_apps' ),
'ad_sidebar' => array( 'ad_apps' ),
);
foreach ( \IPS\Application::allExtensions( 'core', 'AdvertisementLocations', FALSE, 'core' ) as $key => $extension )
$settingFields = [];
/* Add a field to restrict by app */
$settingFields[] = new Node( 'ad_apps', $currentValues['ad_apps'] ?? 0, false, array(
'class' => Application::class,
'multiple' => true,
'zeroVal' => 'all',
'subnodes' => false
), null, null, null, 'ad_apps' );
/* Now grab ad location extensions */
foreach ( Application::allExtensions( 'core', 'AdvertisementLocations', FALSE, 'core' ) as $key => $extension )
{
$result = $extension->getSettings( array() );
$locations = array_merge( $locations, $result['locations'] );
/* @var AdvertisementLocationsAbstract $extension */
$result = $extension->getSettings( $currentValues );
$defaultLocations = array_merge( $defaultLocations, $result['locations'] );
/* If this is a list view, add setting fields for interaval and repeat */
if( $extension::$listView )
{
$settingFields[] = new Number( 'ad_view_number', ( isset( $currentValues['ad_view_number'] ) AND $currentValues['ad_view_number'] ) ? $currentValues['ad_view_number'] : 1, FALSE, array( 'min' => 1 ), NULL, NULL, Member::loggedIn()->language()->addToStack('ad_view_number_suffix'), 'ad_view_number' );
$settingFields[] = new Number( 'ad_view_repeat', $currentValues['ad_view_repeat'] ?? 1, FALSE, array( 'unlimited' => -1 ), NULL, NULL, Member::loggedIn()->language()->addToStack('ad_view_repeat_suffix'), 'ad_view_repeat' );
foreach( $result['locations'] as $location => $toggles )
{
$defaultLocations[$location][] = 'ad_view_number';
$defaultLocations[$location][] = 'ad_view_repeat';
}
}
if( isset( $result['settings'] ) )
{
$settingFields = $settingFields + $result['settings'];
}
}
$locations['_ad_custom_'] = array('p_locations_custom');
$locationValues = array();
$customLocations = array();
/* Add the locations to the form, and make sure the toggles get set properly */
$locations = array();
if ( $package->id )
{
$locationValues = explode( ',', $package->locations );
$customLocations = array_diff( $locationValues, array_keys( $locations ) );
if ( !empty( $customLocations ) )
{
$locationValues[] = '_ad_custom_';
}
$locationValues = array_intersect( $locationValues, array_keys( $locations ) );
$locations = $package->locations ? explode( ',', $package->locations ) : [];
$locations = array_intersect( $locations, array_keys( $defaultLocations ) );
}
$return['package_settings']['p_locations'] = new \IPS\Helpers\Form\CheckboxSet( 'p_locations', $locationValues, TRUE, array( 'options' => array_combine( array_keys( $locations ), array_keys( $locations ) ), 'toggles' => $locations, 'userSuppliedInput' => '_ad_custom_' ) );
$return['package_settings']['p_locations_custom'] = new \IPS\Helpers\Form\Stack( 'p_locations_custom', $customLocations, FALSE, array(), NULL, NULL, NULL, 'p_locations_custom' );
$return['package_settings']['p_exempt'] = new \IPS\Helpers\Form\CheckboxSet( 'p_exempt', $package->id ? ( $package->exempt == '*' ? '*' : explode( ',', $package->exempt ) ) : '*', FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ) );
/* Make locations optional so that we can have ads for widgets only */
$return['package_settings']['p_locations'] = new CheckboxSet( 'p_locations',
$locations,
false,
array(
'options' => array_combine( array_keys( $defaultLocations ), array_keys( $defaultLocations ) ),
'toggles' => $defaultLocations,
'noDefault' => true
),
NULL,
NULL,
NULL,
'p_locations'
);
foreach( $settingFields as $settingField )
{
$return['package_settings'][ $settingField->name ] = $settingField;
}
$return['package_settings']['p_exempt'] = new CheckboxSet( 'p_exempt', ( $package->id and $package->exempt ) ? ( $package->exempt == '*' ? '*' : explode( ',', $package->exempt ) ) : '*', FALSE, array( 'options' => Group::groups(), 'parse' => 'normal', 'multiple' => TRUE, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ) );
$return['package_settings']['p_expire'] = new \IPS\Helpers\Form\Custom( 'p_expire', array( 'value' => ( $package->id ) ? $package->expire : -1, 'type' => ( $package->id ) ? $package->expire_unit : 'i' ), FALSE, array(
$return['package_settings']['p_expire'] = new Custom( 'p_expire', array( 'value' => ( $package->id and $package->expire ) ? $package->expire : -1, 'type' => ( $package->id ) ? $package->expire_unit : 'i' ), FALSE, array(
'getHtml' => function( $element )
{
return \IPS\Theme::i()->getTemplate( 'promotion', 'core' )->imageMaximums( $element->name, $element->value['value'], $element->value['type'] );
return Theme::i()->getTemplate( 'promotion', 'core' )->imageMaximums( $element->name, $element->value['value'], $element->value['type'] );
},
'formatValue' => function( $element )
{
if( !\is_array( $element->value ) AND $element->value == -1 )
if( !is_array( $element->value ) AND $element->value == -1 )
{
return array( 'value' => -1, 'type' => 'i' );
}
@@ -105,7 +184,7 @@ class _Ad extends \IPS\nexus\Package
if ( !$custom )
{
$return['package_settings']['p_max_dims'] = new \IPS\Helpers\Form\WidthHeight( 'p_max_dims', array( $package->max_width, $package->max_height ), FALSE, array( 'unlimited' => array( 0, 0 ), 'resizableDiv' => FALSE ) );
$return['package_settings']['p_max_dims'] = new WidthHeight( 'p_max_dims', ( $package->id and $package->width ) ? array( $package->max_width, $package->max_height ) : null, FALSE, array( 'unlimited' => array( 0, 0 ), 'resizableDiv' => FALSE ) );
}
}
@@ -118,16 +197,16 @@ class _Ad extends \IPS\nexus\Package
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if( isset( $values['p_exempt'] ) )
{
$values['p_exempt'] = \is_array( $values['p_exempt'] ) ? implode( ',', $values['p_exempt'] ) : $values['p_exempt'];
$values['p_exempt'] = is_array( $values['p_exempt'] ) ? implode( ',', $values['p_exempt'] ) : $values['p_exempt'];
}
if ( isset( $values['p_expire'] ) and \is_array( $values['p_expire'] ) )
if ( isset( $values['p_expire'] ) and is_array( $values['p_expire'] ) )
{
$values['expire'] = \intval( $values['p_expire']['value'] );
$values['expire'] = intval( $values['p_expire']['value'] );
$values['expire_unit'] = $values['p_expire']['type'];
unset( $values['p_expire'] );
}
@@ -141,16 +220,49 @@ class _Ad extends \IPS\nexus\Package
if ( isset( $values['p_locations'] ) )
{
$locations = $values['p_locations'];
$customKey = array_search( '_ad_custom_', $locations );
if ( $customKey !== FALSE )
$values['p_locations'] = is_array( $values['p_locations'] ) ? implode( ',', $values['p_locations'] ) : '';
}
$additionalSettings = array(
'ad_apps' => ( isset( $values['ad_apps'] ) AND $values['ad_apps'] != 0 ) ? array_keys( $values['ad_apps'] ) : 0
);
foreach ( Application::allExtensions( 'core', 'AdvertisementLocations', FALSE, 'core' ) as $key => $extension )
{
/* @var AdvertisementLocationsAbstract $extension */
if( $extension::$listView )
{
unset( $locations[ $customKey ] );
$locations = array_merge( $locations, $values['p_locations_custom'] );
$additionalSettings['ad_view_number'] = $values['ad_view_number'] ?? 1;
$additionalSettings['ad_view_repeat'] = $values['ad_view_repeat'] ?? 0;
}
$values['p_locations'] = implode( ',', $locations );
$settings = $extension->parseSettings( $values );
$additionalSettings = array_merge( $additionalSettings, $settings );
/* Unset any elements that were added */
$result = $extension->getSettings( $values );
if( isset( $result['settings'] ) )
{
foreach( $result['settings'] as $settingField )
{
if( isset( $values[ $settingField->name ] ) )
{
unset( $values[ $settingField->name ] );
}
}
}
}
$values['p_settings'] = json_encode( $additionalSettings );
foreach( array( 'ad_apps', 'ad_view_number', 'ad_view_repeat' ) as $field )
{
if( isset( $values[ $field ] ) )
{
unset( $values[ $field ] );
}
}
unset( $values['p_locations_custom'] );
return parent::formatFormValues( $values );
}
@@ -160,7 +272,7 @@ class _Ad extends \IPS\nexus\Package
*
* @return array
*/
public static function updateableFields()
public static function updateableFields() : array
{
return array_merge( parent::updateableFields(), array(
'locations',
@@ -173,22 +285,23 @@ class _Ad extends \IPS\nexus\Package
/**
* Update existing purchases
*
* @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
* @param 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, $cancelBillingAgreementIfNecessary=FALSE )
public function updatePurchase(Purchase $purchase, array $changes, bool $cancelBillingAgreementIfNecessary=FALSE ): void
{
if ( $purchase->extra['ad'] )
{
try
{
$ad = \IPS\Db::i()->select( '*','core_advertisements', array( 'ad_id=?', $purchase->extra['ad'] ) )->first();
$ad = Db::i()->select( '*','core_advertisements', array( 'ad_id=?', $purchase->extra['ad'] ) )->first();
}
catch ( \UnderflowException $e )
catch ( UnderflowException )
{
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
return;
}
$update = array();
@@ -212,7 +325,7 @@ class _Ad extends \IPS\nexus\Package
break;
}
if ( $ad[ $i ] == $changes[ $k ] )
if ( isset( $i ) AND $ad[ $i ] == $changes[ $k ] )
{
$update[ $i ] = $this->$k;
}
@@ -220,11 +333,11 @@ class _Ad extends \IPS\nexus\Package
}
if ( !empty( $update ) )
{
\IPS\Db::i()->update( 'core_advertisements', $update, array( 'ad_id=?', $purchase->extra['ad'] ) );
Db::i()->update( 'core_advertisements', $update, array( 'ad_id=?', $purchase->extra['ad'] ) );
}
}
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
/* !Store */
@@ -232,13 +345,13 @@ class _Ad extends \IPS\nexus\Package
/**
* Store Form
*
* @param \IPS\Helpers\Form $form The form
* @param string $memberCurrency The currency being used
* @return void
* @param Form $form The form
* @param string $memberCurrency The currency being used
* @return void
*/
public function storeForm( \IPS\Helpers\Form $form, $memberCurrency )
public function storeForm(Form $form, string $memberCurrency ): void
{
$form->add( new \IPS\Helpers\Form\Url( 'advertisement_url', NULL, TRUE ) );
$form->add( new Form\Url( 'advertisement_url', NULL, TRUE ) );
$maxDims = TRUE;
if ( $this->max_height or $this->max_width )
@@ -254,41 +367,42 @@ class _Ad extends \IPS\nexus\Package
}
}
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
if ( !isset( 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 ) ) );
$form->add( new 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 ) ) );
Member::loggedIn()->language()->words['advertisement_image_desc'] = Member::loggedIn()->language()->addToStack( 'advertisement_image_max_wh', FALSE, array( 'sprintf' => array( $this->max_width, $this->max_height ) ) );
}
elseif ( $this->max_height )
{
\IPS\Member::loggedIn()->language()->words['advertisement_image_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'advertisement_image_max_h', FALSE, array( 'sprintf' => array( $this->max_height ) ) );
Member::loggedIn()->language()->words['advertisement_image_desc'] = Member::loggedIn()->language()->addToStack( 'advertisement_image_max_h', FALSE, array( 'sprintf' => array( $this->max_height ) ) );
}
elseif ( $this->max_width )
{
\IPS\Member::loggedIn()->language()->words['advertisement_image_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'advertisement_image_max_w', FALSE, array( 'sprintf' => array( $this->max_width ) ) );
Member::loggedIn()->language()->words['advertisement_image_desc'] = Member::loggedIn()->language()->addToStack( 'advertisement_image_max_w', FALSE, array( 'sprintf' => array( $this->max_width ) ) );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'ad_image_more', FALSE, FALSE, array( 'togglesOn' => array( 'ad_image_small', 'ad_image_medium' ) ), NULL, NULL, NULL, 'ad_image_more' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'ad_image_small', NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_small' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'ad_image_medium', NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_medium' ) );
$form->add( new YesNo( 'ad_image_more', FALSE, FALSE, array( 'togglesOn' => array( 'ad_image_small', 'ad_image_medium' ) ), NULL, NULL, NULL, 'ad_image_more' ) );
$form->add( new Upload( 'ad_image_small', NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_small' ) );
$form->add( new Upload( 'ad_image_medium', NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_medium' ) );
}
/**
* Add To Cart
*
* @param \IPS\nexus\extensions\nexus\Item\Package $item The item
* @param \IPS\nexus\Invoice\Item $item The item
* @param array $values Values from form
* @param string $memberCurrency The currency being used
* @return array Additional items to add
* @param string $memberCurrency The currency being used
* @return array Additional items to add
*/
public function addToCart( \IPS\nexus\extensions\nexus\Item\Package $item, array $values, $memberCurrency )
public function addToCart(\IPS\nexus\Invoice\Item $item, array $values, string $memberCurrency ): array
{
$item->extra['image'] = json_encode( array( 'large' => (string) $values['advertisement_image'], 'medium' => (string) $values['ad_image_medium'], 'small' => (string) $values['ad_image_small'] ) );
$item->extra['link'] = (string) $values['advertisement_url'];
return parent::addToCart( $item, $values, $memberCurrency );
}
/* !Client Area */
@@ -298,7 +412,7 @@ class _Ad extends \IPS\nexus\Package
*
* @return bool
*/
public function showPurchaseRecord()
public function showPurchaseRecord(): bool
{
return TRUE;
}
@@ -306,23 +420,23 @@ class _Ad extends \IPS\nexus\Package
/**
* Get Client Area Page HTML
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param Purchase $purchase The purchase
* @return array( 'packageInfo' => '...', 'purchaseInfo' => '...' )
*/
public function clientAreaPage( \IPS\nexus\Purchase $purchase )
public function clientAreaPage( Purchase $purchase ) : array
{
$parent = parent::clientAreaPage( $purchase );
try
{
$advertisement = \IPS\core\Advertisement::load( $purchase->extra['ad'] );
$advertisement = Advertisement::load( $purchase->extra['ad'] );
return array(
'packageInfo' => $parent['packageInfo'],
'purchaseInfo' => $parent['purchaseInfo'] . \IPS\Theme::i()->getTemplate('purchases')->advertisement( $purchase, $advertisement ),
'purchaseInfo' => $parent['purchaseInfo'] . Theme::i()->getTemplate('purchases')->advertisement( $purchase, $advertisement ),
);
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException )
{
return $parent;
}
@@ -333,44 +447,44 @@ class _Ad extends \IPS\nexus\Package
/**
* ACP Generate Invoice Form
*
* @param \IPS\Helpers\Form $form The form
* @param string $k The key to add to the field names
* @return void
* @param Form $form The form
* @param string $k The key to add to the field names
* @return void
*/
public function generateInvoiceForm( \IPS\Helpers\Form $form, $k )
public function generateInvoiceForm(Form $form, string $k ): void
{
$form->attributes['data-bypassValidation'] = true;
$field = new \IPS\Helpers\Form\Url( 'advertisement_url' . $k, NULL, TRUE );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('advertisement_url');
$field = new Form\Url( 'advertisement_url' . $k, NULL, TRUE );
$field->label = Member::loggedIn()->language()->addToStack('advertisement_url');
$form->add( $field );
$field = new \IPS\Helpers\Form\Upload( 'ad_image' . $k, NULL, TRUE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ) );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image');
$field = new Upload( 'ad_image' . $k, NULL, TRUE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ) );
$field->label = Member::loggedIn()->language()->addToStack('ad_image');
$form->add( $field );
$field = new \IPS\Helpers\Form\YesNo( 'ad_image_more' . $k, FALSE, FALSE, array( 'togglesOn' => array( 'ad_image_small' . $k, 'ad_image_medium' . $k ) ) );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image_more');
$field = new YesNo( 'ad_image_more' . $k, FALSE, FALSE, array( 'togglesOn' => array( 'ad_image_small' . $k, 'ad_image_medium' . $k ) ) );
$field->label = Member::loggedIn()->language()->addToStack('ad_image_more');
$form->add( $field );
$field = new \IPS\Helpers\Form\Upload( 'ad_image_small' . $k, NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_small' . $k );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image_small');
$field = new Upload( 'ad_image_small' . $k, NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_small' . $k );
$field->label = Member::loggedIn()->language()->addToStack('ad_image_small');
$form->add( $field );
$field = new \IPS\Helpers\Form\Upload( 'ad_image_medium' . $k, NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_medium' . $k );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image_medium');
$field = new Upload( 'ad_image_medium' . $k, NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_medium' . $k );
$field->label = Member::loggedIn()->language()->addToStack('ad_image_medium');
$form->add( $field );
}
/**
* ACP Add to invoice
*
* @param \IPS\nexus\extensions\nexus\Item\Package $item The item
* @param \IPS\nexus\Invoice\Item $item The item
* @param array $values Values from form
* @param string $k The key to add to the field names
* @param \IPS\nexus\Invoice $invoice The invoice
* @return void
* @param string $k The key to add to the field names
* @param Invoice $invoice The invoice
* @return void
*/
public function acpAddToInvoice( \IPS\nexus\extensions\nexus\Item\Package $item, array $values, $k, \IPS\nexus\Invoice $invoice )
public function acpAddToInvoice(\IPS\nexus\Invoice\Item $item, array $values, string $k, Invoice $invoice ): void
{
$item->extra['image'] = json_encode( array( 'large' => (string) $values[ 'ad_image' . $k ], 'medium' => (string) $values[ 'ad_image_medium' . $k ], 'small' => (string) $values[ 'ad_image_small' . $k ] ) );
$item->extra['link'] = (string) $values[ 'advertisement_url' . $k ];
@@ -379,35 +493,38 @@ class _Ad extends \IPS\nexus\Package
/**
* Get ACP Page HTML
*
* @param \IPS\nexus\Purchase $purchase Purchase record
* @return string
* @param Purchase $purchase Purchase record
* @return string
*/
public function acpPage( \IPS\nexus\Purchase $purchase )
public function acpPage( Purchase $purchase ): string
{
try
{
return \IPS\Theme::i()->getTemplate( 'purchases' )->advertisement( $purchase, \IPS\core\Advertisement::load( $purchase->extra['ad'] ) );
return Theme::i()->getTemplate( 'purchases' )->advertisement( $purchase, Advertisement::load( $purchase->extra['ad'] ) );
}
catch ( OutOfRangeException )
{
return '';
}
catch ( \OutOfRangeException $e ) { }
}
/**
* Get ACP Page Buttons
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param \IPS\Http\Url $url The page URL
* @return array
* @param Purchase $purchase The purchase
* @param Url $url The page URL
* @return array
*/
public function acpButtons( \IPS\nexus\Purchase $purchase, $url )
public function acpButtons(Purchase $purchase, Url $url ): array
{
$return = parent::acpButtons( $purchase, $url );
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'promotion', 'advertisements_edit' ) and isset( $purchase->extra['ad'] ) )
if ( Member::loggedIn()->hasAcpRestriction( 'core', 'promotion', 'advertisements_edit' ) and isset( $purchase->extra['ad'] ) )
{
$return['edit_advertisement'] = array(
'icon' => 'list-alt',
'title' => 'edit_advertisement',
'link' => \IPS\Http\Url::internal( 'app=core&module=promotion&controller=advertisements&do=form&id=' . $purchase->extra['ad'] ),
'link' => Url::internal( 'app=core&module=promotion&controller=advertisements&do=form&id=' . $purchase->extra['ad'] ),
);
}
@@ -419,13 +536,13 @@ class _Ad extends \IPS\nexus\Package
/**
* On Purchase Generated
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param \IPS\nexus\Invoice $invoice The invoice
* @return void
* @param Purchase $purchase The purchase
* @param Invoice $invoice The invoice
* @return void
*/
public function onPurchaseGenerated( \IPS\nexus\Purchase $purchase, \IPS\nexus\Invoice $invoice )
public function onPurchaseGenerated(Purchase $purchase, Invoice $invoice ): void
{
$insertId = \IPS\Db::i()->insert( 'core_advertisements', array(
$insertId = Db::i()->insert( 'core_advertisements', array(
'ad_location' => $this->locations,
'ad_html' => NULL,
'ad_images' => $purchase->extra['image'],
@@ -439,10 +556,10 @@ class _Ad extends \IPS\nexus\Package
'ad_end' => $purchase->expire ? $purchase->expire->getTimestamp() : 0,
'ad_maximum_value' => $this->expire,
'ad_maximum_unit' => $this->expire_unit,
'ad_additional_settings'=> json_encode( array() ),
'ad_additional_settings'=> $this->settings,
'ad_html_https_set' => 0,
'ad_member' => $purchase->member->member_id,
'ad_type' => \IPS\core\Advertisement::AD_IMAGES,
'ad_type' => Advertisement::AD_IMAGES,
) );
$extra = $purchase->extra;
@@ -450,7 +567,7 @@ class _Ad extends \IPS\nexus\Package
$purchase->extra = $extra;
$purchase->save();
\IPS\core\AdminNotification::send( 'nexus', 'Advertisement', NULL, TRUE, $purchase );
AdminNotification::send( 'nexus', 'Advertisement', NULL, TRUE, $purchase );
parent::onPurchaseGenerated( $purchase, $invoice );
}
@@ -458,14 +575,14 @@ class _Ad extends \IPS\nexus\Package
/**
* On Expiration Date Change
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
* @param Purchase $purchase The purchase
* @return void
*/
public function onExpirationDateChange( \IPS\nexus\Purchase $purchase )
public function onExpirationDateChange( Purchase $purchase ): void
{
try
{
$ad = \IPS\core\Advertisement::load( $purchase->extra['ad'] );
$ad = Advertisement::load( $purchase->extra['ad'] );
$ad->end = $purchase->expire ? $purchase->expire->getTimestamp() : 0;
if ( ( !$ad->end or $ad->end > time() ) )
@@ -507,7 +624,7 @@ class _Ad extends \IPS\nexus\Package
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
parent::onExpirationDateChange( $purchase );
}
@@ -515,18 +632,18 @@ class _Ad extends \IPS\nexus\Package
/**
* On Purchase Expired
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
* @param Purchase $purchase The purchase
* @return void
*/
public function onExpire( \IPS\nexus\Purchase $purchase )
public function onExpire( Purchase $purchase ): void
{
try
{
$ad = \IPS\core\Advertisement::load( $purchase->extra['ad'] );
$ad = Advertisement::load( $purchase->extra['ad'] );
$ad->active = 0;
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
parent::onExpire( $purchase );
}
@@ -534,18 +651,18 @@ class _Ad extends \IPS\nexus\Package
/**
* On Purchase Canceled
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
* @param Purchase $purchase The purchase
* @return void
*/
public function onCancel( \IPS\nexus\Purchase $purchase )
public function onCancel( Purchase $purchase ): void
{
try
{
$ad = \IPS\core\Advertisement::load( $purchase->extra['ad'] );
$ad = Advertisement::load( $purchase->extra['ad'] );
$ad->active = 0;
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
parent::onCancel( $purchase );
}
@@ -553,16 +670,16 @@ class _Ad extends \IPS\nexus\Package
/**
* On Purchase Deleted
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
* @param Purchase $purchase The purchase
* @return void
*/
public function onDelete( \IPS\nexus\Purchase $purchase )
public function onDelete( Purchase $purchase ): void
{
try
{
\IPS\core\Advertisement::load( $purchase->extra['ad'] )->delete();
Advertisement::load( $purchase->extra['ad'] )->delete();
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
parent::onDelete( $purchase );
}
@@ -570,10 +687,10 @@ class _Ad extends \IPS\nexus\Package
/**
* On Purchase Reactivated (renewed after being expired or reactivated after being canceled)
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
* @param Purchase $purchase The purchase
* @return void
*/
public function onReactivate( \IPS\nexus\Purchase $purchase )
public function onReactivate( Purchase $purchase ): void
{
$this->onExpirationDateChange( $purchase );
@@ -583,19 +700,19 @@ class _Ad extends \IPS\nexus\Package
/**
* On Transfer (is ran before transferring)
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param \IPS\Member $newCustomer New Customer
* @return void
* @param Purchase $purchase The purchase
* @param Member $newCustomer New Customer
* @return void
*/
public function onTransfer( \IPS\nexus\Purchase $purchase, \IPS\Member $newCustomer )
public function onTransfer( Purchase $purchase, Member $newCustomer ): void
{
try
{
$ad = \IPS\core\Advertisement::load( $purchase->extra['ad'] );
$ad->member = $newCustomer->member_id;
$ad = Advertisement::load( $purchase->extra['ad'] );
$ad->member = $newCustomer->member_id;
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
parent::onTransfer( $purchase, $newCustomer );
}
@@ -603,23 +720,23 @@ class _Ad extends \IPS\nexus\Package
/**
* On Upgrade/Downgrade
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param \IPS\nexus\Package $newPackage The package to upgrade to
* @param int|NULL|\IPS\nexus\Purchase\RenewalTerm $chosenRenewalOption The chosen renewal option
* @return void
* @param Purchase $purchase The purchase
* @param Package $newPackage The package to upgrade to
* @param int|RenewalTerm|NULL $chosenRenewalOption The chosen renewal option
* @return void
*/
public function onChange( \IPS\nexus\Purchase $purchase, \IPS\nexus\Package $newPackage, $chosenRenewalOption = NULL )
public function onChange(Purchase $purchase, Package $newPackage, RenewalTerm|int|null $chosenRenewalOption = NULL ): void
{
try
{
$ad = \IPS\core\Advertisement::load( $purchase->extra['ad'] );
$ad = Advertisement::load( $purchase->extra['ad'] );
$ad->location = $newPackage->locations;
$ad->exempt = $newPackage->exempt === '*' ? '*' : json_encode( explode( ',', $newPackage->exempt ) );
$ad->maximum_value = $newPackage->expire;
$ad->maximum_unit = $newPackage->expire_unit;
$ad->save();
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
parent::onChange( $purchase, $newPackage );
}
@@ -12,41 +12,58 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Content;
use IPS\CustomField as SystemCustomField;
use IPS\Helpers\Form;
use IPS\Helpers\Form\FormAbstract;
use IPS\Helpers\Form\Ftp;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Text;
use IPS\Helpers\Form\YesNo;
use IPS\nexus\Package;
use IPS\Text\Encrypt;
use IPS\Theme;
use OutOfRangeException;
use function defined;
use function in_array;
use function is_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;
}
/**
* Custom Profile Field Node
*/
class _CustomField extends \IPS\CustomField
class CustomField extends SystemCustomField
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'nexus_package_fields';
public static ?string $databaseTable = 'nexus_package_fields';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'cf_';
public static string $databasePrefix = 'cf_';
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'position';
public static ?string $databaseColumnOrder = 'position';
/**
* @brief [CustomField] Title/Description lang prefix
*/
protected static $langKey = 'nexus_pfield';
protected static string $langKey = 'nexus_pfield';
/**
* @brief [Node] ACP Restrictions
@@ -64,7 +81,7 @@ class _CustomField extends \IPS\CustomField
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
* @endcode
*/
protected static $restrictions = array(
protected static ?array $restrictions = array(
'app' => 'nexus',
'module' => 'store',
'prefix' => 'package_fields_',
@@ -73,17 +90,17 @@ class _CustomField extends \IPS\CustomField
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'custom_package_fields';
public static string $nodeTitle = 'custom_package_fields';
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'nexus_pfield_';
public static ?string $titleLangPrefix = 'nexus_pfield_';
/**
* @brief [CustomField] Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'content' => 'extra',
'not_null' => 'required'
);
@@ -91,7 +108,7 @@ class _CustomField extends \IPS\CustomField
/**
* @brief [CustomField] Additional Field Classes
*/
public static $additionalFieldTypes = array(
public static array $additionalFieldTypes = array(
'UserPass' => 'sf_type_UserPass',
'Ftp' => 'sf_type_Ftp'
);
@@ -99,17 +116,17 @@ class _CustomField extends \IPS\CustomField
/**
* @brief [CustomField] Upload Field Storage Extension
*/
public static $uploadStorageExtension = 'nexus_PurchaseFields';
public static string $uploadStorageExtension = 'nexus_PurchaseFields';
/**
* @brief [CustomField] Editor Options
*/
public static $editorOptions = array( 'app' => 'nexus', 'key' => 'Purchases' );
public static array $editorOptions = array( 'app' => 'nexus', 'key' => 'Purchases' );
/**
* @brief [CustomField] Additional Field Toggles
*/
public static $additionalFieldToggles = array(
public static array $additionalFieldToggles = array(
'Address' => array( 'cf_sticky' ),
'Checkbox' => array( 'cf_sticky' ),
'CheckboxSet' => array( 'cf_sticky' ),
@@ -125,10 +142,10 @@ class _CustomField extends \IPS\CustomField
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
parent::form( $form );
@@ -137,22 +154,22 @@ class _CustomField extends \IPS\CustomField
{
try
{
$packages[] = \IPS\nexus\Package::load( $id );
$packages[] = Package::load( $id );
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
}
$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 )
$form->add( new 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 );
return !( $node instanceof Group);
} ) ), 'pf_desc' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_sticky', $this->sticky ?: FALSE, FALSE, array(), NULL, NULL, NULL, 'cf_sticky' ), 'pf_type' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_allow_attachments', $this->id ? $this->allow_attachments : 1, FALSE, array( ), NULL, NULL, NULL, 'pf_allow_attachments' ) );
$form->add( new YesNo( 'cf_sticky', $this->sticky ?: FALSE, FALSE, array(), NULL, NULL, NULL, 'cf_sticky' ), 'pf_type' );
$form->add( new YesNo( 'cf_allow_attachments', $this->id ? $this->allow_attachments : 1, FALSE, array( ), NULL, NULL, NULL, 'pf_allow_attachments' ) );
$form->addHeader( 'display_settings' );
$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 ) );
$form->add( new YesNo( 'cf_purchase', $this->purchase, FALSE ) );
$form->add( new YesNo( 'cf_editable', $this->editable, FALSE ) );
$form->add( new YesNo( 'cf_required', $this->required ?: FALSE, FALSE, array(), NULL, NULL, NULL, 'pf_not_null' ) );
$form->add( new YesNo( 'cf_invoice', $this->invoice ) );
unset( $form->elements[''][1] );
unset( $form->elements['']['pf_not_null'] );
@@ -171,9 +188,9 @@ class _CustomField extends \IPS\CustomField
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if( isset( $values['cf_packages'] ) AND \is_array( $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'] );
@@ -189,23 +206,24 @@ class _CustomField extends \IPS\CustomField
}
/* Disable 'sticky' for fields that don't support it */
if( !\in_array( 'cf_sticky', static::$additionalFieldToggles[ $values['pf_type'] ] ?? [] ) )
if( !in_array( 'cf_sticky', static::$additionalFieldToggles[ $values['pf_type'] ] ?? [] ) )
{
$values['cf_sticky'] = false;
}
return parent::formatFormValues( $values );
}
/**
* Build Form Helper
*
* @param mixed $value The value
* @param callback $customValidationCode Custom validation code
* @param \IPS\Content|NULL $content The associated content, if editing
* @return \IPS\Helpers\Form\FormAbstract
* @param mixed|null $value The value
* @param callback|null $customValidationCode Custom validation code
* @param Content|NULL $content The associated content, if editing
* @param int $flags
* @return Text|FormAbstract
*/
public function buildHelper( $value=NULL, $customValidationCode=NULL, \IPS\Content $content = NULL )
public function buildHelper( mixed $value=NULL, callable $customValidationCode=NULL, ?Content $content = NULL, int $flags=0 ): Text|FormAbstract
{
if ( $this->type === 'UserPass' )
{
@@ -214,7 +232,7 @@ class _CustomField extends \IPS\CustomField
}
elseif ( $this->type === 'Ftp' )
{
return new \IPS\Helpers\Form\Ftp( static::$langKey . '_' . $this->id, $value, $this->not_null, array( 'validate' => $this->validate ), NULL, NULL, NULL, static::$langKey . '_' . $this->id );
return new Ftp( static::$langKey . '_' . $this->id, $value, $this->not_null, array( 'validate' => $this->validate ), NULL, NULL, NULL, static::$langKey . '_' . $this->id );
}
return parent::buildHelper( $value, $customValidationCode, $content );
@@ -225,16 +243,16 @@ class _CustomField extends \IPS\CustomField
*
* @param mixed $value The value
* @param bool $showSensitiveInformation If TRUE, potentially sensitive data (like passwords) will be displayed - otherwise will be blanked out
* @param string $separator Used to separate items when displaying a field with multiple values.
* @return string
* @param string|null $separator Used to separate items when displaying a field with multiple values.
* @return string|null
*/
public function displayValue( $value=NULL, $showSensitiveInformation=FALSE, $separator=NULL )
public function displayValue( mixed $value=NULL, bool $showSensitiveInformation=FALSE, ?string $separator=NULL ): ?string
{
if ( $this->type === 'UserPass' )
{
if ( !\is_array( $value ) )
if ( !is_array( $value ) )
{
$value = json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
$value = json_decode( Encrypt::fromTag( $value )->decrypt(), TRUE );
}
if ( !$showSensitiveInformation )
@@ -242,19 +260,19 @@ class _CustomField extends \IPS\CustomField
$value['pw'] = '********';
}
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->usernamePasswordDisplay( $value );
return Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->usernamePasswordDisplay( $value );
}
/* Select boxes store the value and not the index, but the parent method uses the index to determine the value.
When using numeric options, this causes the wrong value to be returned. */
if( $this->type == 'Select' )
if ( $this->type == 'Select' )
{
if( $this->multiple )
if ( $this->multiple )
{
return implode( ( $separator ) ? $separator : '<br>', explode( ',', htmlspecialchars( $value, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) ) );
return implode( ( $separator ) ?: '<br>', explode( ',', htmlspecialchars( $value, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', false ) ) );
}
return htmlspecialchars( $value, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE );
return htmlspecialchars( $value, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', false );
}
return parent::displayValue( $value, $showSensitiveInformation );
+49 -37
View File
@@ -12,50 +12,62 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Db;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Matrix;
use IPS\Helpers\Form\Text;
use IPS\Helpers\Form\Translatable;
use IPS\Lang;
use IPS\Member;
use IPS\Node\Model;
use function defined;
use function intval;
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;
}
/**
* Package Filter
*/
class _Filter extends \IPS\Node\Model
class Filter extends Model
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'nexus_package_filters';
public static ?string $databaseTable = 'nexus_package_filters';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'pfilter_';
public static string $databasePrefix = 'pfilter_';
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'order';
public static ?string $databaseColumnOrder = 'order';
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'menu__nexus_store_filters';
public static string $nodeTitle = 'menu__nexus_store_filters';
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'nexus_product_filter_';
public static ?string $titleLangPrefix = 'nexus_product_filter_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_public';
public static ?string $descriptionLangSuffix = '_public';
/**
* @brief [Node] ACP Restrictions
@@ -73,7 +85,7 @@ class _Filter extends \IPS\Node\Model
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
* @endcode
*/
protected static $restrictions = array(
protected static ?array $restrictions = array(
'app' => 'nexus',
'module' => 'store',
'all' => 'packages_manage',
@@ -82,31 +94,31 @@ class _Filter extends \IPS\Node\Model
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
$form->addHeader( 'pfilter_basic_settings' );
$form->add( new \IPS\Helpers\Form\Translatable( 'pfilter_admin_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_product_filter_{$this->id}" : NULL ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'pfilter_public_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_product_filter_{$this->id}_public" : NULL ) ) );
$form->add( new Translatable( 'pfilter_admin_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_product_filter_{$this->id}" : NULL ) ) );
$form->add( new Translatable( 'pfilter_public_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_product_filter_{$this->id}_public" : NULL ) ) );
$matrix = new \IPS\Helpers\Form\Matrix;
$matrix = new Matrix;
$matrix->sortable = TRUE;
foreach ( \IPS\Lang::languages() as $lang )
foreach ( Lang::languages() as $lang )
{
if ( $lang->enabled )
{
\IPS\Member::loggedIn()->language()->words["lang_{$lang->id}"] = $lang->title;
Member::loggedIn()->language()->words["lang_{$lang->id}"] = $lang->title;
$matrix->columns["lang_{$lang->id}"] = function( $key, $value, $data )
{
return new \IPS\Helpers\Form\Text( $key, $value );
return new Text( $key, $value );
};
}
}
if ( $this->id )
{
foreach ( \IPS\Db::i()->select( '*', 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ), 'pfv_order' ) as $filterValue )
foreach ( Db::i()->select( '*', 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ), 'pfv_order' ) as $filterValue )
{
$matrix->rows[ $filterValue['pfv_value'] ][ 'lang_' . $filterValue['pfv_lang'] ] = $filterValue['pfv_text'];
}
@@ -122,7 +134,7 @@ class _Filter extends \IPS\Node\Model
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if ( !$this->id )
{
@@ -131,32 +143,32 @@ class _Filter extends \IPS\Node\Model
if( isset( $values['pfilter_admin_name'] ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_product_filter_{$this->id}", $values['pfilter_admin_name'] );
Lang::saveCustom( 'nexus', "nexus_product_filter_{$this->id}", $values['pfilter_admin_name'] );
unset( $values['pfilter_admin_name'] );
}
if( isset( $values['pfilter_public_name'] ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_product_filter_{$this->id}_public", $values['pfilter_public_name'] );
Lang::saveCustom( 'nexus', "nexus_product_filter_{$this->id}_public", $values['pfilter_public_name'] );
unset( $values['pfilter_public_name'] );
}
$order = 1;
$deletedIds = $this->id ? iterator_to_array( \IPS\Db::i()->select( 'pfv_value', 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ) )->setKeyField('pfv_value') ) : array();
$deletedIds = $this->id ? iterator_to_array( Db::i()->select( 'pfv_value', 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ) )->setKeyField('pfv_value') ) : array();
foreach ( $values['pfilter_options'] as $k => $_values )
{
unset( $deletedIds[ $k ] );
if ( mb_substr( $k, 0, 5 ) === '_new_' )
{
$id = \IPS\Db::i()->select( 'MAX(pfv_value)', 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ) )->first() + 1;
$id = Db::i()->select( 'MAX(pfv_value)', 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ) )->first() + 1;
foreach ( $_values as $langId => $text )
{
\IPS\Db::i()->insert( 'nexus_package_filters_values', array(
Db::i()->insert( 'nexus_package_filters_values', array(
'pfv_filter' => $this->id,
'pfv_value' => $id,
'pfv_lang' => \intval( mb_substr( $langId, 5 ) ),
'pfv_lang' => intval( mb_substr( $langId, 5 ) ),
'pfv_text' => $text,
'pfv_order' => $order
) );
@@ -166,10 +178,10 @@ class _Filter extends \IPS\Node\Model
{
foreach ( $_values as $langId => $text )
{
\IPS\Db::i()->update( 'nexus_package_filters_values', array(
Db::i()->update( 'nexus_package_filters_values', array(
'pfv_text' => $text,
'pfv_order' => $order
), array( 'pfv_filter=? AND pfv_value=? AND pfv_lang=?', $this->id, $k, \intval( mb_substr( $langId, 5 ) ) ) );
), array( 'pfv_filter=? AND pfv_value=? AND pfv_lang=?', $this->id, $k, intval( mb_substr( $langId, 5 ) ) ) );
}
}
@@ -177,7 +189,7 @@ class _Filter extends \IPS\Node\Model
}
if ( $deletedIds )
{
\IPS\Db::i()->delete( 'nexus_package_filters_values', \IPS\Db::i()->in( 'pfv_value', $deletedIds ) );
Db::i()->delete( 'nexus_package_filters_values', Db::i()->in( 'pfv_value', $deletedIds ) );
}
unset( $values['pfilter_options'] );
@@ -187,13 +199,13 @@ class _Filter extends \IPS\Node\Model
/**
* [ActiveRecord] Delete Record
*
* @return void
* @return void
*/
public function delete()
public function delete(): void
{
\IPS\Db::i()->delete( 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ) );
\IPS\Lang::deleteCustom( 'nexus', static::$titleLangPrefix . $this->_id );
\IPS\Lang::deleteCustom( 'nexus', static::$titleLangPrefix . $this->_id . static::$descriptionLangSuffix );
Db::i()->delete( 'nexus_package_filters_values', array( 'pfv_filter=?', $this->id ) );
Lang::deleteCustom( 'nexus', static::$titleLangPrefix . $this->_id );
Lang::deleteCustom( 'nexus', static::$titleLangPrefix . $this->_id . static::$descriptionLangSuffix );
parent::delete();
}
@@ -204,9 +216,9 @@ class _Filter extends \IPS\Node\Model
* @param int $filterId ID of the filter
* @param int|null $valueToAdd Value to add to the filter array
* @param int|null $valueToRemove Value to remove from the filter array
* @return void
* @return array
*/
public static function queryString( $queryString, $filterId, $valueToAdd=NULL, $valueToRemove = NULL )
public static function queryString( array $queryString, int $filterId, ?int $valueToAdd=NULL, ?int $valueToRemove = NULL ) : array
{
$queryString = $queryString ?: array();
+96 -75
View File
@@ -12,66 +12,87 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use InvalidArgumentException;
use IPS\Db;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Stack;
use IPS\Helpers\Form\Translatable;
use IPS\Helpers\Form\Upload;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Lang;
use IPS\Member;
use IPS\Node\Model;
use IPS\Request;
use OutOfRangeException;
use function count;
use function defined;
use function get_called_class;
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;
}
/**
* Package Group
*/
class _Group extends \IPS\Node\Model
class Group extends Model
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'nexus_package_groups';
public static ?string $databaseTable = 'nexus_package_groups';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'pg_';
public static string $databasePrefix = 'pg_';
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnParent = 'parent';
public static ?string $databaseColumnParent = 'parent';
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'position';
public static ?string $databaseColumnOrder = 'position';
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'product_groups';
public static string $nodeTitle = 'product_groups';
/**
* @brief [Node] Subnode class
*/
public static $subnodeClass = 'IPS\nexus\Package';
public static ?string $subnodeClass = 'IPS\nexus\Package';
/**
* @brief Content Item Class
*/
public static $contentItemClass = 'IPS\nexus\Package\Item';
public static ?string $contentItemClass = 'IPS\nexus\Package\Item';
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'nexus_pgroup_';
public static ?string $titleLangPrefix = 'nexus_pgroup_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_desc';
public static ?string $descriptionLangSuffix = '_desc';
/**
* @brief [Node] ACP Restrictions
@@ -89,7 +110,7 @@ class _Group extends \IPS\Node\Model
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
* @endcode
*/
protected static $restrictions = array(
protected static ?array $restrictions = array(
'app' => 'nexus',
'module' => 'store',
'prefix' => 'packages_',
@@ -99,17 +120,17 @@ class _Group extends \IPS\Node\Model
* Return only the root groups that have packages OR subcategories/groups
*
* @param string|NULL $permissionCheck The permission key to check for or NULl to not check permissions
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
* @param mixed $where Additional WHERE clause
* @param Member|NULL $member The member to check permissions for or NULL for the currently logged in member
* @param array $where Additional WHERE clause
* @return array
*/
public static function rootsWithViewablePackages( $permissionCheck='view', $member=NULL, $where=array() )
public static function rootsWithViewablePackages( ?string $permissionCheck='view', ?Member $member=NULL, array $where=array() ) : array
{
$roots = static::roots( $permissionCheck, $member, $where );
foreach( $roots as $index => $group )
{
if( !$group->hasSubgroups() AND !$group->hasPackages( NULL, array( array( "p_store=1 AND ( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', \IPS\Member::loggedIn()->groups ) . ' )' ) ) ) )
if( !$group->hasSubgroups() AND !$group->hasPackages( NULL, array( array( "p_store=1 AND ( p_member_groups='*' OR " . Db::i()->findInSet( 'p_member_groups', Member::loggedIn()->groups ) . ' )' ) ) ) )
{
unset( $roots[ $index ] );
}
@@ -121,12 +142,12 @@ class _Group extends \IPS\Node\Model
/**
* Load record based on a URL
*
* @param \IPS\Http\Url $url URL to load from
* @param Url $url URL to load from
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
* @throws InvalidArgumentException
* @throws OutOfRangeException
*/
public static function loadFromUrl( \IPS\Http\Url $url )
public static function loadFromUrl( Url $url ): static
{
$qs = array_merge( $url->queryString, $url->hiddenQueryString );
@@ -135,20 +156,20 @@ class _Group extends \IPS\Node\Model
return static::load( $qs['cat'] );
}
throw new \InvalidArgumentException;
throw new InvalidArgumentException;
}
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
$form->addHeader('pg_basic_settings');
$form->add( new \IPS\Helpers\Form\Translatable( 'pg_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_pgroup_{$this->id}" : NULL ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'pg_desc', NULL, FALSE, array(
$form->add( new Translatable( 'pg_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_pgroup_{$this->id}" : NULL ) ) );
$form->add( new Translatable( 'pg_desc', NULL, FALSE, array(
'app' => 'nexus',
'key' => ( $this->id ? "nexus_pgroup_{$this->id}_desc" : NULL ),
'editor' => array(
@@ -159,18 +180,18 @@ class _Group extends \IPS\Node\Model
)
) ) );
$class = \get_called_class();
$class = get_called_class();
$form->add( new \IPS\Helpers\Form\Node( 'pg_parent', $this->id ? $this->parent : 0, TRUE, array( 'class' => 'IPS\nexus\Package\Group', 'subnodes' => FALSE, 'zeroVal' => 'no_parent', 'permissionCheck' => function( $node ) use ( $class )
$form->add( new Node( 'pg_parent', $this->id ? $this->parent : 0, TRUE, array( 'class' => 'IPS\nexus\Package\Group', 'subnodes' => FALSE, 'zeroVal' => 'no_parent', 'permissionCheck' => function( $node ) use ( $class )
{
if( isset( $class::$subnodeClass ) AND $class::$subnodeClass AND $node instanceof $class::$subnodeClass )
{
return FALSE;
}
return !isset( \IPS\Request::i()->id ) or ( $node->id != \IPS\Request::i()->id and !$node->isChildOf( $node::load( \IPS\Request::i()->id ) ) );
return !isset( Request::i()->id ) or ( $node->id != Request::i()->id and !$node->isChildOf( $node::load( Request::i()->id ) ) );
} ) ) );
$form->add( new \IPS\Helpers\Form\Upload( 'pg_image', $this->image ? \IPS\File::get( 'nexus_PackageGroups', $this->image ) : NULL, FALSE, array( 'storageExtension' => 'nexus_PackageGroups', 'image' => TRUE, 'allowStockPhotos' => TRUE ) ) );
$form->add( new Upload( 'pg_image', $this->image ? File::get( 'nexus_PackageGroups', $this->image ) : NULL, FALSE, array( 'storageExtension' => 'nexus_PackageGroups', 'image' => TRUE, 'allowStockPhotos' => TRUE ) ) );
$priceFilters = array();
if ( $this->price_filters )
@@ -185,8 +206,8 @@ class _Group extends \IPS\Node\Model
}
$form->addHeader('pg_filters_header');
$form->add( new \IPS\Helpers\Form\Node( 'pg_filters', explode( ',', $this->filters ), FALSE, array( 'class' => 'IPS\nexus\Package\Filter', 'multiple' => TRUE ) ) );
$form->add( new \IPS\Helpers\Form\Stack( 'pg_price_filters', $priceFilters, FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\Money' ) ) );
$form->add( new Node( 'pg_filters', $this->filters ? explode( ',', $this->filters ) : [], FALSE, array( 'class' => 'IPS\nexus\Package\Filter', 'multiple' => TRUE ) ) );
$form->add( new Stack( 'pg_price_filters', $priceFilters, FALSE, array( 'stackFieldType' => 'IPS\nexus\Form\Money' ) ) );
}
@@ -196,7 +217,7 @@ class _Group extends \IPS\Node\Model
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if( isset( $values['pg_parent'] ) )
{
@@ -211,7 +232,7 @@ class _Group extends \IPS\Node\Model
if ( !$this->id )
{
$this->save();
\IPS\File::claimAttachments( 'nexus-new-group', $this->id, NULL, 'pgroup', TRUE );
File::claimAttachments( 'nexus-new-group', $this->id, NULL, 'pgroup', TRUE );
}
elseif( isset( $values['pg_name'] ) OR isset( $values['pg_desc'] ) )
{
@@ -220,13 +241,13 @@ class _Group extends \IPS\Node\Model
if( isset( $values['pg_name'] ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_pgroup_{$this->id}", $values['pg_name'] );
Lang::saveCustom( 'nexus', "nexus_pgroup_{$this->id}", $values['pg_name'] );
unset( $values['pg_name'] );
}
if( isset( $values['pg_desc'] ) )
{
\IPS\Lang::saveCustom( 'nexus', "nexus_pgroup_{$this->id}_desc", $values['pg_desc'] );
Lang::saveCustom( 'nexus', "nexus_pgroup_{$this->id}_desc", $values['pg_desc'] );
unset( $values['pg_desc'] );
}
@@ -240,7 +261,7 @@ class _Group extends \IPS\Node\Model
$priceFilters[ $currency ][] = $amount->amount->jsonSerialize();
}
}
$values['pg_price_filters'] = \count( $priceFilters ) ? json_encode( $priceFilters ) : NULL;
$values['pg_price_filters'] = count( $priceFilters ) ? json_encode( $priceFilters ) : NULL;
return $values;
}
@@ -248,18 +269,18 @@ class _Group extends \IPS\Node\Model
/**
* @brief Cached URL
*/
protected $_url = NULL;
protected mixed $_url = NULL;
/**
* Get URL
*
* @return \IPS\Http\Url
* @return Url\Internal|string|null
*/
public function url()
public function url(): Url\Internal|string|null
{
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\Friendly::seoTitle( \IPS\Member::loggedIn()->language()->get( 'nexus_pgroup_' . $this->id ) ) );
$this->_url = Url::internal( "app=nexus&module=store&controller=store&cat={$this->id}", 'front', 'store_group', Friendly::seoTitle( Member::loggedIn()->language()->get( 'nexus_pgroup_' . $this->id ) ) );
}
return $this->_url;
@@ -271,30 +292,30 @@ class _Group extends \IPS\Node\Model
* @param array $indexData Data from the search index
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @return \IPS\Http\Url
* @return Url
*/
public static function urlFromIndexData( $indexData, $itemData, $containerData )
public static function urlFromIndexData( array $indexData, array $itemData, ?array $containerData ): Url
{
return \IPS\Http\Url::internal( "app=nexus&module=store&controller=store&cat={$indexData['index_container_id']}", 'front', 'store_group', \IPS\Member::loggedIn()->language()->addToStack( 'nexus_pgroup_' . $indexData['index_container_id'], FALSE, array( 'seotitle' => TRUE ) ) );
return Url::internal( "app=nexus&module=store&controller=store&cat={$indexData['index_container_id']}", 'front', 'store_group', Member::loggedIn()->language()->addToStack( 'nexus_pgroup_' . $indexData['index_container_id'], FALSE, array( 'seotitle' => TRUE ) ) );
}
/**
* Get full image URL
*
* @return string
* @return string|null
*/
public function get_image()
public function get_image() : string|null
{
return ( isset( $this->_data['image'] ) ) ? (string) \IPS\File::get( 'nexus_PackageGroups', $this->_data['image'] )->url : NULL;
return ( isset( $this->_data['image'] ) ) ? (string) File::get( 'nexus_PackageGroups', $this->_data['image'] )->url : NULL;
}
/**
* Does this group have subgroups?
*
* @param mixed $_where Additional WHERE clause
* @param array $_where Additional WHERE clause
* @return bool
*/
public function hasSubgroups( $_where=array() )
public function hasSubgroups( array $_where=array() ) : bool
{
return ( $this->childrenCount( NULL, NULL, FALSE, $_where ) > 0 );
}
@@ -302,18 +323,18 @@ class _Group extends \IPS\Node\Model
/**
* Does this group have packages?
*
* @param \IPS\Member|NULL|FALSE $member The member to perform the permission check for, or NULL for currently logged in member, or FALSE for no permission check
* @param mixed $_where Additional WHERE clause
* @param Member|NULL|FALSE $member The member to perform the permission check for, or NULL for currently logged in member, or FALSE for no permission check
* @param array $_where Additional WHERE clause
* @param bool $viewableOnly Only check packages the member can view
* @return bool
*/
public function hasPackages( $member=NULL, $_where=array(), $viewableOnly=FALSE )
public function hasPackages( Member|null|bool $member=NULL, array $_where=array(), bool $viewableOnly=FALSE ) : bool
{
if( $viewableOnly === TRUE )
{
$member = $member ?: \IPS\Member::loggedIn();
$member = $member ?: Member::loggedIn();
$_where[] = array( "p_store=1 AND ( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', $member->groups ) . ' )' );
$_where[] = array( "p_store=1 AND ( p_member_groups='*' OR " . Db::i()->findInSet( 'p_member_groups', $member->groups ) . ' )' );
}
return ( $this->childrenCount( $member === FALSE ? FALSE : 'view', $member, NULL, $_where ) > 0 );
@@ -322,21 +343,21 @@ class _Group extends \IPS\Node\Model
/**
* Get Filter Options
*
* @param \IPS\Lang $language The language to return options in
* @param Lang $language The language to return options in
* @return array
*/
public function filters( \IPS\Lang $language )
public function filters( Lang $language ) : array
{
$return = array();
if ( $this->filters )
{
foreach ( \IPS\Db::i()->select( 'pfilter_id', 'nexus_package_filters', array( \IPS\Db::i()->in( 'pfilter_id', explode( ',', $this->filters ) ) ), 'pfilter_order' ) as $filterId )
foreach ( Db::i()->select( 'pfilter_id', 'nexus_package_filters', array( Db::i()->in( 'pfilter_id', explode( ',', $this->filters ) ) ), 'pfilter_order' ) as $filterId )
{
$return[ $filterId ] = array();
}
foreach ( \IPS\Db::i()->select( '*', 'nexus_package_filters_values', array( array( \IPS\Db::i()->in( 'pfv_filter', array_keys( $return ) ) ), array( 'pfv_lang=?', $language->id ) ), 'pfv_order' ) as $value )
foreach ( Db::i()->select( '*', 'nexus_package_filters_values', array( array( Db::i()->in( 'pfv_filter', array_keys( $return ) ) ), array( 'pfv_lang=?', $language->id ) ), 'pfv_order' ) as $value )
{
$return[ $value['pfv_filter'] ][ $value['pfv_value'] ] = $value['pfv_text'];
}
@@ -350,7 +371,7 @@ class _Group extends \IPS\Node\Model
*
* @return void
*/
public function __clone()
public function __clone() : void
{
if ( $this->skipCloneDuplication === TRUE )
{
@@ -365,11 +386,11 @@ class _Group extends \IPS\Node\Model
{
try
{
$icon = \IPS\File::get( 'nexus_PackageGroups', $oldImage );
$newIcon = \IPS\File::create( 'nexus_PackageGroups', $icon->originalFilename, $icon->contents() );
$icon = File::get( 'nexus_PackageGroups', $oldImage );
$newIcon = File::create( 'nexus_PackageGroups', $icon->originalFilename, $icon->contents() );
$this->image = (string) $newIcon;
}
catch ( \Exception $e )
catch ( Exception )
{
$this->pg_image = NULL;
}
@@ -383,7 +404,7 @@ class _Group extends \IPS\Node\Model
*
* @return bool
*/
public function deleteOrMoveQueued()
public function deleteOrMoveQueued(): bool
{
return FALSE;
}
@@ -393,21 +414,21 @@ class _Group extends \IPS\Node\Model
* Example code explains return value
*
* @code
array(
array(
'icon' => 'plus-circle', // Name of FontAwesome icon to use
'title' => 'foo', // Language key to use for button's title parameter
'link' => \IPS\Http\Url::internal( 'app=foo...' ) // URI to link to
'class' => 'modalLink' // CSS Class to use on link (Optional)
),
... // Additional buttons
);
* array(
* array(
* 'icon' => 'plus-circle', // Name of FontAwesome icon to use
* 'title' => 'foo', // Language key to use for button's title parameter
* 'link' => \IPS\Http\Url::internal( 'app=foo...' ) // URI to link to
* 'class' => 'modalLink' // CSS Class to use on link (Optional)
* ),
* ... // Additional buttons
* );
* @endcode
* @param string $url Base URL
* @param Url $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
*/
public function getButtons( $url, $subnode=FALSE )
public function getButtons( Url $url, bool $subnode=FALSE ):array
{
$buttons = parent::getButtons( $url, $subnode );
+234 -257
View File
@@ -12,56 +12,100 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use BadMethodCallException;
use DateInterval;
use Exception;
use InvalidArgumentException;
use IPS\Application;
use IPS\Content\Embeddable;
use IPS\Content\Featurable;
use IPS\Content\Filter;
use IPS\Content\Hideable;
use IPS\Content\Item as ContentItem;
use IPS\Content\MetaData;
use IPS\Content\Shareable;
use IPS\Db;
use IPS\File;
use IPS\File\Iterator;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Member;
use IPS\nexus\Customer;
use IPS\nexus\Money;
use IPS\nexus\Package;
use IPS\nexus\Purchase\RenewalTerm;
use IPS\nexus\Tax;
use IPS\Node\Model;
use IPS\Output;
use IPS\Patterns\ActiveRecordIterator;
use IPS\Request;
use IPS\Theme;
use OutOfRangeException;
use RuntimeException;
use function count;
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;
}
/**
* Package Item Model
*/
class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\Content\Shareable, \IPS\Content\Embeddable, \IPS\Content\MetaData, \IPS\Content\Searchable
class Item extends ContentItem implements Embeddable
{
use MetaData,
Shareable,
Hideable,
Featurable
{
Hideable::moderateNewReviews as public _moderateNewReviews;
Hideable::hiddenStatus as public _hidden;
}
/**
* @brief Application
*/
public static $application = 'nexus';
public static string $application = 'nexus';
/**
* @brief Module
*/
public static $module = 'store';
public static string $module = 'store';
/**
* @brief Database Table
*/
public static $databaseTable = 'nexus_packages';
public static ?string $databaseTable = 'nexus_packages';
/**
* @brief Database Prefix
*/
public static $databasePrefix = 'p_';
public static string $databasePrefix = 'p_';
/**
* @brief Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief Node Class
*/
public static $containerNodeClass = 'IPS\nexus\Package\Group';
public static ?string $containerNodeClass = 'IPS\nexus\Package\Group';
/**
* @brief Review Class
*/
public static $reviewClass = 'IPS\nexus\Package\Review';
public static string $reviewClass = 'IPS\nexus\Package\Review';
/**
* @brief Database Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'title' => 'name',
'container' => 'group',
'featured' => 'featured',
@@ -77,115 +121,138 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
/**
* @brief Title
*/
public static $title = 'product';
public static string $title = 'product';
/**
* @brief Icon
*/
public static $icon = 'archive';
public static string $icon = 'box';
/**
* @brief Include In Sitemap
*/
public static $includeInSitemap = FALSE;
public static bool $includeInSitemap = FALSE;
/**
* @brief Can this content be moderated normally from the front-end (will be FALSE for things like Pages and Commerce Products)
*/
public static $canBeModeratedFromFrontend = FALSE;
public static bool $canBeModeratedFromFrontend = FALSE;
/**
* Get title
*
* @return string
*/
public function get_title()
public function get_title() : string
{
return \IPS\Member::loggedIn()->language()->addToStack("nexus_package_{$this->id}");
return Member::loggedIn()->language()->addToStack("nexus_package_{$this->id}");
}
/**
* Get description
*
* @return string
* @return string|null
*/
public function content()
public function content(): ?string
{
return \IPS\Member::loggedIn()->language()->get("nexus_package_{$this->id}_desc"); // Has to be get() rather than addToStack() so we can reliably strips tags, etc.
return Member::loggedIn()->language()->get("nexus_package_{$this->id}_desc"); // Has to be get() rather than addToStack() so we can reliably strips tags, etc.
}
/**
* Can view?
*
* @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member
* @param Member|NULL $member The member to check for or NULL for the currently logged in member
* @return bool
*/
public function canView( $member=NULL )
public function canView( ?Member $member=null ): bool
{
if ( !$this->store )
{
return FALSE;
}
$member = $member ?: \IPS\Member::loggedIn();
$member = $member ?: Member::loggedIn();
return $this->member_groups === '*' or $member->inGroup( explode( ',', $this->member_groups ) );
}
/**
* Delete Package Data
*
* @return void
* @return void
*/
public function delete()
public function delete(): void
{
/* Delete Images */
foreach( \IPS\Db::i()->select( '*', 'nexus_package_images', array( 'image_product=?', $this->id ) ) as $image )
foreach( Db::i()->select( '*', 'nexus_package_images', array( 'image_product=?', $this->id ) ) as $image )
{
try
{
\IPS\File::get( 'nexus_Products', $image['image_location'] )->delete();
File::get( 'nexus_Products', $image['image_location'] )->delete();
}
catch ( \Exception $e ) { }
catch ( Exception ) { }
}
\IPS\Db::i()->delete( 'nexus_package_images', array( 'image_product=?', $this->id ) );
Db::i()->delete( 'nexus_package_images', array( 'image_product=?', $this->id ) );
/* Delete Product Options */
\IPS\Db::i()->delete( 'nexus_product_options', array( 'opt_package=?', $this->id ) );
Db::i()->delete( 'nexus_product_options', array( 'opt_package=?', $this->id ) );
/* Delete Base Prices */
\IPS\Db::i()->delete( 'nexus_package_base_prices', array( 'id=?', $this->id ) );
Db::i()->delete( 'nexus_package_base_prices', array( 'id=?', $this->id ) );
parent::delete();
if( Application::appIsEnabled( 'downloads') )
{
/* Look for files linked to this product */
foreach( Db::i()->select( '*', 'downloads_files', array( Db::i()->findInSet( 'file_nexus', array( $this->id ) ) ) ) as $file )
{
$packages = explode( ',', $file['file_nexus'] );
$packages = array_filter( $packages, function( $packageId ){
return $packageId != $this->id;
} );
/* If the only package was the one we just deleted, set it as not purchasable but do not remove the file_nexus value or the product will become downloadable by anyone */
if( !count( $packages ) )
{
Db::i()->update( 'downloads_files', array( 'file_purchasable' => 0 ), array( 'file_id=?', $file['file_id'] ) );
}
/* Otherwise if there are other products that can be purchased, remove this product from the purchasable list */
else
{
Db::i()->update( 'downloads_files', array( 'file_nexus' => implode( ',', $packages ) ), array( 'file_id=?', $file['file_id'] ) );
}
}
}
}
/**
* Get items with permisison check
*
* @param array $where Where clause
* @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 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)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @param mixed $skipPermission If you are getting records from a specific container, pass the container to reduce the number of permission checks necessary or pass TRUE to skip conatiner-based permission. You must still specify this in the $where clause
* @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
* @param array|null $location Array of item lat and long
* @return \IPS\Patterns\ActiveRecordIterator|int
* @param array $where Where clause
* @param string|null $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array|null $limit Limit clause
* @param string $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 int|bool|null $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 Member|null $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)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @param bool|Model $skipPermission If you are getting records from a specific container, pass the container to reduce the number of permission checks necessary or pass TRUE to skip conatiner-based permission. You must still specify this in the $where clause
* @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
* @param array|null $location Array of item lat and long
* @return ActiveRecordIterator|int
*/
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, $location=NULL )
public static function getItemsWithPermission( array $where=array(), string $order=NULL, int|array|null $limit=10, string $permissionKey='read', int|bool|null $includeHiddenItems= Filter::FILTER_AUTOMATIC, int $queryFlags=0, ?Member $member=null, bool $joinContainer=FALSE, bool $joinComments=FALSE, bool $joinReviews=FALSE, bool $countOnly=FALSE, array|null $joins=null, bool|Model $skipPermission=FALSE, bool $joinTags=TRUE, bool $joinAuthor=TRUE, bool $joinLastCommenter=TRUE, bool $showMovedLinks=FALSE, array|null $location=null ): ActiveRecordIterator|int
{
$member = $member ?: \IPS\Member::loggedIn();
$member = $member ?: Member::loggedIn();
$where[] = "( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', $member->groups ) . ' )';
$where[] = "( p_member_groups='*' OR " . 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, $showMovedLinks );
@@ -196,21 +263,21 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
/**
* @brief Cached URLs
*/
protected $_url = array();
protected mixed $_url = array();
/**
* Get URL
*
* @param string|NULL $action Action
* @return \IPS\Http\Url
* @return Url
*/
public function url( $action=NULL )
public function url( ?string $action=NULL ): Url
{
$_key = md5( $action );
$_key = $action ? md5( $action ) : NULL;
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\Friendly::seoTitle( \IPS\Member::loggedIn()->language()->get( 'nexus_package_' . $this->id ) ) );
$this->_url[ $_key ] = Url::internal( "app=nexus&module=store&controller=product&id={$this->id}", 'front', 'store_product', Friendly::seoTitle( Member::loggedIn()->language()->get( 'nexus_package_' . $this->id ) ) );
if ( $action )
{
@@ -220,61 +287,32 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
return $this->_url[ $_key ];
}
/**
* Can review?
*
* @param \IPS\Member\NULL $member The member (NULL for currently logged in member)
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
* @return bool
*/
public function canReview( $member=NULL, $considerPostBeforeRegistering = TRUE )
{
if ( !$this->reviewable )
{
return FALSE;
}
$member = $member ?: \IPS\Member::loggedIn();
if ( !parent::canReview( $member, $considerPostBeforeRegistering ) )
{
return FALSE;
}
if ( !\IPS\Db::i()->select( 'COUNT(*)', 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_item_id=? AND ps_member=?', 'nexus', 'package', $this->id, $member->member_id ) )->first() )
{
return FALSE;
}
return TRUE;
}
/**
* Should new reviews be moderated?
*
* @param \IPS\Member $member The member posting
* @param Member $member The member posting
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will check if a newly registered member would be moderated
* @return bool
*/
public function moderateNewReviews( \IPS\Member $member, $considerPostBeforeRegistering = FALSE )
public function moderateNewReviews( Member $member, bool $considerPostBeforeRegistering = FALSE ): bool
{
if ( $this->review_moderate )
{
return TRUE;
}
return parent::moderateNewReviews( $member, $considerPostBeforeRegistering );
return $this->_moderateNewReviews( $member, $considerPostBeforeRegistering );
}
/**
* Images
*
* @return \IPS\File\Iterator
* @return Iterator
*/
public function images()
public function images() : Iterator
{
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 );
return new Iterator( Db::i()->select( 'image_location', 'nexus_package_images', array( 'image_product=?', $this->id ),'image_primary desc' ), 'nexus_Products', NULL, TRUE );
}
/* !Embeddable */
@@ -285,46 +323,45 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param array $params Additional parameters to add to URL
* @return string
*/
public function embedContent( $params )
public function embedContent( array $params ): string
{
$memberCurrency = ( ( isset( \IPS\Request::i()->cookie['currency'] ) and \in_array( \IPS\Request::i()->cookie['currency'], \IPS\nexus\Money::currencies() ) ) ? \IPS\Request::i()->cookie['currency'] : \IPS\nexus\Customer::loggedIn()->defaultCurrency() );
$package = \IPS\nexus\Package::load( $this->id );
$memberCurrency = ( ( isset( Request::i()->cookie['currency'] ) and in_array( Request::i()->cookie['currency'], Money::currencies() ) ) ? Request::i()->cookie['currency'] : Customer::loggedIn()->defaultCurrency() );
$package = Package::load( $this->id );
/* Do we have renewal terms? */
$renewalTerm = NULL;
$renewOptions = $package->renew_options ? json_decode( $package->renew_options, TRUE ) : array();
if ( \count( $renewOptions ) )
if ( count( $renewOptions ) )
{
$renewalTerm = TRUE;
if ( \count( $renewOptions ) === 1 )
if ( count( $renewOptions ) === 1 )
{
$renewalTerm = array_pop( $renewOptions );
$renewalTerm = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $renewalTerm['cost'][ $memberCurrency ]['amount'], $memberCurrency ), new \DateInterval( 'P' . $renewalTerm['term'] . mb_strtoupper( $renewalTerm['unit'] ) ), $package->tax ? \IPS\nexus\Tax::load( $package->tax ) : NULL, $renewalTerm['add'] );
$renewalTerm = new RenewalTerm( new Money( $renewalTerm['cost'][ $memberCurrency ]['amount'], $memberCurrency ), new DateInterval( 'P' . $renewalTerm['term'] . mb_strtoupper( $renewalTerm['unit'] ) ), $package->tax ? Tax::load( $package->tax ) : NULL, $renewalTerm['add'] );
}
}
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'embed.css', 'nexus', 'front' ) );
return \IPS\Theme::i()->getTemplate( 'global', 'nexus' )->embedProduct( $this, $renewalTerm, $this->url()->setQueryString( $params ), $this->embedImage() );
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'embed.css', 'nexus', 'front' ) );
return Theme::i()->getTemplate( 'global', 'nexus' )->embedProduct( $this, $renewalTerm, $this->url()->setQueryString( $params ), $this->embedImage() );
}
/**
* Get image for embed
*
* @return \IPS\File|NULL
* @return File|NULL
*/
public function embedImage()
public function embedImage(): ?File
{
$product = \IPS\nexus\Package::load( $this->id );
return $product->_data['image'] ? \IPS\File::get( 'nexus_Products', $product->_data['image'] ) : NULL;
return $this->_data['image'] ? File::get( 'nexus_Products', $this->_data['image'] ) : NULL;
}
/**
* Get mapped value
*
* @param string $key date,content,ip_address,first
* @param string $key date,content,ip_address,first
* @return mixed
*/
public function mapped( $key )
public function mapped( string $key ): mixed
{
if ( $key === 'title' )
{
@@ -342,72 +379,19 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
*
* @return array
*/
public static function supportedMetaDataTypes()
public static function supportedMetaDataTypes(): array
{
return array();
}
/* !Search */
/**
* Title for search index
*
* @return string
*/
public function searchIndexTitle()
{
$titles = array();
foreach ( \IPS\Lang::languages() as $lang )
{
try
{
$titles[] = $lang->get( "nexus_package_{$this->id}" );
}
catch( \UnderflowException $e )
{
}
}
return implode( ' ', $titles );
}
/**
* Content for search index
*
* @return string
*/
public function searchIndexContent()
{
$descriptions = array();
foreach ( \IPS\Lang::languages() as $lang )
{
try
{
$descriptions[] = $lang->get("nexus_package_{$this->id}_desc");
}
catch ( \UnderflowException $e ) { }
}
return implode( ' ', $descriptions );
}
/**
* Search Index Permissions
*
* @return string Comma-delimited values or '*'
* @li Number indicates a group
* @li Number prepended by "m" indicates a member
* @li Number prepended by "s" indicates a social group
*/
public function searchIndexPermissions()
{
return $this->store ? $this->member_groups : '';
}
/**
* Columns needed to query for search result / stream view
*
* @return array
*/
public static function basicDataColumns()
public static function basicDataColumns(): array
{
return array( 'p_id', 'p_base_price', 'p_reviews', 'p_discounts', 'p_renew_options', 'p_tax', 'p_stock', 'p_initial_term' );
}
@@ -418,34 +402,11 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param array $indexData Data from the search index
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param string|NULL $action Action
* @return \IPS\Http\Url
* @return Url
*/
public static function urlFromIndexData( $indexData, $itemData, $action = NULL )
{
return \IPS\Http\Url::internal( "app=nexus&module=store&controller=product&id={$indexData['index_item_id']}", 'front', 'store_product', \IPS\Member::loggedIn()->language()->addToStack( 'nexus_package_' . $indexData['index_item_id'], FALSE, array( 'seotitle' => TRUE ) ) );
}
/**
* Get HTML for search result display
*
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array $template Optional custom template
* @param array $reactions Reaction Data
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL, $reactions=array() )
public static function urlFromIndexData( array $indexData, array $itemData, ?string $action = NULL ) : Url
{
$indexData['index_title'] = \IPS\Member::loggedIn()->language()->addToStack( 'nexus_package_' . $indexData['index_item_id'] );
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments, $template, $reactions );
return Url::internal( "app=nexus&module=store&controller=product&id={$indexData['index_item_id']}", 'front', 'store_product', Member::loggedIn()->language()->addToStack( 'nexus_package_' . $indexData['index_item_id'], FALSE, array( 'seotitle' => TRUE ) ) );
}
@@ -455,12 +416,12 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
* @param array $items Item data (will be an array containing values from basicDataColumns())
* @return array
*/
public static function searchResultExtraData( $items )
public static function searchResultExtraData( array $items ): array
{
$images = iterator_to_array( \IPS\Db::i()->select( array( 'image_product', 'image_location' ), 'nexus_package_images', array( array( \IPS\Db::i()->in( 'image_product', array_keys( $items ) ) ), array( 'image_primary=1' ) ) )->setKeyField( 'image_product' )->setValueField( 'image_location' ) );
$images = iterator_to_array( Db::i()->select( array( 'image_product', 'image_location' ), 'nexus_package_images', array( array( Db::i()->in( 'image_product', array_keys( $items ) ) ), array( 'image_primary=1' ) ) )->setKeyField( 'image_product' )->setValueField( 'image_location' ) );
$taxIds = array();
foreach ( $items as $k => $data )
foreach ( $items as $data )
{
if ( $data['p_tax'] )
{
@@ -471,58 +432,32 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
$taxData = array();
if ( $taxIds )
{
$taxData = iterator_to_array( \IPS\Db::i()->select( '*', 'nexus_tax', \IPS\Db::i()->in( 't_id', $taxIds ) )->setKeyField('t_id') );
$taxData = iterator_to_array( Db::i()->select( '*', 'nexus_tax', Db::i()->in( 't_id', $taxIds ) )->setKeyField('t_id') );
}
$return = array();
foreach ( $items as $k => $data )
{
$return[ $k ]['image'] = isset( $images[ $k ] ) ? $images[ $k ] : NULL;
$return[ $k ]['image'] = $images[$k] ?? NULL;
$return[ $k ]['tax'] = ( $data['p_tax'] and isset( $taxData[ $data['p_tax'] ] ) ) ? $taxData[ $data['p_tax'] ] : NULL;
}
return $return;
}
/**
* Get snippet HTML for search result display
*
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param string $view 'expanded' or 'condensed'
* @return callable
*/
public static function searchResultSnippet( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $view )
{
$url = static::urlFromIndexData( $indexData, $itemData );
/* Work out the price to display */
$customer = \IPS\nexus\Customer::loggedIn();
$currency = ( isset( \IPS\Request::i()->cookie['currency'] ) and \in_array( \IPS\Request::i()->cookie['currency'], \IPS\nexus\Money::currencies() ) ) ? \IPS\Request::i()->cookie['currency'] : $customer->defaultCurrency();
$renewOptions = $itemData['p_renew_options'] ? json_decode( $itemData['p_renew_options'], TRUE ) : array();
$priceInfo = \IPS\nexus\Package::fullPriceInfoFromData( $customer, $currency, $indexData['index_item_id'], json_decode( $itemData['p_base_price'], TRUE ), json_decode( $itemData['p_discounts'], TRUE ), $renewOptions, $itemData['p_stock'], $itemData['extra']['tax'] ? \IPS\nexus\Tax::constructFromData( $itemData['extra']['tax'] ) : NULL, $itemData['p_initial_term'] ? new \DateInterval("P{$itemData['p_initial_term']}") : NULL );
/* Display */
return \IPS\Theme::i()->getTemplate( 'global', 'nexus', 'front' )->searchResultProductSnippet( $indexData, $itemData, isset( $itemData['extra']['image'] ) ? $itemData['extra']['image'] : NULL, $url, $priceInfo, $view == 'condensed' );
}
/**
* Check Moderator Permission
*
* @param string $type 'edit', 'hide', 'unhide', 'delete', etc.
* @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member
* @param \IPS\Node\Model|NULL $container The container
* @param Member|NULL $member The member to check for or NULL for the currently logged in member
* @param Model|NULL $container The container
* @return bool
*/
public static function modPermission( $type, \IPS\Member $member = NULL, \IPS\Node\Model $container = NULL )
public static function modPermission( string $type, Member $member = NULL, Model $container = NULL ): bool
{
/* Commerce Items have no own content type, so we can use only the general mod permissions */
$member = $member ?: \IPS\Member::loggedIn();
if ( \in_array( $type, array( 'hide', 'unhide', 'delete' ) ) and $container )
$member = $member ?: Member::loggedIn();
if ( in_array( $type, array( 'hide', 'unhide', 'delete' ) ) and $container )
{
if ( $member->modPermission( "can_{$type}" ) )
{
@@ -536,77 +471,119 @@ class _Item extends \IPS\Content\Item implements \IPS\Content\Featurable, \IPS\C
/**
* Should posting this increment the poster's post count?
*
* @param \IPS\Node\Model|NULL $container Container
* @return void
* @param Model|NULL $container Container
* @return bool
*/
public static function incrementPostCount( \IPS\Node\Model $container = NULL )
public static function incrementPostCount( Model $container = NULL ): bool
{
return FALSE;
}
/**
* Can edit?
* Check if a specific action is available for this Content.
* Default to TRUE, but used for overrides in individual Item/Comment classes.
*
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
* @return bool
* @param string $action
* @param Member|null $member
* @return bool
*/
public function canEdit( $member=NULL )
public function actionEnabled( string $action, ?Member $member=null ) : bool
{
return FALSE;
switch( $action )
{
case 'edit':
case 'move':
return FALSE;
case 'review':
if ( !$this->reviewable )
{
return FALSE;
}
$member = $member ?: Member::loggedIn();
if ( !Db::i()->select( 'COUNT(*)', 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_item_id=? AND ps_member=?', 'nexus', 'package', $this->id, $member->member_id ) )->first() )
{
return FALSE;
}
break;
}
return parent::actionEnabled( $action, $member );
}
/**
* Do Moderator Action
*
* @param string $action The action
* @param \IPS\Member|NULL $member The member doing the action (NULL for currently logged in member)
* @param Member|NULL $member The member doing the action (NULL for currently logged in member)
* @param string|NULL $reason Reason (for hides)
* @param bool $immediately Delete immediately
* @return void
* @throws \OutOfRangeException|\InvalidArgumentException|\RuntimeException
* @throws OutOfRangeException|InvalidArgumentException|RuntimeException
*/
public function modAction( $action, \IPS\Member $member = NULL, $reason = NULL, $immediately = FALSE )
public function modAction( string $action, Member $member = NULL, mixed $reason = NULL, bool $immediately=FALSE ): void
{
throw new \InvalidArgumentException;
}
/**
* Can move?
*
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
* @return bool
*/
public function canMove( $member=NULL )
{
return FALSE;
throw new InvalidArgumentException;
}
/**
* Returns the meta description
*
* @param string|NULL $return Specific description to use (useful for paginated displays to prevent having to run extra queries)
* @param string|null $return Specific description to use (useful for paginated displays to prevent having to run extra queries)
* @return string
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function metaDescription( $return = NULL )
public function metaDescription( string $return = NULL ): string
{
return \IPS\Member::loggedIn()->language()->addToStack("nexus_package_{$this->id}_desc", FALSE, array( 'striptags' => TRUE, 'removeNewlines' => TRUE ) );
return Member::loggedIn()->language()->addToStack("nexus_package_{$this->id}_desc", FALSE, array( 'striptags' => TRUE, 'removeNewlines' => TRUE ) );
}
/**
* Get container
*
* @return \IPS\Node\Model
* @return Model
* @note Certain functionality requires a valid container but some areas do not use this functionality (e.g. messenger)
* @throws \OutOfRangeException|\BadMethodCallException
* @throws OutOfRangeException|BadMethodCallException
*/
public function container()
public function container(): Model
{
if( $this->custom > 0 )
{
throw new \BadMethodCallException;
throw new BadMethodCallException;
}
return parent::container();
}
/**
* Content is hidden?
*
* @return int
* @li -3 is a post made by a guest using the "post before register" feature
* @li -2 is pending deletion
* @li -1 is hidden having been hidden by a moderator
* @li 0 is unhidden
* @li 1 is hidden needing approval
* @note The actual column may also contain 2 which means the item is hidden because the parent is hidden, but it is not hidden in itself. This method will return -1 in that case.
*
* @note A piece of content (item and comment) can have an alias for hidden OR approved.
* With hidden: 0=not hidden, 1=hidden (needs moderator approval), -1=hidden by moderator, 2=parent item is hidden, -2=pending deletion, -3=guest post before register
* With approved: 1=not hidden, 0=hidden (needs moderator approval), -1=hidden by moderator, -2=pending deletion, -3=guest post before register
*
* User posting has moderator approval set: When adding an unapproved ITEM (approved=0, hidden=1) you should *not* increment container()->_comments but you should update container()->_unapprovedItems
* User posting has moderator approval set: When adding an unapproved COMMENT (approved=0, hidden=1) you should *not* increment item()->num_comments in item or container()->_comments but you should update item()->unapproved_comments and container()->_unapprovedComments
*
* User post is hidden by moderator (approved=-1, hidden=0) you should decrement item()->num_comments and decrement container()->_comments but *not* increment item()->unapproved_comments or container()->_unapprovedComments
* User item is hidden by a moderator (approved=-1, hidden=0) you should decrement container()->comments and subtract comment count from container()->_comments, but *not* increment container()->_unapprovedComments
*
* Moderator hides item (approved=-1, hidden=-1) you should substract num_comments from container()->_comments. Comments inside item are flagged as approved=-1, hidden=2 but item()->num_comments should not be substracted from
*
* Comments with a hidden value of 2 should increase item()->num_comments but not container()->_comments
* @throws RuntimeException
*/
public function hiddenStatus(): int
{
return 0;
}
}
File diff suppressed because it is too large. Load diff
+97 -139
View File
@@ -12,70 +12,71 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DomainException;
use IPS\Db;
use IPS\Dispatcher;
use IPS\Helpers\Form\Number;
use IPS\Helpers\Form\Radio;
use IPS\Helpers\Form\Select;
use IPS\Helpers\Form\YesNo;
use IPS\IPS;
use IPS\Member;
use IPS\nexus\Invoice;
use IPS\nexus\Purchase;
use IPS\nexus\Purchase\LicenseKey;
use IPS\nexus\Package as NexusPackage;
use IPS\Output;
use IPS\Request;
use IPS\Session;
use IPS\Theme;
use OutOfRangeException;
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;
}
/**
* Product Package
*/
class _Product extends \IPS\nexus\Package
class Product extends NexusPackage
{
/**
* @brief Database Table
*/
protected static $packageDatabaseTable = 'nexus_packages_products';
protected static string $packageDatabaseTable = 'nexus_packages_products';
/**
* @brief Which columns belong to the local table
*/
protected static $packageDatabaseColumns = array( 'p_physical', 'p_subscription', 'p_shipping', 'p_weight', 'p_lkey', 'p_lkey_identifier', 'p_lkey_uses', 'p_show', 'p_length', 'p_width', 'p_height' );
protected static array $packageDatabaseColumns = array( 'p_subscription', 'p_lkey', 'p_lkey_identifier', 'p_lkey_uses', 'p_show' );
/**
* ACP Fields
*
* @param \IPS\nexus\Package $package The package
* @param bool $custom If TRUE, is for a custom package
* @param bool $customEdit If TRUE, is editing a custom package
* @return array
* @param NexusPackage $package The package
* @param bool $custom If TRUE, is for a custom package
* @param bool $customEdit If TRUE, is editing a custom package
* @return array
*/
public static function acpFormFields( \IPS\nexus\Package $package, $custom=FALSE, $customEdit=FALSE )
public static function acpFormFields(NexusPackage $package, bool $custom=FALSE, bool $customEdit=FALSE ): array
{
$return = array();
$formId = $package->id ? "form_{$package->id}" : 'form_new';
if ( !$customEdit ) // After the package has been created, these are unimportant
{
$return['package_settings']['physical'] = new \IPS\Helpers\Form\YesNo( 'p_physical', $package->type === 'product' ? $package->physical : FALSE, FALSE, array( 'disableCopy' => TRUE, 'togglesOn' => array( 'p_weight', 'p_length', 'p_width', 'p_height', 'p_shipping' ), 'togglesOff' => array( 'elProductTaxWarningContainer' ) ) );
$return['package_settings']['weight'] = new \IPS\nexus\Form\Weight( 'p_weight', $package->type === 'product' ? new \IPS\nexus\Shipping\Weight( $package->weight ) : NULL, FALSE, array( 'disableCopy' => TRUE ), NULL, NULL, NULL, 'p_weight' );
$return['package_settings']['length'] = new \IPS\nexus\Form\Length( 'p_length', $package->type === 'product' ? new \IPS\nexus\Shipping\Length( $package->length ) : NULL, FALSE, array( 'disableCopy' => TRUE ), NULL, NULL, NULL, 'p_length' );
$return['package_settings']['width'] = new \IPS\nexus\Form\Length( 'p_width', $package->type === 'product' ? new \IPS\nexus\Shipping\Length( $package->width ) : NULL, FALSE, array( 'disableCopy' => TRUE ), NULL, NULL, NULL, 'p_width' );
$return['package_settings']['height'] = new \IPS\nexus\Form\Length( 'p_height', $package->type === 'product' ? new \IPS\nexus\Shipping\Length( $package->height ) : NULL, FALSE, array( 'disableCopy' => TRUE ), NULL, NULL, NULL, 'p_height' );
$availableShippingMethods = array();
foreach ( \IPS\nexus\Shipping\FlatRate::roots() as $rate )
{
$availableShippingMethods[ $rate->_id ] = $rate->_title;
}
if ( \IPS\Settings::i()->easypost_api_key and \IPS\Settings::i()->easypost_show_rates )
{
$availableShippingMethods['easypost'] = 'enhancements__nexus_EasyPost';
}
$return['package_settings']['shipping'] = new \IPS\Helpers\Form\CheckboxSet( 'p_shipping', ( $package->type === 'product' and $package->shipping !== '*' ) ? explode( ',', $package->shipping ) : '*', FALSE, array( 'options' => $availableShippingMethods, 'multiple' => TRUE, 'unlimited' => '*', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'p_shipping' );
}
$return['package_settings']['show'] = new \IPS\Helpers\Form\YesNo( 'p_show', $package->type === 'product' ? $package->show : TRUE, FALSE, array( 'togglesOn' => array( "{$formId}_tab_package_client_area", "{$formId}_header_package_associations", "{$formId}_header_package_associations_desc", 'p_associate', "{$formId}_header_package_renewals", 'p_renews', 'p_support_severity', 'p_lkey' ) ) );
$return['package_settings']['show'] = new YesNo( 'p_show', $package->type === 'product' ? $package->show : TRUE, FALSE, array( 'togglesOn' => array( "{$formId}_tab_package_client_area", "{$formId}_header_package_associations", "{$formId}_header_package_associations_desc", 'p_associate', "{$formId}_header_package_renewals", 'p_renews', 'p_lkey' ) ) );
if ( !$custom )
{
$return['store_permissions']['subscription'] = new \IPS\Helpers\Form\YesNo( 'p_subscription', $package->type === 'product' ? !$package->subscription : TRUE );
$return['store_permissions']['subscription'] = new YesNo( 'p_subscription', $package->type === 'product' ? !$package->subscription : TRUE );
}
$licenseKeyOptions = array();
$licenseKeyToggles = array();
foreach ( \IPS\nexus\Purchase\LicenseKey::licenseKeyTypes() as $key => $class )
foreach ( LicenseKey::licenseKeyTypes() as $key => $class )
{
$licenseKeyOptions[ mb_strtolower( $key ) ] = 'lkey_' . $key;
$licenseKeyToggles[ mb_strtolower( $key ) ] = array( 'p_lkey_identifier', 'p_lkey_uses' );
@@ -83,10 +84,10 @@ class _Product extends \IPS\nexus\Package
if ( !empty( $licenseKeyOptions ) )
{
array_unshift( $licenseKeyOptions, 'lkey_none' );
$return['package_benefits']['lkey'] = new \IPS\Helpers\Form\Radio( 'p_lkey', $package->type === 'product' ? $package->lkey : 0, FALSE, array( 'options' => $licenseKeyOptions, 'toggles' => $licenseKeyToggles ), NULL, NULL, NULL, 'p_lkey' );
$return['package_benefits']['lkey'] = new Radio( 'p_lkey', $package->type === 'product' ? $package->lkey : 0, FALSE, array( 'options' => $licenseKeyOptions, 'toggles' => $licenseKeyToggles ), NULL, NULL, NULL, 'p_lkey' );
}
$return['package_benefits']['lkey_uses'] = new \IPS\Helpers\Form\Number( 'p_lkey_uses', $package->type === 'product' ? $package->lkey_uses : -1, FALSE, array( 'unlimited' => -1 ) );
$return['package_benefits']['lkey_uses'] = new Number( 'p_lkey_uses', $package->type === 'product' ? $package->lkey_uses : -1, FALSE, array( 'unlimited' => -1 ) );
$identifierOptions = array(
'name' => 'lkey_identifier_name',
@@ -95,13 +96,13 @@ class _Product extends \IPS\nexus\Package
);
if ( $package->id )
{
foreach ( \IPS\nexus\Package\CustomField::roots( NULL, NULL, array( array( \IPS\Db::i()->findInSet( 'cf_packages', array( $package->id ) ) ) ) ) as $field )
foreach (CustomField::roots( NULL, NULL, array( array( Db::i()->findInSet( 'cf_packages', array( $package->id ) ) ) ) ) as $field )
{
$identifierOptions[ $field->id ] = $field->_title;
}
}
$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 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;
}
@@ -112,53 +113,9 @@ class _Product extends \IPS\nexus\Package
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
{
if( isset( $values['p_subscription'] ) )
{
$values['p_subscription'] = isset( $values['p_subscription'] ) ? !$values['p_subscription'] : FALSE;
}
if( isset( $values['p_weight'] ) )
{
$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'] ) )
{
$values['p_shipping'] = \is_array( $values['p_shipping'] ) ? implode( ',', $values['p_shipping'] ) : '*';
}
public function formatFormValues( array $values ): array
{
$values['p_subscription'] = isset( $values['p_subscription'] ) ? !$values['p_subscription'] : FALSE;
return parent::formatFormValues( $values );
}
@@ -168,7 +125,7 @@ class _Product extends \IPS\nexus\Package
*
* @return array
*/
public static function updateableFields()
public static function updateableFields() : array
{
return array_merge( parent::updateableFields(), array(
'lkey',
@@ -181,17 +138,15 @@ class _Product extends \IPS\nexus\Package
/**
* Update existing purchases
*
* @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
* @param 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, $cancelBillingAgreementIfNecessary=FALSE )
public function updatePurchase(Purchase $purchase, array $changes, bool $cancelBillingAgreementIfNecessary=FALSE ): void
{
if ( array_key_exists( 'lkey', $changes ) )
{
$oldKey = NULL;
$lKey = $purchase->licenseKey();
if ( $lKey )
@@ -199,11 +154,11 @@ class _Product extends \IPS\nexus\Package
$lKey->delete();
}
$licenseTypes = \IPS\nexus\Purchase\LicenseKey::licenseKeyTypes();
$licenseTypes = LicenseKey::licenseKeyTypes();
if( class_exists( $licenseTypes[ mb_ucfirst( $this->lkey ) ]) )
if( class_exists( $licenseTypes[ IPS::mb_ucfirst( $this->lkey ) ]) )
{
$class = $licenseTypes[ mb_ucfirst( $this->lkey ) ];
$class = $licenseTypes[ IPS::mb_ucfirst( $this->lkey ) ];
$licenseKey = new $class;
$licenseKey->identifier = $this->lkey_identifier;
$licenseKey->purchase = $purchase;
@@ -230,7 +185,7 @@ class _Product extends \IPS\nexus\Package
$purchase->save();
}
return parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
parent::updatePurchase( $purchase, $changes, $cancelBillingAgreementIfNecessary );
}
/* !Actions */
@@ -238,23 +193,23 @@ class _Product extends \IPS\nexus\Package
/**
* Add To Cart
*
* @param \IPS\nexus\extensions\nexus\Item\Package $item The item
* @param \IPS\nexus\Invoice\Item $item The item
* @param array $values Values from form
* @param string $memberCurrency The currency being used
* @return array Additional items to add
* @param string $memberCurrency The currency being used
* @return array Additional items to add
*/
public function addToCart( \IPS\nexus\extensions\nexus\Item\Package $item, array $values, $memberCurrency )
public function addToCart(\IPS\nexus\Invoice\Item $item, array $values, string $memberCurrency ): array
{
if ( $this->subscription )
{
if ( $item->quantity > 1 )
{
\IPS\Output::i()->error( 'err_subscription_qty', '1X247/2', 403, '' );
Output::i()->error( 'err_subscription_qty', '1X247/2', 403, '' );
}
if ( $this->_memberHasPurchasedSubscription( \IPS\Member::loggedIn() ) )
if ( $this->_memberHasPurchasedSubscription( Member::loggedIn() ) )
{
\IPS\Output::i()->error( 'err_subscription_bought', '1X247/1', 403, '' );
Output::i()->error( 'err_subscription_bought', '1X247/1', 403, '' );
}
if ( isset( $_SESSION['cart'] ) )
@@ -263,7 +218,7 @@ class _Product extends \IPS\nexus\Package
{
if ( $_item->id === $this->id )
{
\IPS\Output::i()->error( 'err_subscription_in_cart', '1X247/3', 403, '' );
Output::i()->error( 'err_subscription_in_cart', '1X247/3', 403, '' );
}
}
}
@@ -278,31 +233,31 @@ class _Product extends \IPS\nexus\Package
* 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
* @param Member $member The new member
* @return void
* @throws DomainException
*/
public function memberCanPurchase( \IPS\Member $member )
public function memberCanPurchase( Member $member ): void
{
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}" ) ) ) ) );
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}" ) ) ) ) );
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
* @param Member $member The new member
* @return bool
*/
protected function _memberHasPurchasedSubscription( \IPS\Member $member )
protected function _memberHasPurchasedSubscription( Member $member ) : bool
{
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();
return (bool) 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();
}
/**
@@ -310,7 +265,7 @@ class _Product extends \IPS\nexus\Package
*
* @return bool
*/
public function showPurchaseRecord()
public function showPurchaseRecord(): bool
{
return $this->show;
}
@@ -318,36 +273,39 @@ class _Product extends \IPS\nexus\Package
/**
* Get ACP Page HTML
*
* @return string
* @param Purchase $purchase
* @return string
*/
public function acpPage( \IPS\nexus\Purchase $purchase )
public function acpPage( Purchase $purchase ): string
{
if ( $this->lkey and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'lkeys_view' ) )
if ( $this->lkey and Member::loggedIn()->hasAcpRestriction( 'nexus', 'customers', 'lkeys_view' ) )
{
if ( $lkey = $purchase->licenseKey() )
{
return \IPS\Theme::i()->getTemplate('purchases')->lkey( $lkey );
return Theme::i()->getTemplate('purchases')->lkey( $lkey );
}
else
{
return \IPS\Theme::i()->getTemplate('purchases')->noLkey( $purchase );
return Theme::i()->getTemplate('purchases')->noLkey( $purchase );
}
}
return '';
}
/**
* ACP Action
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return void
* @param Purchase $purchase The purchase
* @return string|null
*/
public function acpAction( \IPS\nexus\Purchase $purchase )
public function acpAction( Purchase $purchase ): string|null
{
switch ( \IPS\Request::i()->act )
switch ( Request::i()->act )
{
case 'lkeyReset':
\IPS\Dispatcher::i()->checkAcpPermission( 'lkeys_reset' );
\IPS\Session::i()->csrfCheck();
Dispatcher::i()->checkAcpPermission( 'lkeys_reset' );
Session::i()->csrfCheck();
$oldKey = NULL;
try
@@ -358,13 +316,13 @@ class _Product extends \IPS\nexus\Package
$old->delete();
}
}
catch ( \OutOfRangeException $e ) { }
catch ( OutOfRangeException ) { }
/* Invalidate License Key Cache so old data is not loaded */
$purchase->licenseKey = NULL;
$licenseTypes = \IPS\nexus\Purchase\LicenseKey::licenseKeyTypes();
$class = $licenseTypes[ mb_ucfirst( $this->lkey ) ];
$licenseTypes = LicenseKey::licenseKeyTypes();
$class = $licenseTypes[ IPS::mb_ucfirst( $this->lkey ) ];
$licenseKey = new $class;
$licenseKey->identifier = $this->lkey_identifier;
$licenseKey->purchase = $purchase;
@@ -372,7 +330,7 @@ class _Product extends \IPS\nexus\Package
$licenseKey->save();
$purchase->member->log( 'lkey', array( 'type' => 'reset', 'key' => $oldKey, 'new' => $licenseKey->key, 'ps_id' => $purchase->id, 'ps_name' => $purchase->name ) );
break;
return null;
default:
return parent::acpAction( $purchase );
@@ -382,16 +340,16 @@ class _Product extends \IPS\nexus\Package
/**
* On Purchase Generated
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @param \IPS\nexus\Invoice $invoice The invoice
* @return void
* @param Purchase $purchase The purchase
* @param Invoice $invoice The invoice
* @return void
*/
public function onPurchaseGenerated( \IPS\nexus\Purchase $purchase, \IPS\nexus\Invoice $invoice )
public function onPurchaseGenerated(Purchase $purchase, Invoice $invoice ): void
{
if ( $this->lkey )
{
$licenseTypes = \IPS\nexus\Purchase\LicenseKey::licenseKeyTypes();
$class = $licenseTypes[ mb_ucfirst( $this->lkey ) ];
$licenseTypes = LicenseKey::licenseKeyTypes();
$class = $licenseTypes[ IPS::mb_ucfirst( $this->lkey ) ];
$licenseKey = new $class;
$licenseKey->identifier = $this->lkey_identifier;
$licenseKey->purchase = $purchase;
@@ -405,10 +363,10 @@ class _Product extends \IPS\nexus\Package
/**
* Warning to display to admin when cancelling a purchase
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return string
* @param Purchase $purchase The purchase
* @return string|null
*/
public function onCancelWarning( \IPS\nexus\Purchase $purchase )
public function onCancelWarning( Purchase $purchase ): string|null
{
return NULL;
}
+56 -31
View File
@@ -12,53 +12,78 @@
namespace IPS\nexus\Package;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use IPS\Content\EditHistory;
use IPS\Content\Embeddable;
use IPS\Content\Filter;
use IPS\Content\Hideable;
use IPS\Content\Reportable;
use IPS\Content\Review as ContentReview;
use IPS\Content\Shareable;
use IPS\core\Approval;
use IPS\nexus\Customer;
use IPS\nexus\Money;
use IPS\nexus\Package;
use IPS\nexus\Purchase\RenewalTerm;
use IPS\nexus\Tax;
use IPS\Output;
use IPS\Request;
use IPS\Theme;
use OutOfRangeException;
use function count;
use function defined;
use function get_called_class;
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;
}
/**
* Package Review
*/
class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \IPS\Content\Hideable, \IPS\Content\Embeddable
class Review extends ContentReview implements Embeddable,
Filter
{
use \IPS\Content\Reportable;
use Reportable, Shareable, EditHistory, Hideable;
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [Content\Comment] Item Class
*/
public static $itemClass = 'IPS\nexus\Package\Item';
public static ?string $itemClass = 'IPS\nexus\Package\Item';
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'nexus_reviews';
public static ?string $databaseTable = 'nexus_reviews';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'review_';
public static string $databasePrefix = 'review_';
/**
* @brief Title
*/
public static $title = 'product_reviews';
public static string $title = 'product_reviews';
/**
* @brief Icon
*/
public static $icon = 'archive';
public static string $icon = 'box';
/**
* @brief Database Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'item' => 'product',
'author' => 'author_id',
'author_name' => 'author_name',
@@ -81,36 +106,36 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
/**
* @brief Application
*/
public static $application = 'nexus';
public static string $application = 'nexus';
/**
* @brief Module
*/
public static $module = 'store';
public static string $module = 'store';
/**
* @brief [Content] Key for hide reasons
*/
public static $hideLogKey = 'nexus-products';
public static ?string $hideLogKey = 'nexus-products';
/**
* @brief [Content\Item] First "comment" is part of the item?
*/
public static $firstCommentRequired = FALSE;
public static bool $firstCommentRequired = FALSE;
/**
* @brief Include In Sitemap
*/
public static $includeInSitemap = FALSE;
public static bool $includeInSitemap = FALSE;
/**
* Get content for header in content tables
*
* @return callable
*/
public function contentTableHeader()
public function contentTableHeader(): string
{
return \IPS\Theme::i()->getTemplate( 'global', static::$application )->commentTableHeader( $this, \IPS\nexus\Package::load( $this->item()->id ), $this->item() );
return (string) Theme::i()->getTemplate( 'global', static::$application )->commentTableHeader( $this, Package::load( $this->item()->id ), $this->item() );
}
/**
@@ -119,26 +144,26 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
* @param array $params Additional parameters to add to URL
* @return string
*/
public function embedContent( $params )
public function embedContent( array $params ): string
{
$memberCurrency = ( ( isset( \IPS\Request::i()->cookie['currency'] ) and \in_array( \IPS\Request::i()->cookie['currency'], \IPS\nexus\Money::currencies() ) ) ? \IPS\Request::i()->cookie['currency'] : \IPS\nexus\Customer::loggedIn()->defaultCurrency() );
$package = \IPS\nexus\Package::load( $this->id );
$memberCurrency = ( ( isset( Request::i()->cookie['currency'] ) and in_array( Request::i()->cookie['currency'], Money::currencies() ) ) ? Request::i()->cookie['currency'] : Customer::loggedIn()->defaultCurrency() );
$package = Package::load( $this->id );
/* Do we have renewal terms? */
$renewalTerm = NULL;
$renewOptions = $package->renew_options ? json_decode( $package->renew_options, TRUE ) : array();
if ( \count( $renewOptions ) )
if ( count( $renewOptions ) )
{
$renewalTerm = TRUE;
if ( \count( $renewOptions ) === 1 )
if ( count( $renewOptions ) === 1 )
{
$renewalTerm = array_pop( $renewOptions );
$renewalTerm = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $renewalTerm['cost'][ $memberCurrency ]['amount'], $memberCurrency ), new \DateInterval( 'P' . $renewalTerm['term'] . mb_strtoupper( $renewalTerm['unit'] ) ), $package->tax ? \IPS\nexus\Tax::load( $package->tax ) : NULL, $renewalTerm['add'] );
$renewalTerm = new RenewalTerm( new Money( $renewalTerm['cost'][ $memberCurrency ]['amount'], $memberCurrency ), new DateInterval( 'P' . $renewalTerm['term'] . mb_strtoupper( $renewalTerm['unit'] ) ), $package->tax ? Tax::load( $package->tax ) : NULL, $renewalTerm['add'] );
}
}
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'embed.css', 'nexus', 'front' ) );
return \IPS\Theme::i()->getTemplate( 'global', 'nexus' )->embedProductReview( $this, $this->item(), $renewalTerm, $this->url()->setQueryString( $params ) );
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'embed.css', 'nexus', 'front' ) );
return Theme::i()->getTemplate( 'global', 'nexus' )->embedProductReview( $this, $this->item(), $renewalTerm, $this->url()->setQueryString( $params ) );
}
/**
@@ -146,7 +171,7 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
*
* @return void
*/
public function postCreate()
public function postCreate(): void
{
parent::postCreate();
@@ -155,15 +180,15 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
{
try
{
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->id );
Approval::loadFromContent( get_called_class(), $this->id );
}
catch( \OutOfRangeException $e )
catch( OutOfRangeException )
{
/* No reason found - see if product requires approval of reviews */
if ( $this->item()->review_moderate )
{
$log = new \IPS\core\Approval;
$log->content_class = \get_called_class();
$log = new Approval;
$log->content_class = get_called_class();
$log->content_id = $this->id;
$log->held_reason = 'item';
$log->save();