Version 4.0.13.1

This commit is contained in:
Neo committed 2025-12-19 04:57:54 -08:00
1 parent f9e857d255
commit c6a86f0d9b
7517 files changed
+597400 -668041

No files matched your search

+251
View File
@@ -0,0 +1,251 @@
<?php
/**
* @brief Inline Notification Model
* @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 6 Sep 2013
* @version SVN_VERSION_NUMBER
*/
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;
}
/**
* Inline Notification Model
*/
class _Inline extends \IPS\Patterns\ActiveRecord
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'core_notifications';
/**
* Set Default Values
*
* @return void
*/
public function setDefaultValues()
{
$this->sent_time = time();
$this->updated_time = time();
}
/**
* Get sent time
*
* @return \IPS\DateTime
*/
public function get_sent_time()
{
return \IPS\DateTime::ts( $this->_data['sent_time'] );
}
/**
* Get updated time
*
* @return \IPS\DateTime
*/
public function get_updated_time()
{
return \IPS\DateTime::ts( $this->_data['updated_time'] );
}
/**
* Get member
*
* @return \IPS\Member
*/
public function get_member()
{
return \IPS\Member::load( $this->_data['member'] );
}
/**
* Set member
*
* @param |IPS\Member $member The member
* @return void
*/
public function set_member( \IPS\Member $member )
{
$this->_data['member'] = $member->member_id;
}
/**
* Get member data
*
* @return array
*/
public function get_member_data()
{
return json_decode( $this->_data['member_data'], TRUE );
}
/**
* Set member data
*
* @param mixed $data Member data
* @return void
*/
public function set_member_data( $data )
{
$this->_data['member_data'] = $data ? json_encode( $data ) : NULL;
}
/**
* Get item
*
* @return object|NULL
*/
public function get_item()
{
if ( $this->_data['item_class'] and $this->_data['item_id'] )
{
try
{
$class = $this->_data['item_class'];
if ( class_exists( $class ) )
{
return $class::load( $this->_data['item_id'] );
}
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
return NULL;
}
/**
* Set item
*
* @param object $item The item
* @return void
*/
public function set_item( $item )
{
$idColumn = $item::$databaseColumnId;
$this->_data['item_class'] = get_class( $item );
$this->_data['item_id'] = $item->$idColumn;
}
/**
* Get subitem
*
* @return object|NULL
*/
public function get_item_sub()
{
if ( $this->_data['item_sub_class'] and $this->_data['item_sub_id'] )
{
try
{
$class = $this->_data['item_sub_class'];
if ( class_exists( $class ) )
{
return $class::load( $this->_data['item_sub_id'] );
}
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
return NULL;
}
/**
* Get application
*
* @return \IPS\Application
*/
public function get_notification_app()
{
return \IPS\Application::load( $this->_data['notification_app'] );
}
/**
* Set application
*
* @param mixed $data Member data
* @return void
*/
public function set_notification_app( \IPS\Application $app )
{
$this->_data['notification_app'] = $app->directory;
}
/**
* Get extra data
*
* @return array
*/
public function get_extra()
{
return $this->_data['extra'] ? json_decode( $this->_data['extra'], TRUE ) : array();
}
/**
* Set extra data
*
* @param mixed $data Member data
* @return void
*/
public function set_extra( $data )
{
$this->_data['extra'] = $data ? json_encode( $data ) : NULL;
}
/**
* Save Changed Columns
*
* @return void
*/
public function save()
{
parent::save();
$this->member->recountNotifications();
}
/**
* Get data from extension
*
* @return array
* @throws \RuntimeException
*/
public function getData()
{
$method = "parse_{$this->notification_key}";
foreach ( $this->notification_app->extensions( 'core', 'Notifications' ) as $class )
{
if ( method_exists( $class, $method ) )
{
$return = $class->$method( $this );
if ( !isset( $return['unread'] ) )
{
$return['unread'] = !$this->read_time;
}
return $return;
}
}
throw new \RuntimeException;
}
}
+448
View File
@@ -0,0 +1,448 @@
<?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 ] );
}
}
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
/**
* @brief Notifications Table Helper
* @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 3 July 2014
* @version SVN_VERSION_NUMBER
*/
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;
}
/**
* Notifications Table Helper
*/
class _Table extends \IPS\Helpers\Table\Table
{
/**
* @brief Sort options
*/
public $sortOptions = array( 'updated' );
/**
* @brief Rows
*/
protected static $rows = null;
/**
* Constructor
*
* @param \IPS\Http\Url Base URL
* @return void
*/
public function __construct( \IPS\Http\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' );
}
/**
* Set member
*
* @param \IPS\Member $member The member to filter by
* @return void
*/
public function setMember( \IPS\Member $member )
{
$this->where[] = array( 'member=?', $member->member_id );
}
/**
* Get rows
*
* @param array $advancedSearchValues Values from the advanced search form
* @return array
*/
public function getRows( $advancedSearchValues=NULL )
{
if ( static::$rows === NULL )
{
/* Check sortBy */
$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();
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 ] );
}
/* Get Count */
$count = \IPS\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 ) );
$rows = iterator_to_array( $it );
foreach( $rows as $index => $row )
{
try
{
$notification = \IPS\Notification\Inline::constructFromData( $row );
static::$rows[ $index ] = array( 'notification' => $notification, 'data' => $notification->getData() );
}
catch ( \LogicException $e ) { }
}
}
/* Return */
return static::$rows;
}
/**
* Return the table headers
*
* @param array|NULL $advancedSearchValues Advanced search values
* @return array
*/
public function getHeaders( $advancedSearchValues )
{
return array();
}
}
View File
Whitespace-only changes.