Files
IPB/system/Notification/Notification.php
T
2025-12-19 04:57:54 -08:00

448 lines
14 KiB
PHP

<?php
/**
* @brief Notification Class
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @since 23 Apr 2013
* @version SVN_VERSION_NUMBER
*/
namespace IPS;
/* 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;
}
/**
* Notification Class
*/
class _Notification
{
/**
* @brief Default Configuration
*/
protected static $defaultConfiguration = NULL;
/**
* Get default configuration
*
* @return array
*/
public static function defaultConfiguration()
{
if ( static::$defaultConfiguration === NULL )
{
static::$defaultConfiguration = iterator_to_array( \IPS\Db::i()->select( '*', 'core_notification_defaults' )->setKeyField('notification_key') );
foreach( \IPS\Application::allExtensions( 'core', 'Notifications' ) as $group => $class )
{
$configuration = $class->getConfiguration( NULL );
if ( !empty( $configuration ) )
{
foreach ( $configuration as $key => $data )
{
if ( !isset( static::$defaultConfiguration[ $key ] ) )
{
/* 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( 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\Settings::i()->reputation_point_types === 'like' )
{
$return = 'notifications__new_likes_like';
}
else
{
$return = 'notifications__new_likes_rep';
}
}
else
{
$return = 'notifications__' . mb_substr( $key, 0, -7 );
}
return \IPS\Theme::i()->getTemplate( 'members', 'core', 'global' )->notificationLabel( $return, $data );
},
'member_notifications_inline' => function( $key, $value, $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 )
{
$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'] );
}
}
}
return $matrix;
}
/**
* Save Matrix
*
* @param \IPS\Member $member The member
* @param array $values Values from matrix
* @return void
*/
public static function saveMatrix( \IPS\Member $member, array $values )
{
\IPS\Db::i()->delete( 'core_notification_preferences', array( 'member_id=?', $member->member_id ) );
$insert = array();
foreach ( $values['notifications'] as $k => $v )
{
$pref = array();
if ( $v['member_notifications_inline'] )
{
$pref[] = 'inline';
}
if ( $v['member_notifications_email'] )
{
$pref[] = 'email';
}
$insert[] = array(
'member_id' => $member->member_id,
'notification_key' => $k,
'preference' => implode( ',', $pref )
);
}
\IPS\Db::i()->insert( 'core_notification_preferences', $insert );
}
/**
* @brief Application
*/
protected $app;
/**
* @brief Notification key
*/
protected $key;
/**
* @brief Item
*/
protected $item;
/**
* @brief An SplObjectStorage object which contains \IPS\Member objects and replacements to use for that member in the notification content.
* @code
$notification->recipients->attach( $member, array( 'foo' => 'bar' ) );
$notification->recipients->attach( $member2, array( 'foo' => 'baz' ) );
* @endcode
*/
public $recipients;
/**
* @brief Data for notification emails
*/
protected $emailParams = array();
/**
* @brief Extra data to save with inline notifications
*/
protected $inlineExtra = array();
/**
* @brief Unsubscribe Type
*/
public $unsubscribeType = 'notification';
/**
* 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.
* @return void
*/
public function __construct( \IPS\Application $app, $key, $item=NULL, $emailParams=array(), $inlineExtra=array() )
{
$this->app = $app;
$this->key = $key;
$this->item = $item;
$this->recipients = new \SplObjectStorage;
$this->emailParams = $emailParams;
$this->inlineExtra = $inlineExtra;
}
/**
* Send Notification
*
* @return array $sentTo Members notified
*/
public function send()
{
/* Make a placeholder for emails - we'll need to generate one per language */
$emails = array();
$emailRecipients = array();
$sentTo = array();
$thingsBeingFollowed = array();
/* Send */
foreach ( $this->recipients as $member )
{
/* let's not send notifications to banned members */
if ( $member->isBanned() )
{
$this->recipients->detach( $member );
continue;
}
if ( $this->item )
{
$item = $this->item;
if ( $item instanceof \IPS\Content\Item )
{
$application = \IPS\Application::load( $item::$application );
if ( !$application->canAccess( $member ) )
{
$this->recipients->detach( $member );
continue;
}
if ( $member->isIgnoring( $item->author(), 'topics' ) )
{
$this->recipients->detach( $member );
continue;
}
}
foreach( $this->emailParams AS $param )
{
if ( $param instanceof \IPS\Content\Comment OR $param instanceof \IPS\Content\Review )
{
if ( $member->isIgnoring( $param->author(), 'topics' ) )
{
$this->recipients->detach( $member );
continue 2;
}
}
if ( $param instanceof \IPS\Member )
{
if ( $member->isIgnoring( $param, 'topics' ) )
{
$this->recipients->detach( $member );
continue 2;
}
}
}
}
$notificationPreferences = $member->notificationsConfiguration();
$info = $this->recipients->getInfo();
if ( $info['follow_app'] === 'core' and $info['follow_area'] === 'member' )
{
$keyToCheck = 'follower_content';
}
else
{
$keyToCheck = $this->key;
if ( $this->key === 'new_content_bulk' )
{
$keyToCheck = 'new_content';
}
if ( $this->key === 'unapproved_content_bulk' )
{
$keyToCheck = 'unapproved_content';
}
}
if ( isset( $notificationPreferences[ $keyToCheck ] ) AND in_array( 'email', $notificationPreferences[ $keyToCheck ] ) )
{
$language = $member->language()->id;
if ( !isset( $emails[ $language ] ) )
{
$emails[ $language ] = \IPS\Email::buildFromTemplate( $this->app->directory, 'notification_' . $this->key, $this->emailParams, TRUE );
}
$unsubscribeBlurb = NULL;
$unfollowLink = NULL;
if ( $info )
{
if ( !isset( $thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ] ) )
{
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'] );
}
else
{
$classname = 'IPS\\' . $info['follow_app'] . '\\' . ucfirst( $info['follow_area'] );
$thingsBeingFollowed[ $info['follow_app'] ][ $info['follow_area'] ][ $info['follow_rel_id'] ] = $classname::load( $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 ) ) );
}
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 ) ) );
}
else
{
$unsubscribeBlurb = $member->language()->addToStack( 'unsubscribe_blurb_follow', FALSE, array( 'sprintf' => array( $member->language()->addToStack( $thingBeingFollowed::$title ), $thingBeingFollowed->mapped('title') ) ) );
}
$unfollowLink = (string) \IPS\Http\Url::internal( "app=core&module=system&section=notifications&do=follow&follow_app={$info['follow_app']}&follow_area={$info['follow_area']}&follow_id={$info['follow_rel_id']}", 'front' );
}
$emails[ $language ]->buildUnsubscribe( $this->unsubscribeType, $this->key );
$emailRecipients[ $language ][ $member->email ] = array(
'recipient_name' => $member->name,
'recipient_photo' => (string) $member->photo,
'unsubscribe_blurb' => $unsubscribeBlurb,
'unfollow_link' => $unfollowLink
);
$sentTo[ $member->member_id ] = $member->member_id;
}
/* Send inline notifications, but only if this is not for report center. Report center 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 ] ) )
{
if ( $this->item )
{
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->member = $member;
$notification->updated_time = time();
$notification->extra = array_merge( $notification->extra, $this->inlineExtra );
$notification->save();
continue;
}
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 )
{
$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
* @see <a href='http://community.invisionpower.com/resources/bugs.html/_/4-0-0/reply-notifications-missing-names-r46841'>Bug Report</a>
*/
if ( ( $param instanceof \IPS\Content\Comment OR $param instanceof \IPS\Content\Review ) && in_array( $this->key, array( 'new_comment', 'new_review', 'quote' ) ) )
{
$notification->sent_time = $param->mapped('date');
}
}
}
$notification->extra = $this->inlineExtra;
$notification->save();
$sentTo[ $member->member_id ] = $member->member_id;
}
}
$this->sendEmails( $emails, $emailRecipients );
return $sentTo;
}
/**
* Send emails
*
* @param array $emails Emails to send
* @param array $emailRecipients Email recipients
* @return void
*/
protected function sendEmails( $emails, $emailRecipients )
{
foreach ( $emails as $languageId => $email )
{
if ( !empty( $emailRecipients[ $languageId ] ) )
{
$email->mergeAndSend( $emailRecipients[ $languageId ] );
}
}
}
}