diff --git a/config/wpn_ucp.yml b/config/wpn_ucp.yml index f340196..eb39d21 100644 --- a/config/wpn_ucp.yml +++ b/config/wpn_ucp.yml @@ -13,3 +13,7 @@ phpbb_webpushnotifications_ucp_push_subscribe_controller: phpbb_webpushnotifications_ucp_push_unsubscribe_controller: path: /push/unsubscribe defaults: { _controller: phpbb.wpn.ucp.controller.webpush:unsubscribe } + +phpbb_webpushnotifications_ucp_push_toggle_popup_controller: + path: /push/toggle-popup + defaults: { _controller: phpbb.wpn.ucp.controller.webpush:toggle_popup } diff --git a/controller/manifest.php b/controller/manifest.php index 0201769..968a84e 100644 --- a/controller/manifest.php +++ b/controller/manifest.php @@ -44,7 +44,7 @@ public function handle(): JsonResponse { // Get the board URL and extract the path component $board_url = generate_board_url(); - $board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : (parse_url($board_url)['path'] ?? ''); + $board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : (parse_url($board_url, PHP_URL_PATH) ?? ''); // Ensure path ends with '/' for PWA scope $scope = rtrim($board_path, '/\\') . '/'; diff --git a/language/en/webpushnotifications_module_ucp.php b/language/en/webpushnotifications_module_ucp.php index 8a481e3..cf5e237 100644 --- a/language/en/webpushnotifications_module_ucp.php +++ b/language/en/webpushnotifications_module_ucp.php @@ -49,8 +49,10 @@ 'NOTIFY_WEBPUSH_DROPDOWN_TITLE' => 'Visit notifications settings to set your preferred push notifications.', 'NOTIFY_WEBPUSH_DENIED' => 'You have denied notifications from this site. To enable push notifications, allow notifications from this site in your browser settings.', 'NOTIFY_WEBPUSH_NOT_SUPPORTED' => 'Push notifications not supported', - 'NOTIFY_WEBPUSH_POPUP_TITLE' => 'Allow browser notifications?', + 'NOTIFY_WEBPUSH_POPUP_TITLE' => 'Allow browser notifications', 'NOTIFY_WEBPUSH_POPUP_MESSAGE' => 'We would like to send you browser notifications for replies, private messages, and relevant forum activity. Optional — you can manage these settings at any time.', 'NOTIFY_WEBPUSH_POPUP_ALLOW' => 'Allow', 'NOTIFY_WEBPUSH_POPUP_DENY' => 'Deny', + 'NOTIFY_WEBPUSH_POPUP_DISABLE' => 'Disable “Allow Browser Notifications” prompts', + 'NOTIFY_WEBPUSH_POPUP_DISABLE_EXPLAIN' => 'Toggle this on to stop us from asking you to enable web push notifications on any of your devices. Note that we won’t be able to alert you if your web push notifications ever become disabled.', ]); diff --git a/language/ru/webpushnotifications_module_ucp.php b/language/ru/webpushnotifications_module_ucp.php index c670739..ae6d364 100644 --- a/language/ru/webpushnotifications_module_ucp.php +++ b/language/ru/webpushnotifications_module_ucp.php @@ -49,8 +49,10 @@ 'NOTIFY_WEBPUSH_DROPDOWN_TITLE' => 'Посетите настройки уведомлений, чтобы установить предпочтительные типы браузерных уведомлений.', 'NOTIFY_WEBPUSH_DENIED' => 'Вы запретили браузерные уведомления для даного сайта. Для того, чтобы подписаться, необходимо их разрешить в настройках браузера.', 'NOTIFY_WEBPUSH_NOT_SUPPORTED' => 'Не поддерживается', - 'NOTIFY_WEBPUSH_POPUP_TITLE' => 'Включить браузерные уведомления?', + 'NOTIFY_WEBPUSH_POPUP_TITLE' => 'Включить браузерные уведомления', 'NOTIFY_WEBPUSH_POPUP_MESSAGE' => 'Браузерные уведомления позволяют быстро получать информацию о новых ответах, личных сообщениях и других активностях на данной конференции. Функцию можно отключить или включить в любое время в настройках уведомлений в Личном разделе.', 'NOTIFY_WEBPUSH_POPUP_ALLOW' => 'Включить', 'NOTIFY_WEBPUSH_POPUP_DENY' => 'Отклонить', + 'NOTIFY_WEBPUSH_POPUP_DISABLE' => 'Отключить запросы «Включить браузерные уведомления»', + 'NOTIFY_WEBPUSH_POPUP_DISABLE_EXPLAIN' => 'Включите этот параметр, чтобы мы больше не спрашивали вас о включении веб-push-уведомлений на любом из ваших устройств. Обратите внимание, что в этом случае мы не сможем предупредить вас, если веб-push-уведомления будут отключены.', ]); diff --git a/migrations/add_user_popup_preference.php b/migrations/add_user_popup_preference.php new file mode 100644 index 0000000..d6649d1 --- /dev/null +++ b/migrations/add_user_popup_preference.php @@ -0,0 +1,48 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\webpushnotifications\migrations; + +use phpbb\db\migration\migration; + +class add_user_popup_preference extends migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_wpn_popup_disabled'); + } + + public static function depends_on() + { + return ['\phpbb\webpushnotifications\migrations\add_popup_prompt']; + } + + public function update_schema() + { + return [ + 'add_columns' => [ + $this->table_prefix . 'users' => [ + 'user_wpn_popup_disabled' => ['BOOL', 0], + ], + ], + ]; + } + + public function revert_schema() + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'users' => [ + 'user_wpn_popup_disabled', + ], + ], + ]; + } +} diff --git a/notification/method/webpush.php b/notification/method/webpush.php index b8eee4f..162c637 100644 --- a/notification/method/webpush.php +++ b/notification/method/webpush.php @@ -390,11 +390,13 @@ public function get_ucp_template_data(helper $controller_helper, form_helper $fo 'NOTIFICATIONS_WEBPUSH_ENABLE' => ($this->config['load_notifications'] && $this->config['allow_board_notifications'] && $this->config['wpn_webpush_dropdown_subscribe']) || stripos($this->user->page['page'], 'notification_options'), 'U_WEBPUSH_SUBSCRIBE' => $controller_helper->route('phpbb_webpushnotifications_ucp_push_subscribe_controller'), 'U_WEBPUSH_UNSUBSCRIBE' => $controller_helper->route('phpbb_webpushnotifications_ucp_push_unsubscribe_controller'), + 'U_WEBPUSH_TOGGLE_POPUP' => $controller_helper->route('phpbb_webpushnotifications_ucp_push_toggle_popup_controller'), 'VAPID_PUBLIC_KEY' => $this->config['wpn_webpush_vapid_public'], 'U_WEBPUSH_WORKER_URL' => $controller_helper->route('phpbb_webpushnotifications_ucp_push_worker_controller'), 'SUBSCRIPTIONS' => $subscriptions, 'WEBPUSH_FORM_TOKENS' => $form_helper->get_form_tokens(\phpbb\webpushnotifications\ucp\controller\webpush::FORM_TOKEN_UCP), - 'S_WEBPUSH_POPUP_PROMPT' => $this->config['wpn_webpush_popup_prompt'] && $this->user->id() != ANONYMOUS && $this->user->data['user_type'] != USER_IGNORE, + 'S_WEBPUSH_POPUP_PROMPT' => $this->config['wpn_webpush_popup_prompt'] && $this->user->id() != ANONYMOUS && $this->user->data['user_type'] != USER_IGNORE && !($this->user->data['user_wpn_popup_disabled'] ?? 0), + 'S_WEBPUSH_POPUP_DISABLED' => $this->user->data['user_wpn_popup_disabled'] ?? 0, ]; } diff --git a/styles/all/template/ucp_notifications_webpush.html b/styles/all/template/ucp_notifications_webpush.html index 35a0298..2967f1f 100644 --- a/styles/all/template/ucp_notifications_webpush.html +++ b/styles/all/template/ucp_notifications_webpush.html @@ -3,6 +3,7 @@ serviceWorkerUrl: '{{ U_WEBPUSH_WORKER_URL }}', subscribeUrl: '{{ U_WEBPUSH_SUBSCRIBE }}', unsubscribeUrl: '{{ U_WEBPUSH_UNSUBSCRIBE }}', + togglePopupUrl: '{{ U_WEBPUSH_TOGGLE_POPUP }}', ajaxErrorTitle: '{{ lang_js('AJAX_ERROR_TITLE') }}', vapidPublicKey: '{{ VAPID_PUBLIC_KEY }}', formTokens: { diff --git a/styles/all/template/webpush.js b/styles/all/template/webpush.js index 3481bb9..440406c 100644 --- a/styles/all/template/webpush.js +++ b/styles/all/template/webpush.js @@ -33,6 +33,15 @@ function PhpbbWebpush() { /** @type {HTMLElement} Unsubscribe button */ let unsubscribeButton; + /** @type {HTMLElement} Toggle popup button */ + let togglePopupButton; + + /** @type {string} URL to toggle popup prompt preference */ + let togglePopupUrl = ''; + + /** @type {function} Escape key handler for popup */ + let popupEscapeHandler; + /** * Init function for phpBB Web Push * @type {array} options @@ -41,6 +50,7 @@ function PhpbbWebpush() { serviceWorkerUrl = options.serviceWorkerUrl; subscribeUrl = options.subscribeUrl; unsubscribeUrl = options.unsubscribeUrl; + togglePopupUrl = options.togglePopupUrl; this.formTokens = options.formTokens; subscriptions = options.subscriptions; ajaxErrorTitle = options.ajaxErrorTitle; @@ -48,6 +58,12 @@ function PhpbbWebpush() { subscribeButton = document.querySelector('#subscribe_webpush'); unsubscribeButton = document.querySelector('#unsubscribe_webpush'); + togglePopupButton = document.querySelector('#toggle_popup_prompt'); + + // Set up toggle popup button handler if it exists (on UCP settings page) + if (togglePopupButton) { + togglePopupButton.addEventListener('click', togglePopupHandler); + } // Service workers are only supported in secure context if (window.isSecureContext !== true) { @@ -163,7 +179,7 @@ function PhpbbWebpush() { if (allowBtn) { allowBtn.addEventListener('click', (event) => { event.stopPropagation(); - popup.style.display = 'none'; + hidePopup(popup); subscribeButtonHandler(event).catch(error => { console.error('Subscription handler error:', error); }); @@ -173,7 +189,7 @@ function PhpbbWebpush() { if (denyBtn) { denyBtn.addEventListener('click', (event) => { event.stopPropagation(); - popup.style.display = 'none'; + hidePopup(popup); promptDenied.set(); }); } @@ -181,11 +197,32 @@ function PhpbbWebpush() { if (overlay) { overlay.addEventListener('click', (event) => { if (event.target === overlay) { - popup.style.display = 'none'; + hidePopup(popup); promptDenied.set(); } }); + + popupEscapeHandler = (event) => { + if (event.key === 'Escape') { + hidePopup(popup); + promptDenied.set(); + } + }; + + document.addEventListener('keydown', popupEscapeHandler); + } + } + + /** + * Hide popup + * @param popup + */ + function hidePopup(popup) { + if (popup) { + popup.style.display = 'none'; } + document.removeEventListener('keydown', popupEscapeHandler); + popupEscapeHandler = null; } /** @@ -282,10 +319,7 @@ function PhpbbWebpush() { }); } catch (error) { promptDenied.set(); // deny the prompt on error to prevent repeated prompting - const popup = document.getElementById('wpn_popup_prompt'); - if (popup) { - popup.style.display = 'none'; - } + hidePopup(document.getElementById('wpn_popup_prompt')); console.error('Push subscription error:', error); phpbb.alert(subscribeButton.getAttribute('data-l-err'), error.message || subscribeButton.getAttribute('data-l-unsupported')); } finally { @@ -331,6 +365,55 @@ function PhpbbWebpush() { }); } + /** + * Handler for toggle popup prompt button + * + * @param {Object} event Toggle button push event + */ + function togglePopupHandler(event) { + event.preventDefault(); + + const loadingIndicator = phpbb.loadingIndicator(); + const formData = new FormData(); + formData.append('form_token', phpbb.webpush.formTokens.formToken); + formData.append('creation_time', phpbb.webpush.formTokens.creationTime.toString()); + + fetch(togglePopupUrl, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + body: formData, + }) + .then(response => response.json()) + .then(data => { + loadingIndicator.fadeOut(phpbb.alertTime); + if (data.success) { + // Update toggle icon based on new state + const button = document.getElementById('toggle_popup_prompt'); + if (button) { + const icon = button.querySelector('i'); + if (icon) { + if (data.disabled) { + icon.classList.remove('fa-toggle-off'); + icon.classList.add('fa-toggle-on'); + } else { + icon.classList.remove('fa-toggle-on'); + icon.classList.add('fa-toggle-off'); + } + } + } + if ('form_tokens' in data) { + updateFormTokens(data.form_tokens); + } + } + }) + .catch(error => { + loadingIndicator.fadeOut(phpbb.alertTime); + phpbb.alert(ajaxErrorTitle, error); + }); + } + /** * Handle subscribe response * @@ -343,10 +426,7 @@ function PhpbbWebpush() { updateFormTokens(response.form_tokens); } promptDenied.remove(); - const popup = document.getElementById('wpn_popup_prompt'); - if (popup) { - popup.style.display = 'none'; - } + hidePopup(document.getElementById('wpn_popup_prompt')); } } diff --git a/styles/all/template/webpush_popup.html b/styles/all/template/webpush_popup.html index deb9133..cff48bd 100644 --- a/styles/all/template/webpush_popup.html +++ b/styles/all/template/webpush_popup.html @@ -1,15 +1,15 @@ {% if S_WEBPUSH_POPUP_PROMPT %} -