* @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\notification\method; use phpbb\config\config; use phpbb\db\driver\driver_interface; use phpbb\json\sanitizer; use phpbb\notification\type\type_interface; use phpbb\user; use phpbb\user_loader; /** * Web push notification method class * This class handles sending push messages for notifications */ class webpush extends \phpbb\notification\method\messenger_base { /** @var config */ protected $config; /** @var driver_interface */ protected $db; /** @var user */ protected $user; /** @var string Notification web push table */ protected $notification_webpush_table; /** * Notification Method web push constructor * * @param user_loader $user_loader * @param user $user * @param config $config * @param driver_interface $db * @param string $phpbb_root_path * @param string $php_ext * @param string $notification_webpush_table */ public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db, string $phpbb_root_path, string $php_ext, string $notification_webpush_table) { parent::__construct($user_loader, $phpbb_root_path, $php_ext); $this->user = $user; $this->config = $config; $this->db = $db; $this->notification_webpush_table = $notification_webpush_table; } /** * {@inheritDoc} */ public function get_type(): string { return 'notification.method.webpush'; } /** * {@inheritDoc} */ public function is_available(type_interface $notification_type = null): bool { return parent::is_available($notification_type) && $this->config['webpush_enable'] && !empty($this->config['webpush_vapid_public']) && !empty($this->config['webpush_vapid_private']); } /** * {@inheritdoc} */ public function get_notified_users($notification_type_id, array $options): array { $notified_users = []; $sql = 'SELECT user_id FROM ' . $this->notification_webpush_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_webpush_table); /** @var type_interface $notification */ foreach ($this->queue as $notification) { $data = self::clean_data($notification->get_insert_array()); $insert_buffer->insert($data); } $insert_buffer->flush(); $this->notify_using_webpush(); return false; } /** * Notify using web push * * @return void */ protected function notify_using_webpush(): void { if (empty($this->queue)) { return; } // Load all users we want to notify (we need their email address) $user_ids = []; foreach ($this->queue as $notification) { $user_ids[] = $notification->user_id; } // We do not send emails 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 $this->user_loader->load_users(array_diff($user_ids, $banned_users), array(USER_IGNORE)); $web_push = new \Minishlink\WebPush\WebPush(); // Time to go through the queue and send emails /** @var type_interface $notification */ foreach ($this->queue as $notification) { $user = $this->user_loader->get_user($notification->user_id); $user_subscriptions = sanitizer::decode($this->user->data['user_push_subscriptions']); if ($user['user_type'] == USER_INACTIVE && $user['user_inactive_reason'] == INACTIVE_MANUAL || empty($user_subscriptions)) { continue; } // add actual web push data $data['data'] = [ 'body' => $notification->get_title(), 'icon' => '', // @todo: to be filled? 'image' => '', // @todo: to be filled? 'title' => $this->config['sitename'], 'url' => $notification->get_url(), 'user_id' => $notification->user_id, ]; $json_data = json_encode($data); // @todo: start implementing actual web push code foreach ($user_subscriptions as $subscription) { try { $push_subscription = \Minishlink\WebPush\Subscription::create($subscription); $web_push->queueNotification($push_subscription, $json_data); } catch (\ErrorException $exception) { // @todo: decide whether we want to remove invalid subscriptions directly? // Might need too many resources ... } } } // @todo: Try offloading to after request try { $web_push->flush(); } catch (\ErrorException $exception) { // @todo: Add to error log if we can't flush ... } // We're done, empty the queue $this->empty_queue(); } /** * {@inheritdoc} */ public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) { $sql = 'DELETE FROM ' . $this->notification_webpush_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_webpush_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 webpush notifications table * * @param array $data Notification data * @return array Cleaned notification data */ public static 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); } }