Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Provides notifications results in API-friendly format
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 3 July 2014
|
||||
*/
|
||||
|
||||
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' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Notification Model
|
||||
*/
|
||||
class _Api extends \IPS\Notification\Inline
|
||||
{
|
||||
/**
|
||||
* Get sent time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_sent()
|
||||
{
|
||||
return $this->_data['sent_time'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updated time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_updated()
|
||||
{
|
||||
return $this->_data['updated_time'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification app
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_app()
|
||||
{
|
||||
return $this->_data['notification_app'];
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Notification;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -139,7 +139,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
public function set_item( $item )
|
||||
{
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
$this->_data['item_class'] = get_class( $item );
|
||||
$this->_data['item_class'] = \get_class( $item );
|
||||
$this->_data['item_id'] = $item->$idColumn;
|
||||
}
|
||||
|
||||
@@ -224,10 +224,11 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Get data from extension
|
||||
*
|
||||
* @param bool $htmlEscape TRUE to escape HTML
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getData()
|
||||
public function getData( $htmlEscape = TRUE )
|
||||
{
|
||||
$method = "parse_{$this->notification_key}";
|
||||
|
||||
@@ -235,7 +236,7 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
if ( method_exists( $class, $method ) )
|
||||
{
|
||||
$return = $class->$method( $this );
|
||||
$return = $class->$method( $this, $htmlEscape );
|
||||
|
||||
if ( !isset( $return['unread'] ) )
|
||||
{
|
||||
@@ -247,4 +248,66 @@ class _Inline extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
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
|
||||
* @return array
|
||||
* @apiresponse string notificationType The type (key) of notification sent
|
||||
* @apiresponse string notificationApp The app that processes this type of notification
|
||||
* @apiresponse string itemClass The class that triggered the notification
|
||||
* @apiresponse int itemId The ID of the content that triggered the notification
|
||||
* @apiresponse string subItemClass The sub-class that triggered the notification (e.g. for comments or reviews)
|
||||
* @apiresponse int subItemId The sub-ID of the content that triggered the notification (e.g. for comments or reviews)
|
||||
* @apiresponse object|null item A representation of the item being notified about (if supported), or null
|
||||
* @apiresponse object|null itemSub A representation of the subitem being notified about (if supported), or null
|
||||
* @apiresponse datetime sentDate Date and time the notification was sent
|
||||
* @apiresponse datetime updatedDate Date and time the notification was last updated
|
||||
* @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 )
|
||||
{
|
||||
try
|
||||
{
|
||||
$ourData = $this->getData();
|
||||
}
|
||||
catch( \LogicException $e )
|
||||
{
|
||||
$ourData = array();
|
||||
}
|
||||
|
||||
return array(
|
||||
'notificationType' => $this->notification_key,
|
||||
'notificationApp' => $this->notification_app->directory,
|
||||
'itemClass' => $this->item_class,
|
||||
'itemId' => $this->item_id,
|
||||
'subItemClass' => $this->item_sub_class,
|
||||
'subItemId' => $this->item_sub_id,
|
||||
'item' => ( $this->item AND method_exists( $this->item, 'apiOutput' ) ) ? $this->item->apiOutput( $authorizedMember ) : NULL,
|
||||
'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,
|
||||
'notificationData' => array_map( function( $val ) {
|
||||
if( $val instanceof \IPS\Http\Url )
|
||||
{
|
||||
return (string) $val;
|
||||
}
|
||||
elseif( $val instanceof \IPS\Member )
|
||||
{
|
||||
return $val->apiOutput();
|
||||
}
|
||||
elseif( $val instanceof \IPS\DateTime )
|
||||
{
|
||||
return $val->rfc3339();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $val;
|
||||
}
|
||||
}, $ourData )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -36,149 +36,353 @@ class _Notification
|
||||
{
|
||||
if ( static::$defaultConfiguration === NULL )
|
||||
{
|
||||
static::$defaultConfiguration = iterator_to_array( \IPS\Db::i()->select( '*', 'core_notification_defaults' )->setKeyField('notification_key') );
|
||||
|
||||
/* Get data from the actual extensions */
|
||||
$extensionDefaults = array();
|
||||
foreach( \IPS\Application::allExtensions( 'core', 'Notifications' ) as $group => $class )
|
||||
{
|
||||
$configuration = $class->getConfiguration( NULL );
|
||||
if ( !empty( $configuration ) )
|
||||
if ( method_exists( $class, 'configurationOptions' ) )
|
||||
{
|
||||
foreach ( $configuration as $key => $data )
|
||||
foreach ( $class->configurationOptions( NULL ) as $key => $details )
|
||||
{
|
||||
if ( !isset( static::$defaultConfiguration[ $key ] ) )
|
||||
if ( $details['type'] === 'standard' )
|
||||
{
|
||||
/* Row isn't in DB, add it */
|
||||
\IPS\Db::i()->insert( 'core_notification_defaults', array(
|
||||
'notification_key' => $key,
|
||||
'default' => implode( ',', $data['default'] ),
|
||||
'disabled' => implode( ',', $data['disabled'] )
|
||||
) );
|
||||
$extensionDefaults[ $key ] = array(
|
||||
'default' => $details['default'],
|
||||
'disabled' => $details['disabled']
|
||||
);
|
||||
|
||||
static::$defaultConfiguration[ $key ] = array_merge( $data, array( 'editable' => TRUE ) );
|
||||
foreach ( $details['notificationTypes'] as $type )
|
||||
{
|
||||
$extensionDefaults[ $type ] = array(
|
||||
'default' => $details['default'],
|
||||
'disabled' => $details['disabled']
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static::$defaultConfiguration[ $key ]['default'] = array_filter( explode( ',', static::$defaultConfiguration[ $key ]['default'] ) );
|
||||
static::$defaultConfiguration[ $key ]['disabled'] = array_filter( explode( ',', static::$defaultConfiguration[ $key ]['disabled'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return static::$defaultConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Matrix
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return \IPS\Helpers\Form\Matrix
|
||||
*/
|
||||
public static function buildMatrix( \IPS\Member $member )
|
||||
{
|
||||
$matrix = new \IPS\Helpers\Form\Matrix();
|
||||
$matrix->manageable = FALSE;
|
||||
$matrix->langPrefix = FALSE;
|
||||
$matrix->columns = array(
|
||||
'label' => function( $key, $value, $data )
|
||||
{
|
||||
if ( mb_substr( $key, 0, -7 ) === 'new_likes' )
|
||||
{
|
||||
if ( \IPS\Content\Reaction::isLikeMode() )
|
||||
{
|
||||
$return = 'notifications__new_likes_like';
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = 'notifications__new_likes_rep';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = 'notifications__' . mb_substr( $key, 0, -7 );
|
||||
$configuration = $class->getConfiguration( NULL );
|
||||
if ( !empty( $configuration ) )
|
||||
{
|
||||
foreach ( $configuration as $key => $data )
|
||||
{
|
||||
$extensionDefaults[ $key ] = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'global' )->notificationLabel( $return, $data );
|
||||
},
|
||||
'member_notifications_inline' => function( $key, $value, $data )
|
||||
}
|
||||
|
||||
/* Combine that with what the admin has set */
|
||||
static::$defaultConfiguration = iterator_to_array( \IPS\Db::i()->select( '*', 'core_notification_defaults' )->setKeyField('notification_key') );
|
||||
foreach ( $extensionDefaults as $key => $data )
|
||||
{
|
||||
return new \IPS\Helpers\Form\YesNo( $key, in_array( 'inline', ( is_array( $data['selected'] ) ) ? $data['selected'] : array() ), FALSE, array( 'disabled' => ( !$data['editable'] OR in_array( 'inline', $data['disabled'] ) ), 'tooltip' => ( !$data['editable'] OR in_array( 'inline', $data['disabled'] ) ) ? \IPS\Member::loggedIn()->language()->addToStack('admin_notification_disabled') : NULL ) );
|
||||
},
|
||||
'member_notifications_email' => function( $key, $value, $data )
|
||||
{
|
||||
return new \IPS\Helpers\Form\YesNo( $key, in_array( 'email', ( is_array( $data['selected'] ) ) ? $data['selected'] : array() ), FALSE, array( 'disabled' => ( !$data['editable'] OR in_array( 'email', $data['disabled'] ) ), 'tooltip' => ( !$data['editable'] OR in_array( 'email', $data['disabled'] ) ) ? \IPS\Member::loggedIn()->language()->addToStack('admin_notification_disabled') : NULL ) );
|
||||
},
|
||||
);
|
||||
|
||||
/* Add rows */
|
||||
$defaultConfiguration = static::defaultConfiguration();
|
||||
$memberConfiguration = $member->notificationsConfiguration();
|
||||
foreach( \IPS\Application::allExtensions( 'core', 'Notifications' ) as $group => $class )
|
||||
{
|
||||
$configuration = $class->getConfiguration( $member );
|
||||
if ( !empty( $configuration ) )
|
||||
{
|
||||
$lang = "notifications__{$group}";
|
||||
$header = \IPS\Member::loggedIn()->language()->addToStack( $lang );
|
||||
$matrix->rows[] = $header;
|
||||
|
||||
foreach ( $configuration as $key => $data )
|
||||
if ( !isset( static::$defaultConfiguration[ $key ] ) )
|
||||
{
|
||||
$matrix->rows[ $key ] = array( 'selected' => ( !empty( $memberConfiguration[ $key ] ) ) ? $memberConfiguration[ $key ] : NULL, 'disabled' => $defaultConfiguration[ $key ]['disabled'], 'icon' => isset( $data['icon'] ) ? $data['icon'] : NULL, 'editable' => $defaultConfiguration[ $key ]['editable'] );
|
||||
/* Row isn't in DB, add it */
|
||||
\IPS\Db::i()->insert( 'core_notification_defaults', array(
|
||||
'notification_key' => $key,
|
||||
'default' => implode( ',', $data['default'] ),
|
||||
'disabled' => implode( ',', $data['disabled'] )
|
||||
) );
|
||||
|
||||
static::$defaultConfiguration[ $key ] = array_merge( $data, array( 'editable' => TRUE ) );
|
||||
}
|
||||
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'] ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matrix;
|
||||
return static::$defaultConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Matrix
|
||||
* Get available options for an extension
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param array $values Values from matrix
|
||||
* @param \IPS\Member|NULL $member The member or NULL to get all
|
||||
* @param object $extension The noticiations extension
|
||||
* @return void
|
||||
*/
|
||||
public static function saveMatrix( \IPS\Member $member, array $values )
|
||||
public static function availableOptions( $member, $extension )
|
||||
{
|
||||
/* Remove the current preferences */
|
||||
\IPS\Db::i()->delete( 'core_notification_preferences', array( 'member_id=?', $member->member_id ) );
|
||||
|
||||
/* Get the default configuration so we know what is forced enabled by the admin */
|
||||
$defaults = static::defaultConfiguration();
|
||||
|
||||
/* Now loop over the notifications and set preferences */
|
||||
$insert = array();
|
||||
foreach ( $values['notifications'] as $k => $v )
|
||||
/* First of all, get the settings from the extension, maintaining backwards compatibility with older extensions */
|
||||
if ( method_exists( $extension, 'configurationOptions' ) )
|
||||
{
|
||||
$pref = array();
|
||||
if ( $v['member_notifications_inline'] )
|
||||
$settings = $extension::configurationOptions( $member );
|
||||
}
|
||||
else
|
||||
{
|
||||
$configuration = $extension->getConfiguration( $member );
|
||||
|
||||
$settings = array();
|
||||
foreach ( $configuration as $key => $details )
|
||||
{
|
||||
$pref[] = 'inline';
|
||||
$settings[ $key ] = array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( $key ),
|
||||
'title' => "notifications__{$key}",
|
||||
'showTitle' => TRUE,
|
||||
'description' => NULL,
|
||||
);
|
||||
}
|
||||
else if( !$defaults[ $k ]['editable'] AND in_array( 'inline', $defaults[ $k ]['default'] ) )
|
||||
}
|
||||
|
||||
/* Now loop through each of those and get the value for each */
|
||||
if ( $member )
|
||||
{
|
||||
$defaultConfiguration = \IPS\Notification::defaultConfiguration();
|
||||
$memberConfiguration = $member->notificationsConfiguration();
|
||||
$finalSettings = array();
|
||||
foreach ( $settings as $key => $details )
|
||||
{
|
||||
$pref[] = 'inline';
|
||||
}
|
||||
if ( $v['member_notifications_email'] )
|
||||
{
|
||||
$pref[] = 'email';
|
||||
}
|
||||
else if( !$defaults[ $k ]['editable'] AND in_array( 'email', $defaults[ $k ]['default'] ) )
|
||||
{
|
||||
$pref[] = 'email';
|
||||
if ( $details['type'] === 'standard' )
|
||||
{
|
||||
$options = array();
|
||||
$availableTypes = array('inline');
|
||||
if ( \IPS\Settings::i()->mobile_app_id and $member->members_bitoptions['mobile_notifications'] )
|
||||
{
|
||||
$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'] ) ) )
|
||||
{
|
||||
$enabled = array();
|
||||
$haveMemberPreferences = FALSE;
|
||||
if ( $defaultConfiguration[ $key ]['editable'] )
|
||||
{
|
||||
foreach ( $details['notificationTypes'] as $notificationType )
|
||||
{
|
||||
if ( isset( $memberConfiguration[ $notificationType ] ) )
|
||||
{
|
||||
$haveMemberPreferences = TRUE;
|
||||
$enabled += $memberConfiguration[ $notificationType ];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !$haveMemberPreferences )
|
||||
{
|
||||
$enabled = $defaultConfiguration[ $key ]['default'];
|
||||
}
|
||||
|
||||
$options[ $type ] = array(
|
||||
'title' => "member_notifications_{$type}",
|
||||
'value' => \in_array( $type, $enabled ),
|
||||
'editable' => $defaultConfiguration[ $key ]['editable']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $options ) or ( isset( $details['extra'] ) and \count( $details['extra'] ) ) )
|
||||
{
|
||||
$details['options'] = $options;
|
||||
$finalSettings[ $key ] = $details;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$finalSettings[ $key ] = $details;
|
||||
}
|
||||
}
|
||||
|
||||
$insert[] = array(
|
||||
'member_id' => $member->member_id,
|
||||
'notification_key' => $k,
|
||||
'preference' => implode( ',', $pref )
|
||||
);
|
||||
/* And return */
|
||||
return $finalSettings;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $settings;
|
||||
}
|
||||
|
||||
\IPS\Db::i()->insert( 'core_notification_preferences', $insert );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @return array
|
||||
*/
|
||||
public static function membersOptionCategories( \IPS\Member $member, $extensions )
|
||||
{
|
||||
$categories = array();
|
||||
foreach( $extensions as $group => $extension )
|
||||
{
|
||||
$options = static::availableOptions( $member, $extension );
|
||||
if ( !empty( $options ) )
|
||||
{
|
||||
if ( !isset( $categories[ $group ] ) )
|
||||
{
|
||||
$categories[ $group ] = array();
|
||||
}
|
||||
|
||||
foreach ( $options as $key => $details )
|
||||
{
|
||||
if ( isset( $details['extra'] ) )
|
||||
{
|
||||
foreach ( $details['extra'] as $extraKey => $extraDetails )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $details['type'] === 'standard' )
|
||||
{
|
||||
foreach ( $details['options'] as $type => $typeDetails )
|
||||
{
|
||||
if ( $typeDetails['value'] )
|
||||
{
|
||||
switch ( $type )
|
||||
{
|
||||
case 'inline':
|
||||
if ( isset( $details['options']['push'] ) and $details['options']['push']['value'] )
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
$icon = 'bell-o';
|
||||
break;
|
||||
case 'push':
|
||||
$icon = 'mobile';
|
||||
break;
|
||||
case 'email':
|
||||
$icon = 'envelope-o';
|
||||
break;
|
||||
}
|
||||
|
||||
$categories[ $group ][ $type ] = array( 'title' => \IPS\Member::loggedIn()->language()->get( 'member_notifications_' . $type ), 'icon' => $icon );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function membersTypeForm( \IPS\Member $member, $extension )
|
||||
{
|
||||
$form = NULL;
|
||||
$formIsEditable = FALSE;
|
||||
|
||||
if ( $options = \IPS\Notification::availableOptions( $member, $extension ) )
|
||||
{
|
||||
$form = new \IPS\Helpers\Form;
|
||||
foreach ( $options as $key => $details )
|
||||
{
|
||||
if ( $details['type'] === 'separator' and \IPS\Dispatcher::i()->controllerLocation === 'front' )
|
||||
{
|
||||
$form->addSeparator();
|
||||
}
|
||||
elseif ( $details['type'] === 'header' )
|
||||
{
|
||||
$form->addHeader( $details['header'] );
|
||||
}
|
||||
elseif ( $details['type'] === 'custom' and ( !isset( $details['adminOnly'] ) ) )
|
||||
{
|
||||
$form->add( $details['field'] );
|
||||
$formIsEditable = TRUE;
|
||||
}
|
||||
elseif ( $details['type'] === 'standard' )
|
||||
{
|
||||
if ( !$formIsEditable )
|
||||
{
|
||||
if ( isset( $details['extra'] ) and \count( $details['extra'] ) )
|
||||
{
|
||||
$formIsEditable = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( $details['options'] as $option )
|
||||
{
|
||||
if ( $option['editable'] )
|
||||
{
|
||||
$formIsEditable = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Custom( "notifications_{$key}", NULL, TRUE, array(
|
||||
'rowHtml' => function( $field ) use ( $details, $options ) {
|
||||
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'global' )->notificationsSettingsRow( $field, $details );
|
||||
}
|
||||
) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
foreach ( $options as $key => $details )
|
||||
{
|
||||
if ( $details['type'] === 'standard' )
|
||||
{
|
||||
$value = array();
|
||||
if ( isset( $values["notifications_{$key}"] ) )
|
||||
{
|
||||
foreach ( $values["notifications_{$key}"] as $k => $v )
|
||||
{
|
||||
if ( $v === 'push' )
|
||||
{
|
||||
$value['inline'] = 'inline';
|
||||
$value['push'] = 'push';
|
||||
}
|
||||
elseif ( \in_array( $k, array( 'inline', 'push', 'email' ) ) and $v )
|
||||
{
|
||||
$value[ $k ] = $k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $details['notificationTypes'] as $notificationKey )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_notification_preferences', array(
|
||||
'member_id' => $member->member_id,
|
||||
'notification_key' => $notificationKey,
|
||||
'preference' => implode( ',', $value )
|
||||
), TRUE );
|
||||
}
|
||||
}
|
||||
elseif ( $details['type'] === 'custom' and ( !isset( $details['adminOnly'] ) or !$details['adminOnly'] ) )
|
||||
{
|
||||
$extension::saveExtra( $member, $key, $values[ $key ] );
|
||||
}
|
||||
|
||||
if ( isset( $details['extra'] ) )
|
||||
{
|
||||
foreach ( $details['extra'] as $extraKey => $extraDetails )
|
||||
{
|
||||
$extension::saveExtra( $member, $extraKey, array_key_exists( $extraKey, $values["notifications_{$key}"] ?? [] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$member->save();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $form and !$formIsEditable )
|
||||
{
|
||||
$form->actionButtons = array();
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
@@ -265,6 +469,60 @@ class _Notification
|
||||
$emails = array();
|
||||
$emailRecipients = array();
|
||||
$thingsBeingFollowed = array();
|
||||
$pushNotifications = array();
|
||||
|
||||
/* First, loop over the members so we can load their notification preferences en-masse */
|
||||
$membersForNotifications = array();
|
||||
foreach ( $this->recipients as $member )
|
||||
{
|
||||
/* Let's not send notifications to deleted members, banned members or spammers */
|
||||
if ( $member === NULL or !$member->member_id or $member->isBanned() or $member->members_bitoptions['bw_is_spammer'] )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$membersForNotifications[ $member->member_id ] = $member;
|
||||
}
|
||||
if( \count( $membersForNotifications ) )
|
||||
{
|
||||
/* Fill in any that may not have customized their preferences */
|
||||
foreach( $membersForNotifications as $member )
|
||||
{
|
||||
if( $member->notificationsConfiguration === NULL )
|
||||
{
|
||||
$member->notificationsConfiguration = array();
|
||||
}
|
||||
}
|
||||
|
||||
/* Get all preferences at once */
|
||||
$preferenceSet = array();
|
||||
|
||||
foreach (
|
||||
\IPS\Db::i()->select(
|
||||
'd.*, p.preference, p.member_id',
|
||||
array( 'core_notification_defaults', 'd' )
|
||||
)->join(
|
||||
array( 'core_notification_preferences', 'p' ),
|
||||
array( 'd.notification_key=p.notification_key AND p.member_id IN(' . implode( ',', array_keys( $membersForNotifications ) ) . ')' )
|
||||
)
|
||||
as $row
|
||||
) {
|
||||
if( !\in_array( $row['notification_key'], $preferenceSet ) )
|
||||
{
|
||||
foreach( $membersForNotifications as $member )
|
||||
{
|
||||
$member->notificationsConfiguration[ $row['notification_key'] ] = explode( ',', $row['default'] );
|
||||
}
|
||||
|
||||
$preferenceSet[] = $row['notification_key'];
|
||||
}
|
||||
|
||||
if ( $row['preference'] !== NULL AND $row['editable'] )
|
||||
{
|
||||
$membersForNotifications[ $row['member_id'] ]->notificationsConfiguration[ $row['notification_key'] ] = array_diff( explode( ',', $row['preference'] ), explode( ',', $row['disabled'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop recipients */
|
||||
foreach ( $this->recipients as $member )
|
||||
@@ -320,7 +578,7 @@ class _Notification
|
||||
/* Work out how the user wants to receive this notification */
|
||||
$notificationPreferences = $member->notificationsConfiguration();
|
||||
$info = $this->recipients->getInfo();
|
||||
if ( $info['follow_app'] === 'core' and $info['follow_area'] === 'member' )
|
||||
if ( $info !== NULL and $info['follow_app'] === 'core' and $info['follow_area'] === 'member' )
|
||||
{
|
||||
$keyToCheck = 'follower_content';
|
||||
}
|
||||
@@ -336,9 +594,9 @@ class _Notification
|
||||
$keyToCheck = 'unapproved_content';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -374,26 +632,38 @@ class _Notification
|
||||
{
|
||||
$classname = 'IPS\\' . $info['follow_app'] . '\\' . 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 */
|
||||
$email->setAdvertisementParameters( $classname, $info['follow_rel_id'] );
|
||||
}
|
||||
}
|
||||
|
||||
$thingBeingFollowed = $thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ];
|
||||
if ( $thingBeingFollowed instanceof \IPS\Member )
|
||||
{
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow_member', FALSE, array( 'sprintf' => array( $thingBeingFollowed->name ) ) );
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow_member', FALSE, array( 'htmlsprintf' => array( $thingBeingFollowed->name ) ) );
|
||||
}
|
||||
elseif ( $thingBeingFollowed instanceof \IPS\Node\Model )
|
||||
{
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow', FALSE, array( 'sprintf' => array( $member->language()->addToStack( $thingBeingFollowed::$nodeTitle . '_sg' ), $thingBeingFollowed->_title ) ) );
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow', FALSE, array( 'htmlsprintf' => array( $member->language()->addToStack( $thingBeingFollowed::$nodeTitle . '_sg' ), $thingBeingFollowed->getTitleForLanguage( $member->language() ) ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow', FALSE, array( 'sprintf' => array( $member->language()->addToStack( $thingBeingFollowed::$title ), $thingBeingFollowed->mapped('title') ) ) );
|
||||
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow', FALSE, array( 'htmlsprintf' => array( $member->language()->addToStack( $thingBeingFollowed::$title ), $thingBeingFollowed->mapped('title') ) ) );
|
||||
}
|
||||
|
||||
$unfollowLink = (string) \IPS\Http\Url::internal( "app=core&module=system§ion=notifications&do=follow&follow_app={$info['follow_app']}&follow_area={$info['follow_area']}&follow_id={$info['follow_rel_id']}", 'front' );
|
||||
$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' );
|
||||
|
||||
/* 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 )
|
||||
{
|
||||
$unfollowLink = $unfollowLink->setQueryString( array( 'email' => 1, 'type' => $this->emailKey ) );
|
||||
}
|
||||
|
||||
$unfollowLink = (string) $unfollowLink;
|
||||
|
||||
if ( $member->members_bitoptions['email_notifications_once'] and $member->last_activity < $info['follow_notify_sent'] )
|
||||
if ( $member->members_bitoptions['email_notifications_once'] and max( $member->last_activity, $member->last_visit ) < $info['follow_notify_sent'] )
|
||||
{
|
||||
$okToEmail = FALSE;
|
||||
}
|
||||
@@ -413,7 +683,8 @@ 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) */
|
||||
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 ] ) ) )
|
||||
$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->item AND $this->allowMerging )
|
||||
{
|
||||
@@ -421,7 +692,7 @@ class _Notification
|
||||
{
|
||||
$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 = \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->member = $member;
|
||||
$notification->updated_time = time();
|
||||
@@ -448,13 +719,13 @@ class _Notification
|
||||
if ( $param instanceof \IPS\Content )
|
||||
{
|
||||
$subIdColumn = $param::$databaseColumnId;
|
||||
$notification->item_sub_class = get_class( $param );
|
||||
$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 ( ( $param instanceof \IPS\Content\Comment OR $param instanceof \IPS\Content\Review ) && \in_array( $this->key, array( 'new_comment', 'new_review', 'quote', 'new_likes' ) ) )
|
||||
{
|
||||
if ( $this->key === 'new_likes' and $this->emailParams[1] instanceof \IPS\Member )
|
||||
{
|
||||
@@ -482,10 +753,78 @@ class _Notification
|
||||
|
||||
$sentTo[ $member->member_id ][] = 'inline';
|
||||
}
|
||||
|
||||
/* They want to receive a mobile app push notification (we don't send until the end once we've collated all the notifications to send) */
|
||||
if ( \IPS\Settings::i()->mobile_app_id and $member->members_bitoptions['mobile_notifications'] and isset( $notificationPreferences[ $keyToCheck ] ) AND \in_array( 'push', $notificationPreferences[ $keyToCheck ] ) and ( !isset( $sentTo[ $member->member_id ] ) or !\in_array( 'push', $sentTo[ $member->member_id ] ) ) and $licenseKey = \IPS\IPS::licenseKey() and $licenseKey['active'] )
|
||||
{
|
||||
$language = $member->language();
|
||||
if ( !isset( $pushNotifications[ $language->id ] ) )
|
||||
{
|
||||
$method = "parse_mobile_{$this->key}";
|
||||
foreach ( $this->app->extensions( 'core', 'Notifications' ) as $class )
|
||||
{
|
||||
if ( method_exists( $class, $method ) or $this->key === 'follower_content' )
|
||||
{
|
||||
$data = $class::$method( $language, ...$this->emailParams );
|
||||
$language->parseOutputForDisplay( $data );
|
||||
|
||||
if ( !isset( $data['data'] ) )
|
||||
{
|
||||
$data['data'] = array();
|
||||
}
|
||||
$data['data']['type'] = $this->app->directory . '/' . $this->key;
|
||||
|
||||
if ( isset( $data['data']['url'] ) )
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $data['data']['url'] );
|
||||
if ( $url instanceof \IPS\Http\Url\Internal )
|
||||
{
|
||||
foreach ( array( 'app', 'module', 'controller', 'id', 'comment', 'review' ) as $k )
|
||||
{
|
||||
if ( isset( $url->hiddenQueryString[ $k ] ) )
|
||||
{
|
||||
$data['data'][ $k ] = $url->hiddenQueryString[ $k ];
|
||||
}
|
||||
elseif ( isset( $url->queryString[ $k ] ) )
|
||||
{
|
||||
$data['data'][ $k ] = $url->queryString[ $k ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( isset( $data['data']['author'] ) )
|
||||
{
|
||||
$data['data']['image'] = $data['data']['author']->photo;
|
||||
unset( $data['data']['author'] );
|
||||
}
|
||||
|
||||
$pushNotifications[ $language->id ] = $data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $pushNotifications[ $language->id ] ) )
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
$sentTo[ $member->member_id ][] = 'push';
|
||||
}
|
||||
}
|
||||
|
||||
/* Send any emails */
|
||||
$this->sendEmails( $emails, $emailRecipients );
|
||||
/* Send any emails and push notifications */
|
||||
if ( \count( $pushNotifications ) )
|
||||
{
|
||||
$this->sendPushNotifications( $pushRecipients );
|
||||
}
|
||||
if ( \count( $emails ) )
|
||||
{
|
||||
$this->sendEmails( $emails, $emailRecipients );
|
||||
}
|
||||
|
||||
/* And return */
|
||||
return $sentTo;
|
||||
@@ -508,4 +847,59 @@ class _Notification
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send push notifications
|
||||
*
|
||||
* @param array $pushNotifications Push notifications to send
|
||||
* @return void
|
||||
*/
|
||||
protected function sendPushNotifications( $pushNotifications )
|
||||
{
|
||||
$limit = 100;
|
||||
$offset = 0;
|
||||
while ( $slice = \array_slice( $pushNotifications, $offset, $limit ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Send to remoteservices */
|
||||
$response = \IPS\Http\Url::ips('mobile/notify')->request()->login( \IPS\Settings::i()->mobile_app_id, \IPS\Settings::i()->mobile_app_secret )->post( array( 'data' => $slice ) );
|
||||
if ( $response->httpResponseCode != 200 )
|
||||
{
|
||||
throw new \DomainException( $response );
|
||||
}
|
||||
$response = $response->decodeJson();
|
||||
|
||||
/* If any users have turned off notifications on all of their devices, we can remove them */
|
||||
if ( isset( $response['unsubscribe'] ) and $response['unsubscribe'] )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_members', "members_bitoptions2 = members_bitoptions2 & ~" . \IPS\Member::$bitOptions['members_bitoptions']['members_bitoptions2']['mobile_notifications'], \IPS\Db::i()->in( 'member_id', $response['unsubscribe'] ) );
|
||||
}
|
||||
|
||||
/* Log any errors */
|
||||
if ( isset( $response['errors'] ) and $response['errors'] )
|
||||
{
|
||||
\IPS\Log::log( json_encode( $slice, JSON_PRETTY_PRINT ) . "\n\n==============================================================================\n\n" . json_encode( $response, JSON_PRETTY_PRINT ), 'mobile-push-notifications' );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'mobile-push-notifications' );
|
||||
}
|
||||
|
||||
$offset += $limit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HTML to plaintext for use in notifications
|
||||
*
|
||||
* @param string $html HTML Text
|
||||
* @return string
|
||||
* @todo Almost certainly will need to make this more thorough
|
||||
*/
|
||||
public static function textForPushNotification( $html )
|
||||
{
|
||||
return preg_replace( "/\n\s+/", "\n", trim( html_entity_decode( strip_tags( $html ) ) ) );
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Notification;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Notification;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -60,7 +60,7 @@ class _Table extends \IPS\Helpers\Table\Table
|
||||
*/
|
||||
public function setMember( \IPS\Member $member )
|
||||
{
|
||||
$this->where[] = array( 'member=?', $member->member_id );
|
||||
$this->where[] = array( '`member`=?', $member->member_id );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,21 +73,23 @@ class _Table extends \IPS\Helpers\Table\Table
|
||||
{
|
||||
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' );
|
||||
|
||||
/* 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() ) ) . "')" );
|
||||
|
||||
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 */
|
||||
|
||||
Reference in new issue
Block a user