diff --git a/config/services.yml b/config/services.yml index 4ec48f3..07cd894 100644 --- a/config/services.yml +++ b/config/services.yml @@ -30,7 +30,6 @@ services: arguments: - '@config' - '@dbal.conn' - - '@language' - '@log' - '@user_loader' - '@user' @@ -49,8 +48,11 @@ services: - '@controller.helper' - '@dbal.conn' - '@phpbb.wpn.form_helper' + - '@language' + - '@notification_manager' - '@path_helper' - '@request' + - '@user_loader' - '@user' - '@template.twig.environment' - '%tables.phpbb.wpn.notification_push%' diff --git a/notification/method/webpush.php b/notification/method/webpush.php index 824bfc7..fc7f0c3 100644 --- a/notification/method/webpush.php +++ b/notification/method/webpush.php @@ -14,7 +14,6 @@ use phpbb\config\config; use phpbb\controller\helper; use phpbb\db\driver\driver_interface; -use phpbb\language\language; use phpbb\log\log_interface; use phpbb\notification\method\messenger_base; use phpbb\notification\type\type_interface; @@ -36,9 +35,6 @@ class webpush extends messenger_base implements extended_method_interface /** @var driver_interface */ protected $db; - /** @var language */ - protected $language; - /** @var log_interface */ protected $log; @@ -65,7 +61,6 @@ class webpush extends messenger_base implements extended_method_interface * * @param config $config * @param driver_interface $db - * @param language $language * @param log_interface $log * @param user_loader $user_loader * @param user $user @@ -75,14 +70,13 @@ class webpush extends messenger_base implements extended_method_interface * @param string $notification_webpush_table * @param string $push_subscriptions_table */ - public function __construct(config $config, driver_interface $db, language $language, log_interface $log, user_loader $user_loader, user $user, path_helper $path_helper, + public function __construct(config $config, driver_interface $db, log_interface $log, user_loader $user_loader, user $user, path_helper $path_helper, string $phpbb_root_path, string $php_ext, string $notification_webpush_table, string $push_subscriptions_table) { parent::__construct($user_loader, $phpbb_root_path, $php_ext); $this->config = $config; $this->db = $db; - $this->language = $language; $this->log = $log; $this->user = $user; $this->path_helper = $path_helper; @@ -145,29 +139,15 @@ public function notify() { $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notification_webpush_table); - // Load all users data we want to notify - $notify_users = $this->load_recipients_data(); - /** @var type_interface $notification */ foreach ($this->queue as $notification) { $data = $notification->get_insert_array(); - - // Change notification language if needed only - $recipient_data = $this->user_loader->get_user($notification->user_id); - if ($this->language->get_used_language() !== $recipient_data['user_lang']) - { - $this->language->set_user_language($recipient_data['user_lang'], true); - } - $data += [ - 'push_data' => json_encode([ - 'heading' => $this->config['sitename'], - 'title' => strip_tags($notification->get_title()), - 'text' => strip_tags($notification->get_reference()), - 'url' => htmlspecialchars_decode($notification->get_url()), - 'avatar' => $this->prepare_avatar($notification->get_avatar()), - ]), + 'push_data' => json_encode(array_merge( + $data, + ['notification_type_name' => $notification->get_type()] + )), 'notification_time' => time(), 'push_token' => hash('sha256', random_bytes(32)) ]; @@ -178,13 +158,7 @@ public function notify() $insert_buffer->flush(); - // Restore current user's language if needed only - if ($this->language->get_used_language() !== $this->user->data['user_lang']) - { - $this->language->set_user_language($this->user->data['user_lang'], true); - } - - $this->notify_using_webpush($notify_users); + $this->notify_using_webpush(); return false; } @@ -192,16 +166,33 @@ public function notify() /** * Notify using Web Push * - * @param array $notify_users Array of user ids to notify * @return void */ - protected function notify_using_webpush($notify_users = []): void + protected function notify_using_webpush(): void { if (empty($this->queue)) { return; } + // Load all users we want to notify + $user_ids = []; + foreach ($this->queue as $notification) + { + $user_ids[] = $notification->user_id; + } + + // Do not send push notifications to banned users + if (!function_exists('phpbb_get_banned_user_ids')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + $banned_users = phpbb_get_banned_user_ids($user_ids); + + // Load all the users we need + $notify_users = array_diff($user_ids, $banned_users); + $this->user_loader->load_users($notify_users, [USER_IGNORE]); + // Get subscriptions for users $user_subscription_map = $this->get_user_subscription_map($notify_users); @@ -361,6 +352,14 @@ public static function clean_data(array $data): array return array_intersect_key($data, $row); } + /** + * Get template data for the UCP + * + * @param helper $controller_helper + * @param form_helper $form_helper + * + * @return array + */ public function get_ucp_template_data(helper $controller_helper, form_helper $form_helper): array { $subscription_map = $this->get_user_subscription_map([$this->user->id()]); @@ -461,46 +460,6 @@ protected function clean_expired_subscriptions(array $user_subscription_map, arr $this->remove_subscriptions($remove_subscriptions); } - /** - * Takes an avatar string (usually in full html format already) and extracts the url. - * If the avatar url is a relative path, it's converted to an absolute path. - * - * Converts: - * User avatar - * or User avatar - * into https://myboard.url/path/to/avatar=123456789.gif - * - * @param string $avatar - * @return array 'src' => Absolute path to avatar image - */ - protected function prepare_avatar($avatar): array - { - $pattern = '/src=["\']?([^"\'>]+)["\']?/'; - - preg_match_all($pattern, $avatar, $matches); - - $path = !empty($matches[1]) ? end($matches[1]) : $avatar; - - return ['src' => preg_replace('#^' . preg_quote($this->path_helper->get_web_root_path(), '#') . '#', $this->get_board_url(), $path, 1)]; - } - - /** - * Returns the board url (and caches it in the function) - * - * @return string the generated board url - */ - protected function get_board_url() - { - static $board_url; - - if (empty($board_url)) - { - $board_url = generate_board_url() . '/'; - } - - return $board_url; - } - /** * Set web push padding for endpoint * @@ -523,31 +482,4 @@ protected function set_endpoint_padding(\Minishlink\WebPush\WebPush $web_push, s } } } - - /** - * Load all users data to send notifications - * - * @return array Array of user ids to notify - */ - protected function load_recipients_data(): array - { - $notify_users = $user_ids = []; - foreach ($this->queue as $notification) - { - $user_ids[] = $notification->user_id; - } - - // Do not send push notifications to banned users - if (!function_exists('phpbb_get_banned_user_ids')) - { - include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); - } - $banned_users = phpbb_get_banned_user_ids($user_ids); - - // Load all the users we need - $notify_users = array_diff($user_ids, $banned_users); - $this->user_loader->load_users($notify_users, [USER_IGNORE]); - - return $notify_users; - } } diff --git a/tests/event/listener_test.php b/tests/event/listener_test.php index a26ef00..ce75a8d 100644 --- a/tests/event/listener_test.php +++ b/tests/event/listener_test.php @@ -108,7 +108,6 @@ protected function setUp(): void $this->notification_method_webpush = new \phpbb\webpushnotifications\notification\method\webpush( $this->config, $db, - $this->language, new \phpbb\log\dummy(), $user_loader, $this->user, diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 6829ffe..a60ee31 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -171,7 +171,6 @@ protected function setUp(): void $this->notification_method_webpush = new webpush( $phpbb_container->get('config'), $phpbb_container->get('dbal.conn'), - $phpbb_container->get('language'), $phpbb_container->get('log'), $phpbb_container->get('user_loader'), $phpbb_container->get('user'), diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index ce79538..08b3961 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -14,12 +14,15 @@ use phpbb\controller\helper as controller_helper; use phpbb\db\driver\driver_interface; use phpbb\exception\http_exception; +use phpbb\language\language; +use phpbb\notification\manager; use phpbb\webpushnotifications\form\form_helper; use phpbb\webpushnotifications\json\sanitizer as json_sanitizer; use phpbb\path_helper; use phpbb\request\request_interface; use phpbb\symfony_request; use phpbb\user; +use phpbb\user_loader; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Twig\Environment; @@ -44,12 +47,21 @@ class webpush /** @var form_helper */ protected $form_helper; + /** @var language */ + protected $language; + + /** @var manager */ + protected $notification_manager; + /** @var path_helper */ protected $path_helper; /** @var request_interface */ protected $request; + /** @var user_loader */ + protected $user_loader; + /** @var user */ protected $user; @@ -69,22 +81,28 @@ class webpush * @param controller_helper $controller_helper * @param driver_interface $db * @param form_helper $form_helper + * @param language $language + * @param manager $notification_manager * @param path_helper $path_helper * @param request_interface $request + * @param user_loader $user_loader * @param user $user * @param Environment $template * @param string $notification_webpush_table * @param string $push_subscriptions_table */ - public function __construct(config $config, controller_helper $controller_helper, driver_interface $db, form_helper $form_helper, path_helper $path_helper, - request_interface $request, user $user, Environment $template, string $notification_webpush_table, string $push_subscriptions_table) + public function __construct(config $config, controller_helper $controller_helper, driver_interface $db, form_helper $form_helper, language $language, manager $notification_manager, + path_helper $path_helper, request_interface $request, user_loader $user_loader, user $user, Environment $template, string $notification_webpush_table, string $push_subscriptions_table) { $this->config = $config; $this->controller_helper = $controller_helper; $this->db = $db; $this->form_helper = $form_helper; + $this->language = $language; + $this->notification_manager = $notification_manager; $this->path_helper = $path_helper; $this->request = $request; + $this->user_loader = $user_loader; $this->user = $user; $this->template = $template; $this->notification_webpush_table = $notification_webpush_table; @@ -105,10 +123,9 @@ public function notification(): JsonResponse $notification_data = $this->get_user_notifications(); - // Decode and return data if everything is fine; update url paths and decode message emoji + // Decode and return data if everything is fine $data = json_decode($notification_data, true); $data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : ''; - $data['text'] = isset($data['text']) ? html_entity_decode($data['text'], ENT_NOQUOTES, 'UTF-8') : ''; return new JsonResponse($data); } @@ -143,7 +160,7 @@ private function get_user_notifications(): string $notification_data = $this->db->sql_fetchfield('push_data'); $this->db->sql_freeresult($result); - return $notification_data; + return $this->get_notification_data($notification_data); } /** @@ -174,23 +191,62 @@ private function get_anonymous_notifications(): string $push_token = $notification_row['push_token']; // Check if passed push token is valid - $sql = 'SELECT user_form_salt + $sql = 'SELECT user_form_salt, user_lang FROM ' . USERS_TABLE . ' WHERE user_id = ' . (int) $user_id; $result = $this->db->sql_query($sql); - $user_form_token = $this->db->sql_fetchfield('user_form_salt'); + $row = $this->db->sql_fetchrow($result); $this->db->sql_freeresult($result); + $user_form_token = $row['user_form_salt']; + $user_lang = $row['user_lang']; + $expected_push_token = hash('sha256', $user_form_token . $push_token); if ($expected_push_token === $token) { - return $notification_data; + if ($user_lang !== $this->language->get_used_language()) + { + $this->language->set_user_language($user_lang, true); + } + return $this->get_notification_data($notification_data); } } throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); } + /** + * Get notification data for output from json encoded data stored in database + * + * @param string $notification_data Encoded data stored in database + * + * @return string Data for notification output with javascript + */ + private function get_notification_data(string $notification_data): string + { + $row_data = json_decode($notification_data, true); + + // Old notification data is pre-parsed and just needs to be returned + if (isset($row_data['heading'])) + { + return $notification_data; + } + + // Get notification from row_data + $notification = $this->notification_manager->get_item_type_class($row_data['notification_type_name'], $row_data); + + // Load users for notification + $this->user_loader->load_users($notification->users_to_query()); + + return json_encode([ + 'heading' => $this->config['sitename'], + 'title' => strip_tags(html_entity_decode($notification->get_title(), ENT_NOQUOTES, 'UTF-8')), + 'text' => strip_tags(html_entity_decode($notification->get_reference(), ENT_NOQUOTES, 'UTF-8')), + 'url' => htmlspecialchars_decode($notification->get_url()), + 'avatar' => $this->prepare_avatar($notification->get_avatar()), + ]); + } + /** * Handle request to push worker javascript * @@ -201,7 +257,6 @@ private function get_anonymous_notifications(): string */ public function worker(): Response { - // @todo: only work for logged in users, no anonymous & bot $content = $this->template->render('@phpbb_webpushnotifications/push_worker.js.twig', [ 'U_WEBPUSH_GET_NOTIFICATION' => $this->controller_helper->route('phpbb_webpushnotifications_ucp_push_get_notification_controller'), 'ASSETS_VERSION' => $this->config['assets_version'], @@ -219,20 +274,6 @@ public function worker(): Response return $response; } - /** - * Get template variables for subscribe type pages - * - * @return array - */ - protected function get_subscribe_vars(): array - { - return [ - 'U_WEBPUSH_SUBSCRIBE' => $this->controller_helper->route('phpbb_webpushnotifications_ucp_push_subscribe_controller'), - 'U_WEBPUSH_UNSUBSCRIBE' => $this->controller_helper->route('phpbb_webpushnotifications_ucp_push_unsubscribe_controller'), - 'FORM_TOKENS' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), - ]; - } - /** * Check (un)subscribe form for valid link hash * @@ -305,4 +346,44 @@ public function unsubscribe(symfony_request $symfony_request): JsonResponse 'form_tokens' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), ]); } + + /** + * Takes an avatar string (usually in full html format already) and extracts the url. + * If the avatar url is a relative path, it's converted to an absolute path. + * + * Converts: + * User avatar + * or User avatar + * into https://myboard.url/path/to/avatar=123456789.gif + * + * @param string $avatar + * @return array 'src' => Absolute path to avatar image + */ + protected function prepare_avatar($avatar): array + { + $pattern = '/src=["\']?([^"\'>]+)["\']?/'; + + preg_match_all($pattern, $avatar, $matches); + + $path = !empty($matches[1]) ? end($matches[1]) : $avatar; + + return ['src' => preg_replace('#^' . preg_quote($this->path_helper->get_web_root_path(), '#') . '#', $this->get_board_url(), $path, 1)]; + } + + /** + * Returns the board url (and caches it in the function) + * + * @return string the generated board url + */ + protected function get_board_url() + { + static $board_url; + + if (empty($board_url)) + { + $board_url = generate_board_url() . '/'; + } + + return $board_url; + } }