Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
@@ -13,21 +13,21 @@ namespace IPS\Notification;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Notification Model
|
||||
*/
|
||||
class _Api extends \IPS\Notification\Inline
|
||||
class Api extends Inline
|
||||
{
|
||||
/**
|
||||
* Get sent time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_sent()
|
||||
public function get_sent(): int
|
||||
{
|
||||
return $this->_data['sent_time'];
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class _Api extends \IPS\Notification\Inline
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_updated()
|
||||
public function get_updated(): int
|
||||
{
|
||||
return $this->_data['updated_time'];
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class _Api extends \IPS\Notification\Inline
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_app()
|
||||
public function get_app(): string
|
||||
{
|
||||
return $this->_data['notification_app'];
|
||||
}
|
||||
|
||||
@@ -11,33 +11,45 @@
|
||||
namespace IPS\Notification;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\DateTime;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use LogicException;
|
||||
use OutOfRangeException;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function get_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline Notification Model
|
||||
*/
|
||||
class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
class Inline extends ActiveRecord
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_notifications';
|
||||
public static ?string $databaseTable = 'core_notifications';
|
||||
|
||||
/**
|
||||
* Set Default Values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultValues()
|
||||
public function setDefaultValues() : void
|
||||
{
|
||||
$this->sent_time = time();
|
||||
$this->updated_time = time();
|
||||
@@ -46,40 +58,40 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Get sent time
|
||||
*
|
||||
* @return \IPS\DateTime
|
||||
* @return DateTime
|
||||
*/
|
||||
public function get_sent_time()
|
||||
public function get_sent_time(): DateTime
|
||||
{
|
||||
return \IPS\DateTime::ts( $this->_data['sent_time'] );
|
||||
return DateTime::ts( $this->_data['sent_time'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updated time
|
||||
*
|
||||
* @return \IPS\DateTime
|
||||
* @return DateTime
|
||||
*/
|
||||
public function get_updated_time()
|
||||
public function get_updated_time(): DateTime
|
||||
{
|
||||
return \IPS\DateTime::ts( $this->_data['updated_time'] );
|
||||
return DateTime::ts( $this->_data['updated_time'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get member
|
||||
*
|
||||
* @return \IPS\Member
|
||||
* @return Member
|
||||
*/
|
||||
public function get_member()
|
||||
public function get_member(): Member
|
||||
{
|
||||
return \IPS\Member::load( $this->_data['member'] );
|
||||
return Member::load( $this->_data['member'] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set member
|
||||
*
|
||||
* @param |IPS\Member $member The member
|
||||
* @return void
|
||||
* @param Member $member
|
||||
* @return void
|
||||
*/
|
||||
public function set_member( \IPS\Member $member )
|
||||
public function set_member( Member $member ) : void
|
||||
{
|
||||
$this->_data['member'] = $member->member_id;
|
||||
}
|
||||
@@ -89,7 +101,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_member_data()
|
||||
public function get_member_data(): array
|
||||
{
|
||||
return json_decode( $this->_data['member_data'], TRUE );
|
||||
}
|
||||
@@ -100,7 +112,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
* @param mixed $data Member data
|
||||
* @return void
|
||||
*/
|
||||
public function set_member_data( $data )
|
||||
public function set_member_data( mixed $data ) : void
|
||||
{
|
||||
$this->_data['member_data'] = $data ? json_encode( $data ) : NULL;
|
||||
}
|
||||
@@ -110,7 +122,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @return object|NULL
|
||||
*/
|
||||
public function get_item()
|
||||
public function get_item(): ?object
|
||||
{
|
||||
if ( $this->_data['item_class'] and $this->_data['item_id'] )
|
||||
{
|
||||
@@ -122,7 +134,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
return $class::load( $this->_data['item_id'] );
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
catch ( OutOfRangeException $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -133,13 +145,13 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Set item
|
||||
*
|
||||
* @param object $item The item
|
||||
* @param object $item The item
|
||||
* @return void
|
||||
*/
|
||||
public function set_item( $item )
|
||||
public function set_item( object $item ) : void
|
||||
{
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
$this->_data['item_class'] = \get_class( $item );
|
||||
$this->_data['item_class'] = get_class( $item );
|
||||
$this->_data['item_id'] = $item->$idColumn;
|
||||
}
|
||||
|
||||
@@ -148,7 +160,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @return object|NULL
|
||||
*/
|
||||
public function get_item_sub()
|
||||
public function get_item_sub(): ?object
|
||||
{
|
||||
if ( $this->_data['item_sub_class'] and $this->_data['item_sub_id'] )
|
||||
{
|
||||
@@ -160,7 +172,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
return $class::load( $this->_data['item_sub_id'] );
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
catch ( OutOfRangeException $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -171,20 +183,20 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Get application
|
||||
*
|
||||
* @return \IPS\Application
|
||||
* @return Application
|
||||
*/
|
||||
public function get_notification_app()
|
||||
public function get_notification_app(): Application
|
||||
{
|
||||
return \IPS\Application::load( $this->_data['notification_app'] );
|
||||
return Application::load( $this->_data['notification_app'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set application
|
||||
*
|
||||
* @param mixed $data Member data
|
||||
* @param Application $app
|
||||
* @return void
|
||||
*/
|
||||
public function set_notification_app( \IPS\Application $app )
|
||||
public function set_notification_app( Application $app ) : void
|
||||
{
|
||||
$this->_data['notification_app'] = $app->directory;
|
||||
}
|
||||
@@ -194,7 +206,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_extra()
|
||||
public function get_extra(): array
|
||||
{
|
||||
return $this->_data['extra'] ? json_decode( $this->_data['extra'], TRUE ) : array();
|
||||
}
|
||||
@@ -205,7 +217,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
* @param mixed $data Member data
|
||||
* @return void
|
||||
*/
|
||||
public function set_extra( $data )
|
||||
public function set_extra( mixed $data ) : void
|
||||
{
|
||||
$this->_data['extra'] = $data ? json_encode( $data ) : NULL;
|
||||
}
|
||||
@@ -213,9 +225,9 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
public function save(): void
|
||||
{
|
||||
parent::save();
|
||||
$this->member->recountNotifications();
|
||||
@@ -224,11 +236,11 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Get data from extension
|
||||
*
|
||||
* @param bool $htmlEscape TRUE to escape HTML
|
||||
* @param bool $htmlEscape TRUE to escape HTML
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getData( $htmlEscape = TRUE )
|
||||
public function getData( bool $htmlEscape = TRUE ): array
|
||||
{
|
||||
$method = "parse_{$this->notification_key}";
|
||||
|
||||
@@ -246,13 +258,13 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException;
|
||||
throw new RuntimeException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output for API
|
||||
*
|
||||
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @return array
|
||||
* @apiresponse string notificationType The type (key) of notification sent
|
||||
* @apiresponse string notificationApp The app that processes this type of notification
|
||||
@@ -267,13 +279,13 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
* @apiresponse datetime|null readDate Date and time the notification was read by the user
|
||||
* @apiresponse array notificationData Array of additional data relevant to this specific notification
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
public function apiOutput( Member $authorizedMember = NULL ): array
|
||||
{
|
||||
try
|
||||
{
|
||||
$ourData = $this->getData();
|
||||
}
|
||||
catch( \LogicException | \RuntimeException $e )
|
||||
catch( LogicException | RuntimeException $e )
|
||||
{
|
||||
$ourData = array();
|
||||
}
|
||||
@@ -289,17 +301,17 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
'itemSub' => ( $this->item_sub AND method_exists( $this->item_sub, 'apiOutput' ) ) ? $this->item_sub->apiOutput( $authorizedMember ) : NULL,
|
||||
'sentDate' => $this->sent_time->rfc3339(),
|
||||
'updatedDate' => $this->updated_time->rfc3339(),
|
||||
'readDate' => $this->read_time ? \IPS\DateTime::ts( $this->read_time )->rfc3339() : NULL,
|
||||
'readDate' => $this->read_time ? DateTime::ts( $this->read_time )->rfc3339() : NULL,
|
||||
'notificationData' => array_map( function( $val ) {
|
||||
if( $val instanceof \IPS\Http\Url )
|
||||
if( $val instanceof Url )
|
||||
{
|
||||
return (string) $val;
|
||||
}
|
||||
elseif( $val instanceof \IPS\Member )
|
||||
elseif( $val instanceof Member )
|
||||
{
|
||||
return $val->apiOutput();
|
||||
}
|
||||
elseif( $val instanceof \IPS\DateTime )
|
||||
elseif( $val instanceof DateTime )
|
||||
{
|
||||
return $val->rfc3339();
|
||||
}
|
||||
|
||||
@@ -11,26 +11,54 @@
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use DateInterval;
|
||||
use IPS\Content\Comment;
|
||||
use IPS\Content\Item;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Custom;
|
||||
use IPS\Http\Request\Exception;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Http\Url\Internal;
|
||||
use IPS\Member\Device;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Notification\Inline;
|
||||
use IPS\Notification\Recipients;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Platform\Bridge;
|
||||
use Minishlink\WebPush\Encryption;
|
||||
use Minishlink\WebPush\Utils;
|
||||
use Minishlink\WebPush\VAPID;
|
||||
use OutOfRangeException;
|
||||
use UnderflowException;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function function_exists;
|
||||
use function get_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Class
|
||||
*/
|
||||
class _Notification
|
||||
class Notification
|
||||
{
|
||||
/**
|
||||
* @brief Default Configuration
|
||||
*/
|
||||
protected static $defaultConfiguration = NULL;
|
||||
protected static ?array $defaultConfiguration = NULL;
|
||||
|
||||
/**
|
||||
* @brief Cache of vapid headers to save processing time
|
||||
*/
|
||||
protected static $vapidHeaderCache = array();
|
||||
protected static array $vapidHeaderCache = array();
|
||||
|
||||
/**
|
||||
* @brief Pre-defined web push TTL values
|
||||
@@ -51,7 +79,7 @@ class _Notification
|
||||
/**
|
||||
* @brief Allow notifications to be programatically silenced
|
||||
*/
|
||||
protected static $silenced = FALSE;
|
||||
protected static bool $silenced = FALSE;
|
||||
|
||||
/**
|
||||
* Silence notifications programatically
|
||||
@@ -59,7 +87,7 @@ class _Notification
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function silence()
|
||||
public static function silence() : void
|
||||
{
|
||||
static::$silenced = TRUE;
|
||||
}
|
||||
@@ -69,7 +97,7 @@ class _Notification
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unsilence()
|
||||
public static function unsilence() : void
|
||||
{
|
||||
static::$silenced = FALSE;
|
||||
}
|
||||
@@ -81,12 +109,13 @@ class _Notification
|
||||
*/
|
||||
public static function canUseWebPush(): bool
|
||||
{
|
||||
if ( \IPS\IN_DEV )
|
||||
if ( Application::appIsEnabled('cloud') or IN_DEV )
|
||||
{
|
||||
/* Cloud is clearly the superior premium platform, so it already has GMP */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return \function_exists('gmp_init');
|
||||
return function_exists('gmp_init');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,23 +125,23 @@ class _Notification
|
||||
*/
|
||||
public static function webPushEnabled(): bool
|
||||
{
|
||||
return static::canUseWebPush() and ( ( isset( \IPS\Settings::i()->vapid_public_key ) && !empty( \IPS\Settings::i()->vapid_public_key ) &&
|
||||
isset( \IPS\Settings::i()->vapid_private_key ) && !empty( \IPS\Settings::i()->vapid_private_key ) ) );
|
||||
return static::canUseWebPush() and ( ( isset( Settings::i()->vapid_public_key ) && !empty( Settings::i()->vapid_public_key ) &&
|
||||
isset( Settings::i()->vapid_private_key ) && !empty( Settings::i()->vapid_private_key ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration
|
||||
*
|
||||
* @return array
|
||||
* @return array|null
|
||||
*/
|
||||
public static function defaultConfiguration()
|
||||
public static function defaultConfiguration(): ?array
|
||||
{
|
||||
if ( static::$defaultConfiguration === NULL )
|
||||
{
|
||||
/* Get data from the actual extensions */
|
||||
$extensionDefaults = array();
|
||||
$notificationGroups = [];
|
||||
foreach( \IPS\Application::allExtensions( 'core', 'Notifications' ) as $group => $class )
|
||||
foreach(Application::allExtensions( 'core', 'Notifications' ) as $group => $class )
|
||||
{
|
||||
if ( method_exists( $class, 'configurationOptions' ) )
|
||||
{
|
||||
@@ -150,7 +179,7 @@ class _Notification
|
||||
}
|
||||
|
||||
/* Combine that with what the admin has set */
|
||||
static::$defaultConfiguration = iterator_to_array( \IPS\Db::i()->select( '*', 'core_notification_defaults' )->setKeyField('notification_key') );
|
||||
static::$defaultConfiguration = iterator_to_array( Db::i()->select( '*', 'core_notification_defaults' )->setKeyField('notification_key') );
|
||||
foreach ( $extensionDefaults as $key => $data )
|
||||
{
|
||||
if ( !isset( static::$defaultConfiguration[ $key ] ) )
|
||||
@@ -158,7 +187,7 @@ class _Notification
|
||||
/* If parent group already has defaults stored, use those */
|
||||
if( isset( $notificationGroups[ $key ] ) AND isset( static::$defaultConfiguration[ $notificationGroups[ $key ] ] ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_notification_defaults', array(
|
||||
Db::i()->insert( 'core_notification_defaults', array(
|
||||
'notification_key' => $key,
|
||||
'default' => implode( ',', static::$defaultConfiguration[ $notificationGroups[ $key ] ]['default'] ),
|
||||
'disabled' => implode( ',', static::$defaultConfiguration[ $notificationGroups[ $key ] ]['disabled'] )
|
||||
@@ -169,7 +198,7 @@ class _Notification
|
||||
else
|
||||
{
|
||||
/* Row isn't in DB, add it */
|
||||
\IPS\Db::i()->insert( 'core_notification_defaults', array(
|
||||
Db::i()->insert( 'core_notification_defaults', array(
|
||||
'notification_key' => $key,
|
||||
'default' => implode( ',', $data['default'] ),
|
||||
'disabled' => implode( ',', $data['disabled'] )
|
||||
@@ -181,7 +210,7 @@ class _Notification
|
||||
else
|
||||
{
|
||||
static::$defaultConfiguration[ $key ]['default'] = array_filter( explode( ',', static::$defaultConfiguration[ $key ]['default'] ) );
|
||||
static::$defaultConfiguration[ $key ]['disabled'] = array_filter( array_merge( $extensionDefaults[ $key ]['disabled'], explode( ',', static::$defaultConfiguration[ $key ]['disabled'] ) ) );
|
||||
static::$defaultConfiguration[ $key ]['disabled'] = array_filter( array_merge( $data['disabled'], explode( ',', static::$defaultConfiguration[ $key ]['disabled'] ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,38 +221,18 @@ class _Notification
|
||||
/**
|
||||
* Get available options for an extension
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member or NULL to get all
|
||||
* @param object $extension The noticiations extension
|
||||
* @return void
|
||||
* @param Member|null $member The member or NULL to get all
|
||||
* @param object $extension The notifications extension
|
||||
* @return array
|
||||
*/
|
||||
public static function availableOptions( $member, $extension )
|
||||
public static function availableOptions( ?Member $member, object $extension ) : array
|
||||
{
|
||||
/* First of all, get the settings from the extension, maintaining backwards compatibility with older extensions */
|
||||
if ( method_exists( $extension, 'configurationOptions' ) )
|
||||
{
|
||||
$settings = $extension::configurationOptions( $member );
|
||||
}
|
||||
else
|
||||
{
|
||||
$configuration = $extension->getConfiguration( $member );
|
||||
|
||||
$settings = array();
|
||||
foreach ( $configuration as $key => $details )
|
||||
{
|
||||
$settings[ $key ] = array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( $key ),
|
||||
'title' => "notifications__{$key}",
|
||||
'showTitle' => TRUE,
|
||||
'description' => NULL,
|
||||
);
|
||||
}
|
||||
}
|
||||
$settings = $extension::configurationOptions( $member );
|
||||
|
||||
/* Now loop through each of those and get the value for each */
|
||||
if ( $member )
|
||||
{
|
||||
$defaultConfiguration = \IPS\Notification::defaultConfiguration();
|
||||
$defaultConfiguration = Notification::defaultConfiguration();
|
||||
$memberConfiguration = $member->notificationsConfiguration();
|
||||
$finalSettings = array();
|
||||
foreach ( $settings as $key => $details )
|
||||
@@ -232,14 +241,14 @@ class _Notification
|
||||
{
|
||||
$options = array();
|
||||
$availableTypes = array('inline');
|
||||
if ( static::webPushEnabled() && \count( $member->getPwaAuths() ) )
|
||||
if ( static::webPushEnabled() && count( $member->getPwaAuths() ) )
|
||||
{
|
||||
$availableTypes[] = 'push';
|
||||
}
|
||||
$availableTypes[] = 'email';
|
||||
foreach ( $availableTypes as $type )
|
||||
{
|
||||
if ( !\in_array( $type, $defaultConfiguration[ $key ]['disabled'] ) )//if ( !\in_array( $type, $defaultConfiguration[ $key ]['disabled'] ) and ( $type !== 'push' or !\in_array( 'inline', $defaultConfiguration[ $key ]['disabled'] ) ) )
|
||||
if ( !in_array( $type, $defaultConfiguration[ $key ]['disabled'] ) )//if ( !\in_array( $type, $defaultConfiguration[ $key ]['disabled'] ) and ( $type !== 'push' or !\in_array( 'inline', $defaultConfiguration[ $key ]['disabled'] ) ) )
|
||||
{
|
||||
$enabled = array();
|
||||
$haveMemberPreferences = FALSE;
|
||||
@@ -261,13 +270,13 @@ class _Notification
|
||||
|
||||
$options[ $type ] = array(
|
||||
'title' => "member_notifications_{$type}",
|
||||
'value' => \in_array( $type, $enabled ),
|
||||
'value' => in_array( $type, $enabled ),
|
||||
'editable' => $defaultConfiguration[ $key ]['editable']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $options ) or ( isset( $details['extra'] ) and \count( $details['extra'] ) ) )
|
||||
if ( count( $options ) or ( isset( $details['extra'] ) and count( $details['extra'] ) ) )
|
||||
{
|
||||
$details['options'] = $options;
|
||||
$finalSettings[ $key ] = $details;
|
||||
@@ -291,11 +300,11 @@ class _Notification
|
||||
/**
|
||||
* Get the notification categories to show to a member
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param array $extensions The notification extensions (passed so we don't have to load it more than once)
|
||||
* @param Member $member The member
|
||||
* @param array $extensions The notification extensions (passed so we don't have to load it more than once)
|
||||
* @return array
|
||||
*/
|
||||
public static function membersOptionCategories( \IPS\Member $member, $extensions )
|
||||
public static function membersOptionCategories( Member $member, array $extensions ): array
|
||||
{
|
||||
$categories = array();
|
||||
foreach( $extensions as $group => $extension )
|
||||
@@ -316,7 +325,7 @@ class _Notification
|
||||
{
|
||||
if ( $extraDetails['value'] )
|
||||
{
|
||||
$categories[ $group ][ $extraKey ] = array( 'title' => \IPS\Member::loggedIn()->language()->get( $extraDetails['title'] ), 'icon' => $extraDetails['icon'], 'description' => isset( $extraDetails['description'] ) ? $extraDetails['description'] : NULL );
|
||||
$categories[ $group ][ $extraKey ] = array( 'title' => Member::loggedIn()->language()->get( $extraDetails['title'] ), 'icon' => $extraDetails['icon'], 'description' => $extraDetails['description'] ?? NULL );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,17 +342,17 @@ class _Notification
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
$icon = 'bell-o';
|
||||
$icon = 'bell';
|
||||
break;
|
||||
case 'push':
|
||||
$icon = 'mobile';
|
||||
break;
|
||||
case 'email':
|
||||
$icon = 'envelope-o';
|
||||
$icon = 'envelope';
|
||||
break;
|
||||
}
|
||||
|
||||
$categories[ $group ][ $type ] = array( 'title' => \IPS\Member::loggedIn()->language()->get( 'member_notifications_' . $type ), 'icon' => $icon );
|
||||
$categories[ $group ][ $type ] = array( 'title' => Member::loggedIn()->language()->get( 'member_notifications_' . $type ), 'icon' => $icon );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,21 +366,21 @@ class _Notification
|
||||
/**
|
||||
* Get the form for editing a member's notification preferences for a given notification extension
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $extension The notification extension to edit preferences for
|
||||
* @return \IPS\Helpers\Form|NULL|TRUE
|
||||
* @param Member $member The member
|
||||
* @param object $extension The notification extension to edit preferences for
|
||||
* @return Form|NULL|TRUE
|
||||
*/
|
||||
public static function membersTypeForm( \IPS\Member $member, $extension )
|
||||
public static function membersTypeForm( Member $member, object $extension ): Form|bool|null
|
||||
{
|
||||
$form = NULL;
|
||||
$formIsEditable = FALSE;
|
||||
|
||||
if ( $options = \IPS\Notification::availableOptions( $member, $extension ) )
|
||||
if ( $options = Notification::availableOptions( $member, $extension ) )
|
||||
{
|
||||
$form = new \IPS\Helpers\Form;
|
||||
$form = new Form;
|
||||
foreach ( $options as $key => $details )
|
||||
{
|
||||
if ( $details['type'] === 'separator' and \IPS\Dispatcher::i()->controllerLocation === 'front' )
|
||||
if ( $details['type'] === 'separator' and Dispatcher::i()->controllerLocation === 'front' )
|
||||
{
|
||||
$form->addSeparator();
|
||||
}
|
||||
@@ -388,7 +397,7 @@ class _Notification
|
||||
{
|
||||
if ( !$formIsEditable )
|
||||
{
|
||||
if ( isset( $details['extra'] ) and \count( $details['extra'] ) )
|
||||
if ( isset( $details['extra'] ) and count( $details['extra'] ) )
|
||||
{
|
||||
$formIsEditable = TRUE;
|
||||
}
|
||||
@@ -405,9 +414,9 @@ class _Notification
|
||||
}
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Custom( "notifications_{$key}", NULL, TRUE, array(
|
||||
$form->add( new Custom( "notifications_{$key}", NULL, TRUE, array(
|
||||
'rowHtml' => function( $field ) use ( $details, $options ) {
|
||||
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'global' )->notificationsSettingsRow( $field, $details );
|
||||
return Theme::i()->getTemplate( 'members', 'core', 'global' )->notificationsSettingsRow( $field, $details );
|
||||
}
|
||||
) ) );
|
||||
}
|
||||
@@ -429,7 +438,7 @@ class _Notification
|
||||
$value['inline'] = 'inline';
|
||||
$value['push'] = 'push';
|
||||
}
|
||||
elseif ( \in_array( $k, array( 'inline', 'push', 'email' ) ) and $v )
|
||||
elseif ( in_array( $k, array( 'inline', 'push', 'email' ) ) and $v )
|
||||
{
|
||||
$value[ $k ] = $k;
|
||||
}
|
||||
@@ -438,7 +447,7 @@ class _Notification
|
||||
|
||||
foreach ( $details['notificationTypes'] as $notificationKey )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_notification_preferences', array(
|
||||
Db::i()->insert( 'core_notification_preferences', array(
|
||||
'member_id' => $member->member_id,
|
||||
'notification_key' => $notificationKey,
|
||||
'preference' => implode( ',', $value )
|
||||
@@ -477,23 +486,23 @@ class _Notification
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
protected $app;
|
||||
protected Application $app;
|
||||
|
||||
/**
|
||||
* @brief Notification key
|
||||
*/
|
||||
protected $key;
|
||||
protected ?string $key = NULL;
|
||||
|
||||
/**
|
||||
* @brief Email template key
|
||||
* @note Typically this is "notification__{key}"
|
||||
*/
|
||||
protected $emailKey;
|
||||
protected ?string $emailKey = NULL;
|
||||
|
||||
/**
|
||||
* @brief Item
|
||||
*/
|
||||
protected $item;
|
||||
protected ?object $item;
|
||||
|
||||
/**
|
||||
* @brief An \IPS\Notification\Recipients object which contains \IPS\Member objects and replacements to use for that member in the notification content.
|
||||
@@ -502,48 +511,48 @@ class _Notification
|
||||
$notification->recipients->attach( $member2, array( 'foo' => 'baz' ) );
|
||||
* @endcode
|
||||
*/
|
||||
public $recipients;
|
||||
public Recipients $recipients;
|
||||
|
||||
/**
|
||||
* @brief Data for notification emails
|
||||
*/
|
||||
protected $emailParams = array();
|
||||
protected array $emailParams = array();
|
||||
|
||||
/**
|
||||
* @brief Extra data to save with inline notifications
|
||||
*/
|
||||
protected $inlineExtra = array();
|
||||
protected array $inlineExtra = array();
|
||||
|
||||
/**
|
||||
* @brief Unsubscribe Type
|
||||
*/
|
||||
public $unsubscribeType = 'notification';
|
||||
public string $unsubscribeType = 'notification';
|
||||
|
||||
/**
|
||||
* @brief Allow merging of notifications
|
||||
*/
|
||||
protected $allowMerging = TRUE;
|
||||
protected bool $allowMerging = TRUE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Application $app The application the notification belongs to
|
||||
* @param string $key Notification key
|
||||
* @param object|NULL $item The thing the notification is about
|
||||
* @param array $emailParams Data for notification emails
|
||||
* @param array $inlineExtra Extra data to save with inline notifications. Use sparingly: only in cases where it is not possible to obtain the same data later. Will be merged for duplicate notifications.
|
||||
* @param bool $allowMerging Allow two identical notification types to be merged
|
||||
* @param string|NULL $emailKey Custom email template to use, or NULL to use default
|
||||
* @param Application $app The application the notification belongs to
|
||||
* @param string $key Notification key
|
||||
* @param object|null $item The thing the notification is about
|
||||
* @param array $emailParams Data for notification emails
|
||||
* @param array|null $inlineExtra Extra data to save with inline notifications. Use sparingly: only in cases where it is not possible to obtain the same data later. Will be merged for duplicate notifications.
|
||||
* @param bool $allowMerging Allow two identical notification types to be merged
|
||||
* @param string|null $emailKey Custom email template to use, or NULL to use default
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Application $app, $key, $item=NULL, $emailParams=array(), $inlineExtra=array(), $allowMerging=TRUE, $emailKey=NULL )
|
||||
public function __construct( Application $app, string $key, object $item=NULL, array $emailParams=array(), ?array $inlineExtra=array(), bool $allowMerging=TRUE, string $emailKey=NULL )
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->key = $key;
|
||||
$this->item = $item;
|
||||
$this->recipients = new \IPS\Notification\Recipients;
|
||||
$this->recipients = new Recipients;
|
||||
$this->emailParams = $emailParams;
|
||||
$this->inlineExtra = $inlineExtra;
|
||||
$this->inlineExtra = $inlineExtra ?: array();
|
||||
$this->allowMerging = $allowMerging;
|
||||
$this->emailKey = ( $emailKey === NULL ) ? 'notification_' . $this->key : $emailKey;
|
||||
}
|
||||
@@ -551,10 +560,10 @@ class _Notification
|
||||
/**
|
||||
* Send Notification
|
||||
*
|
||||
* @param array $sentTo Members who have already received a notification and how (same format as the return value) to prevent duplicates
|
||||
* @param array $sentTo Members who have already received a notification and how (same format as the return value) to prevent duplicates
|
||||
* @return array The members that were notified and how they were notified
|
||||
*/
|
||||
public function send( $sentTo = array() )
|
||||
public function send( array $sentTo = array() ) : array
|
||||
{
|
||||
if ( static::$silenced === TRUE )
|
||||
{
|
||||
@@ -579,7 +588,7 @@ class _Notification
|
||||
|
||||
$membersForNotifications[ $member->member_id ] = $member;
|
||||
}
|
||||
if( \count( $membersForNotifications ) )
|
||||
if( count( $membersForNotifications ) )
|
||||
{
|
||||
/* Fill in any that may not have customized their preferences */
|
||||
foreach( $membersForNotifications as $member )
|
||||
@@ -594,7 +603,7 @@ class _Notification
|
||||
$preferenceSet = array();
|
||||
|
||||
foreach (
|
||||
\IPS\Db::i()->select(
|
||||
Db::i()->select(
|
||||
'd.*, p.preference, p.member_id',
|
||||
array( 'core_notification_defaults', 'd' )
|
||||
)->join(
|
||||
@@ -603,7 +612,7 @@ class _Notification
|
||||
)
|
||||
as $row
|
||||
) {
|
||||
if( !\in_array( $row['notification_key'], $preferenceSet ) )
|
||||
if( !in_array( $row['notification_key'], $preferenceSet ) )
|
||||
{
|
||||
foreach( $membersForNotifications as $member )
|
||||
{
|
||||
@@ -634,9 +643,9 @@ class _Notification
|
||||
{
|
||||
/* Permission check */
|
||||
$item = $this->item;
|
||||
if ( $item instanceof \IPS\Content\Item )
|
||||
if ( $item instanceof Item )
|
||||
{
|
||||
$application = \IPS\Application::load( $item::$application );
|
||||
$application = Application::load( $item::$application );
|
||||
if ( !$application->canAccess( $member ) )
|
||||
{
|
||||
continue;
|
||||
@@ -653,7 +662,7 @@ class _Notification
|
||||
/* Not ignoring the comment this is about */
|
||||
foreach( $this->emailParams AS $param )
|
||||
{
|
||||
if ( $param instanceof \IPS\Content\Comment OR $param instanceof \IPS\Content\Review )
|
||||
if ( $param instanceof Comment )
|
||||
{
|
||||
if ( $member->isIgnoring( $param->author(), 'topics' ) )
|
||||
{
|
||||
@@ -661,7 +670,7 @@ class _Notification
|
||||
}
|
||||
}
|
||||
|
||||
if ( $param instanceof \IPS\Member )
|
||||
if ( $param instanceof Member)
|
||||
{
|
||||
if ( $member->isIgnoring( $param, 'topics' ) )
|
||||
{
|
||||
@@ -692,13 +701,13 @@ class _Notification
|
||||
}
|
||||
|
||||
/* They want to receive an email (we don't send until the end once we've collated all the emails to send) */
|
||||
if ( isset( $notificationPreferences[ $keyToCheck ] ) AND \in_array( 'email', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !\in_array( 'email', $sentTo[ $member->member_id ] ) ) )
|
||||
if ( isset( $notificationPreferences[ $keyToCheck ] ) AND in_array( 'email', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !in_array( 'email', $sentTo[ $member->member_id ] ) ) )
|
||||
{
|
||||
$language = $member->language()->id;
|
||||
|
||||
if ( !isset( $emails[ $language ] ) )
|
||||
{
|
||||
$email = \IPS\Email::buildFromTemplate( $this->app->directory, $this->emailKey, $this->emailParams, \IPS\Email::TYPE_LIST );
|
||||
$email = Email::buildFromTemplate( $this->app->directory, $this->emailKey, $this->emailParams, Email::TYPE_LIST );
|
||||
|
||||
if ( $info )
|
||||
{
|
||||
@@ -714,7 +723,6 @@ class _Notification
|
||||
|
||||
$unsubscribeBlurb = NULL;
|
||||
$unfollowLink = NULL;
|
||||
$listUnsubscribeLink = null;
|
||||
$okToEmail = TRUE;
|
||||
|
||||
if ( $info )
|
||||
@@ -723,11 +731,12 @@ class _Notification
|
||||
{
|
||||
if ( $info['follow_app'] === 'core' and $info['follow_area'] === 'member' )
|
||||
{
|
||||
$thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ] = \IPS\Member::load( $info['follow_rel_id'] );
|
||||
$thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ] = Member::load( $info['follow_rel_id'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$classname = 'IPS\\' . $info['follow_app'] . '\\' . mb_ucfirst( $info['follow_area'] );
|
||||
/* @var ActiveRecord $classname */
|
||||
$classname = 'IPS\\' . $info['follow_app'] . '\\' . IPS::mb_ucfirst( $info['follow_area'] );
|
||||
$thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ] = $classname::load( $info['follow_rel_id'] );
|
||||
|
||||
/* Set some parameters so the best advertisement possible can be loaded later */
|
||||
@@ -736,11 +745,11 @@ class _Notification
|
||||
}
|
||||
|
||||
$thingBeingFollowed = $thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ];
|
||||
if ( $thingBeingFollowed instanceof \IPS\Member )
|
||||
if ( $thingBeingFollowed instanceof Member)
|
||||
{
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow_member', FALSE, array( 'htmlsprintf' => array( $thingBeingFollowed->name ) ) );
|
||||
}
|
||||
elseif ( $thingBeingFollowed instanceof \IPS\Node\Model )
|
||||
elseif ( $thingBeingFollowed instanceof Model )
|
||||
{
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow', FALSE, array( 'htmlsprintf' => array( $member->language()->addToStack( $thingBeingFollowed::$nodeTitle . '_sg' ), $thingBeingFollowed->getTitleForLanguage( $member->language() ) ) ) );
|
||||
}
|
||||
@@ -750,11 +759,10 @@ class _Notification
|
||||
}
|
||||
|
||||
$guestKey = md5( $info['follow_app'] . ';' . $info['follow_area'] . ';' . $info['follow_rel_id'] . ';' . $info['follow_member_id'] . ';' . $info['follow_added'] ) . '-' . md5( $member->email . ';' . $member->ip_address . ';' . $member->joined->getTimestamp() );
|
||||
$unfollowLink = \IPS\Http\Url::internal( "app=core&module=system&controller=notifications&do=unfollowFromEmail&follow_app={$info['follow_app']}&follow_area={$info['follow_area']}&follow_id={$info['follow_rel_id']}&gkey={$guestKey}", 'front' );
|
||||
$listUnsubscribeLink = $unfollowLink->setQueryString( 'listunsubscribe', 1 );
|
||||
$unfollowLink = Url::internal( "app=core&module=system&controller=notifications&do=unfollowFromEmail&follow_app={$info['follow_app']}&follow_area={$info['follow_area']}&follow_id={$info['follow_rel_id']}&gkey={$guestKey}", 'front' );
|
||||
|
||||
/* If we are tracking email views/clicks, add the tracking info to this URL as the email handler won't be able to */
|
||||
if( \IPS\Settings::i()->prune_log_emailstats != 0 )
|
||||
if( Settings::i()->prune_log_emailstats != 0 )
|
||||
{
|
||||
$unfollowLink = $unfollowLink->setQueryString( array( 'email' => 1, 'type' => $this->emailKey ) );
|
||||
}
|
||||
@@ -772,8 +780,7 @@ class _Notification
|
||||
$emailRecipients[ $language ][ $member->email ] = array(
|
||||
'member_name' => $member->name,
|
||||
'unsubscribe_blurb' => $unsubscribeBlurb,
|
||||
'unfollow_link' => $unfollowLink,
|
||||
'list_unsubscribe_link' => $listUnsubscribeLink
|
||||
'unfollow_link' => $unfollowLink
|
||||
);
|
||||
}
|
||||
|
||||
@@ -783,77 +790,85 @@ class _Notification
|
||||
/* They want to receive an inline notification... (ignore for report center which is treated special and the 'inline' notification
|
||||
preference actually instead controls whether the bubble should be shown on the report center icon at the top or not) */
|
||||
$notification = NULL;
|
||||
if ( $this->key != 'report_center' and isset( $notificationPreferences[ $keyToCheck ] ) and \in_array( 'inline', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !\in_array( 'inline', $sentTo[ $member->member_id ] ) ) )
|
||||
if ( $this->key != 'report_center' and isset( $notificationPreferences[ $keyToCheck ] ) and in_array( 'inline', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !in_array( 'inline', $sentTo[ $member->member_id ] ) ) )
|
||||
{
|
||||
if ( $this->item AND $this->allowMerging )
|
||||
$hasMerged = false;
|
||||
if ( $this->item and $this->allowMerging )
|
||||
{
|
||||
try
|
||||
{
|
||||
$item = $this->item;
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
$notification = \IPS\Notification\Inline::constructFromData( \IPS\Db::i()->select( '*', 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=? AND `member`=? AND read_time IS NULL', $this->key, \get_class( $this->item ), $item->$idColumn, $member->member_id ) )->first() );
|
||||
|
||||
$notification = Inline::constructFromData( Db::i()->select( '*', 'core_notifications', ['notification_key=? AND item_class=? AND item_id=? AND `member`=? AND read_time IS NULL', $this->key, get_class( $this->item ), $item->$idColumn, $member->member_id] )->first() );
|
||||
|
||||
$notification->member = $member;
|
||||
$notification->updated_time = time();
|
||||
$notification->extra = array_merge( $notification->extra, $this->inlineExtra );
|
||||
$notification->save();
|
||||
|
||||
continue;
|
||||
|
||||
$hasMerged = true;
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
}
|
||||
|
||||
$notification = new \IPS\Notification\Inline;
|
||||
$notification->member = $member;
|
||||
$notification->notification_app = $this->app;
|
||||
$notification->notification_key = $this->key;
|
||||
if ( $this->item )
|
||||
{
|
||||
$notification->item = $this->item;
|
||||
}
|
||||
$notification->member_data = $info;
|
||||
|
||||
foreach( $this->emailParams AS $param )
|
||||
{
|
||||
if ( $param instanceof \IPS\Content )
|
||||
catch ( UnderflowException )
|
||||
{
|
||||
$subIdColumn = $param::$databaseColumnId;
|
||||
$notification->item_sub_class = \get_class( $param );
|
||||
$notification->item_sub_id = $param->$subIdColumn;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a grouped comment or review, set the sent time to the same time as the comment just in case there is a slight delay
|
||||
*/
|
||||
if ( ( $param instanceof \IPS\Content\Comment OR $param instanceof \IPS\Content\Review ) && \in_array( $this->key, array( 'new_comment', 'new_review', 'quote', 'new_likes' ) ) )
|
||||
if ( ! $hasMerged )
|
||||
{
|
||||
$notification = new Inline;
|
||||
$notification->member = $member;
|
||||
$notification->notification_app = $this->app;
|
||||
$notification->notification_key = $this->key;
|
||||
if ( $this->item )
|
||||
{
|
||||
$notification->item = $this->item;
|
||||
}
|
||||
$notification->member_data = $info;
|
||||
|
||||
foreach ( $this->emailParams as $param )
|
||||
{
|
||||
if ( $param instanceof Content )
|
||||
{
|
||||
if ( $this->key === 'new_likes' and $this->emailParams[1] instanceof \IPS\Member )
|
||||
$subIdColumn = $param::$databaseColumnId;
|
||||
$notification->item_sub_class = get_class( $param );
|
||||
$notification->item_sub_id = $param->$subIdColumn;
|
||||
|
||||
/*
|
||||
* If this is a grouped comment or review, set the sent time to the same time as the comment just in case there is a slight delay
|
||||
*/
|
||||
if ( ( $param instanceof Comment ) && in_array( $this->key, ['new_comment', 'new_review', 'quote', 'new_likes'] ) )
|
||||
{
|
||||
/* Reset the time to the time of the rep to prevent a slight delay from missing this notification */
|
||||
try
|
||||
if ( $this->key === 'new_likes' and $this->emailParams[1] instanceof Member )
|
||||
{
|
||||
$where = $param->getReactionWhereClause();
|
||||
$where[] = array( 'member_id = ?', $this->emailParams[1]->member_id );
|
||||
|
||||
$notification->sent_time = \IPS\Db::i()->select( 'rep_date', 'core_reputation_index', $where )->join( 'core_reactions', 'reaction=reaction_id' )->first();
|
||||
/* Reset the time to the time of the rep to prevent a slight delay from missing this notification */
|
||||
try
|
||||
{
|
||||
$where = $param->getReactionWhereClause();
|
||||
$where[] = ['member_id = ?', $this->emailParams[1]->member_id];
|
||||
|
||||
$notification->sent_time = Db::i()->select( 'rep_date', 'core_reputation_index', $where )->join( 'core_reactions', 'reaction=reaction_id' )->first();
|
||||
}
|
||||
catch ( \Exception $ex )
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$notification->sent_time = $param->mapped( 'date' );
|
||||
}
|
||||
catch( \Exception $ex ) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
$notification->sent_time = $param->mapped('date');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$notification->extra = $this->inlineExtra;
|
||||
$notification->save();
|
||||
}
|
||||
|
||||
$notification->extra = $this->inlineExtra;
|
||||
$notification->save();
|
||||
|
||||
$sentTo[ $member->member_id ][] = 'inline';
|
||||
$sentTo[$member->member_id][] = 'inline';
|
||||
}
|
||||
|
||||
|
||||
/* They want to receive a push notification (we don't send until the end once we've collated all the notifications to send) */
|
||||
if ( ( static::webPushEnabled() && \count( $member->getPwaAuths() ) ) and isset( $notificationPreferences[ $keyToCheck ] ) AND \in_array( 'push', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !\in_array( 'push', $sentTo[ $member->member_id ] ) ) )
|
||||
if ( ( static::webPushEnabled() && count( $member->getPwaAuths() ) ) and isset( $notificationPreferences[ $keyToCheck ] ) AND in_array( 'push', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !in_array( 'push', $sentTo[ $member->member_id ] ) ) )
|
||||
{
|
||||
$language = $member->language();
|
||||
if ( !isset( $pushNotifications[ $language->id ] ) )
|
||||
@@ -874,8 +889,8 @@ class _Notification
|
||||
|
||||
if ( isset( $data['data']['url'] ) )
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $data['data']['url'] );
|
||||
if ( $url instanceof \IPS\Http\Url\Internal )
|
||||
$url = Url::createFromString( $data['data']['url'] );
|
||||
if ( $url instanceof Internal )
|
||||
{
|
||||
foreach ( array( 'app', 'module', 'controller', 'id', 'comment', 'review' ) as $k )
|
||||
{
|
||||
@@ -907,19 +922,39 @@ class _Notification
|
||||
$pushRecipients[ $member->member_id ] = $pushNotifications[ $language->id ];
|
||||
$pushRecipients[ $member->member_id ]['member'] = $member->member_id;
|
||||
$pushRecipients[ $member->member_id ]['unreadCount'] = $member->notification_cnt;
|
||||
$pushRecipients[ $member->member_id ]['notificationId'] = $notification ? $notification->id : NULL;
|
||||
$pushRecipients[ $member->member_id ]['notificationId'] = $notification?->id;
|
||||
}
|
||||
|
||||
$sentTo[ $member->member_id ][] = 'push';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* On cloud, push this to the browser */
|
||||
if ( Bridge::i()->featureIsEnabled( 'realtime' ) )
|
||||
{
|
||||
$memberIds = [];
|
||||
foreach ( $sentTo as $memberId => $data )
|
||||
{
|
||||
if ( in_array( 'push', $data ) OR in_array( 'inline', $data ) )
|
||||
{
|
||||
$memberIds[] = $memberId;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $memberIds ) )
|
||||
{
|
||||
Bridge::i()->publishRealtimeEvent( 'notifications_available', location: array_map( 'intval', $memberIds ), locationAsMember: true );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Send any emails and push notifications */
|
||||
if ( \count( $pushNotifications ) )
|
||||
if ( count( $pushNotifications ) )
|
||||
{
|
||||
$this->sendPushNotifications( $pushRecipients );
|
||||
}
|
||||
if ( \count( $emails ) )
|
||||
if ( count( $emails ) )
|
||||
{
|
||||
$this->sendEmails( $emails, $emailRecipients );
|
||||
}
|
||||
@@ -931,20 +966,17 @@ class _Notification
|
||||
/**
|
||||
* Send emails
|
||||
*
|
||||
* @param array $emails Emails to send
|
||||
* @param array $emailRecipients Email recipients
|
||||
* @param array $emails Emails to send
|
||||
* @param array $emailRecipients Email recipients
|
||||
* @return void
|
||||
*/
|
||||
protected function sendEmails( $emails, $emailRecipients )
|
||||
protected function sendEmails( array $emails, array $emailRecipients ) : void
|
||||
{
|
||||
foreach ( $emails as $languageId => $email )
|
||||
{
|
||||
if ( !empty( $emailRecipients[ $languageId ] ) )
|
||||
{
|
||||
$email->mergeAndSend( $emailRecipients[ $languageId ], NULL, NULL, array(
|
||||
'List-Unsubscribe' => '<*|list_unsubscribe_link|*>',
|
||||
'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click'
|
||||
), \IPS\Lang::load( $languageId ) );
|
||||
$email->mergeAndSend( $emailRecipients[ $languageId ], NULL, NULL, array(), Lang::load( $languageId ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -952,10 +984,10 @@ class _Notification
|
||||
/**
|
||||
* Send push notifications
|
||||
*
|
||||
* @param array $pushNotifications Push notifications to send
|
||||
* @param array $pushNotifications Push notifications to send
|
||||
* @return void
|
||||
*/
|
||||
protected function sendPushNotifications( $pushNotifications )
|
||||
protected function sendPushNotifications( array $pushNotifications ) : void
|
||||
{
|
||||
/* Send PWA notifications */
|
||||
$pwaPushNotifications = [];
|
||||
@@ -964,9 +996,9 @@ class _Notification
|
||||
{
|
||||
$data = json_encode( array(
|
||||
'member' => $memberId,
|
||||
'title' => static::textForPushNotification( $data['title'] ?? \IPS\Settings::i()->board_name ),
|
||||
'title' => static::textForPushNotification( $data['title'] ?? Settings::i()->board_name ),
|
||||
'body' => static::textForPushNotification( $data['body'] ),
|
||||
'url' => (string) $data['data']['url'],
|
||||
'url' => isset( $data['data']['url'] ) ? (string) $data['data']['url'] : NULL,
|
||||
'icon' => $data['icon'] ?? NULL,
|
||||
'image' => $data['image'] ?? NULL,
|
||||
'tag' => $data['tag'] ?? NULL,
|
||||
@@ -975,7 +1007,7 @@ class _Notification
|
||||
'groupedUrl' => isset( $data['data']['groupedUrl'] ) ? (string) $data['data']['groupedUrl'] : NULL
|
||||
) );
|
||||
|
||||
$notificationId = \IPS\Db::i()->insert( 'core_notifications_pwa_queue', array( 'notification_data' => $data, 'expiration' => \IPS\DateTime::ts( time() )->add( new \DateInterval('P1D') )->getTimestamp() ) );
|
||||
$notificationId = Db::i()->insert( 'core_notifications_pwa_queue', array( 'notification_data' => $data, 'expiration' => DateTime::ts( time() )->add( new DateInterval('P1D') )->getTimestamp() ) );
|
||||
|
||||
$pwaPushNotifications[ $memberId ] = [
|
||||
'member' => $memberId,
|
||||
@@ -987,8 +1019,8 @@ class _Notification
|
||||
];
|
||||
}
|
||||
|
||||
$memberIds = \array_keys( $pwaPushNotifications );
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_notifications_pwa_keys', \IPS\Db::i()->in( '`member`', $memberIds ) )->first();
|
||||
$memberIds = array_keys( $pwaPushNotifications );
|
||||
$count = Db::i()->select( 'COUNT(*)', 'core_notifications_pwa_keys', Db::i()->in( '`member`', $memberIds ) )->first();
|
||||
|
||||
if ( $count )
|
||||
{
|
||||
@@ -998,7 +1030,7 @@ class _Notification
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Task::queue( 'core', 'PWANotifications', $pwaPushNotifications, 2 );
|
||||
Task::queue( 'core', 'PWANotifications', $pwaPushNotifications, 2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1010,64 +1042,65 @@ class _Notification
|
||||
*/
|
||||
public static function generateVapidKeys(): array
|
||||
{
|
||||
\IPS\IPS::$PSR0Namespaces['Jose'] = \IPS\ROOT_PATH . '/system/3rd_party/JwtFramework/src';
|
||||
\IPS\IPS::$PSR0Namespaces['Minishlink'] = \IPS\ROOT_PATH . '/system/3rd_party/Minishlink';
|
||||
\IPS\IPS::$PSR0Namespaces['Base64Url'] = \IPS\ROOT_PATH .'/system/3rd_party/Base64Url';
|
||||
\IPS\IPS::$PSR0Namespaces['Brick'] = \IPS\ROOT_PATH . '/system/3rd_party/Brick';
|
||||
IPS::$PSR0Namespaces['Jose'] = ROOT_PATH . '/system/3rd_party/JwtFramework/src';
|
||||
IPS::$PSR0Namespaces['Minishlink'] = ROOT_PATH . '/system/3rd_party/Minishlink';
|
||||
IPS::$PSR0Namespaces['Base64Url'] = ROOT_PATH .'/system/3rd_party/Base64Url';
|
||||
IPS::$PSR0Namespaces['Brick'] = ROOT_PATH . '/system/3rd_party/Brick';
|
||||
|
||||
return \Minishlink\WebPush\VAPID::createVapidKeys();
|
||||
return VAPID::createVapidKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send PWA notifications
|
||||
*
|
||||
* @param array $data PWA notifications to send
|
||||
* @param array $data PWA notifications to send
|
||||
* @return void
|
||||
*/
|
||||
public static function sendPWANotifications( $data )
|
||||
public static function sendPWANotifications( array $data ) : void
|
||||
{
|
||||
\IPS\IPS::$PSR0Namespaces['Jose'] = \IPS\ROOT_PATH . '/system/3rd_party/JwtFramework/src';
|
||||
\IPS\IPS::$PSR0Namespaces['Minishlink'] = \IPS\ROOT_PATH . '/system/3rd_party/Minishlink';
|
||||
\IPS\IPS::$PSR0Namespaces['Base64Url'] = \IPS\ROOT_PATH .'/system/3rd_party/Base64Url';
|
||||
\IPS\IPS::$PSR0Namespaces['Brick'] = \IPS\ROOT_PATH . '/system/3rd_party/Brick';
|
||||
IPS::$PSR0Namespaces['Jose'] = ROOT_PATH . '/system/3rd_party/JwtFramework/src';
|
||||
IPS::$PSR0Namespaces['Minishlink'] = ROOT_PATH . '/system/3rd_party/Minishlink';
|
||||
IPS::$PSR0Namespaces['Base64Url'] = ROOT_PATH .'/system/3rd_party/Base64Url';
|
||||
IPS::$PSR0Namespaces['Brick'] = ROOT_PATH . '/system/3rd_party/Brick';
|
||||
|
||||
// Step 1: Validate VAPID details
|
||||
$vapid = \Minishlink\WebPush\VAPID::validate( array(
|
||||
'subject' => "mailto:" . \IPS\Settings::i()->email_in,
|
||||
'publicKey' => \IPS\Settings::i()->vapid_public_key,
|
||||
'privateKey' => \IPS\Settings::i()->vapid_private_key
|
||||
$vapid = VAPID::validate( array(
|
||||
'subject' => "mailto:" . Settings::i()->email_in,
|
||||
'publicKey' => Settings::i()->vapid_public_key,
|
||||
'privateKey' => Settings::i()->vapid_private_key
|
||||
) );
|
||||
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_notifications_pwa_keys', \IPS\Db::i()->in( '`member`', array_keys( $data ) ) ) as $auth )
|
||||
foreach (Db::i()->select( '*', 'core_notifications_pwa_keys', Db::i()->in( '`member`', array_keys( $data ) ) ) as $auth )
|
||||
{
|
||||
/* Check device is logged in */
|
||||
try
|
||||
{
|
||||
$device = \IPS\Member\Device::load( $auth['device'], NULL, [ 'member_id=?', $auth['member'] ] );
|
||||
$device = Device::load( $auth['device'], NULL, [ 'member_id=?', $auth['member'] ] );
|
||||
|
||||
if ( !$device->login_key )
|
||||
{
|
||||
/* Is not, continue */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
/* Device does not exist so delete & skip */
|
||||
\IPS\Db::i()->delete( 'core_notifications_pwa_keys', array( 'device=?', $auth['device'] ) );
|
||||
Db::i()->delete( 'core_notifications_pwa_keys', array( 'device=?', $auth['device'] ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$endpoint = \IPS\Http\Url::external( $auth['endpoint'] );
|
||||
$endpoint = Url::external( $auth['endpoint'] );
|
||||
|
||||
// Step 2: Content encoding (provided by browser)
|
||||
$contentEncoding = $auth['encoding'];
|
||||
|
||||
// Step 3: Get and pad the payload
|
||||
$payload = json_encode( $data[ $auth['member'] ]['data'] );
|
||||
$payload = \Minishlink\WebPush\Encryption::padPayload( $payload, \Minishlink\WebPush\Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH, $contentEncoding );
|
||||
$payload = Encryption::padPayload( $payload, Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH, $contentEncoding );
|
||||
|
||||
// Step 4: Build Vapid headers
|
||||
$audience = $endpoint->data[ \IPS\Http\Url::COMPONENT_SCHEME ] . "://" . $endpoint->data[ \IPS\Http\Url::COMPONENT_HOST ];
|
||||
$audience = $endpoint->data[ Url::COMPONENT_SCHEME ] . "://" . $endpoint->data[ Url::COMPONENT_HOST ];
|
||||
$cacheKey = implode( '#', array( $audience, $contentEncoding, md5(json_encode($vapid)) ) );
|
||||
|
||||
if( isset( static::$vapidHeaderCache[ $cacheKey ] ) )
|
||||
@@ -1076,18 +1109,18 @@ class _Notification
|
||||
}
|
||||
else
|
||||
{
|
||||
$vapidHeaders = \Minishlink\WebPush\VAPID::getVapidHeaders( $audience, $vapid['subject'], $vapid['publicKey'], $vapid['privateKey'], $contentEncoding );
|
||||
$vapidHeaders = VAPID::getVapidHeaders( $audience, $vapid['subject'], $vapid['publicKey'], $vapid['privateKey'], $contentEncoding );
|
||||
static::$vapidHeaderCache[ $cacheKey ] = $vapidHeaders;
|
||||
}
|
||||
|
||||
// Step 5: Encrypt the payload with user's keys
|
||||
$encryptedPayload = \Minishlink\WebPush\Encryption::encrypt( $payload, $auth['p256dh'], $auth['auth'], $contentEncoding );
|
||||
$encryptedPayload = Encryption::encrypt( $payload, $auth['p256dh'], $auth['auth'], $contentEncoding );
|
||||
$salt = $encryptedPayload['salt'];
|
||||
$localPublicKey = $encryptedPayload['localPublicKey'];
|
||||
$cipherText = $encryptedPayload['cipherText'];
|
||||
|
||||
// Step 6: Get the content coding header and prepend it to the content
|
||||
$encryptionContentCodingHeader = \Minishlink\WebPush\Encryption::getContentCodingHeader($salt, $localPublicKey, $contentEncoding);
|
||||
$encryptionContentCodingHeader = Encryption::getContentCodingHeader($salt, $localPublicKey, $contentEncoding);
|
||||
$content = $encryptionContentCodingHeader.$cipherText;
|
||||
|
||||
// Step 7: Set headers
|
||||
@@ -1103,11 +1136,11 @@ class _Notification
|
||||
|
||||
if ( $contentEncoding === "aesgcm" )
|
||||
{
|
||||
$headers['Encryption'] = 'salt=' . \Base64Url\Base64Url::encode($salt);
|
||||
$headers['Crypto-Key'] = 'dh=' . \Base64Url\Base64Url::encode($localPublicKey) . ';' . $vapidHeaders['Crypto-Key'];
|
||||
$headers['Encryption'] = 'salt=' . Base64Url::encode($salt);
|
||||
$headers['Crypto-Key'] = 'dh=' . Base64Url::encode($localPublicKey) . ';' . $vapidHeaders['Crypto-Key'];
|
||||
}
|
||||
|
||||
$headers['Content-Length'] = \Minishlink\WebPush\Utils::safeStrlen($content);
|
||||
$headers['Content-Length'] = Utils::safeStrlen($content);
|
||||
$headers['Authorization'] = $vapidHeaders['Authorization'];
|
||||
|
||||
try
|
||||
@@ -1115,17 +1148,17 @@ class _Notification
|
||||
// Step 8: Send the damn thing
|
||||
$response = $endpoint->request()->setHeaders( $headers )->post( $content );
|
||||
}
|
||||
catch( \IPS\Http\Request\Exception $e )
|
||||
catch( Exception $e )
|
||||
{
|
||||
/* If the request failed (DNS issues, etc.). Log it and move on. */
|
||||
\IPS\Log::log( $e, 'pwa_notification' );
|
||||
Log::log( $e, 'pwa_notification' );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 9: Check the response - 404/410 indicates a permanent problem, so delete that key
|
||||
if( \in_array( $response->httpResponseCode, array( 404, 410 ) ) )
|
||||
if( in_array( $response->httpResponseCode, array( 404, 410 ) ) )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_notifications_pwa_keys', array('id = ?', $auth['id'] ) );
|
||||
Db::i()->delete( 'core_notifications_pwa_keys', array('id = ?', $auth['id'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1133,11 +1166,11 @@ class _Notification
|
||||
/**
|
||||
* Convert HTML to plaintext for use in notifications
|
||||
*
|
||||
* @param string $html HTML Text
|
||||
* @param string $html HTML Text
|
||||
* @return string
|
||||
* @todo Almost certainly will need to make this more thorough
|
||||
*/
|
||||
public static function textForPushNotification( $html )
|
||||
public static function textForPushNotification( string $html ): string
|
||||
{
|
||||
return preg_replace( "/\n\s+/", "\n", trim( html_entity_decode( strip_tags( $html ) ) ) );
|
||||
}
|
||||
|
||||
@@ -11,40 +11,47 @@
|
||||
namespace IPS\Notification;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use IPS\Member;
|
||||
use Iterator;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Structure for storing notification recipieints - basically a non-unique SplObjectStorage
|
||||
*/
|
||||
class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
class Recipients implements Countable, Iterator, ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @brief Recipients
|
||||
*/
|
||||
protected $recipients = array();
|
||||
protected array $recipients = array();
|
||||
|
||||
/**
|
||||
* @brief Count
|
||||
*/
|
||||
protected $count = 0;
|
||||
protected int $count = 0;
|
||||
|
||||
/**
|
||||
* @brief Current position
|
||||
*/
|
||||
protected $position = 0;
|
||||
protected int $position = 0;
|
||||
|
||||
/**
|
||||
* [SplObjectStorage] Add a recipient
|
||||
*
|
||||
* @param \IPS\Member $member The member object
|
||||
* @param array|null $followData If the notification is about something being followed, the appropriate row from core_follow
|
||||
* @param Member $member The member object
|
||||
* @param array|null $followData If the notification is about something being followed, the appropriate row from core_follow
|
||||
* @return void
|
||||
*/
|
||||
public function attach( \IPS\Member $member, $followData = NULL )
|
||||
public function attach( Member $member, array $followData = NULL ) : void
|
||||
{
|
||||
$this->recipients[] = array( 'member' => $member, 'followData' => $followData );
|
||||
$this->count++;
|
||||
@@ -53,10 +60,10 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
/**
|
||||
* [SplObjectStorage] Remove a recipient
|
||||
*
|
||||
* @param \IPS\Member $member The member object
|
||||
* @param Member $member The member object
|
||||
* @return void
|
||||
*/
|
||||
public function detach( \IPS\Member $member, $followData = NULL )
|
||||
public function detach( Member $member ) : void
|
||||
{
|
||||
foreach ( $this->recipients as $k => $data )
|
||||
{
|
||||
@@ -73,7 +80,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
public function count(): int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
@@ -81,9 +88,9 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
/**
|
||||
* [Iterator] Get current
|
||||
*
|
||||
* @return \IPS\Member
|
||||
* @return Member
|
||||
*/
|
||||
public function current()
|
||||
public function current(): Member
|
||||
{
|
||||
return $this->recipients[ $this->position ]['member'];
|
||||
}
|
||||
@@ -93,7 +100,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getInfo()
|
||||
public function getInfo(): ?array
|
||||
{
|
||||
return $this->recipients[ $this->position ]['followData'];
|
||||
}
|
||||
@@ -103,7 +110,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function key()
|
||||
public function key(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
@@ -113,7 +120,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next()
|
||||
public function next() : void
|
||||
{
|
||||
$this->position++;
|
||||
}
|
||||
@@ -123,7 +130,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind()
|
||||
public function rewind() : void
|
||||
{
|
||||
$this->position = 0;
|
||||
}
|
||||
@@ -133,7 +140,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid()
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->position < $this->count;
|
||||
}
|
||||
@@ -144,7 +151,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
* @param mixed $offset The offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists( $offset )
|
||||
public function offsetExists( mixed $offset ): bool
|
||||
{
|
||||
return $offset < $this->count;
|
||||
}
|
||||
@@ -153,9 +160,9 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
* [ArrayAccess] Get offset
|
||||
*
|
||||
* @param mixed $offset The offset
|
||||
* @return \IPS\Member
|
||||
* @return Member
|
||||
*/
|
||||
public function offsetGet( $offset )
|
||||
public function offsetGet( mixed $offset ): Member
|
||||
{
|
||||
return $this->recipients[ $offset ]['member'];
|
||||
}
|
||||
@@ -165,9 +172,9 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
*
|
||||
* @param mixed $offset The offset
|
||||
* @param mixed $value The value
|
||||
* @return bool
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet( $offset, $value )
|
||||
public function offsetSet( mixed $offset, mixed $value ): void
|
||||
{
|
||||
$this->recipients[] = array( 'member' => $value, 'followData' => NULL );
|
||||
|
||||
@@ -183,7 +190,7 @@ class _Recipients implements \Countable, \Iterator, \ArrayAccess
|
||||
* @param mixed $offset The offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset( $offset )
|
||||
public function offsetUnset( mixed $offset ) : void
|
||||
{
|
||||
if ( isset( $this->recipients[ $offset ] ) )
|
||||
{
|
||||
|
||||
@@ -13,21 +13,21 @@ namespace IPS\Notification;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API Notification RESPONSE
|
||||
*/
|
||||
class _RestApi extends \IPS\Notification\Inline
|
||||
class RestApi extends Inline
|
||||
{
|
||||
/**
|
||||
* Get data from extension
|
||||
*
|
||||
* @param bool $htmlEscape TRUE to escape HTML
|
||||
* @param bool $htmlEscape TRUE to escape HTML
|
||||
*/
|
||||
public function getData( $htmlEscape = TRUE )
|
||||
public function getData( bool $htmlEscape = TRUE ): array
|
||||
{
|
||||
$methodName = "parse_rest_{$this->notification_key}";
|
||||
|
||||
|
||||
@@ -11,54 +11,67 @@
|
||||
namespace IPS\Notification;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Table\Table as TableHelper;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifications Table Helper
|
||||
*/
|
||||
class _Table extends \IPS\Helpers\Table\Table
|
||||
class Table extends TableHelper
|
||||
{
|
||||
/**
|
||||
* @brief Sort options
|
||||
*/
|
||||
public $sortOptions = array( 'updated_time' );
|
||||
public array $sortOptions = array( 'updated_time' );
|
||||
|
||||
/**
|
||||
* @brief Rows
|
||||
*/
|
||||
protected static $rows = null;
|
||||
protected static ?array $rows = null;
|
||||
|
||||
/**
|
||||
* @brief WHERE clause
|
||||
*/
|
||||
protected $where = array();
|
||||
protected ?array $where = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Http\Url Base URL
|
||||
* @param Url|null $url Base URL
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Http\Url $url=NULL )
|
||||
public function __construct( Url $url=NULL )
|
||||
{
|
||||
/* Init */
|
||||
parent::__construct( $url );
|
||||
|
||||
$this->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' ), 'notificationsTable' );
|
||||
$this->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' ), 'notificationsRows' );
|
||||
$this->tableTemplate = array( Theme::i()->getTemplate( 'system', 'core', 'front' ), 'notificationsTable' );
|
||||
$this->rowsTemplate = array( Theme::i()->getTemplate( 'system', 'core', 'front' ), 'notificationsRows' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set member
|
||||
*
|
||||
* @param \IPS\Member $member The member to filter by
|
||||
* @param Member $member The member to filter by
|
||||
* @return void
|
||||
*/
|
||||
public function setMember( \IPS\Member $member )
|
||||
public function setMember( Member $member ) : void
|
||||
{
|
||||
$this->where[] = array( '`member`=?', $member->member_id );
|
||||
}
|
||||
@@ -66,48 +79,48 @@ class _Table extends \IPS\Helpers\Table\Table
|
||||
/**
|
||||
* Get rows
|
||||
*
|
||||
* @param array $advancedSearchValues Values from the advanced search form
|
||||
* @param array|null $advancedSearchValues Values from the advanced search form
|
||||
* @return array
|
||||
*/
|
||||
public function getRows( $advancedSearchValues=NULL )
|
||||
public function getRows( array $advancedSearchValues=NULL ): array
|
||||
{
|
||||
if ( static::$rows === NULL )
|
||||
{
|
||||
static::$rows = array();
|
||||
|
||||
/* Check sortBy */
|
||||
$this->sortBy = \in_array( $this->sortBy, $this->sortOptions ) ? $this->sortBy : 'updated_time';
|
||||
$this->sortBy = in_array( $this->sortBy, $this->sortOptions ) ? $this->sortBy : 'updated_time';
|
||||
|
||||
/* What are we sorting by? */
|
||||
$sortBy = $this->sortBy . ' ' . ( mb_strtolower( $this->sortDirection ) == 'asc' ? 'asc' : 'desc' );
|
||||
$sortBy = $this->sortBy . ' ' . ( ( $this->sortDirection and mb_strtolower( $this->sortDirection ) == 'asc' ) ? 'asc' : 'desc' );
|
||||
|
||||
/* Specify filter in where clause */
|
||||
$where = isset( $this->where ) ? \is_array( $this->where ) ? $this->where : array( $this->where ) : array();
|
||||
$where = isset( $this->where ) ? is_array( $this->where ) ? $this->where : array( $this->where ) : array();
|
||||
|
||||
/* Limit applications */
|
||||
$where[] = array( "notification_app IN('" . implode( "','", array_keys( \IPS\Application::enabledApplications() ) ) . "')" );
|
||||
$where[] = array( "notification_app IN('" . implode( "','", array_keys( Application::enabledApplications() ) ) . "')" );
|
||||
|
||||
if ( $this->filter and isset( $this->filters[ $this->filter ] ) )
|
||||
{
|
||||
$where[] = \is_array( $this->filters[ $this->filter ] ) ? $this->filters[ $this->filter ] : array( $this->filters[ $this->filter ] );
|
||||
$where[] = is_array( $this->filters[ $this->filter ] ) ? $this->filters[ $this->filter ] : array( $this->filters[ $this->filter ] );
|
||||
}
|
||||
|
||||
/* Get Count */
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*) as cnt', 'core_notifications', $where )->first();
|
||||
$count = Db::i()->select( 'COUNT(*) as cnt', 'core_notifications', $where )->first();
|
||||
$this->pages = ceil( $count / $this->limit );
|
||||
|
||||
/* Get results */
|
||||
$it = \IPS\Db::i()->select( '*', 'core_notifications', $where, $sortBy, array( ( $this->limit * ( $this->page - 1 ) ), $this->limit ) );
|
||||
$it = Db::i()->select( '*', 'core_notifications', $where, $sortBy, array( ( $this->limit * ( $this->page - 1 ) ), $this->limit ) );
|
||||
$rows = iterator_to_array( $it );
|
||||
|
||||
foreach( $rows as $index => $row )
|
||||
{
|
||||
try
|
||||
{
|
||||
$notification = \IPS\Notification\Inline::constructFromData( $row );
|
||||
$notification = Inline::constructFromData( $row );
|
||||
static::$rows[ $index ] = array( 'notification' => $notification, 'data' => $notification->getData() );
|
||||
}
|
||||
catch( \LogicException | \RuntimeException $e ){}
|
||||
catch( LogicException | RuntimeException $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +134,7 @@ class _Table extends \IPS\Helpers\Table\Table
|
||||
* @param array|NULL $advancedSearchValues Advanced search values
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders( $advancedSearchValues )
|
||||
public function getHeaders( array $advancedSearchValues=NULL ): array
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user