From d8e18847f836e9b4c5343b92b97c961802768ad3 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 17 Jul 2024 17:39:35 -0700 Subject: [PATCH 1/7] Revert commits from multi-language issue Revert "Fix #62 (multilanguage issue)." This reverts commit 917e7620a69e577afdb347236a23425bb6aaf22b. --- config/services.yml | 1 - notification/method/webpush.php | 75 ++++++------------- tests/event/listener_test.php | 1 - .../notification_method_webpush_test.php | 1 - 4 files changed, 21 insertions(+), 57 deletions(-) diff --git a/config/services.yml b/config/services.yml index 4ec48f3..9caefb9 100644 --- a/config/services.yml +++ b/config/services.yml @@ -30,7 +30,6 @@ services: arguments: - '@config' - '@dbal.conn' - - '@language' - '@log' - '@user_loader' - '@user' diff --git a/notification/method/webpush.php b/notification/method/webpush.php index 824bfc7..c9f8b89 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,21 +139,10 @@ 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'], @@ -178,13 +161,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 +169,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, array(USER_IGNORE)); + // Get subscriptions for users $user_subscription_map = $this->get_user_subscription_map($notify_users); @@ -523,31 +517,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'), From 965c753d7c12e2f52adffec2407e901a792f88f7 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 17 Jul 2024 17:55:57 -0700 Subject: [PATCH 2/7] Alternate multi lang issue fix Signed-off-by: Matt Friedman --- config/services.yml | 2 ++ notification/method/webpush.php | 8 +---- ucp/controller/webpush.php | 53 +++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/config/services.yml b/config/services.yml index 9caefb9..c288428 100644 --- a/config/services.yml +++ b/config/services.yml @@ -48,6 +48,8 @@ services: - '@controller.helper' - '@dbal.conn' - '@phpbb.wpn.form_helper' + - '@language' + - '@notification_manager' - '@path_helper' - '@request' - '@user' diff --git a/notification/method/webpush.php b/notification/method/webpush.php index c9f8b89..a700b16 100644 --- a/notification/method/webpush.php +++ b/notification/method/webpush.php @@ -144,13 +144,7 @@ public function notify() { $data = $notification->get_insert_array(); $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($notification->get_insert_array(), ['notification_type_name' => $notification->get_type()])), 'notification_time' => time(), 'push_token' => hash('sha256', random_bytes(32)) ]; diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index ce79538..bb52d2c 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -14,6 +14,8 @@ 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; @@ -44,6 +46,12 @@ class webpush /** @var form_helper */ protected $form_helper; + /** @var language */ + protected $language; + + /** @var manager */ + protected $notification_manager; + /** @var path_helper */ protected $path_helper; @@ -69,6 +77,8 @@ 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 $user @@ -76,13 +86,15 @@ class webpush * @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 $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 = $user; @@ -143,7 +155,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 +186,52 @@ 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'); } + 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); + + return 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' => $notification->get_avatar(), + ]); + } + /** * Handle request to push worker javascript * From 076062199e45043823d205363aa11541c6abc79f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 1 Oct 2024 13:18:14 -0700 Subject: [PATCH 3/7] Show usernames correctly in push notifications Signed-off-by: Matt Friedman --- config/services.yml | 1 + ucp/controller/webpush.php | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config/services.yml b/config/services.yml index c288428..07cd894 100644 --- a/config/services.yml +++ b/config/services.yml @@ -52,6 +52,7 @@ services: - '@notification_manager' - '@path_helper' - '@request' + - '@user_loader' - '@user' - '@template.twig.environment' - '%tables.phpbb.wpn.notification_push%' diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index bb52d2c..1a9a2cb 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -22,6 +22,7 @@ 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; @@ -58,6 +59,9 @@ class webpush /** @var request_interface */ protected $request; + /** @var user_loader */ + protected $user_loader; + /** @var user */ protected $user; @@ -81,13 +85,14 @@ class webpush * @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, language $language, manager $notification_manager, - path_helper $path_helper, request_interface $request, user $user, Environment $template, string $notification_webpush_table, string $push_subscriptions_table) + 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; @@ -97,6 +102,7 @@ public function __construct(config $config, controller_helper $controller_helper $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; @@ -223,6 +229,9 @@ private function get_notification_data(string $notification_data): string // 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($notification->get_title()), From 242e3cac88803d16f2793b0d2499fb94e54a49a9 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 1 Oct 2024 13:19:42 -0700 Subject: [PATCH 4/7] Re-visit decoding emojis Signed-off-by: Matt Friedman --- ucp/controller/webpush.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index 1a9a2cb..73dbd90 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -123,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); } @@ -234,8 +233,8 @@ private function get_notification_data(string $notification_data): string return json_encode([ 'heading' => $this->config['sitename'], - 'title' => strip_tags($notification->get_title()), - 'text' => strip_tags($notification->get_reference()), + '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' => $notification->get_avatar(), ]); From 71923432acdc60d3a5c8f9ea1af8467f1a7e5199 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 1 Oct 2024 13:36:11 -0700 Subject: [PATCH 5/7] Fix user ignores Signed-off-by: Matt Friedman --- notification/method/webpush.php | 2 +- ucp/controller/webpush.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/notification/method/webpush.php b/notification/method/webpush.php index a700b16..6eed9cd 100644 --- a/notification/method/webpush.php +++ b/notification/method/webpush.php @@ -188,7 +188,7 @@ protected function notify_using_webpush(): void // Load all the users we need $notify_users = array_diff($user_ids, $banned_users); - $this->user_loader->load_users($notify_users, array(USER_IGNORE)); + $this->user_loader->load_users($notify_users, [USER_IGNORE]); // Get subscriptions for users $user_subscription_map = $this->get_user_subscription_map($notify_users); diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index 73dbd90..6537190 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -229,7 +229,7 @@ private function get_notification_data(string $notification_data): string $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()); + $this->user_loader->load_users($notification->users_to_query(), [USER_IGNORE]); return json_encode([ 'heading' => $this->config['sitename'], From 9ef8134beec78a9a16133f45a3aad9bb31b69278 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 2 Oct 2024 14:17:01 -0700 Subject: [PATCH 6/7] Code cleanup Signed-off-by: Matt Friedman --- notification/method/webpush.php | 13 ++++++++++++- ucp/controller/webpush.php | 24 ++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/notification/method/webpush.php b/notification/method/webpush.php index 6eed9cd..512d5cb 100644 --- a/notification/method/webpush.php +++ b/notification/method/webpush.php @@ -144,7 +144,10 @@ public function notify() { $data = $notification->get_insert_array(); $data += [ - 'push_data' => json_encode(array_merge($notification->get_insert_array(), ['notification_type_name' => $notification->get_type()])), + 'push_data' => json_encode(array_merge( + $data, + ['notification_type_name' => $notification->get_type()] + )), 'notification_time' => time(), 'push_token' => hash('sha256', random_bytes(32)) ]; @@ -349,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()]); diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index 6537190..d6311db 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -215,6 +215,13 @@ private function get_anonymous_notifications(): string 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); @@ -229,7 +236,7 @@ private function get_notification_data(string $notification_data): string $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(), [USER_IGNORE]); + $this->user_loader->load_users($notification->users_to_query()); return json_encode([ 'heading' => $this->config['sitename'], @@ -250,7 +257,6 @@ private function get_notification_data(string $notification_data): 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'], @@ -268,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 * From 127009fa9773580fe5c6a403f5d2db7cbb0455e6 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 3 Oct 2024 09:09:51 -0700 Subject: [PATCH 7/7] Fix avatar urls after refactorings Signed-off-by: Matt Friedman --- notification/method/webpush.php | 40 ------------------------------- ucp/controller/webpush.php | 42 ++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/notification/method/webpush.php b/notification/method/webpush.php index 512d5cb..fc7f0c3 100644 --- a/notification/method/webpush.php +++ b/notification/method/webpush.php @@ -460,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 * diff --git a/ucp/controller/webpush.php b/ucp/controller/webpush.php index d6311db..08b3961 100644 --- a/ucp/controller/webpush.php +++ b/ucp/controller/webpush.php @@ -243,7 +243,7 @@ private function get_notification_data(string $notification_data): string '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' => $notification->get_avatar(), + 'avatar' => $this->prepare_avatar($notification->get_avatar()), ]); } @@ -346,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; + } }