From 5c898022c2377208971d6538bb0521c14f1955d4 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 23 May 2026 16:46:51 -0700 Subject: [PATCH 1/8] Implement some PWA Enhancer features --- acp/wpn_acp_info.php | 5 + acp/wpn_acp_module.php | 170 ++++++++++++++ adm/style/event/acp_overall_footer_after.html | 18 -- adm/style/wpn_acp_pwa.html | 55 +++++ config/services.yml | 3 +- controller/manifest.php | 3 + event/listener.php | 202 +++-------------- language/en/common.php | 3 + language/en/info_acp_webpushnotifications.php | 1 + .../en/webpushnotifications_common_acp.php | 53 ----- .../en/webpushnotifications_module_acp.php | 22 +- language/ru/common.php | 3 + language/ru/info_acp_webpushnotifications.php | 1 + .../ru/webpushnotifications_common_acp.php | 53 ----- .../ru/webpushnotifications_module_acp.php | 22 +- migrations/add_pwa_enhancer_configs.php | 56 +++++ .../event/overall_footer_body_after.html | 19 ++ .../event/overall_header_head_append.html | 2 + styles/all/template/pwa_install_banner.js | 87 ++++++++ styles/all/theme/phpbb_wpn.css | 90 ++++++++ tests/controller/controller_manifest_test.php | 12 + tests/controller/controller_webpush_test.php | 6 +- tests/event/listener_test.php | 209 +++--------------- tests/functional/functional_test.php | 4 +- ucp/controller/webpush.php | 4 +- 25 files changed, 612 insertions(+), 491 deletions(-) delete mode 100644 adm/style/event/acp_overall_footer_after.html create mode 100644 adm/style/wpn_acp_pwa.html delete mode 100644 language/en/webpushnotifications_common_acp.php delete mode 100644 language/ru/webpushnotifications_common_acp.php create mode 100644 migrations/add_pwa_enhancer_configs.php create mode 100644 styles/all/template/pwa_install_banner.js diff --git a/acp/wpn_acp_info.php b/acp/wpn_acp_info.php index cb17476..03335bd 100644 --- a/acp/wpn_acp_info.php +++ b/acp/wpn_acp_info.php @@ -23,6 +23,11 @@ public function module() 'auth' => 'ext_phpbb/webpushnotifications && acl_a_server', 'cat' => ['ACP_CLIENT_COMMUNICATION'] ], + 'pwa' => [ + 'title' => 'ACP_WEBPUSH_PWA_SETTINGS', + 'auth' => 'ext_phpbb/webpushnotifications && acl_a_board', + 'cat' => ['ACP_CLIENT_COMMUNICATION'] + ], ], ]; } diff --git a/acp/wpn_acp_module.php b/acp/wpn_acp_module.php index 4015c4b..ed079b9 100644 --- a/acp/wpn_acp_module.php +++ b/acp/wpn_acp_module.php @@ -17,6 +17,7 @@ use phpbb\symfony_request; use phpbb\template\template; use phpbb\user; +use phpbb\webpushnotifications\ext; class wpn_acp_module { @@ -33,6 +34,9 @@ class wpn_acp_module /** @var log $log */ protected $log; + /** @var \FastImageSize\FastImageSize $imagesize */ + protected $imagesize; + /** @var request $request */ protected $request; @@ -45,6 +49,9 @@ class wpn_acp_module /** @var user $user */ protected $user; + /** @var string */ + protected $root_path; + /** @var array $errors */ protected $errors = []; @@ -63,12 +70,14 @@ public function main($id, $mode) global $phpbb_container; $this->config = $phpbb_container->get('config'); + $this->imagesize = $phpbb_container->get('upload_imagesize'); $this->lang = $phpbb_container->get('language'); $this->log = $phpbb_container->get('log'); $this->request = $phpbb_container->get('request'); $this->symfony_request = $phpbb_container->get('symfony_request'); $this->template = $phpbb_container->get('template'); $this->user = $phpbb_container->get('user'); + $this->root_path = $phpbb_container->getParameter('core.root_path'); $form_key = 'phpbb/webpushnotifications'; add_form_key($form_key); @@ -95,6 +104,26 @@ public function main($id, $mode) $this->display_settings(); } + else if ($mode === 'pwa') + { + $this->tpl_name = 'wpn_acp_pwa'; + + $this->lang->add_lang('webpushnotifications_module_acp', 'phpbb/webpushnotifications'); + + $this->page_title = $this->lang->lang('ACP_WEBPUSH_PWA_SETTINGS'); + + if ($this->request->is_set_post('submit')) + { + if (!check_form_key($form_key)) + { + trigger_error($this->lang->lang('FORM_INVALID'), E_USER_WARNING); + } + + $this->save_pwa_settings(); + } + + $this->display_pwa_settings(); + } } /** @@ -164,6 +193,147 @@ public function save_settings() trigger_error($this->lang->lang('CONFIG_UPDATED') . adm_back_link($this->u_action), E_USER_NOTICE); } + /** + * Add PWA settings template vars to the form + */ + public function display_pwa_settings() + { + $this->template->assign_vars([ + 'S_PWA_SHOW_INSTALL_BANNER' => (bool) $this->config['pwa_show_install_banner'], + 'PWA_SHORT_NAME' => $this->config['pwa_short_name'], + 'PWA_ICON_SMALL' => $this->config['pwa_icon_small'], + 'PWA_ICON_LARGE' => $this->config['pwa_icon_large'], + 'PWA_THEME_COLOUR' => $this->config['pwa_theme_colour'], + 'PWA_BACKGROUND_COLOUR' => $this->config['pwa_background_colour'], + 'U_ACTION' => $this->u_action, + ]); + + $this->display_errors(); + } + + /** + * Save PWA settings data to the database + * + * @return void + */ + public function save_pwa_settings() + { + $config_array = $this->request->variable('config', ['' => ''], true); + + $config_array['pwa_short_name'] = $config_array['pwa_short_name'] ?? ''; + $config_array['pwa_icon_small'] = $config_array['pwa_icon_small'] ?? ''; + $config_array['pwa_icon_large'] = $config_array['pwa_icon_large'] ?? ''; + $config_array['pwa_theme_colour'] = $this->normalise_colour($config_array['pwa_theme_colour'] ?? '#000000', '#000000'); + $config_array['pwa_background_colour'] = $this->normalise_colour($config_array['pwa_background_colour'] ?? '#ffffff', '#ffffff'); + + $this->validate_pwa_short_name($config_array['pwa_short_name']); + $this->validate_pwa_icons($config_array['pwa_icon_small'], $config_array['pwa_icon_large']); + + if ($this->display_errors()) + { + return; + } + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CONFIG_WEBPUSH'); + + // Ensure 4-byte emoji can be stored correctly + $config_array['pwa_short_name'] = utf8_encode_ucr($config_array['pwa_short_name']); + + foreach ([ + 'pwa_short_name', + 'pwa_icon_small', + 'pwa_icon_large', + 'pwa_theme_colour', + 'pwa_background_colour', + 'pwa_show_install_banner', + ] as $config_name) + { + $this->config->set($config_name, $config_array[$config_name] ?? 0); + } + + trigger_error($this->lang->lang('CONFIG_UPDATED') . adm_back_link($this->u_action), E_USER_NOTICE); + } + + /** + * Validate PWA short site name + */ + protected function validate_pwa_short_name(string $short_name): void + { + if ($short_name === '') + { + return; + } + + $short_name = ext::decode_entities($short_name, ENT_QUOTES); + if (utf8_strlen($short_name) > 12) + { + $this->errors[] = $this->lang->lang('PWA_SHORT_NAME_INVALID'); + } + } + + /** + * Validate PWA icon filenames and dimensions + */ + protected function validate_pwa_icons(string $small_icon, string $large_icon): void + { + if ($small_icon === '' && $large_icon === '') + { + return; + } + + if ($small_icon === '') + { + $this->errors[] = $this->lang->lang('PWA_ICON_NOT_PROVIDED', $this->lang->lang('PWA_ICON_SMALL')); + return; + } + + if ($large_icon === '') + { + $this->errors[] = $this->lang->lang('PWA_ICON_NOT_PROVIDED', $this->lang->lang('PWA_ICON_LARGE')); + return; + } + + $this->validate_pwa_icon($small_icon, 192); + $this->validate_pwa_icon($large_icon, 512); + } + + /** + * Validate one PWA icon file + */ + protected function validate_pwa_icon(string $filename, int $size): void + { + if (basename($filename) !== $filename) + { + $this->errors[] = $this->lang->lang('PWA_ICON_INVALID', $filename); + return; + } + + $image = $this->root_path . ext::PWA_ICON_DIR . '/' . $filename; + $image_info = $this->imagesize->getImageSize($image); + if ($image_info === false) + { + $this->errors[] = $this->lang->lang('PWA_ICON_INVALID', $filename); + return; + } + + if ($image_info['width'] !== $size || $image_info['height'] !== $size) + { + $this->errors[] = $this->lang->lang('PWA_ICON_SIZE_INVALID', $filename); + } + + if ($image_info['type'] !== IMAGETYPE_PNG) + { + $this->errors[] = $this->lang->lang('PWA_ICON_MIME_INVALID', $filename); + } + } + + protected function normalise_colour($colour, $default): string + { + $colour = strtolower(trim((string) $colour)); + + return preg_match('/^#[a-f0-9]{6}$/', $colour) ? $colour : $default; + } + /** * Display any errors * diff --git a/adm/style/event/acp_overall_footer_after.html b/adm/style/event/acp_overall_footer_after.html deleted file mode 100644 index 09748c1..0000000 --- a/adm/style/event/acp_overall_footer_after.html +++ /dev/null @@ -1,18 +0,0 @@ -{% if S_PWA_OPTIONS %} - -{% endif %} diff --git a/adm/style/wpn_acp_pwa.html b/adm/style/wpn_acp_pwa.html new file mode 100644 index 0000000..75eb51f --- /dev/null +++ b/adm/style/wpn_acp_pwa.html @@ -0,0 +1,55 @@ +{% include 'overall_header.html' %} + +

{{ lang('ACP_WEBPUSH_PWA_SETTINGS') }}

+ +

{{ lang('ACP_PWA_SETTINGS_EXPLAIN') }}

+ +{% if S_ERROR %} +
+

{{ lang('WARNING') }}

+

{{ ERROR_MSG }}

+
+{% endif %} + +
+
+ {{ lang('GENERAL_SETTINGS') }} +
+

{{ lang('PWA_SHORT_NAME_EXPLAIN') }}
+
+
+
+

{{ lang('PWA_ICON_SMALL_EXPLAIN') }}
+
{{ constant('phpbb\\webpushnotifications\\ext::PWA_ICON_DIR') }}/
+
+
+

{{ lang('PWA_ICON_LARGE_EXPLAIN') }}
+
{{ constant('phpbb\\webpushnotifications\\ext::PWA_ICON_DIR') }}/
+
+
+

{{ lang('PWA_SHOW_INSTALL_BANNER_EXPLAIN') }}
+
+
+
+
+ +
+ {{ lang('PWA_COLOURS') }} +
+
+
+
+
+
+
+
+
+ +
+   + + {{ S_FORM_TOKEN }} +
+
+ +{% include 'overall_footer.html' %} diff --git a/config/services.yml b/config/services.yml index e21730d..bc3120c 100644 --- a/config/services.yml +++ b/config/services.yml @@ -14,13 +14,12 @@ services: arguments: - '@config' - '@controller.helper' - - '@upload_imagesize' - '@phpbb.wpn.form_helper' - '@language' + - '@request' - '@template' - '@user' - '@notification_manager' - - '%core.root_path%' tags: - { name: event.listener } diff --git a/controller/manifest.php b/controller/manifest.php index 968a84e..d5ab299 100644 --- a/controller/manifest.php +++ b/controller/manifest.php @@ -61,6 +61,8 @@ public function handle(): JsonResponse 'orientation' => 'portrait', 'start_url' => $start_url, 'scope' => $scope, + 'theme_color' => $this->config['pwa_theme_colour'] ?? '#000000', + 'background_color' => $this->config['pwa_background_colour'] ?? '#ffffff', ]; if (!empty($this->config['pwa_icon_small']) && !empty($this->config['pwa_icon_large'])) @@ -80,6 +82,7 @@ public function handle(): JsonResponse } $response = new JsonResponse($manifest); + $response->headers->set('Content-Type', 'application/manifest+json'); $response->setPublic(); $response->setMaxAge(3600); $response->headers->addCacheControlDirective('must-revalidate', true); diff --git a/event/listener.php b/event/listener.php index 462a9ff..f61a90d 100644 --- a/event/listener.php +++ b/event/listener.php @@ -10,11 +10,11 @@ namespace phpbb\webpushnotifications\event; -use FastImageSize\FastImageSize; use phpbb\config\config; use phpbb\controller\helper as controller_helper; use phpbb\language\language; use phpbb\notification\manager; +use phpbb\request\request_interface; use phpbb\template\template; use phpbb\user; use phpbb\webpushnotifications\ext; @@ -35,12 +35,12 @@ class listener implements EventSubscriberInterface /* @var form_helper */ protected $form_helper; - /** @var FastImageSize */ - protected $imagesize; - /* @var language */ protected $language; + /** @var request_interface */ + protected $request; + /* @var template */ protected $template; @@ -50,33 +50,28 @@ class listener implements EventSubscriberInterface /* @var manager */ protected $phpbb_notifications; - /** @var string */ - protected $root_path; - /** * Constructor * * @param config $config * @param controller_helper $controller_helper Controller helper object - * @param FastImageSize $imagesize * @param form_helper $form_helper Form helper object * @param language $language Language object + * @param request_interface $request * @param template $template Template object * @param user $user * @param manager $phpbb_notifications Notifications manager object - * @param string $root_path */ - public function __construct(config $config, controller_helper $controller_helper, FastImageSize $imagesize, form_helper $form_helper, language $language, template $template, user $user, manager $phpbb_notifications, $root_path) + public function __construct(config $config, controller_helper $controller_helper, form_helper $form_helper, language $language, request_interface $request, template $template, user $user, manager $phpbb_notifications) { $this->config = $config; $this->controller_helper = $controller_helper; - $this->imagesize = $imagesize; $this->form_helper = $form_helper; $this->language = $language; + $this->request = $request; $this->template = $template; $this->user = $user; $this->phpbb_notifications = $phpbb_notifications; - $this->root_path = $root_path; } public static function getSubscribedEvents() @@ -86,9 +81,6 @@ public static function getSubscribedEvents() 'core.page_header_after' => [['load_template_data'], ['pwa_manifest']], 'core.ucp_display_module_before' => 'load_language', 'core.acp_main_notice' => 'compatibility_notice', - 'core.acp_board_config_edit_add' => 'acp_pwa_options', - 'core.acp_board_config_emoji_enabled' => 'acp_pwa_allow_emoji', - 'core.validate_config_variable' => 'validate_pwa_options', 'core.help_manager_add_block_after' => 'wpn_faq', ]; } @@ -163,155 +155,12 @@ public function pwa_manifest() 'U_MANIFEST_URL' => $this->controller_helper->route('phpbb_webpushnotifications_manifest_controller'), 'U_TOUCH_ICON' => $this->config['pwa_icon_small'] ? ext::PWA_ICON_DIR . '/' . $this->config['pwa_icon_small'] : null, 'SHORT_SITE_NAME' => $this->config['pwa_short_name'] ?: $this->trim_shortname($this->config['sitename']), + 'PWA_THEME_COLOUR' => $this->config['pwa_theme_colour'] ?? '#000000', + 'S_PWA_SHOW_BANNER' => !empty($this->config['pwa_show_install_banner']), + 'S_PWA_IS_MOBILE' => $this->is_mobile_phone(), ]); } - /** - * Progressive web app options for the ACP - * - * @param \phpbb\event\data $event - * @return void - */ - public function acp_pwa_options($event) - { - if ($event['mode'] === 'settings' && array_key_exists('legend4', $event['display_vars']['vars'])) - { - $this->language->add_lang('webpushnotifications_common_acp', 'phpbb/webpushnotifications'); - - $my_config_vars = [ - 'legend_pwa_settings'=> 'PWA_SETTINGS', - 'pwa_short_name' => ['lang' => 'PWA_SHORT_NAME', 'validate' => 'pwa_options:string', 'type' => 'custom', 'function' => [$this, 'pwa_short_sitename'], 'explain' => true], - 'pwa_icon_small' => ['lang' => 'PWA_ICON_SMALL', 'validate' => 'pwa_options:icons', 'type' => 'custom', 'function' => [$this, 'pwa_icon_name'], 'explain' => true], - 'pwa_icon_large' => ['lang' => 'PWA_ICON_LARGE', 'validate' => 'pwa_options:icons', 'type' => 'custom', 'function' => [$this, 'pwa_icon_name'], 'explain' => true], - ]; - - $event->update_subarray('display_vars', 'vars', phpbb_insert_config_array($event['display_vars']['vars'], $my_config_vars, ['before' => 'legend4'])); - - $this->template->assign_var('S_PWA_OPTIONS', true); - } - } - - /** - * Allow PWA short name ACP field to accept emoji characters - * - * @param \phpbb\event\data $event - * @return void - */ - public function acp_pwa_allow_emoji($event) - { - if (in_array('pwa_short_name', $event['config_name_ary'], true)) - { - return; - } - - $config_name_ary = $event['config_name_ary']; - $config_name_ary[] = 'pwa_short_name'; - $event['config_name_ary'] = $config_name_ary; - } - - /** - * Return HTML for PWA icon name settings - * - * @param string $value Value of config - * @param string $key Name of config - * @return string - */ - public function pwa_icon_name($value, $key) - { - return ext::PWA_ICON_DIR . '/'; - } - - /** - * Return HTML for PWA short site name setting - * - * @param string $value Value of config - * @param string $key Name of config - * @return string - */ - public function pwa_short_sitename($value, $key) - { - $placeholder = $this->trim_shortname($this->config['sitename']); - - return ''; - } - - /** - * Validate PWA options - * - * @param \phpbb\event\data $event - * @return void - */ - public function validate_pwa_options($event) - { - $type = 0; - $mode = 1; - - $validator = explode(':', $event['config_definition']['validate']); - - if ($validator[$type] !== 'pwa_options') - { - return; - } - - switch ($validator[$mode]) - { - case 'string': - // Ignore validation if icon fields are empty - if (empty($event['cfg_array']['pwa_short_name'])) - { - return; - } - - $short_name = ext::decode_entities($event['cfg_array']['pwa_short_name'], ENT_QUOTES); - - // Do not allow strings longer than 12 characters - if (utf8_strlen($short_name) > 12) - { - $this->add_error($event, 'PWA_SHORT_NAME_INVALID'); - return; - } - break; - - case 'icons': - // Ignore validation if icon fields are empty - if (empty($event['cfg_array']['pwa_icon_small']) && empty($event['cfg_array']['pwa_icon_large'])) - { - return; - } - - $value = $event['cfg_array'][$event['config_name']]; - - // Don't allow empty values, if one icon is set, both must be set. - if (empty($value)) - { - $this->add_error($event, 'PWA_ICON_NOT_PROVIDED', $this->language->lang(strtoupper($event['config_name']))); - return; - } - - // Check if image is valid - $image = $this->root_path . ext::PWA_ICON_DIR . '/' . $value; - $image_info = $this->imagesize->getImageSize($image); - if ($image_info !== false) - { - if (($event['config_name'] === 'pwa_icon_small' && $image_info['width'] !== 192 && $image_info['height'] !== 192) || - ($event['config_name'] === 'pwa_icon_large' && $image_info['width'] !== 512 && $image_info['height'] !== 512)) - { - $this->add_error($event, 'PWA_ICON_SIZE_INVALID', $value); - } - - if ($image_info['type'] !== IMAGETYPE_PNG) - { - $this->add_error($event, 'PWA_ICON_MIME_INVALID', $value); - } - } - else - { - $this->add_error($event, 'PWA_ICON_INVALID', $value); - } - break; - } - } - /** * Add Web Push info to the phpBB FAQ * @@ -350,21 +199,6 @@ public function wpn_faq($event) } } - /** - * Add errors to the error array - * - * @param \phpbb\event\data $event - * @param string $error_key - * @param string $param - * @return void - */ - protected function add_error($event, $error_key, $param = null) - { - $error = $event['error']; - $error[] = $this->language->lang($error_key, $param); - $event['error'] = $error; - } - /** * Can notifications be used by the user? * @@ -389,4 +223,20 @@ protected function trim_shortname($name) $trimmed = utf8_substr($decoded, 0, 12); return utf8_htmlspecialchars($trimmed); } + + /** + * Lightweight phone detection for install-banner display + * + * @return bool + */ + protected function is_mobile_phone() + { + $user_agent = (string) $this->request->server('HTTP_USER_AGENT', ''); + if ($user_agent === '' || preg_match('/ipad|tablet|kindle|silk|playbook/i', $user_agent)) + { + return false; + } + + return (bool) preg_match('/mobile|iphone|ipod|android.*mobile|windows phone|blackberry|opera mini/i', $user_agent); + } } diff --git a/language/en/common.php b/language/en/common.php index 36a6e24..3f539b3 100644 --- a/language/en/common.php +++ b/language/en/common.php @@ -38,5 +38,8 @@ // $lang = array_merge($lang, [ + 'PWA_INSTALL_BUTTON' => 'Install app', + 'PWA_INSTALL_DISMISS' => 'Close', + 'PWA_INSTALL_SUBTITLE' => 'Add to home screen', 'WEBPUSH_INVALID_ENDPOINT' => 'The push notification endpoint is not from a recognised push service.', ]); diff --git a/language/en/info_acp_webpushnotifications.php b/language/en/info_acp_webpushnotifications.php index 7e01c05..dfecca9 100644 --- a/language/en/info_acp_webpushnotifications.php +++ b/language/en/info_acp_webpushnotifications.php @@ -39,6 +39,7 @@ $lang = array_merge($lang, [ 'ACP_WEBPUSH_EXT_SETTINGS' => 'Web Push settings', + 'ACP_WEBPUSH_PWA_SETTINGS' => 'PWA settings', 'ACP_WEBPUSH_REMOVE_WARNING' => 'Web Push Notifications are now built into phpBB', 'ACP_WEBPUSH_REMOVE_NOTICE' => 'The extension “phpBB Browser Push Notifications” is no longer needed and should be uninstalled and removed.
All settings and user preferences associated with the extension will be migrated into phpBB’s built-in push notifications when you uninstall the extension.', 'LOG_CONFIG_WEBPUSH' => 'Altered Web Push settings', diff --git a/language/en/webpushnotifications_common_acp.php b/language/en/webpushnotifications_common_acp.php deleted file mode 100644 index 7cfb060..0000000 --- a/language/en/webpushnotifications_common_acp.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - */ - -/** - * DO NOT CHANGE - */ -if (!defined('IN_PHPBB')) -{ - exit; -} - -if (empty($lang) || !is_array($lang)) -{ - $lang = []; -} - -// DEVELOPERS PLEASE NOTE -// -// All language files should use UTF-8 as their encoding and the files must not contain a BOM. -// -// Placeholders can now contain order information, e.g. instead of -// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows -// translators to re-order the output of data while ensuring it remains correct -// -// You do not need this where single placeholders are used, e.g. 'Message %d' is fine -// equally where a string contains only two placeholders which are used to wrap text -// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine -// -// Some characters you may want to copy&paste: -// ’ » “ ” … -// - -$lang = array_merge($lang, [ - 'PWA_SETTINGS' => 'Progressive web application options', - 'PWA_SHORT_NAME' => 'Short site name', - 'PWA_SHORT_NAME_EXPLAIN' => 'Your site name in 12 characters or fewer, which may be used as a label for an icon on a mobile device’s home screen. (If this field is left empty, the first 12 characters of the Site name will be used.)', - 'PWA_SHORT_NAME_INVALID' => '“Short site name” exceeds the 12 character limit.', - 'PWA_ICON_SMALL' => 'Small mobile device icon', - 'PWA_ICON_SMALL_EXPLAIN' => 'File name of a 192px x 192px PNG image. This file must be uploaded to your board’s ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . ' directory.', - 'PWA_ICON_LARGE' => 'Large mobile device icon', - 'PWA_ICON_LARGE_EXPLAIN' => 'File name of a 512px x 512px PNG image. This file must be uploaded to your board’s ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . ' directory.', - 'PWA_ICON_SIZE_INVALID' => '“%s” does not have the correct image dimensions.', - 'PWA_ICON_MIME_INVALID' => '“%s” must be a PNG image file.', - 'PWA_ICON_INVALID' => '“%s” is not a valid image file or is missing from the expected location. Verify the file name and location are correct.', - 'PWA_ICON_NOT_PROVIDED' => '%s field must not be empty. All icon fields must contain an image.', -]); diff --git a/language/en/webpushnotifications_module_acp.php b/language/en/webpushnotifications_module_acp.php index 9d83cd0..2138d86 100644 --- a/language/en/webpushnotifications_module_acp.php +++ b/language/en/webpushnotifications_module_acp.php @@ -38,7 +38,8 @@ // $lang = array_merge($lang, [ - 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable Web Push for board notifications. Web Push is a protocol for the real-time delivery of events to user agents, commonly referred to as push messages. It is compatible with the majority of modern browsers on both desktop and mobile devices. Users can opt to receive Web Push alerts in their browser by subscribing and enabling their preferred notifications in the UCP.

To enable push notifications on Apple mobile devices, your site needs to function as a Progressive Web Application (PWA). This requires users to add your site to their device’s home screen. For an improved user experience, you can find additional PWA settings under Board settings, where you can configure an optional app name and app icons for how your site will appear on users’ home screens.', + // Web push settings + 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable Web Push for board notifications. Web Push is a protocol for the real-time delivery of events to user agents, commonly referred to as push messages. It is compatible with the majority of modern browsers on both desktop and mobile devices. Users can opt to receive Web Push alerts in their browser by subscribing and enabling their preferred notifications in the UCP.

To enable push notifications on Apple mobile devices, your site needs to function as a Progressive Web Application (PWA). This requires users to add your site to their device’s home screen. Configure app metadata and install behaviour under PWA settings.', 'WEBPUSH_ENABLE' => 'Enable Web Push', 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow users to receive notifications in their browser or device via Web Push. To utilise Web Push, you must input or generate valid VAPID identification keys.', 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate Identification keys', @@ -53,4 +54,23 @@ 'WEBPUSH_POPUP_PROMPT' => 'Show popup prompt for unsubscribed members', 'WEBPUSH_POPUP_PROMPT_EXPLAIN' => 'Display a popup message asking registered members if they want to receive push notifications. The popup will only appear to members who are not currently subscribed and have not previously denied.', 'WEBPUSH_INSECURE_SERVER_ERROR' => 'This board is not using a secure SSL/HTTPS protocol, which is required for enabling web push notifications. Alternatively, the server environment might be misconfigured. Ensure that the HTTPS and HEADER_CLIENT_PROTO server environment variables are correctly configured.', + + // PWA Settings + 'ACP_PWA_SETTINGS_EXPLAIN' => 'Here you can configure Progressive Web App behaviour, including app manifest metadata and install banner display.', + 'PWA_SHORT_NAME' => 'Short site name', + 'PWA_SHORT_NAME_EXPLAIN' => 'Your site name in 12 characters or fewer, which may be used as a label for an icon on a mobile device’s home screen. (If this field is left empty, the first 12 characters of the Site name will be used.)', + 'PWA_SHORT_NAME_INVALID' => '“Short site name” exceeds the 12 character limit.', + 'PWA_ICON_SMALL' => 'Small mobile device icon', + 'PWA_ICON_SMALL_EXPLAIN' => 'File name of a 192px x 192px PNG image. This file must be uploaded to your board’s ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . ' directory.', + 'PWA_ICON_LARGE' => 'Large mobile device icon', + 'PWA_ICON_LARGE_EXPLAIN' => 'File name of a 512px x 512px PNG image. This file must be uploaded to your board’s ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . ' directory.', + 'PWA_ICON_SIZE_INVALID' => '“%s” does not have the correct image dimensions.', + 'PWA_ICON_MIME_INVALID' => '“%s” must be a PNG image file.', + 'PWA_ICON_INVALID' => '“%s” is not a valid image file or is missing from the expected location. Verify the file name and location are correct.', + 'PWA_ICON_NOT_PROVIDED' => '%s field must not be empty. All icon fields must contain an image.', + 'PWA_COLOURS' => 'Colours', + 'PWA_THEME_COLOUR' => 'Theme colour', + 'PWA_BACKGROUND_COLOUR' => 'Background colour', + 'PWA_SHOW_INSTALL_BANNER' => 'Show install banner', + 'PWA_SHOW_INSTALL_BANNER_EXPLAIN' => 'Display a mobile install prompt when the browser reports that your board can be installed as an app.', ]); diff --git a/language/ru/common.php b/language/ru/common.php index 8ea5c41..960bf56 100644 --- a/language/ru/common.php +++ b/language/ru/common.php @@ -38,5 +38,8 @@ // $lang = array_merge($lang, [ + 'PWA_INSTALL_BUTTON' => 'Install app', + 'PWA_INSTALL_DISMISS' => 'Close', + 'PWA_INSTALL_SUBTITLE' => 'Add to home screen', 'WEBPUSH_INVALID_ENDPOINT' => 'Конечная точка push-уведомления не принадлежит известному сервису push-уведомлений.', ]); diff --git a/language/ru/info_acp_webpushnotifications.php b/language/ru/info_acp_webpushnotifications.php index b1c1564..8355db6 100644 --- a/language/ru/info_acp_webpushnotifications.php +++ b/language/ru/info_acp_webpushnotifications.php @@ -39,6 +39,7 @@ $lang = array_merge($lang, [ 'ACP_WEBPUSH_EXT_SETTINGS' => 'Браузерные уведомления', + 'ACP_WEBPUSH_PWA_SETTINGS' => 'PWA settings', 'ACP_WEBPUSH_REMOVE_WARNING' => 'Браузерные push—уведомления теперь встроены в phpBB', 'ACP_WEBPUSH_REMOVE_NOTICE' => 'Расширение «phpBB Browser Push Notifications» больше не требуется и должно быть удалено.
Все системные и пользовательские настройки, связанные с данным расширением, будут перенесены в соответствующие настройки браузерных push—уведомлений конференции автоматически при удалении данного расширения.', 'LOG_CONFIG_WEBPUSH' => 'Изменены настройки браузерных push—уведомлений', diff --git a/language/ru/webpushnotifications_common_acp.php b/language/ru/webpushnotifications_common_acp.php deleted file mode 100644 index 7107b4f..0000000 --- a/language/ru/webpushnotifications_common_acp.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - */ - -/** - * DO NOT CHANGE - */ -if (!defined('IN_PHPBB')) -{ - exit; -} - -if (empty($lang) || !is_array($lang)) -{ - $lang = []; -} - -// DEVELOPERS PLEASE NOTE -// -// All language files should use UTF-8 as their encoding and the files must not contain a BOM. -// -// Placeholders can now contain order information, e.g. instead of -// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows -// translators to re-order the output of data while ensuring it remains correct -// -// You do not need this where single placeholders are used, e.g. 'Message %d' is fine -// equally where a string contains only two placeholders which are used to wrap text -// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine -// -// Some characters you may want to copy&paste: -// ’ » “ ” … -// - -$lang = array_merge($lang, [ - 'PWA_SETTINGS' => 'Настройки прогрессивного веб—приложения (PWA)', - 'PWA_SHORT_NAME' => 'Краткое имя сайта', - 'PWA_SHORT_NAME_EXPLAIN' => 'Краткое имя сайта длиной не более 12 символов, которое будет использовано в качестве подписи к значку на домашнем экране мобильного устройства. Если оставить пустым, будут использованы первые 12 символов значения настройки, заданной в поле Название конференции.', - 'PWA_SHORT_NAME_INVALID' => 'Заданное значение в поле «Краткое имя сайта» превышает 12 символов.', - 'PWA_ICON_SMALL' => 'Маленький значок для мобильного устройства', - 'PWA_ICON_SMALL_EXPLAIN' => 'Имя файла изображения формата PNG размером 192 x 192 пикселя. Файл изображения должен быть загружен на сервер в папку ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . '.', - 'PWA_ICON_LARGE' => 'Большой значок для мобильного устройства', - 'PWA_ICON_LARGE_EXPLAIN' => 'Имя файла изображения формата PNG размером 512 x 512 пикселей. Файл изображения должен быть загружен на сервер в папку ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . '.', - 'PWA_ICON_SIZE_INVALID' => 'Изображение «%s» имеет некорректные размеры.', - 'PWA_ICON_MIME_INVALID' => 'Файл изображения «%s» должен иметь формат PNG.', - 'PWA_ICON_INVALID' => 'Файл «%s» не является файлом изображения или отсутствует по указанному пути. Проверьте правильность имени файла и его расположения.', - 'PWA_ICON_NOT_PROVIDED' => 'Настройка «%s» не может быть пустой. Необходимо задать все пути к файлам значков для мобильных устройств.', -]); diff --git a/language/ru/webpushnotifications_module_acp.php b/language/ru/webpushnotifications_module_acp.php index 5232e58..3a8a669 100644 --- a/language/ru/webpushnotifications_module_acp.php +++ b/language/ru/webpushnotifications_module_acp.php @@ -38,7 +38,8 @@ // $lang = array_merge($lang, [ - 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Здесь вы можете включить браузерные push—уведомления. Браузерные push—уведомления — это протокол мгновенной доставки пользователю сообщений о различных событиях. Они совместимы с большинством современных браузеров как на настольных, так и на мобильных устройствах. Пользователи могут включить их в своих браузерах и выбрать предпочтительные виды браузерных уведомлений в личном разделе.
Для работы браузерных push—уведомлений необходимо ввести или сгенерировать ключи идентификации ниже.

Для того, чтобы браузерные push—уведомления стали доступны на мобильных устройствах Apple, конференция должна функционировать как прогрессивное веб—приложение (PWA). Параметры прогрессивного веб—приложения (краткое имя сайта и значки для его отображения на домашнем экране) можно задать в разделе Настройки конференции. После этого пользователи смогут добавлять сайт конференции на домашний экран своего мобильного устройства Apple и получать браузерные push—уведомления.', + // Web push settings + 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Здесь вы можете включить браузерные push—уведомления. Браузерные push—уведомления — это протокол мгновенной доставки пользователю сообщений о различных событиях. Они совместимы с большинством современных браузеров как на настольных, так и на мобильных устройствах. Пользователи могут включить их в своих браузерах и выбрать предпочтительные виды браузерных уведомлений в личном разделе.
Для работы браузерных push—уведомлений необходимо ввести или сгенерировать ключи идентификации ниже.

Для того, чтобы браузерные push—уведомления стали доступны на мобильных устройствах Apple, конференция должна функционировать как прогрессивное веб—приложение (PWA). Параметры прогрессивного веб—приложения (краткое имя сайта и значки для его отображения на домашнем экране) можно задать в разделе PWA settings. После этого пользователи смогут добавлять сайт конференции на домашний экран своего мобильного устройства Apple и получать браузерные push—уведомления.', 'WEBPUSH_ENABLE' => 'Включить браузерные push—уведомления', 'WEBPUSH_ENABLE_EXPLAIN' => 'Включение возможности получения браузерных push—уведомлений для всех пользователей. Для использования браузерных push—уведомлений необходимо задать или сгенерировать корректные ключи идентификации VAPID.', 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Сгенерировать ключи идентификации', @@ -53,4 +54,23 @@ 'WEBPUSH_POPUP_PROMPT' => 'Показывать всплывающее приглашение', 'WEBPUSH_POPUP_PROMPT_EXPLAIN' => 'Показывать всплывающее сообщение зарегистрированным пользователям с приглашением подписаться на браузерные уведомления данной конференции. Сообщение будет показано только тем зарегистрированным пользователям, которые не подписаны на браузерные уведомления и ранее не отклоняли такое приглашение.', 'WEBPUSH_INSECURE_SERVER_ERROR' => 'На данной конференции не применяется защищённый протокол SSL/HTTPS, без которого использование браузерных push—уведомлений невозможно, либо соответствующие переменные серверного окружения неверно сконфигурированы. Убедитесь, что значения переменных серверного окружения HTTPS и/или HEADER_CLIENT_PROTO заданы верно.', + + // PWA Settings + 'ACP_PWA_SETTINGS_EXPLAIN' => 'Here you can configure Progressive Web App behaviour, including app manifest metadata and install banner display.', + 'PWA_SHORT_NAME' => 'Short site name', + 'PWA_SHORT_NAME_EXPLAIN' => 'Your site name in 12 characters or fewer, which may be used as a label for an icon on a mobile device’s home screen. (If this field is left empty, the first 12 characters of the Site name will be used.)', + 'PWA_SHORT_NAME_INVALID' => '“Short site name” exceeds the 12 character limit.', + 'PWA_ICON_SMALL' => 'Small mobile device icon', + 'PWA_ICON_SMALL_EXPLAIN' => 'File name of a 192px x 192px PNG image. This file must be uploaded to your board’s ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . ' directory.', + 'PWA_ICON_LARGE' => 'Large mobile device icon', + 'PWA_ICON_LARGE_EXPLAIN' => 'File name of a 512px x 512px PNG image. This file must be uploaded to your board’s ' . \phpbb\webpushnotifications\ext::PWA_ICON_DIR . ' directory.', + 'PWA_ICON_SIZE_INVALID' => '“%s” does not have the correct image dimensions.', + 'PWA_ICON_MIME_INVALID' => '“%s” must be a PNG image file.', + 'PWA_ICON_INVALID' => '“%s” is not a valid image file or is missing from the expected location. Verify the file name and location are correct.', + 'PWA_ICON_NOT_PROVIDED' => '%s field must not be empty. All icon fields must contain an image.', + 'PWA_COLOURS' => 'Colours', + 'PWA_THEME_COLOUR' => 'Theme colour', + 'PWA_BACKGROUND_COLOUR' => 'Background colour', + 'PWA_SHOW_INSTALL_BANNER' => 'Show install banner', + 'PWA_SHOW_INSTALL_BANNER_EXPLAIN' => 'Display a mobile install prompt when the browser reports that your board can be installed as an app.', ]); diff --git a/migrations/add_pwa_enhancer_configs.php b/migrations/add_pwa_enhancer_configs.php new file mode 100644 index 0000000..e4bc35b --- /dev/null +++ b/migrations/add_pwa_enhancer_configs.php @@ -0,0 +1,56 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\webpushnotifications\migrations; + +use phpbb\db\migration\migration; + +class add_pwa_enhancer_configs extends migration +{ + public function effectively_installed(): bool + { + return $this->config->offsetExists('pwa_theme_colour'); + } + + public static function depends_on() + { + return ['\phpbb\webpushnotifications\migrations\add_acp_pwa_configs']; + } + + public function update_data(): array + { + return [ + ['config.add', ['pwa_theme_colour', '#000000']], + ['config.add', ['pwa_background_colour', '#ffffff']], + ['config.add', ['pwa_show_install_banner', false]], + ['module.add', ['acp', 'ACP_CLIENT_COMMUNICATION', [ + 'module_basename' => '\phpbb\webpushnotifications\acp\wpn_acp_module', + 'module_langname' => 'ACP_WEBPUSH_PWA_SETTINGS', + 'module_mode' => 'pwa', + 'module_auth' => 'ext_phpbb/webpushnotifications && acl_a_board', + 'after' => 'ACP_WEBPUSH_EXT_SETTINGS', + ]]], + ]; + } + + public function revert_data(): array + { + return [ + ['config.remove', ['pwa_theme_colour']], + ['config.remove', ['pwa_background_colour']], + ['config.remove', ['pwa_show_install_banner']], + ['module.remove', ['acp', 'ACP_CLIENT_COMMUNICATION', [ + 'module_basename' => '\phpbb\webpushnotifications\acp\wpn_acp_module', + 'module_langname' => 'ACP_WEBPUSH_PWA_SETTINGS', + 'module_mode' => 'pwa', + ]]], + ]; + } +} diff --git a/styles/all/template/event/overall_footer_body_after.html b/styles/all/template/event/overall_footer_body_after.html index 36ac651..23d3b59 100644 --- a/styles/all/template/event/overall_footer_body_after.html +++ b/styles/all/template/event/overall_footer_body_after.html @@ -16,3 +16,22 @@ } {% endif %} + +{% if S_PWA_SHOW_BANNER and S_PWA_IS_MOBILE %} + {% INCLUDEJS '@phpbb_webpushnotifications/pwa_install_banner.js' %} + +{% endif %} diff --git a/styles/all/template/event/overall_header_head_append.html b/styles/all/template/event/overall_header_head_append.html index d92a189..179cb6a 100644 --- a/styles/all/template/event/overall_header_head_append.html +++ b/styles/all/template/event/overall_header_head_append.html @@ -1,5 +1,7 @@ + + diff --git a/styles/all/template/pwa_install_banner.js b/styles/all/template/pwa_install_banner.js new file mode 100644 index 0000000..158e4c2 --- /dev/null +++ b/styles/all/template/pwa_install_banner.js @@ -0,0 +1,87 @@ +(function() { + 'use strict'; + + const dismissedKey = 'phpbb_wpn_pwa_banner_dismissed'; + let deferredPrompt = null; + + function isStandalone() { + return (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) || window.navigator.standalone === true; + } + + function localStorageGet(key) { + try { + return window.localStorage.getItem(key); + } catch (e) { + return null; + } + } + + function localStorageSet(key, value) { + try { + window.localStorage.setItem(key, value); + } catch (e) { + // Private browsing may block localStorage. + } + } + + function hideInstallBanner(banner) { + if (banner) { + banner.hidden = true; + } + } + + function setupInstallBanner() { + const banner = document.getElementById('pwa-install-banner'); + const installButton = document.getElementById('pwa-btn-install'); + const dismissButton = document.getElementById('pwa-btn-dismiss'); + + if (!banner || !installButton || !dismissButton || isStandalone()) { + hideInstallBanner(banner); + return; + } + + if (localStorageGet(dismissedKey) === '1') { + hideInstallBanner(banner); + return; + } + + window.addEventListener('beforeinstallprompt', event => { + event.preventDefault(); + deferredPrompt = event; + banner.hidden = false; + }); + + installButton.addEventListener('click', () => { + if (!deferredPrompt) { + hideInstallBanner(banner); + return; + } + + deferredPrompt.prompt(); + deferredPrompt.userChoice.then(choice => { + if (choice.outcome === 'accepted') { + hideInstallBanner(banner); + } + + deferredPrompt = null; + }); + }); + + dismissButton.addEventListener('click', () => { + hideInstallBanner(banner); + localStorageSet(dismissedKey, '1'); + }); + + window.addEventListener('appinstalled', () => { + hideInstallBanner(banner); + localStorageSet(dismissedKey, '1'); + deferredPrompt = null; + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', setupInstallBanner); + } else { + setupInstallBanner(); + } +})(); diff --git a/styles/all/theme/phpbb_wpn.css b/styles/all/theme/phpbb_wpn.css index aa52eca..26cfeb2 100644 --- a/styles/all/theme/phpbb_wpn.css +++ b/styles/all/theme/phpbb_wpn.css @@ -205,3 +205,93 @@ width: 100%; } } + +.pwa-install-banner { + font-family: sans-serif; + background: var(--pwa-theme-colour, #000000); + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.25); + color: #ffffff; + position: fixed; + z-index: 9999; + right: 0; + bottom: 0; + left: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 16px; + gap: 12px; +} + +.pwa-install-banner[hidden] { + display: none; +} + +.pwa-install-banner__content { + display: flex; + flex: 1; + align-items: center; + min-width: 0; + gap: 10px; +} + +.pwa-install-banner__icon { + border-radius: 10px; + flex-shrink: 0; + width: 44px; + height: 44px; +} + +.pwa-install-banner__text { + font-size: 14px; + font-weight: 500; + line-height: 1.4; + overflow: hidden; + min-width: 0; +} + +.pwa-install-banner__title { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.pwa-install-banner__subtitle { + font-size: 12px; + opacity: 0.85; +} + +.pwa-install-banner__actions { + display: flex; + flex-shrink: 0; + gap: 8px; +} + +.pwa-install-banner__dismiss, +.pwa-install-banner__install { + font-size: 13px; + white-space: nowrap; + border-radius: 4px; + padding: 7px 10px; + cursor: pointer; +} + +.pwa-install-banner__dismiss { + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.5); + color: #ffffff; +} + +.pwa-install-banner__install { + font-weight: bold; + background: #ffffff; + border: 0; + color: var(--pwa-theme-colour, #000000); + padding-inline: 12px; +} + +@media all and (display-mode: standalone) { + .pwa-install-banner { + display: none !important; + } +} diff --git a/tests/controller/controller_manifest_test.php b/tests/controller/controller_manifest_test.php index 8dac316..138859d 100644 --- a/tests/controller/controller_manifest_test.php +++ b/tests/controller/controller_manifest_test.php @@ -31,6 +31,8 @@ protected function setUp(): void 'pwa_short_name' => '', 'pwa_icon_small' => '', 'pwa_icon_large' => '', + 'pwa_theme_colour' => '#000000', + 'pwa_background_colour' => '#ffffff', ]); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); @@ -57,6 +59,8 @@ public function manifest_data() 'orientation' => 'portrait', 'start_url' => '/', 'scope' => '/', + 'theme_color' => '#000000', + 'background_color' => '#ffffff', ], ], 'using script path' => [ @@ -73,6 +77,8 @@ public function manifest_data() 'orientation' => 'portrait', 'start_url' => '/foo/', 'scope' => '/foo/', + 'theme_color' => '#000000', + 'background_color' => '#ffffff', ], ], 'with shortname' => [ @@ -87,6 +93,8 @@ public function manifest_data() 'orientation' => 'portrait', 'start_url' => '/', 'scope' => '/', + 'theme_color' => '#000000', + 'background_color' => '#ffffff', ], ], 'without shortname' => [ @@ -101,6 +109,8 @@ public function manifest_data() 'orientation' => 'portrait', 'start_url' => '/', 'scope' => '/', + 'theme_color' => '#000000', + 'background_color' => '#ffffff', ], ], 'with icons' => [ @@ -117,6 +127,8 @@ public function manifest_data() 'orientation' => 'portrait', 'start_url' => '/', 'scope' => '/', + 'theme_color' => '#000000', + 'background_color' => '#ffffff', 'icons' => [ [ 'src' => 'http://images/site_icons/foo_sm.png', diff --git a/tests/controller/controller_webpush_test.php b/tests/controller/controller_webpush_test.php index 2317f42..89cd11f 100644 --- a/tests/controller/controller_webpush_test.php +++ b/tests/controller/controller_webpush_test.php @@ -328,7 +328,11 @@ public function test_worker() $response = $this->controller->worker(); - $this->assertEquals('text/javascript; charset=UTF-8', $response->headers->get('Content-Type')); + $this->assertEquals('application/javascript; charset=UTF-8', $response->headers->get('Content-Type')); + $this->assertStringContainsString('no-store', $response->headers->get('Cache-Control')); + $this->assertStringContainsString('no-cache', $response->headers->get('Cache-Control')); + $this->assertStringContainsString('must-revalidate', $response->headers->get('Cache-Control')); + $this->assertEquals('noindex, nofollow', $response->headers->get('X-Robots-Tag')); $this->assertEquals('rendered_content', $response->getContent()); $this->assertNull($response->headers->get('X-PHPBB-IS-BOT')); } diff --git a/tests/event/listener_test.php b/tests/event/listener_test.php index 94f52b9..30ab8ee 100644 --- a/tests/event/listener_test.php +++ b/tests/event/listener_test.php @@ -31,6 +31,9 @@ class listener_test extends \phpbb_database_test_case /** @var \phpbb\language\language */ protected $language; + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\request\request_interface */ + protected $request; + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\template\template */ protected $template; @@ -66,8 +69,12 @@ protected function setUp(): void $this->language = new \phpbb\language\language($lang_loader); $this->template = $this->getMockBuilder('\phpbb\template\template') ->getMock(); - $request = new \phpbb\request\request(); - $request->enable_super_globals(); + $form_request = new \phpbb\request\request(); + $form_request->enable_super_globals(); + $this->request = $this->getMockBuilder('\phpbb\request\request_interface') + ->getMock(); + $this->request->method('server') + ->willReturn(''); $user = new \phpbb\user($this->language, '\phpbb\datetime'); $this->user = $user; $this->user->data['user_form_salt'] = ''; @@ -87,11 +94,21 @@ protected function setUp(): void 'load_notifications' => true, 'allow_board_notifications' => true, 'wpn_webpush_enable' => true, + 'force_server_vars' => false, + 'script_path' => '', + 'sitename' => 'phpBB', + 'cookie_name' => 'phpbb3', + 'pwa_show_install_banner' => true, + 'pwa_theme_colour' => '#000000', + 'pwa_background_colour' => '#ffffff', + 'pwa_icon_small' => '', + 'pwa_icon_large' => '', + 'pwa_short_name' => '', ]); $this->form_helper = new \phpbb\webpushnotifications\form\form_helper( $this->config, - $request, + $form_request, $this->user ); @@ -122,13 +139,12 @@ protected function set_listener() $this->listener = new \phpbb\webpushnotifications\event\listener( $this->config, $this->controller_helper, - $this->imagesize, $this->form_helper, $this->language, + $this->request, $this->template, $this->user, - $this->notifications, - $this->root_path + $this->notifications ); } @@ -145,9 +161,6 @@ public function test_getSubscribedEvents() 'core.page_header_after', 'core.ucp_display_module_before', 'core.acp_main_notice', - 'core.acp_board_config_edit_add', - 'core.acp_board_config_emoji_enabled', - 'core.validate_config_variable', 'core.help_manager_add_block_after', ], array_keys(\phpbb\webpushnotifications\event\listener::getSubscribedEvents())); } @@ -338,6 +351,9 @@ public function test_pwa_manifest() 'U_MANIFEST_URL' => $this->controller_helper->route('phpbb_webpushnotifications_manifest_controller'), 'U_TOUCH_ICON' => ext::PWA_ICON_DIR . '/icon-192x192.png', 'SHORT_SITE_NAME' => 'Test', + 'PWA_THEME_COLOUR' => '#000000', + 'S_PWA_IS_MOBILE' => false, + 'S_PWA_SHOW_BANNER' => true, ]); $dispatcher = new \phpbb\event\dispatcher(); @@ -345,181 +361,6 @@ public function test_pwa_manifest() $dispatcher->trigger_event('core.acp_main_notice'); } - public function acp_pwa_options_data() - { - return [ - [ // expected config and mode - 'settings', - ['vars' => ['legend4' => []]], - ['legend_pwa_settings', 'pwa_short_name', 'pwa_icon_small', 'pwa_icon_large', 'legend4'], - ], - [ // unexpected mode - 'foobar', - ['vars' => ['legend4' => []]], - ['legend4'], - ], - [ // unexpected config - 'post', - ['vars' => ['foobar' => []]], - ['foobar'], - ], - [ // unexpected config and mode - 'foobar', - ['vars' => ['foobar' => []]], - ['foobar'], - ], - ]; - } - - /** - * @dataProvider acp_pwa_options_data - */ - public function test_acp_pwa_options($mode, $display_vars, $expected_keys) - { - $this->set_listener(); - - $dispatcher = new \phpbb\event\dispatcher(); - $dispatcher->addListener('core.acp_board_config_edit_add', [$this->listener, 'acp_pwa_options']); - - $event_data = ['display_vars', 'mode']; - $event_data_after = $dispatcher->trigger_event('core.acp_board_config_edit_add', compact($event_data)); - - foreach ($event_data as $expected) - { - self::assertArrayHasKey($expected, $event_data_after); - } - extract($event_data_after, EXTR_OVERWRITE); - - $keys = array_keys($display_vars['vars']); - - self::assertEquals($expected_keys, $keys); - } - - public function validate_pwa_options_data() - { - return [ - [ - 'pwa_options:icons', - ['pwa_icon_small' => '192.png', 'pwa_icon_large' => '512.png'], - [], - ], - [ - 'pwa_options:icons', - ['pwa_icon_small' => '1.png', 'pwa_icon_large' => '512.png'], - ['PWA_ICON_SIZE_INVALID'], - ], - [ - 'pwa_options:icons', - ['pwa_icon_small' => '1.png', 'pwa_icon_large' => '12.png'], - ['PWA_ICON_SIZE_INVALID'], - ], - [ - 'pwa_options:icons', - ['pwa_icon_small' => '192.jpg', 'pwa_icon_large' => '512.gif'], - ['PWA_ICON_MIME_INVALID'], - ], - [ - 'pwa_options:icons', - ['pwa_icon_small' => '', 'pwa_icon_large' => ''], - [], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => 'foo'], - [], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => ''], - [], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => 'foo❤️'], - [], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => 'Фаны phpBB'], - [], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => 'Фаны phpBB Board'], - ['PWA_SHORT_NAME_INVALID'], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => 'foo❤️bar foo bar'], - ['PWA_SHORT_NAME_INVALID'], - ], - [ - 'pwa_options:string', - ['pwa_short_name' => str_repeat('a', 50)], - ['PWA_SHORT_NAME_INVALID'], - ], - ]; - } - - /** - * @dataProvider validate_pwa_options_data - */ - public function test_validate_pwa_options($validate, $cfg_array, $expected_error) - { - $config_name = key($cfg_array); - $config_definition = ['validate' => $validate]; - - $pwa_icon_small = $cfg_array['pwa_icon_small'] ?? ''; - $pwa_icon_large = $cfg_array['pwa_icon_large'] ?? ''; - - [$small_image_name, $small_image_ext] = $pwa_icon_small ? explode('.', $pwa_icon_small, 2) : ['', '']; - [$large_image_name, $large_image_ext] = $pwa_icon_large ? explode('.', $pwa_icon_large, 2) : ['', '']; - - $error = []; - - $this->set_listener(); - - $this->imagesize->expects($pwa_icon_small && $pwa_icon_large ? self::once() : self::never()) - ->method('getImageSize') - ->willReturnMap([ - [$this->root_path . ext::PWA_ICON_DIR . '/', '', false], - [$this->root_path . ext::PWA_ICON_DIR . '/' . $pwa_icon_small, '', ['width' => (int) $small_image_name, 'height' => (int) $small_image_name, 'type' => $small_image_ext === 'png' ? IMAGETYPE_PNG : IMAGETYPE_UNKNOWN]], - [$this->root_path . ext::PWA_ICON_DIR . '/' . $pwa_icon_large, '', ['width' => (int) $large_image_name, 'height' => (int) $large_image_name, 'type' => $large_image_ext === 'png' ? IMAGETYPE_PNG : IMAGETYPE_UNKNOWN]], - ]); - - $dispatcher = new \phpbb\event\dispatcher(); - $dispatcher->addListener('core.validate_config_variable', [$this->listener, 'validate_pwa_options']); - - $event_data = ['cfg_array', 'config_name', 'config_definition', 'error']; - $event_data_after = $dispatcher->trigger_event('core.validate_config_variable', compact($event_data)); - - foreach ($event_data as $expected) - { - self::assertArrayHasKey($expected, $event_data_after); - } - extract($event_data_after, EXTR_OVERWRITE); - - self::assertEquals($expected_error, $error); - } - - public function test_acp_pwa_allow_emoji() - { - $config_name_ary = ['foo']; - $expected = ['foo', 'pwa_short_name']; - - $this->set_listener(); - - $dispatcher = new \phpbb\event\dispatcher(); - $dispatcher->addListener('core.acp_board_config_emoji_enabled', [$this->listener, 'acp_pwa_allow_emoji']); - - $event_data = ['config_name_ary']; - $event_data_after = $dispatcher->trigger_event('core.acp_board_config_emoji_enabled', compact($event_data)); - - extract($event_data_after, EXTR_OVERWRITE); - - self::assertEquals($expected, $config_name_ary); - } - public function test_wpn_faq() { $this->language->add_lang('webpushnotifications_faq', 'phpbb/webpushnotifications'); diff --git a/tests/functional/functional_test.php b/tests/functional/functional_test.php index 5c4d9ec..9ad95c6 100644 --- a/tests/functional/functional_test.php +++ b/tests/functional/functional_test.php @@ -122,12 +122,14 @@ public function test_manifest() 'orientation' => 'portrait', 'start_url' => '/', 'scope' => '/', + 'theme_color' => '#000000', + 'background_color' => '#ffffff', ]; $this->login(); $this->admin_login(); - $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=settings&sid=' . $this->sid); + $crawler = self::request('GET', 'adm/index.php?i=-phpbb-webpushnotifications-acp-wpn_acp_module&mode=pwa&sid=' . $this->sid); $form_data = [ 'config[pwa_short_name]' => $expected['short_name'], diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index e66a53f..47bf2c5 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -283,7 +283,9 @@ public function worker(): Response ]); $response = new Response($content); - $response->headers->set('Content-Type', 'text/javascript; charset=UTF-8'); + $response->headers->set('Content-Type', 'application/javascript; charset=UTF-8'); + $response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate'); + $response->headers->set('X-Robots-Tag', 'noindex, nofollow'); if (!empty($this->user->data['is_bot'])) { From 6d0f1047f43a1f9a837cef193439706cab5fc7bf Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 24 May 2026 10:37:10 -0700 Subject: [PATCH 2/8] Split out PWA css to new file --- .../event/overall_footer_body_after.html | 1 + styles/all/theme/phpbb_pwa.css | 90 +++++++++++++++++++ styles/all/theme/phpbb_wpn.css | 90 ------------------- 3 files changed, 91 insertions(+), 90 deletions(-) create mode 100644 styles/all/theme/phpbb_pwa.css diff --git a/styles/all/template/event/overall_footer_body_after.html b/styles/all/template/event/overall_footer_body_after.html index 23d3b59..92c7862 100644 --- a/styles/all/template/event/overall_footer_body_after.html +++ b/styles/all/template/event/overall_footer_body_after.html @@ -19,6 +19,7 @@ {% if S_PWA_SHOW_BANNER and S_PWA_IS_MOBILE %} {% INCLUDEJS '@phpbb_webpushnotifications/pwa_install_banner.js' %} + {% INCLUDECSS '@phpbb_webpushnotifications/phpbb_pwa.css' %}