From b887fcc3d180860e3b7fdcb2a70e2cd8a519bea2 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sat, 8 Sep 2012 10:49:58 -0500 Subject: [PATCH] [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 --- phpBB/config/services.yml | 5 + phpBB/includes/constants.php | 1 + phpBB/includes/notifications/method/base.php | 51 +++++ phpBB/includes/notifications/method/email.php | 28 +++ .../notifications/method/interface.php | 24 +++ phpBB/includes/notifications/service.php | 197 ++++++++++++++++++ phpBB/includes/notifications/type/base.php | 128 ++++++++++++ .../includes/notifications/type/interface.php | 31 +++ phpBB/includes/notifications/type/post.php | 65 ++++++ 9 files changed, 530 insertions(+) create mode 100644 phpBB/includes/notifications/method/base.php create mode 100644 phpBB/includes/notifications/method/email.php create mode 100644 phpBB/includes/notifications/method/interface.php create mode 100644 phpBB/includes/notifications/service.php create mode 100644 phpBB/includes/notifications/type/base.php create mode 100644 phpBB/includes/notifications/type/interface.php create mode 100644 phpBB/includes/notifications/type/post.php diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 133a43b77e..b9c697b481 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -89,6 +89,11 @@ services: - .%core.php_ext% - @cache.driver + notifications: + class: phpbb_notifications_service + arguments: + - @container + processor.config: class: phpbb_di_processor_ext arguments: diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 68af41ab20..de289c73dc 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -239,6 +239,7 @@ define('LOG_TABLE', $table_prefix . 'log'); define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache'); define('MODULES_TABLE', $table_prefix . 'modules'); +define('NOTIFICATIONS_TABLE', $table_prefix . 'notifications'); define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); define('POLL_VOTES_TABLE', $table_prefix . 'poll_votes'); define('POSTS_TABLE', $table_prefix . 'posts'); diff --git a/phpBB/includes/notifications/method/base.php b/phpBB/includes/notifications/method/base.php new file mode 100644 index 0000000000..a70f37db95 --- /dev/null +++ b/phpBB/includes/notifications/method/base.php @@ -0,0 +1,51 @@ +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'); + } +} diff --git a/phpBB/includes/notifications/method/email.php b/phpBB/includes/notifications/method/email.php new file mode 100644 index 0000000000..b06e2c018e --- /dev/null +++ b/phpBB/includes/notifications/method/email.php @@ -0,0 +1,28 @@ +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(); + } + } +} diff --git a/phpBB/includes/notifications/type/base.php b/phpBB/includes/notifications/type/base.php new file mode 100644 index 0000000000..959516fd19 --- /dev/null +++ b/phpBB/includes/notifications/type/base.php @@ -0,0 +1,128 @@ +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; + } +} diff --git a/phpBB/includes/notifications/type/interface.php b/phpBB/includes/notifications/type/interface.php new file mode 100644 index 0000000000..ace5ca67da --- /dev/null +++ b/phpBB/includes/notifications/type/interface.php @@ -0,0 +1,31 @@ +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); + } +}