diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index 6c3cea3dbc..41f80cdd30 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -206,8 +206,10 @@ services: - '@user_loader' - '@user' - '@config' + - '@dbal.conn' - '%core.root_path%' - '%core.php_ext%' + - '%tables.notification_emails%' tags: - { name: notification.method } diff --git a/phpBB/config/default/container/tables.yml b/phpBB/config/default/container/tables.yml index f3c2282de9..2117794b43 100644 --- a/phpBB/config/default/container/tables.yml +++ b/phpBB/config/default/container/tables.yml @@ -36,6 +36,7 @@ parameters: tables.migrations: '%core.table_prefix%migrations' tables.moderator_cache: '%core.table_prefix%moderator_cache' tables.modules: '%core.table_prefix%modules' + tables.notification_emails: '%core.table_prefix%notification_emails' tables.notification_types: '%core.table_prefix%notification_types' tables.notifications: '%core.table_prefix%notifications' tables.poll_options: '%core.table_prefix%poll_options' diff --git a/phpBB/phpbb/db/migration/data/v33x/add_notification_emails_table.php b/phpBB/phpbb/db/migration/data/v33x/add_notification_emails_table.php new file mode 100644 index 0000000000..3bbf7d8db3 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/add_notification_emails_table.php @@ -0,0 +1,53 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class add_notification_emails_table extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return [ + '\phpbb\db\migration\data\v330\v330', + ]; + } + + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'notification_emails'); + } + + public function update_schema() + { + return [ + 'add_tables' => [ + $this->table_prefix . 'notification_emails' => [ + 'COLUMNS' => [ + 'notification_type_id' => ['USINT', 0], + 'item_id' => ['ULINT', 0], + 'item_parent_id' => ['ULINT', 0], + 'user_id' => ['ULINT', 0], + ], + 'PRIMARY_KEY' => ['notification_type_id', 'item_id', 'item_parent_id', 'user_id'], + ], + ], + ]; + } + + public function revert_schema() + { + return [ + 'drop_tables' => [$this->table_prefix . 'notification_emails'], + ]; + } +} diff --git a/phpBB/phpbb/notification/method/email.php b/phpBB/phpbb/notification/method/email.php index 6376d13dc7..a9717e70df 100644 --- a/phpBB/phpbb/notification/method/email.php +++ b/phpBB/phpbb/notification/method/email.php @@ -28,21 +28,31 @@ class email extends \phpbb\notification\method\messenger_base /** @var \phpbb\config\config */ protected $config; + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var string Notification emails table */ + protected $notification_emails_table; + /** * Notification Method email Constructor * * @param \phpbb\user_loader $user_loader * @param \phpbb\user $user * @param \phpbb\config\config $config + * @param \phpbb\db\driver\driver_interface $db * @param string $phpbb_root_path * @param string $php_ext + * @param string $notification_emails_table */ - public function __construct(\phpbb\user_loader $user_loader, \phpbb\user $user, \phpbb\config\config $config, $phpbb_root_path, $php_ext) + public function __construct(\phpbb\user_loader $user_loader, \phpbb\user $user, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, $phpbb_root_path, $php_ext, $notification_emails_table) { parent::__construct($user_loader, $phpbb_root_path, $php_ext); $this->user = $user; $this->config = $config; + $this->db = $db; + $this->notification_emails_table = $notification_emails_table; } /** @@ -68,11 +78,87 @@ class email extends \phpbb\notification\method\messenger_base return parent::is_available($notification_type) && $this->config['email_enable'] && !empty($this->user->data['user_email']); } + /** + * {@inheritdoc} + */ + public function get_notified_users($notification_type_id, array $options) + { + $notified_users = []; + + $sql = 'SELECT user_id + FROM ' . $this->notification_emails_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id . + (isset($options['item_id']) ? ' AND item_id = ' . (int) $options['item_id'] : '') . + (isset($options['item_parent_id']) ? ' AND item_parent_id = ' . (int) $options['item_parent_id'] : '') . + (isset($options['user_id']) ? ' AND user_id = ' . (int) $options['user_id'] : ''); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $notified_users[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + + return $notified_users; + } + /** * Parse the queue and notify the users */ public function notify() { + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notification_emails_table); + + /** @var \phpbb\notification\type\type_interface $notification */ + foreach ($this->queue as $notification) + { + $data = self::clean_data($notification->get_insert_array()); + $insert_buffer->insert($data); + } + + $insert_buffer->flush(); + return $this->notify_using_messenger(NOTIFY_EMAIL); } + + /** + * {@inheritdoc} + */ + public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) + { + $sql = 'DELETE FROM ' . $this->notification_emails_table . ' + WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : '1=1') . + ($user_id !== false ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . + ($item_id !== false ? ' AND ' . $this->db->sql_in_set('item_id', $item_id) : ''); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) + { + $sql = 'DELETE FROM ' . $this->notification_emails_table . ' + WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : '1=1') . + ($user_id !== false ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . + ($item_parent_id !== false ? ' AND ' . $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : ''); + $this->db->sql_query($sql); + } + + /** + * Clean data to contain only what we need for email notifications table + * + * @param array $data Notification data + * @return array Cleaned notification data + */ + static public function clean_data(array $data) + { + $row = [ + 'notification_type_id' => null, + 'item_id' => null, + 'item_parent_id' => null, + 'user_id' => null, + ]; + + return array_intersect_key($data, $row); + } } diff --git a/phpBB/phpbb/notification/method/method_interface.php b/phpBB/phpbb/notification/method/method_interface.php index c2e4940485..aa13bfde69 100644 --- a/phpBB/phpbb/notification/method/method_interface.php +++ b/phpBB/phpbb/notification/method/method_interface.php @@ -41,7 +41,7 @@ interface method_interface /** * Return the list of the users already notified * - * @param int $notification_type_id Type of the notification + * @param int $notification_type_id ID of the notification type * @param array $options * @return array User */ diff --git a/phpBB/phpbb/notification/type/post.php b/phpBB/phpbb/notification/type/post.php index f0e938d3ce..64ed91a5f5 100644 --- a/phpBB/phpbb/notification/type/post.php +++ b/phpBB/phpbb/notification/type/post.php @@ -457,6 +457,12 @@ class post extends \phpbb\notification\type\base } $data_array = array_merge(array( + 'poster_id' => $post['poster_id'], + 'topic_title' => $post['topic_title'], + 'post_subject' => $post['post_subject'], + 'post_username' => $post['post_username'], + 'forum_id' => $post['forum_id'], + 'forum_name' => $post['forum_name'], 'post_time' => $post['post_time'], 'post_id' => $post['post_id'], 'topic_id' => $post['topic_id'] diff --git a/tests/notification/base.php b/tests/notification/base.php index f7faf50d68..1cc5893ebd 100644 --- a/tests/notification/base.php +++ b/tests/notification/base.php @@ -19,7 +19,9 @@ require_once dirname(__FILE__) . '/manager_helper.php'; abstract class phpbb_tests_notification_base extends phpbb_database_test_case { - protected $notifications, $db, $container, $user, $config, $auth, $cache; + /** @var phpbb_notification_manager_helper */ + protected $notifications; + protected $db, $container, $user, $config, $auth, $cache; protected function get_notification_types() { @@ -103,6 +105,7 @@ abstract class phpbb_tests_notification_base extends phpbb_database_test_case $phpbb_container->setParameter('tables.notifications', 'phpbb_notifications'); $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); + $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); $this->notifications = new phpbb_notification_manager_helper( array(), diff --git a/tests/notification/fixtures/email_notification.type.post.xml b/tests/notification/fixtures/email_notification.type.post.xml new file mode 100644 index 0000000000..8787f5bbe5 --- /dev/null +++ b/tests/notification/fixtures/email_notification.type.post.xml @@ -0,0 +1,217 @@ + + + + forum_id + user_id + notify_status + + 1 + 6 + 0 + + + 1 + 7 + 0 + + + 1 + 8 + 0 + +
+ + notification_id + notification_type_id + user_id + item_id + item_parent_id + notification_read + notification_data + + 1 + 1 + 5 + 1 + 1 + 0 + + + + 2 + 1 + 8 + 1 + 1 + 0 + + +
+ + notification_type_id + notification_type_name + notification_type_enabled + + 1 + notification.type.post + 1 + +
+ + post_id + topic_id + forum_id + post_text + + 1 + 1 + 1 + + +
+ + topic_id + forum_id + + 1 + 1 + + + 2 + 1 + +
+ + topic_id + user_id + notify_status + + 1 + 2 + 0 + + + 2 + 2 + 0 + + + 1 + 3 + 0 + + + 1 + 4 + 0 + + + 1 + 5 + 0 + + + 1 + 6 + 0 + +
+ + user_id + username_clean + user_permissions + user_sig + + 2 + poster + + + + + 3 + test + + + + + 4 + unauthorized + + + + + 5 + notified + + + + + 6 + disabled + + + + + 7 + default + + + +
+ + item_type + item_id + user_id + method + notify + + notification.type.post + 0 + 2 + notification.method.email + 1 + + + notification.type.post + 0 + 3 + notification.method.email + 1 + + + notification.type.post + 0 + 4 + notification.method.email + 1 + + + notification.type.post + 0 + 5 + notification.method.email + 1 + + + notification.type.post + 0 + 6 + notification.method.email + 1 + + + notification.type.post + 0 + 7 + notification.method.email + 1 + + + notification.type.post + 0 + 8 + notification.method.email + 1 + +
+
diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php new file mode 100644 index 0000000000..b4734c872d --- /dev/null +++ b/tests/notification/notification_method_email_test.php @@ -0,0 +1,229 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +require_once dirname(__FILE__) . '/base.php'; + +class notification_method_email_test extends phpbb_tests_notification_base +{ + /** @var \phpbb\notification\method\email */ + protected $notification_method_email; + + public function getDataSet() + { + return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/email_notification.type.post.xml'); + } + + protected function get_notification_methods() + { + return [ + 'notification.method.email', + ]; + } + + protected function setUp() : void + { + phpbb_database_test_case::setUp(); + + global $phpbb_root_path, $phpEx; + + include_once(__DIR__ . '/ext/test/notification/type/test.' . $phpEx); + + global $db, $config, $user, $auth, $cache, $phpbb_container; + + $db = $this->db = $this->new_dbal(); + $config = $this->config = new \phpbb\config\config([ + 'allow_privmsg' => true, + 'allow_bookmarks' => true, + 'allow_topic_notify' => true, + 'allow_forum_notify' => true, + 'allow_board_notifications' => true, + ]); + $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); + $lang = new \phpbb\language\language($lang_loader); + $user = new \phpbb\user($lang, '\phpbb\datetime'); + $this->user = $user; + $this->user_loader = new \phpbb\user_loader($this->db, $phpbb_root_path, $phpEx, 'phpbb_users'); + $auth = $this->auth = new phpbb_mock_notifications_auth(); + $cache_driver = new \phpbb\cache\driver\dummy(); + $cache = $this->cache = new \phpbb\cache\service( + $cache_driver, + $this->config, + $this->db, + $phpbb_root_path, + $phpEx + ); + + $this->phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + + $phpbb_container = $this->container = new ContainerBuilder(); + $loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__ . '/fixtures')); + $loader->load('services_notification.yml'); + $phpbb_container->set('user_loader', $this->user_loader); + $phpbb_container->set('user', $user); + $phpbb_container->set('language', $lang); + $phpbb_container->set('config', $this->config); + $phpbb_container->set('dbal.conn', $this->db); + $phpbb_container->set('auth', $auth); + $phpbb_container->set('cache.driver', $cache_driver); + $phpbb_container->set('cache', $cache); + $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); + $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); + $phpbb_container->setParameter('core.root_path', $phpbb_root_path); + $phpbb_container->setParameter('core.php_ext', $phpEx); + $phpbb_container->setParameter('tables.notifications', 'phpbb_notifications'); + $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); + $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); + $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); + + $this->notification_method_email = $this->getMockBuilder('\phpbb\notification\method\email') + ->setConstructorArgs([ + $phpbb_container->get('user_loader'), + $phpbb_container->get('user'), + $phpbb_container->get('config'), + $phpbb_container->get('dbal.conn'), + $phpbb_root_path, + $phpEx, + $phpbb_container->getParameter('tables.notification_emails') + ]) + ->setMethods(['notify_using_messenger']) + ->getMock(); + $notification_method_email = $this->notification_method_email; + + $class = new ReflectionClass($notification_method_email); + $empty_queue_method = $class->getMethod('empty_queue'); + $empty_queue_method->setAccessible(true); + + $this->notification_method_email->method('notify_using_messenger') + ->will($this->returnCallback(function () use ($notification_method_email, $empty_queue_method) { + $empty_queue_method->invoke($notification_method_email); + })); + + $phpbb_container->set('notification.method.email', $this->notification_method_email); + + $this->notifications = new phpbb_notification_manager_helper( + array(), + array(), + $this->container, + $this->user_loader, + $this->phpbb_dispatcher, + $this->db, + $this->cache, + $lang, + $this->user, + 'phpbb_notification_types', + 'phpbb_user_notifications' + ); + + $phpbb_container->set('notification_manager', $this->notifications); + $phpbb_container->compile(); + + $this->notifications->setDependencies($this->auth, $this->config); + + $types = array(); + foreach ($this->get_notification_types() as $type) + { + $class = $this->build_type($type); + + $types[$type] = $class; + } + + $this->notifications->set_var('notification_types', $types); + + $methods = array(); + foreach ($this->get_notification_methods() as $method) + { + $class = $this->container->get($method); + + $methods[$method] = $class; + } + + $this->notifications->set_var('notification_methods', $methods); + } + + public function data_notification_email() + { + return [ + [ + [ + 'forum_id' => '1', + 'post_id' => '2', + 'topic_id' => '1', + ], + [ + 2 => ['user_id' => '2'], + 3 => ['user_id' => '3'], + 4 => ['user_id' => '4'], + 5 => ['user_id' => '5'], + 6 => ['user_id' => '6'], + 7 => ['user_id' => '7'], + 8 => ['user_id' => '8'] + ], + ], + [ + [ + 'forum_id' => '1', + 'post_id' => '4', + 'topic_id' => '2', + ], + [ + 2 => ['user_id' => '2'], + 6 => ['user_id' => '6'], + 7 => ['user_id' => '7'], + 8 => ['user_id' => '8'], + ], + ], + [ + [ + 'forum_id' => '2', + 'post_id' => '6', + 'topic_id' => '3', + ], + [ + ], + ], + ]; + } + + /** + * @dataProvider data_notification_email + */ + public function test_notification_email($post_data, $expected_users) + { + $post_data = array_merge(['post_time' => 1349413322], $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_email->get_notified_users($this->notifications->get_notification_type_id('notification.type.post'), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + $this->notifications->add_notifications('notification.type.post', $post_data); + + $notified_users = $this->notification_method_email->get_notified_users($this->notifications->get_notification_type_id('notification.type.post'), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + $post_data['post_id']++; + $notification_options['item_id'] = $post_data['post_id']; + $post_data['post_time'] = 1349413323; + + $this->notifications->add_notifications('notification.type.post', $post_data); + + $notified_users2 = $this->notification_method_email->get_notified_users($this->notifications->get_notification_type_id('notification.type.post'), $notification_options); + $this->assertEquals($expected_users, $notified_users2, 'Assert that expected users stay the same after replying to same topic'); + } +} diff --git a/tests/notification/submit_post_base.php b/tests/notification/submit_post_base.php index fe0e937837..11852473d1 100644 --- a/tests/notification/submit_post_base.php +++ b/tests/notification/submit_post_base.php @@ -134,6 +134,7 @@ abstract class phpbb_notification_submit_post_base extends phpbb_database_test_c $phpbb_container->setParameter('tables.notifications', 'phpbb_notifications'); $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); + $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); $phpbb_container->set('content.visibility', new \phpbb\content_visibility($auth, $config, $phpbb_dispatcher, $db, $user, $phpbb_root_path, $phpEx, FORUMS_TABLE, POSTS_TABLE, TOPICS_TABLE, USERS_TABLE)); $phpbb_container->compile();