[ticket/11103] The start of an all-encompassing notifications system

This system will take input from various systems to store notifications
and send notifications to users all in one nice extendable system.

This system should act something like the notifications system on
other social networking sites (in that, there is a single location where
a user can see all of their notifications for various events).

PHPBB3-11103
This commit is contained in:
Nathan Guse 2012-09-08 10:49:58 -05:00
parent 7bf598954c
commit b887fcc3d1
9 changed files with 530 additions and 0 deletions

View file

@ -89,6 +89,11 @@ services:
- .%core.php_ext% - .%core.php_ext%
- @cache.driver - @cache.driver
notifications:
class: phpbb_notifications_service
arguments:
- @container
processor.config: processor.config:
class: phpbb_di_processor_ext class: phpbb_di_processor_ext
arguments: arguments:

View file

@ -239,6 +239,7 @@ define('LOG_TABLE', $table_prefix . 'log');
define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts');
define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache'); define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache');
define('MODULES_TABLE', $table_prefix . 'modules'); define('MODULES_TABLE', $table_prefix . 'modules');
define('NOTIFICATIONS_TABLE', $table_prefix . 'notifications');
define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options');
define('POLL_VOTES_TABLE', $table_prefix . 'poll_votes'); define('POLL_VOTES_TABLE', $table_prefix . 'poll_votes');
define('POSTS_TABLE', $table_prefix . 'posts'); define('POSTS_TABLE', $table_prefix . 'posts');

View file

@ -0,0 +1,51 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Base notifications method class
* @package notifications
*/
abstract class phpbb_notifications_method_base implements phpbb_notifications_method_interface
{
protected $phpbb_container;
protected $db;
protected $user;
/**
* notification_id
* item_type
* item_id
*
* by_user_id (one who caused the notification)
* user_id
* time
* unread
*
* data (special serialized field that each notification type can use to store stuff)
*/
protected $data = array();
public function __construct(Symfony\Component\DependencyInjection\ContainerBuilder $phpbb_container, $data = array())
{
// phpBB Container
$this->phpbb_container = $phpbb_container;
// Some common things we're going to use
$this->db = $phpbb_container->get('dbal.conn');
$this->user = $phpbb_container->get('user');
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Email notification method class
* @package notifications
*/
class phpbb_notifications_method_email extends phpbb_notifications_method_base
{
function notify()
{
// email the user
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Base notifications method interface
* @package notifications
*/
interface phpbb_notifications_method_interface
{
}

View file

@ -0,0 +1,197 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Notifications service class
* @package notifications
*/
class phpbb_notifications_service
{
protected $phpbb_container;
protected $db;
/**
* Users loaded from the DB
*
* @var array Array of user data that we've loaded from the DB
*/
protected $users;
/**
* Desired notifications
* unique by (type, type_id, user_id, method)
* if multiple methods are desired, multiple rows will exist.
*
* method of "none" will over-ride any other options
*
* type
* type_id
* user_id
* method
* none (will never receive notifications)
* standard (listed in notifications window
* popup?
* email
* jabber
* sms?
*/
public function __construct(Symfony\Component\DependencyInjection\ContainerBuilder $phpbb_container)
{
$this->phpbb_container = $phpbb_container;
// Some common things we're going to use
$this->db = $phpbb_container->get('dbal.conn');
}
private function get_type_class_name(&$type, $safe = false)
{
if (!$safe)
{
$type = preg_replace('#[^a-z]#', '', $type);
}
return 'phpbb_notifications_type_' . $type;
}
/**
* Load the user's notifications
*
* @param array $options Optional options to control what notifications are loaded
* user_id User id to load notifications for (Default: $user->data['user_id'])
* limit Number of notifications to load (Default: 5)
* start Notifications offset (Default: 0)
*/
public function load_notifications($options = array())
{
$user = $this->phpbb_container->get('user');
// Merge default options
$options = array_merge(array(
'user_id' => $user->data['user_id'],
'limit' => 5,
'start' => 0,
), $options);
$notifications = $user_ids = array();
$sql = 'SELECT * FROM ' . NOTIFICATIONS_TABLE . '
WHERE user_id = ' . (int) $options['user_id'];
$result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']);
while ($row = $this->db->sql_fetchrow($result))
{
$type_class_name = $this->get_type_class_name($row['type'], true);
$notification = new $type_class_name($this->phpbb_container, $row);
$notification->users($this->users);
$user_ids = array_merge($user_ids, $notification->users_to_query());
$notifications[] = $notification();
}
$this->db->sql_freeresult($result);
// Load the users
$user_ids = array_unique($user_ids);
// @todo do not load users we already have in $this->users
if (sizeof($user_ids))
{
// @todo do not select everything
$sql = 'SELECT * FROM ' . USERS_TABLE . '
WHERE ' . $this->db->sql_in_set('user_id', $user_ids);
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$this->users[$row['user_id']] = $row;
}
$this->db->sql_freeresult($result);
}
return $notifications;
}
public function add_notifications($type, $data)
{
$type_class_name = $this->get_type_class_name($type);
$notification_objects = array(); // 'user_id' => object
$methods = $new_rows = array();
// find out which users want to receive this type of notification
$sql = 'SELECT user_id FROM ' . USERS_TABLE . '
WHERE ' . $this->db->sql_in_set('user_id', array(2));
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$row['method'] = '';
$notification = new $type_class_name($this->phpbb_container);
$notification->user_id = $row['user_id'];
$new_rows[] = $notification->create_insert_array($data);
// setup the notification methods and add the notification to the queue
if ($row['method'])
{
if (!isset($methods[$row['method']]))
{
$method_class_name = 'phpbb_notifications_method_' . $row['method'];
$methods[$row['method']] = new $$method_class_name();
}
$methods[$row['method']]->add_to_queue($notification);
}
}
// insert into the db
$this->db->sql_multi_insert(NOTIFICATIONS_TABLE, $new_rows);
// run the queue for each method to send notifications
foreach ($methods as $method)
{
$method->run_queue();
}
}
public function update_notifications($type, $type_id, $data)
{
$type_class_name = $this->get_type_class_name($type);
$object = new $$type_class($this->phpbb_container);
$update = $object->update($data);
$sql = 'UPDATE ' . NOTIFICATIONS_TABLE . '
SET ' . $this->db->sql_build_array('UPDATE', $update) . "
WHERE type = '" . $this->db->sql_escape($type) . "'
AND type_id = " . (int) $type_id;
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$object = new $type_class_name($this->phpbb_container, $row);
$object->update($data);
$update_rows[] = $object->getForUpdate();
}
}
}

View file

@ -0,0 +1,128 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Base notifications class
* @package notifications
*/
abstract class phpbb_notifications_type_base implements phpbb_notifications_type_interface
{
protected $phpbb_container;
protected $db;
protected $phpbb_root_path;
protected $php_ext;
protected $users;
/**
* Indentification data
* notification_id
* item_type
* item_id
* user_id
* unread
*
* time
* data (special serialized field that each notification type can use to store stuff)
*
* @var array $data Notification row from the database
* This must be private, all interaction should use __get(), __set()
*/
private $data = array();
public function __construct(Symfony\Component\DependencyInjection\ContainerBuilder $phpbb_container, $data = array())
{
// phpBB Container
$this->phpbb_container = $phpbb_container;
// Some common things we're going to use
$this->db = $phpbb_container->get('dbal.conn');
$this->phpbb_root_path = $phpbb_container->getParameter('core.root_path');
$this->php_ext = $phpbb_container->getParameter('core.php_ext');
// The row from the database (unless this is a new notification we're going to add)
$this->data = $data;
$this->data['data'] = (isset($this->data['data'])) ? unserialize($this->data['data']) : array();
}
public function __get($name)
{
return $this->data[$name];
}
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function get_data($name)
{
return $this->data['data'][$name];
}
public function set_data($name, $value)
{
$this->data['data'][$name] = $value;
}
public function users(&$users)
{
$this->users = $users;
}
/**
* Output the notification to the template
*
* @param array $options Array of options
* template_block Template block name to output to (Default: notifications)
*/
public function display($options = array())
{
$template = $this->phpbb_container->get('template');
$user = $this->phpbb_container->get('user');
// Merge default options
$options = array_merge(array(
'template_block' => 'notifications',
), $options);
$template->assign_block_vars($options['template_block'], array(
'TITLE' => $this->get_title(),
'URL' => $this->get_url(),
'TIME' => $user->format_date($this->time),
'ID' => $this->notification_id,
'UNREAD' => $this->unread,
));
}
public function create_insert_array($data)
{
// Defaults
$data = array_merge(array(
'item_type' => $this->get_type(),
'time' => time(),
'unread' => true,
'data' => array(),
), $this->data);
$data['data'] = serialize($data['data']);
return $data;
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Base notifications interface
* @package notifications
*/
interface phpbb_notifications_type_interface
{
public function get_type();
public function get_title();
public function get_url();
public function create_insert_array($data);
}

View file

@ -0,0 +1,65 @@
<?php
/**
*
* @package notifications
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Post notifications class
* @package notifications
*/
class phpbb_notifications_type_post extends phpbb_notifications_type_base
{
/**
* Get the type of notification this is
* phpbb_notifications_type_
*/
public function get_type()
{
return 'post';
}
public function get_title()
{
return $this->data['post_username'] . ' posted in the topic ' . censor_text($this->data['topic_title']);
}
public function get_url()
{
return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}");
}
/**
* Users needed to query before this notification can be displayed
*
* @return array Array of user_ids
*/
public function users_to_query()
{
return array($this->data['poster_id']);
}
public function create_insert_array($post)
{
$this->item_id = $post['post_id'];
$this->set_data('poster_id', $post['poster_id']);
$this->set_data('topic_title', $post['topic_title']);
$this->set_data('post_username', $post['post_username']);
return parent::create_insert_array($post);
}
}