From 464f37fe46074074633ec6d65daa60d15b37c542 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 28 Jul 2022 21:09:57 +0200 Subject: [PATCH 0001/1214] [ticket/17010] Add dummy for web push notification method PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 163 ++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 phpBB/phpbb/notification/method/webpush.php diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php new file mode 100644 index 00000000000..f88cfc7cf96 --- /dev/null +++ b/phpBB/phpbb/notification/method/webpush.php @@ -0,0 +1,163 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\notification\type\type_interface; +use phpbb\user; +use phpbb\user_loader; + +/** +* Web push notification method class +* This class handles sending push messages for notifications +*/ + +class webpush extends \phpbb\notification\method\messenger_base +{ + /** @var config */ + protected $config; + + /** @var driver_interface */ + protected $db; + + /** @var user */ + protected $user; + + /** @var string Notification web push table */ + protected $notification_webpush_table; + + /** + * Notification Method web push constructor + * + * @param user_loader $user_loader + * @param user $user + * @param config $config + * @param driver_interface $db + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $notification_webpush_table + */ + public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db, string $phpbb_root_path, string $php_ext, string $notification_webpush_table) + { + parent::__construct($user_loader, $phpbb_root_path, $php_ext); + + $this->user = $user; + $this->config = $config; + $this->db = $db; + $this->notification_webpush_table = $notification_webpush_table; + } + + /** + * {@inheritDoc} + */ + public function get_type(): string + { + return 'notification.method.webpush'; + } + + /** + * {@inheritDoc} + */ + public function is_available(type_interface $notification_type = null): bool + { + return parent::is_available($notification_type) && $this->config['webpush_enable'] && !empty($this->user->data['user_push_subscriptions']); + } + + /** + * {@inheritdoc} + */ + public function get_notified_users($notification_type_id, array $options): array + { + $notified_users = []; + + $sql = 'SELECT user_id + FROM ' . $this->notification_webpush_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id . + (isset($options['item_id']) ? ' AND item_id = ' . (int) $options['item_id'] : '') . + (isset($options['item_parent_id']) ? ' AND item_parent_id = ' . (int) $options['item_parent_id'] : '') . + (isset($options['user_id']) ? ' AND user_id = ' . (int) $options['user_id'] : ''); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $notified_users[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + + return $notified_users; + } + + /** + * Parse the queue and notify the users + */ + public function notify() + { + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notification_webpush_table); + + /** @var type_interface $notification */ + foreach ($this->queue as $notification) + { + $data = self::clean_data($notification->get_insert_array()); + $insert_buffer->insert($data); + } + + $insert_buffer->flush(); + + // @todo: add actual web push code + + return false; + } + + /** + * {@inheritdoc} + */ + public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) + { + $sql = 'DELETE FROM ' . $this->notification_webpush_table . ' + WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : '1=1') . + ($user_id !== false ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . + ($item_id !== false ? ' AND ' . $this->db->sql_in_set('item_id', $item_id) : ''); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) + { + $sql = 'DELETE FROM ' . $this->notification_webpush_table . ' + WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : '1=1') . + ($user_id !== false ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . + ($item_parent_id !== false ? ' AND ' . $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : ''); + $this->db->sql_query($sql); + } + + /** + * Clean data to contain only what we need for webpush notifications table + * + * @param array $data Notification data + * @return array Cleaned notification data + */ + public static function clean_data(array $data) + { + $row = [ + 'notification_type_id' => null, + 'item_id' => null, + 'item_parent_id' => null, + 'user_id' => null, + ]; + + return array_intersect_key($data, $row); + } +} From 07d3376612db776548d584f09397733c7414ddc9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 28 Jul 2022 22:19:53 +0200 Subject: [PATCH 0002/1214] [ticket/17010] Add web-push-lib dependency PHPBB3-17010 --- phpBB/composer.json | 1 + phpBB/composer.lock | 1607 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 1316 insertions(+), 292 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index da6875f9bc4..6d59cefe266 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -40,6 +40,7 @@ "google/recaptcha": "~1.1", "guzzlehttp/guzzle": "~6.3", "marc1706/fast-image-size": "^1.1", + "minishlink/web-push": "^8.0", "s9e/text-formatter": "^2.0", "symfony/config": "^6.3", "symfony/console": "^6.3", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 36d1379632c..287f56b08ba 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "75425a269d37301eb11d494437c2f5ff", + "content-hash": "26143c95732859f6f0849abd2fe74ae5", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -40,6 +40,66 @@ }, "time": "2014-09-15T13:12:35+00:00" }, + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, { "name": "carlos-mg89/oauth", "version": "0.8.15", @@ -1974,6 +2034,276 @@ }, "time": "2022-01-12T16:29:39+00:00" }, + { + "name": "minishlink/web-push", + "version": "v8.0.0", + "source": { + "type": "git", + "url": "https://github.com/web-push-libs/web-push-php.git", + "reference": "ec034f1e287cd1e74235e349bd017d71a61e9d8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/ec034f1e287cd1e74235e349bd017d71a61e9d8d", + "reference": "ec034f1e287cd1e74235e349bd017d71a61e9d8d", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^7.0.1|^6.2", + "php": ">=8.0", + "spomky-labs/base64url": "^2.0", + "web-token/jwt-key-mgmt": "^2.0|^3.0.2", + "web-token/jwt-signature": "^2.0|^3.0.2", + "web-token/jwt-signature-algorithm-ecdsa": "^2.0|^3.0.2", + "web-token/jwt-util-ecc": "^2.0|^3.0.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.13.2", + "phpstan/phpstan": "^1.9.8", + "phpunit/phpunit": "^9.5.27" + }, + "suggest": { + "ext-gmp": "Optional for performance." + }, + "type": "library", + "autoload": { + "psr-4": { + "Minishlink\\WebPush\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Louis Lagrange", + "email": "lagrange.louis@gmail.com", + "homepage": "https://github.com/Minishlink" + } + ], + "description": "Web Push library for PHP", + "homepage": "https://github.com/web-push-libs/web-push-php", + "keywords": [ + "Push API", + "WebPush", + "notifications", + "push", + "web" + ], + "support": { + "issues": "https://github.com/web-push-libs/web-push-php/issues", + "source": "https://github.com/web-push-libs/web-push-php/tree/v8.0.0" + }, + "time": "2023-01-10T17:14:44+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-06-14T06:56:20+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "shasum": "" + }, + "require": { + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0" + }, + "time": "2023-04-30T00:54:53+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -2023,6 +2353,54 @@ }, "time": "2021-02-03T23:26:27+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -2127,31 +2505,32 @@ "time": "2019-01-08T18:20:26+00:00" }, { - "name": "psr/http-message", - "version": "1.1", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2161,50 +2540,49 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ "http", - "http-message", + "http-client", "psr", - "psr-7", - "request", - "response" + "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-04T09:50:52+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "psr/log", - "version": "3.0.0", + "name": "psr/http-factory", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2217,38 +2595,145 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "log", + "factory", + "http", + "message", "psr", - "psr-3" + "psr-17", + "psr-7", + "request", + "response" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { - "name": "ralouphie/getallheaders", - "version": "3.0.3", + "name": "psr/http-message", + "version": "1.1", "source": { "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { - "php": ">=5.6" + "php": "^7.2 || ^8.0" }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { @@ -2680,6 +3165,182 @@ }, "time": "2023-09-03T09:24:00+00:00" }, + { + "name": "spomky-labs/base64url", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/base64url.git", + "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.11|^0.12", + "phpstan/phpstan-beberlei-assert": "^0.11|^0.12", + "phpstan/phpstan-deprecation-rules": "^0.11|^0.12", + "phpstan/phpstan-phpunit": "^0.11|^0.12", + "phpstan/phpstan-strict-rules": "^0.11|^0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "description": "Base 64 URL Safe Encoding/Decoding PHP Library", + "homepage": "https://github.com/Spomky-Labs/base64url", + "keywords": [ + "base64", + "rfc4648", + "safe", + "url" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/base64url/issues", + "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2020-11-03T09:10:25+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "86102bdd19379b2c6e5b0feb94fd490d40e7d133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/86102bdd19379b2c6e5b0feb94fd490d40e7d133", + "reference": "86102bdd19379b2c6e5b0feb94fd490d40e7d133", + "shasum": "" + }, + "require": { + "brick/math": "^0.10|^0.11|^0.12", + "ext-mbstring": "*", + "php": ">=8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.27", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^10.1", + "rector/rector": "^0.19", + "roave/security-advisories": "dev-latest", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symplify/easy-coding-standard": "^12.0" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" + }, + "type": "library", + "autoload": { + "psr-4": { + "SpomkyLabs\\Pki\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" + } + ], + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", + "keywords": [ + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.1.1" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2024-02-05T20:37:46+00:00" + }, { "name": "symfony/config", "version": "v6.4.3", @@ -3334,10 +3995,181 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-10-31T17:30:12+00:00" + }, + { + "name": "symfony/http-client", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/a9034bc119fab8238f76cf49c770f3135f3ead86", + "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T15:01:07+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "1ee70e699b41909c209a0c930f11034b93578654" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", + "reference": "1ee70e699b41909c209a0c930f11034b93578654", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" }, "funding": [ { @@ -3353,7 +4185,7 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:30:12+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/http-foundation", @@ -5161,127 +5993,489 @@ "conflict": { "symfony/console": "<5.4" }, - "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T14:51:35+00:00" + }, + { + "name": "twig/twig", + "version": "v3.8.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-11-21T18:54:41+00:00" + }, + { + "name": "web-token/jwt-key-mgmt", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-key-mgmt.git", + "reference": "4d2a5a1a86477dd50b89aff76962816ddbd64590" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-key-mgmt/zipball/4d2a5a1a86477dd50b89aff76962816ddbd64590", + "reference": "4d2a5a1a86477dd50b89aff76962816ddbd64590", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "web-token/jwt-library": "^3.3" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.3.0" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "abandoned": "web-token/jwt-library", + "time": "2024-02-22T07:19:34+00:00" + }, + { + "name": "web-token/jwt-library", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-library.git", + "reference": "5edf0f193425bb9c695a433180ddf9d263f55063" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/5edf0f193425bb9c695a433180ddf9d263f55063", + "reference": "5edf0f193425bb9c695a433180ddf9d263f55063", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "ext-json": "*", + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.6", + "paragonie/sodium_compat": "^1.20", + "php": ">=8.1", + "psr/clock": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "spomky-labs/pki-framework": "^1.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.12" + }, + "conflict": { + "spomky-labs/jose": "*" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", + "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", + "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", + "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW...)", + "symfony/http-client": "To enable JKU/X5U support." + }, + "type": "library", + "autoload": { + "psr-4": { + "Jose\\Component\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "JWT library", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "issues": "https://github.com/web-token/jwt-library/issues", + "source": "https://github.com/web-token/jwt-library/tree/3.3.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2024-02-22T08:15:45+00:00" + }, + { + "name": "web-token/jwt-signature", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-signature.git", + "reference": "eccfd59e658d4118414cf6d14229aa52eec387e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/eccfd59e658d4118414cf6d14229aa52eec387e7", + "reference": "eccfd59e658d4118414cf6d14229aa52eec387e7", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "web-token/jwt-library": "^3.3" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-signature/tree/3.3.0" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "abandoned": "web-token/jwt-library", + "time": "2024-02-22T07:19:34+00:00" + }, + { + "name": "web-token/jwt-signature-algorithm-ecdsa", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git", + "reference": "28516e170f6ee6d13766d9e2b912c2853e1ac5e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-signature-algorithm-ecdsa/zipball/28516e170f6ee6d13766d9e2b912c2853e1ac5e4", + "reference": "28516e170f6ee6d13766d9e2b912c2853e1ac5e4", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=8.1", + "web-token/jwt-library": "^3.3" }, - "bin": [ - "Resources/bin/yaml-lint" - ], "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" } ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.3" + "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.3.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" } ], - "time": "2024-01-23T14:51:35+00:00" + "abandoned": "web-token/jwt-library", + "time": "2024-02-22T07:19:34+00:00" }, { - "name": "twig/twig", - "version": "v3.8.0", + "name": "web-token/jwt-util-ecc", + "version": "3.3.0", "source": { "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + "url": "https://github.com/web-token/jwt-util-ecc.git", + "reference": "667934c5c6e37238f4e67d51aa3ba55abc703e1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "url": "https://api.github.com/repos/web-token/jwt-util-ecc/zipball/667934c5c6e37238f4e67d51aa3ba55abc703e1a", + "reference": "667934c5c6e37238f4e67d51aa3ba55abc703e1a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" - }, - "require-dev": { - "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "php": ">=8.1", + "web-token/jwt-library": "^3.3" }, "type": "library", - "autoload": { - "psr-4": { - "Twig\\": "src/" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" } ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", + "description": "[DEPRECATED] Please use web-token/jwt-library instead.", + "homepage": "https://github.com/web-token", "keywords": [ - "templating" + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" ], "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + "source": "https://github.com/web-token/jwt-util-ecc/tree/3.3.0" }, "funding": [ { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" } ], - "time": "2023-11-21T18:54:41+00:00" + "abandoned": "web-token/jwt-library", + "time": "2024-02-22T07:19:34+00:00" } ], "packages-dev": [ @@ -8614,177 +9808,6 @@ ], "time": "2024-01-29T15:02:55+00:00" }, - { - "name": "symfony/http-client", - "version": "v6.4.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/a9034bc119fab8238f76cf49c770f3135f3ead86", - "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-01-29T15:01:07+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "1ee70e699b41909c209a0c930f11034b93578654" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", - "reference": "1ee70e699b41909c209a0c930f11034b93578654", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-30T20:28:31+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.2", From 5c51f2ef7f56a4faee684999a701ef75a2e6bda2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 17 Aug 2022 22:29:23 +0200 Subject: [PATCH 0003/1214] [ticket/17010] Add webpush to container definition PHPBB3-17010 --- .../default/container/services_notification.yml | 12 ++++++++++++ phpBB/config/default/container/tables.yml | 1 + tests/notification/base.php | 1 + .../notification/notification_method_email_test.php | 1 + tests/notification/submit_post_base.php | 1 + 5 files changed, 16 insertions(+) diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index a037ad73526..ebde7bcf5ab 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -243,3 +243,15 @@ services: - '%core.php_ext%' tags: - { name: notification.method } + + notification.method.webpush: + class: phpbb\notification\method\webpush + shared: false + arguments: + - '@user_loader' + - '@user' + - '@config' + - '@dbal.conn' + - '%core.root_path%' + - '%core.php_ext%' + - '%tables.notification_push%' diff --git a/phpBB/config/default/container/tables.yml b/phpBB/config/default/container/tables.yml index 005f3ba9277..f3a4e1b4abd 100644 --- a/phpBB/config/default/container/tables.yml +++ b/phpBB/config/default/container/tables.yml @@ -38,6 +38,7 @@ parameters: tables.modules: '%core.table_prefix%modules' tables.notification_emails: '%core.table_prefix%notification_emails' tables.notification_types: '%core.table_prefix%notification_types' + tables.notification_push: '%core.table_prefix%notification_push' tables.notifications: '%core.table_prefix%notifications' tables.poll_options: '%core.table_prefix%poll_options' tables.poll_votes: '%core.table_prefix%poll_votes' diff --git a/tests/notification/base.php b/tests/notification/base.php index 545508eedd3..eb888cb0ae6 100644 --- a/tests/notification/base.php +++ b/tests/notification/base.php @@ -124,6 +124,7 @@ protected function setUp(): void $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); + $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); $this->notifications = new phpbb_notification_manager_helper( array(), diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php index d9b52cae2d6..efcac83035f 100644 --- a/tests/notification/notification_method_email_test.php +++ b/tests/notification/notification_method_email_test.php @@ -91,6 +91,7 @@ protected function setUp(): void $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); + $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); $phpbb_container->set( 'text_formatter.s9e.mention_helper', new \phpbb\textformatter\s9e\mention_helper( diff --git a/tests/notification/submit_post_base.php b/tests/notification/submit_post_base.php index e8ad00e5ef9..cb5b8d0d2ae 100644 --- a/tests/notification/submit_post_base.php +++ b/tests/notification/submit_post_base.php @@ -154,6 +154,7 @@ protected function setUp(): void $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); + $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); $phpbb_container->set('content.visibility', new \phpbb\content_visibility($auth, $config, $phpbb_dispatcher, $db, $user, $phpbb_root_path, $phpEx, FORUMS_TABLE, POSTS_TABLE, TOPICS_TABLE, USERS_TABLE)); $phpbb_container->addCompilerPass(new phpbb\di\pass\markpublic_pass()); $phpbb_container->compile(); From 3feeb237caaab696657c01624261d8fdf3d294d6 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 19 Aug 2022 22:43:52 +0200 Subject: [PATCH 0004/1214] [ticket/17010] Add migration for notificatio_push table PHPBB3-17010 --- .../data/v400/add_notification_push_table.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php diff --git a/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php b/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php new file mode 100644 index 00000000000..0d71f5fb600 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php @@ -0,0 +1,55 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class add_notification_push_table extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev', + ]; + } + + public function effectively_installed(): bool + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'notification_push'); + } + + public function update_schema(): array + { + return [ + 'add_tables' => [ + $this->table_prefix . 'notification_push' => [ + 'COLUMNS' => [ + 'notification_type_id' => ['USINT', 0], + 'item_id' => ['ULINT', 0], + 'item_parent_id' => ['ULINT', 0], + 'user_id' => ['ULINT', 0], + ], + 'PRIMARY_KEY' => ['notification_type_id', 'item_id', 'item_parent_id', 'user_id'], + ], + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'drop_tables' => [$this->table_prefix . 'notification_push'], + ]; + } +} From 769f5bc3977df5579c2d70812efabecc71f32087 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 20 Aug 2022 22:56:34 +0200 Subject: [PATCH 0005/1214] [ticket/17010] Add settings and some more notification code PHPBB3-17010 --- phpBB/includes/acp/acp_board.php | 13 +++++ phpBB/includes/acp/info/acp_board.php | 1 + .../data/v400/add_notification_push_table.php | 27 ++++++++++ phpBB/phpbb/notification/method/webpush.php | 53 +++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index df7c8bf6576..d6ce8f23c45 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -485,6 +485,19 @@ function main($id, $mode) ); break; + case 'webpush': + $display_vars = [ + 'title' => 'ACP_WEBPUSH_SETTINGS', + 'vars' => [ + 'legend1' => 'GENERAL_SETTINGS', + 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], + 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], + + 'legend3' => 'ACP_SUBMIT_CHANGES', + ], + ]; + break; + default: trigger_error('NO_MODE', E_USER_ERROR); break; diff --git a/phpBB/includes/acp/info/acp_board.php b/phpBB/includes/acp/info/acp_board.php index 1a3ee7b6be5..a1d9f4fdd1b 100644 --- a/phpBB/includes/acp/info/acp_board.php +++ b/phpBB/includes/acp/info/acp_board.php @@ -30,6 +30,7 @@ function module() 'auth' => array('title' => 'ACP_AUTH_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_CLIENT_COMMUNICATION')), 'email' => array('title' => 'ACP_EMAIL_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_CLIENT_COMMUNICATION')), + 'webpush' => array('title' => 'ACP_WEBPUSH_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_CLIENT_COMMUNICATION')), 'cookie' => array('title' => 'ACP_COOKIE_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), 'server' => array('title' => 'ACP_SERVER_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), diff --git a/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php b/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php index 0d71f5fb600..e822a394057 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php +++ b/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php @@ -52,4 +52,31 @@ public function revert_schema(): array 'drop_tables' => [$this->table_prefix . 'notification_push'], ]; } + + public function update_data(): array + { + return [ + ['config.add', ['webpush_vapid_public', '']], + ['config.add', ['webpush_vapid_private', '']], + ['module.add', [ + 'acp', + 'ACP_BOARD_CONFIGURATION', + [ + 'module_basename' => 'acp_board', + 'module_langname' => 'ACP_WEBPUSH_SETTINGS', + 'module_mode' => 'webpush', + 'module_auth' => 'acl_a_board', + 'after' => ['settings', 'ACP_JABBER_SETTINGS'], + ], + ]], + ]; + } + + public function revert_data(): array + { + return [ + ['config.remove', ['webpush_vapid_public']], + ['config.remove', ['webpush_vapid_private']], + ]; + } } diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index f88cfc7cf96..48787901b03 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -115,10 +115,63 @@ public function notify() $insert_buffer->flush(); // @todo: add actual web push code + $this->notify_using_webpush(); return false; } + protected function notify_using_webpush() + { + if (empty($this->queue)) + { + return; + } + + // Load all users we want to notify (we need their email address) + $user_ids = []; + foreach ($this->queue as $notification) + { + $user_ids[] = $notification->user_id; + } + + // We do not send emails to banned users + if (!function_exists('phpbb_get_banned_user_ids')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + $banned_users = phpbb_get_banned_user_ids($user_ids); + + // Load all the users we need + $this->user_loader->load_users(array_diff($user_ids, $banned_users), array(USER_IGNORE)); + + // Time to go through the queue and send emails + /** @var type_interface $notification */ + foreach ($this->queue as $notification) + { + $user = $this->user_loader->get_user($notification->user_id); + + if ($user['user_type'] == USER_INACTIVE && $user['user_inactive_reason'] == INACTIVE_MANUAL) + { + continue; + } + + // add actual web push data + $data['data'] = [ + 'badge' => '', // @todo: to be filled? + 'body' => $notification->get_title(), + 'icon' => '', // @todo: to be filled? + 'image' => '', // @todo: to be filled? + 'title' => $this->config['sitename'], + 'url' => $notification->get_url(), + ]; + + // @todo: start implementing actual web push code + } + + // We're done, empty the queue + $this->empty_queue(); + } + /** * {@inheritdoc} */ From d05e2dd043a6fe17517bb980062079b61dccc643 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Aug 2022 21:31:04 +0200 Subject: [PATCH 0006/1214] [ticket/17010] Add first web push settings to ACP PHPBB3-17010 --- phpBB/includes/acp/acp_board.php | 2 +- phpBB/language/en/acp/board.php | 8 ++++++++ phpBB/language/en/acp/common.php | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d6ce8f23c45..2530c3e2548 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -491,7 +491,7 @@ function main($id, $mode) 'vars' => [ 'legend1' => 'GENERAL_SETTINGS', 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], - 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], + 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], 'legend3' => 'ACP_SUBMIT_CHANGES', ], diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index ec2d0236ecb..37303d481a9 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -599,6 +599,14 @@ 'USE_SMTP_EXPLAIN' => 'Select “Yes” if you want or have to send email via a named server instead of the local mail function.', )); +$lang = array_merge($lang, [ + 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Webpush for board notifications. Webpush is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', + 'WEBPUSH_VAPID_PUBLIC' => 'VAPID public key', + 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The VAPID public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all webpush subscriptions.', + 'WEBPUSH_VAPID_PRIVATE' => 'VAPID private key', + 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The VAPID private key will be used to create authenticated push messages sent by your site. The VAPID private key must be a valid public-private key pair with the VAPID public key.
Warning: Changing the VAPID private key will automatically invalidate all webpush subscriptions.', +]); + // Jabber settings $lang = array_merge($lang, array( 'ACP_JABBER_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Jabber for instant messaging and board notifications. Jabber is an open source protocol and therefore available for use by anyone. Some Jabber servers include gateways or transports which allow you to contact users on other networks. Not all servers offer all transports and changes in protocols can prevent transports from operating. Please be sure to enter already registered account details - phpBB will use the details you enter here as is.', diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index aeab0af262c..84c047af54b 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -219,6 +219,7 @@ 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS' => 'View global moderation permissions', 'ACP_VIEW_USER_PERMISSIONS' => 'View user-based permissions', + 'ACP_WEBPUSH_SETTINGS' => 'Webpush settings', 'ACP_WORDS' => 'Word censoring', 'ACTION' => 'Action', From 873a22176dbe477ba6e3f4af2481feb0e6f81bf1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 30 Aug 2022 20:21:03 +0200 Subject: [PATCH 0007/1214] [ticket/17010] Add Webpush settings and language entries PHPBB3-17010 --- phpBB/includes/acp/acp_board.php | 23 +++++++++++++++++++ phpBB/install/schemas/schema_data.sql | 3 +++ phpBB/language/en/acp/board.php | 2 ++ ...ication_push_table.php => add_webpush.php} | 6 ++++- .../migrations_check_config_added_test.php | 4 ++-- 5 files changed, 35 insertions(+), 3 deletions(-) rename phpBB/phpbb/db/migration/data/v400/{add_notification_push_table.php => add_webpush.php} (88%) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 2530c3e2548..ceb7f47d58f 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -19,6 +19,7 @@ * @ignore */ +use Minishlink\WebPush\VAPID; use phpbb\config\config; use phpbb\language\language; use phpbb\user; @@ -490,6 +491,7 @@ function main($id, $mode) 'title' => 'ACP_WEBPUSH_SETTINGS', 'vars' => [ 'legend1' => 'GENERAL_SETTINGS', + 'webpush_enable' => ['lang' => 'WEBPUSH_ENABLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], @@ -537,6 +539,27 @@ function main($id, $mode) } } + if ($mode == 'webpush') + { + // Create VAPID keys if keys are empty and web push is enabled + if ($submit && $cfg_array['webpush_enable'] && $cfg_array['webpush_enable'] != $config['webpush_enable'] + && empty($cfg_array['webpush_vapid_public']) && empty($cfg_array['webpush_vapid_private']) + && empty($config['webpush_vapid_public']) && empty($config['webpush_vapid_private'])) + { + try + { + $vapid_keys = VAPID::createVapidKeys(); + $cfg_array['webpush_vapid_public'] = $vapid_keys['publicKey']; + $cfg_array['webpush_vapid_private'] = $vapid_keys['privateKey']; + } + catch (\ErrorException $exception) + { + // Nothing we can do about this, user will have to follow the + // documentation and manually create these. + } + } + } + // We validate the complete config if wished validate_config_vars($display_vars['vars'], $cfg_array, $error); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 844ba402b39..b924f706035 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -326,6 +326,9 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\avatar\pro INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\avatar\config\path', 'images/avatars/upload'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\backup\provider', 'phpbb\storage\provider\local'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\backup\config\path', 'store'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_enable', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_public', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_private', ''); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 37303d481a9..8fe9f832f2e 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -601,6 +601,8 @@ $lang = array_merge($lang, [ 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Webpush for board notifications. Webpush is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', + 'WEBPUSH_ENABLE' => 'Enable Webpush', + 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush.
Note: If VAPID keys have not been set, phpBB will try to automatically create them when enabling Webpush.', 'WEBPUSH_VAPID_PUBLIC' => 'VAPID public key', 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The VAPID public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all webpush subscriptions.', 'WEBPUSH_VAPID_PRIVATE' => 'VAPID private key', diff --git a/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php similarity index 88% rename from phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php rename to phpBB/phpbb/db/migration/data/v400/add_webpush.php index e822a394057..3230acfc8db 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_notification_push_table.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -13,9 +13,10 @@ namespace phpbb\db\migration\data\v400; +use Minishlink\WebPush\VAPID; use phpbb\db\migration\migration; -class add_notification_push_table extends migration +class add_webpush extends migration { public static function depends_on(): array { @@ -56,6 +57,7 @@ public function revert_schema(): array public function update_data(): array { return [ + ['config.add', ['webpush_enable', false]], ['config.add', ['webpush_vapid_public', '']], ['config.add', ['webpush_vapid_private', '']], ['module.add', [ @@ -75,8 +77,10 @@ public function update_data(): array public function revert_data(): array { return [ + ['config.remove', ['webpush_enable']], ['config.remove', ['webpush_vapid_public']], ['config.remove', ['webpush_vapid_private']], + ['module.remove', ['acp', 'ACP_BOARD_CONFIGURATION', 'ACP_WEBPUSH_SETTINGS']] ]; } } diff --git a/tests/migrations/migrations_check_config_added_test.php b/tests/migrations/migrations_check_config_added_test.php index 969a0f5e795..1472f9e3709 100644 --- a/tests/migrations/migrations_check_config_added_test.php +++ b/tests/migrations/migrations_check_config_added_test.php @@ -142,7 +142,7 @@ public function get_config_options_from_migrations() continue; } - // Fill error entries for configuration options which were not added to shema_data.sql + // Fill error entries for configuration options which were not added to schema_data.sql if (!isset($config_names[$config_name])) { $config_names[$config_name] = [$config_name, $class]; @@ -160,7 +160,7 @@ public function get_config_options_from_migrations() */ public function test_config_option_exists_in_schema_data($config_name, $class) { - $message = 'Migration: %1$s, config_name: %2$s; not added to shema_data.sql'; + $message = 'Migration: %1$s, config_name: %2$s; not added to schema_data.sql'; $this->assertNotFalse(strpos($this->schema_data, $config_name), sprintf($message, $class, $config_name) From 1c64c695a9832b8d00ff9546443e68b0ff6c4821 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 30 Aug 2022 20:49:07 +0200 Subject: [PATCH 0008/1214] [ticket/17010] Implement basic logic for webpush notifications PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 46 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 48787901b03..eab1dac6811 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -15,6 +15,7 @@ use phpbb\config\config; use phpbb\db\driver\driver_interface; +use phpbb\json\sanitizer; use phpbb\notification\type\type_interface; use phpbb\user; use phpbb\user_loader; @@ -72,7 +73,8 @@ public function get_type(): string */ public function is_available(type_interface $notification_type = null): bool { - return parent::is_available($notification_type) && $this->config['webpush_enable'] && !empty($this->user->data['user_push_subscriptions']); + return parent::is_available($notification_type) && $this->config['webpush_enable'] + && !empty($this->config['webpush_vapid_public']) && !empty($this->config['webpush_vapid_private']); } /** @@ -114,13 +116,17 @@ public function notify() $insert_buffer->flush(); - // @todo: add actual web push code $this->notify_using_webpush(); return false; } - protected function notify_using_webpush() + /** + * Notify using web push + * + * @return void + */ + protected function notify_using_webpush(): void { if (empty($this->queue)) { @@ -144,28 +150,58 @@ protected function notify_using_webpush() // Load all the users we need $this->user_loader->load_users(array_diff($user_ids, $banned_users), array(USER_IGNORE)); + $web_push = new \Minishlink\WebPush\WebPush(); + // Time to go through the queue and send emails /** @var type_interface $notification */ foreach ($this->queue as $notification) { $user = $this->user_loader->get_user($notification->user_id); - if ($user['user_type'] == USER_INACTIVE && $user['user_inactive_reason'] == INACTIVE_MANUAL) + $user_subscriptions = sanitizer::decode($this->user->data['user_push_subscriptions']); + + if ($user['user_type'] == USER_INACTIVE && $user['user_inactive_reason'] == INACTIVE_MANUAL + || empty($user_subscriptions)) { continue; } // add actual web push data $data['data'] = [ - 'badge' => '', // @todo: to be filled? 'body' => $notification->get_title(), 'icon' => '', // @todo: to be filled? 'image' => '', // @todo: to be filled? 'title' => $this->config['sitename'], 'url' => $notification->get_url(), + 'user_id' => $notification->user_id, ]; + $json_data = json_encode($data); // @todo: start implementing actual web push code + + foreach ($user_subscriptions as $subscription) + { + try + { + $push_subscription = \Minishlink\WebPush\Subscription::create($subscription); + $web_push->queueNotification($push_subscription, $json_data); + } + catch (\ErrorException $exception) + { + // @todo: decide whether we want to remove invalid subscriptions directly? + // Might need too many resources ... + } + } + } + + // @todo: Try offloading to after request + try + { + $web_push->flush(); + } + catch (\ErrorException $exception) + { + // @todo: Add to error log if we can't flush ... } // We're done, empty the queue From 83cf915758f830c0544165b12411f6f8b8d385a8 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 31 Aug 2022 20:06:07 +0200 Subject: [PATCH 0009/1214] [ticket/17010] Add column for user push subscriptions PHPBB3-17010 --- phpBB/phpbb/db/migration/data/v400/add_webpush.php | 7 +++++++ phpBB/phpbb/notification/method/webpush.php | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php index 3230acfc8db..abc0ffc0794 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -44,6 +44,13 @@ public function update_schema(): array 'PRIMARY_KEY' => ['notification_type_id', 'item_id', 'item_parent_id', 'user_id'], ], ], + 'add_columns' => [ + $this->table_prefix . 'users' => [ + 'COLUMNS' => [ + 'user_push_subscriptions' => ['MTEXT_UNI', ''] + ], + ], + ], ]; } diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index eab1dac6811..2532246624b 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -168,17 +168,15 @@ protected function notify_using_webpush(): void // add actual web push data $data['data'] = [ + 'title' => $this->config['sitename'], 'body' => $notification->get_title(), 'icon' => '', // @todo: to be filled? 'image' => '', // @todo: to be filled? - 'title' => $this->config['sitename'], 'url' => $notification->get_url(), 'user_id' => $notification->user_id, ]; $json_data = json_encode($data); - // @todo: start implementing actual web push code - foreach ($user_subscriptions as $subscription) { try From af3108044576586efb1d9b67697319fb1bba89a2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 18 Sep 2022 21:28:25 +0200 Subject: [PATCH 0010/1214] [ticket/17010] Add web-push-testing for implementing web push tests PHPBB3-17010 --- package-lock.json | 1671 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 1650 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7471a2fec21..5801393ff31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "postcss": "^8.4.31", "postcss-sorting": "^7.0.1", "stylelint": "^14.7.0", - "stylelint-order": "^5.0.0" + "stylelint-order": "^5.0.0", + "web-push-testing": "^1.0.0" } }, "node_modules/@babel/code-frame": { @@ -325,6 +326,19 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -471,6 +485,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -537,6 +557,12 @@ "node": ">=0.10.0" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, "node_modules/array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -819,6 +845,45 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -894,12 +959,27 @@ "node": ">=0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -1377,6 +1457,53 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "node_modules/connected-domain": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/connected-domain/-/connected-domain-1.0.0.tgz", + "integrity": "sha512-lHlohUiJxlpunvDag2Y0pO20bnvarMjnrdciZeuJUqRwrf/5JHNhdpiPIr5GQ8IkqrFj5TDMQwcCjblGo1oeuA==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -1386,6 +1513,21 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -1754,6 +1896,25 @@ "node": ">=0.10.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -1885,6 +2046,21 @@ "node": ">=0.10.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.4.111", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz", @@ -1897,6 +2073,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1983,6 +2168,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2170,6 +2361,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -2317,6 +2517,83 @@ "node": ">=0.10.0" } }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -2549,6 +2826,39 @@ "node": ">=0.10.0" } }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2664,6 +2974,15 @@ "node": ">=0.10.0" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -2689,6 +3008,15 @@ "node": ">=0.10.0" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -3396,6 +3724,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http_ece": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.1.0.tgz", + "integrity": "sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==", + "dev": true, + "dependencies": { + "urlsafe-base64": "~1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -3506,6 +3874,15 @@ "node": ">=0.10.0" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -3830,12 +4207,64 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", "dev": true }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", @@ -4065,6 +4494,12 @@ "lodash.restparam": "^3.0.0" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -4077,6 +4512,36 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -4100,6 +4565,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "node_modules/lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", @@ -4251,6 +4722,15 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/memoizee": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", @@ -4305,6 +4785,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4314,6 +4800,15 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -4384,6 +4879,39 @@ "node": ">=0.10.0" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -4405,6 +4933,12 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, "node_modules/minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", @@ -4465,6 +4999,18 @@ "node": ">=0.10.0" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4546,45 +5092,111 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "dependencies": { - "is-plain-object": "^2.0.4" + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "node_modules/node-persist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz", + "integrity": "sha512-NI30KmynAIpKtvl3XaLE/Q/hPUNfh2bFM0U9zgWyIVzBUL/fh1EMk2/rTAqWY6KXrX8jqusVA6avPJ6I2S9B4w==", + "dev": true, + "dependencies": { + "is-absolute": "^0.2.6", + "mkdirp": "~0.5.1", + "q": "~1.1.1" + } + }, + "node_modules/node-persist/node_modules/is-absolute": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", + "integrity": "sha512-7Kr05z5LkcOpoMvxHN1PC11WbPabdNFmMYYo0eZvWu3BfVS0T03yoqYDczoCBx17xqk2x1XAZrcKiFVL88jxlQ==", + "dev": true, + "dependencies": { + "is-relative": "^0.2.1", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-persist/node_modules/is-relative": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", + "integrity": "sha512-9AMzjRmLqcue629b4ezEVSK6kJsYJlUIhMcygmYORUgwUNJiavHcC3HkaGx0XYpyVKQSOqFbMEZmW42cY87sYw==", + "dev": true, + "dependencies": { + "is-unc-path": "^0.1.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/nanomatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/node-persist/node_modules/is-unc-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", + "integrity": "sha512-HhLc5VDMH4pu3oMtIuunz/DFQUIoR561kMME3U3Afhj8b7vH085vkIkemrz1kLXCEIuoMAmO3yVmafWdSbGW8w==", "dev": true, "dependencies": { - "isobject": "^3.0.1" + "unc-path-regex": "^0.1.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/node-persist/node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, "node_modules/node-releases": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", @@ -4775,6 +5387,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -4867,6 +5488,18 @@ "node": ">=0.10.0" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5024,6 +5657,15 @@ "node": ">=0.10.0" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -5093,6 +5735,12 @@ "node": ">=0.10.0" } }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5759,6 +6407,28 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ps-node": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ps-node/-/ps-node-0.1.6.tgz", + "integrity": "sha512-w7QJhUTbu70hpDso0YXDRNKCPNuchV8UTUZsAv0m7Qj5g85oHOJfr9drA1EjvK4nQK/bG8P97W4L6PJ3IQLoOA==", + "dev": true, + "dependencies": { + "table-parser": "^0.1.3" + } + }, "node_modules/pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -5789,6 +6459,31 @@ "node": ">=6" } }, + "node_modules/q": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", + "integrity": "sha512-ROtylwux7Vkc4C07oKE/ReigUmb33kVoLtcR4SJ1QVqwaZkBEDL3vX4/kwFzIERQ5PfCl0XafbU8u2YUhyGgVA==", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5818,6 +6513,30 @@ "node": ">=8" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -6326,6 +7045,12 @@ "ret": "~0.1.10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6353,6 +7078,66 @@ "node": ">= 0.10" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6386,6 +7171,12 @@ "node": ">=0.10.0" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6407,6 +7198,20 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6862,6 +7667,15 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -7275,6 +8089,15 @@ "node": ">=10.0.0" } }, + "node_modules/table-parser": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/table-parser/-/table-parser-0.1.3.tgz", + "integrity": "sha512-LCYeuvqqoPII3lzzYaXKbC3Forb+d2u4bNwhk/9FlivuGRxPE28YEWAYcujeSlLLDlMfvy29+WPybFJZFiKMYg==", + "dev": true, + "dependencies": { + "connected-domain": "^1.0.0" + } + }, "node_modules/table/node_modules/ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -7506,6 +8329,15 @@ "xtend": "~4.0.1" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -7545,6 +8377,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -7630,6 +8475,15 @@ "through2-filter": "^3.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -7716,6 +8570,12 @@ "node": ">=0.10.0" } }, + "node_modules/urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==", + "dev": true + }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -7731,6 +8591,15 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -7768,6 +8637,15 @@ "node": ">= 0.10" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vinyl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", @@ -7871,6 +8749,27 @@ "node": ">=0.10.0" } }, + "node_modules/web-push-testing": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.0.0.tgz", + "integrity": "sha512-p14iWiQSPY4kjpbaz59QfczaxEyD4uWg1aBjUS+Y9LAzQPU4Z95Jnq3FiOIazrvrfY0/1uDBbQkjXVxHq6wI/Q==", + "dev": true, + "dependencies": { + "arg": "^5.0.1", + "express": "^4.17.2", + "http_ece": "^1.1.0", + "jsonwebtoken": "^8.5.1", + "node-persist": "^2.1.0", + "ps-node": "^0.1.6", + "urlsafe-base64": "^1.0.0" + }, + "bin": { + "web-push-testing": "src/bin/cli.js" + }, + "engines": { + "node": ">=15.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8447,6 +9346,16 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", @@ -8556,6 +9465,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -8604,6 +9519,12 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -8813,6 +9734,43 @@ "file-uri-to-path": "1.0.0" } }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -8866,12 +9824,24 @@ "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -9255,6 +10225,35 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "connected-domain": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/connected-domain/-/connected-domain-1.0.0.tgz", + "integrity": "sha512-lHlohUiJxlpunvDag2Y0pO20bnvarMjnrdciZeuJUqRwrf/5JHNhdpiPIr5GQ8IkqrFj5TDMQwcCjblGo1oeuA==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -9264,6 +10263,18 @@ "safe-buffer": "~5.1.1" } }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -9545,6 +10556,18 @@ "isobject": "^3.0.1" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -9645,6 +10668,21 @@ } } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, "electron-to-chromium": { "version": "1.4.111", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz", @@ -9657,6 +10695,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -9739,6 +10783,12 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -9871,6 +10921,12 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -9997,6 +11053,68 @@ "homedir-polyfill": "^1.0.1" } }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -10196,6 +11314,38 @@ "to-regex-range": "^2.1.0" } }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -10289,6 +11439,12 @@ "for-in": "^1.0.1" } }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, "fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -10304,6 +11460,12 @@ "map-cache": "^0.2.2" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -10865,6 +12027,37 @@ "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true }, + "http_ece": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.1.0.tgz", + "integrity": "sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==", + "dev": true, + "requires": { + "urlsafe-base64": "~1.0.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -10945,6 +12138,12 @@ "integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=", "dev": true }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -11202,12 +12401,59 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", "dev": true }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", @@ -11405,6 +12651,12 @@ "lodash.restparam": "^3.0.0" } }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -11417,6 +12669,36 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -11440,6 +12722,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "lodash.restparam": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", @@ -11561,6 +12849,12 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, "memoizee": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", @@ -11605,12 +12899,24 @@ } } }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -11668,6 +12974,27 @@ } } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -11683,6 +13010,12 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, "minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", @@ -11732,6 +13065,15 @@ } } }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -11818,12 +13160,65 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node-persist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz", + "integrity": "sha512-NI30KmynAIpKtvl3XaLE/Q/hPUNfh2bFM0U9zgWyIVzBUL/fh1EMk2/rTAqWY6KXrX8jqusVA6avPJ6I2S9B4w==", + "dev": true, + "requires": { + "is-absolute": "^0.2.6", + "mkdirp": "~0.5.1", + "q": "~1.1.1" + }, + "dependencies": { + "is-absolute": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", + "integrity": "sha512-7Kr05z5LkcOpoMvxHN1PC11WbPabdNFmMYYo0eZvWu3BfVS0T03yoqYDczoCBx17xqk2x1XAZrcKiFVL88jxlQ==", + "dev": true, + "requires": { + "is-relative": "^0.2.1", + "is-windows": "^0.2.0" + } + }, + "is-relative": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", + "integrity": "sha512-9AMzjRmLqcue629b4ezEVSK6kJsYJlUIhMcygmYORUgwUNJiavHcC3HkaGx0XYpyVKQSOqFbMEZmW42cY87sYw==", + "dev": true, + "requires": { + "is-unc-path": "^0.1.1" + } + }, + "is-unc-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", + "integrity": "sha512-HhLc5VDMH4pu3oMtIuunz/DFQUIoR561kMME3U3Afhj8b7vH085vkIkemrz1kLXCEIuoMAmO3yVmafWdSbGW8w==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.0" + } + }, + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true + } + } + }, "node-releases": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", @@ -11970,6 +13365,12 @@ } } }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -12038,6 +13439,15 @@ "make-iterator": "^1.0.0" } }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -12156,6 +13566,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -12207,6 +13623,12 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -12629,6 +14051,25 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "ps-node": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ps-node/-/ps-node-0.1.6.tgz", + "integrity": "sha512-w7QJhUTbu70hpDso0YXDRNKCPNuchV8UTUZsAv0m7Qj5g85oHOJfr9drA1EjvK4nQK/bG8P97W4L6PJ3IQLoOA==", + "dev": true, + "requires": { + "table-parser": "^0.1.3" + } + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -12656,6 +14097,21 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "q": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", + "integrity": "sha512-ROtylwux7Vkc4C07oKE/ReigUmb33kVoLtcR4SJ1QVqwaZkBEDL3vX4/kwFzIERQ5PfCl0XafbU8u2YUhyGgVA==", + "dev": true + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12668,6 +14124,24 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -13072,6 +14546,12 @@ "ret": "~0.1.10" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -13090,6 +14570,64 @@ "sver-compat": "^1.5.0" } }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -13119,6 +14657,12 @@ } } }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -13134,6 +14678,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -13514,6 +15069,12 @@ } } }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -13855,6 +15416,15 @@ } } }, + "table-parser": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/table-parser/-/table-parser-0.1.3.tgz", + "integrity": "sha512-LCYeuvqqoPII3lzzYaXKbC3Forb+d2u4bNwhk/9FlivuGRxPE28YEWAYcujeSlLLDlMfvy29+WPybFJZFiKMYg==", + "dev": true, + "requires": { + "connected-domain": "^1.0.0" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -14044,6 +15614,12 @@ } } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -14071,6 +15647,16 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -14145,6 +15731,12 @@ "through2-filter": "^3.0.0" } }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -14215,6 +15807,12 @@ "ip-regex": "^1.0.1" } }, + "urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -14227,6 +15825,12 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -14258,6 +15862,12 @@ "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", "dev": true }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, "vinyl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", @@ -14352,6 +15962,21 @@ } } }, + "web-push-testing": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.0.0.tgz", + "integrity": "sha512-p14iWiQSPY4kjpbaz59QfczaxEyD4uWg1aBjUS+Y9LAzQPU4Z95Jnq3FiOIazrvrfY0/1uDBbQkjXVxHq6wI/Q==", + "dev": true, + "requires": { + "arg": "^5.0.1", + "express": "^4.17.2", + "http_ece": "^1.1.0", + "jsonwebtoken": "^8.5.1", + "node-persist": "^2.1.0", + "ps-node": "^0.1.6", + "urlsafe-base64": "^1.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 932fb51f6d3..22103522f3f 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "postcss": "^8.4.31", "postcss-sorting": "^7.0.1", "stylelint": "^14.7.0", - "stylelint-order": "^5.0.0" + "stylelint-order": "^5.0.0", + "web-push-testing": "^1.0.0" } } From ec3dc28b5107b19fc409dc8d02268e918e2d11cc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 18 Sep 2022 21:29:25 +0200 Subject: [PATCH 0011/1214] [ticket/17010] Switch to using table for tracking push subscriptions PHPBB3-17010 --- .../container/services_notification.yml | 1 + phpBB/config/default/container/tables.yml | 1 + .../db/migration/data/v400/add_webpush.php | 20 ++++-- phpBB/phpbb/notification/method/webpush.php | 62 ++++++++++++++----- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index ebde7bcf5ab..c018416da36 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -255,3 +255,4 @@ services: - '%core.root_path%' - '%core.php_ext%' - '%tables.notification_push%' + - '%tables.push_subscriptions%' diff --git a/phpBB/config/default/container/tables.yml b/phpBB/config/default/container/tables.yml index f3a4e1b4abd..8e38c63a4b4 100644 --- a/phpBB/config/default/container/tables.yml +++ b/phpBB/config/default/container/tables.yml @@ -51,6 +51,7 @@ parameters: tables.profile_fields_data: '%core.table_prefix%profile_fields_data' tables.profile_fields_options_language: '%core.table_prefix%profile_fields_lang' tables.profile_fields_language: '%core.table_prefix%profile_lang' + tables.push_subscriptions: '%core.table_prefix%push_subscriptions' tables.ranks: '%core.table_prefix%ranks' tables.reports: '%core.table_prefix%reports' tables.reports_reasons: '%core.table_prefix%reports_reasons' diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php index abc0ffc0794..f426df5b9b8 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -43,13 +43,18 @@ public function update_schema(): array ], 'PRIMARY_KEY' => ['notification_type_id', 'item_id', 'item_parent_id', 'user_id'], ], - ], - 'add_columns' => [ - $this->table_prefix . 'users' => [ + $this->table_prefix . 'push_subscriptions' => [ 'COLUMNS' => [ - 'user_push_subscriptions' => ['MTEXT_UNI', ''] + 'subscription_id' => ['ULINT', null, 'auto_increment'], + 'user_id' => ['ULINT', 0], + 'device_name' => ['VCHAR:64', ''], + 'endpoint' => ['TEXT', ''], + 'p256dh' => ['VCHAR', ''], + 'auth' => ['VCHAR', ''], + 'encoding' => ['VCHAR:32', ''], ], - ], + 'PRIMARY_KEY' => ['subscription_id', 'user_id'], + ] ], ]; } @@ -57,7 +62,10 @@ public function update_schema(): array public function revert_schema(): array { return [ - 'drop_tables' => [$this->table_prefix . 'notification_push'], + 'drop_tables' => [ + $this->table_prefix . 'notification_push', + $this->table_prefix . 'push_subscriptions', + ], ]; } diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 2532246624b..0fc704e8de2 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -39,6 +39,9 @@ class webpush extends \phpbb\notification\method\messenger_base /** @var string Notification web push table */ protected $notification_webpush_table; + /** @var string Notification push subscriptions table */ + protected $push_subscriptions_table; + /** * Notification Method web push constructor * @@ -49,8 +52,10 @@ class webpush extends \phpbb\notification\method\messenger_base * @param string $phpbb_root_path * @param string $php_ext * @param string $notification_webpush_table + * @param string $push_subscriptions_table */ - public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db, string $phpbb_root_path, string $php_ext, string $notification_webpush_table) + public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db,string $phpbb_root_path, + string $php_ext, string $notification_webpush_table, string $push_subscriptions_table) { parent::__construct($user_loader, $phpbb_root_path, $php_ext); @@ -58,6 +63,7 @@ public function __construct(user_loader $user_loader, user $user, config $config $this->config = $config; $this->db = $db; $this->notification_webpush_table = $notification_webpush_table; + $this->push_subscriptions_table = $push_subscriptions_table; } /** @@ -148,9 +154,36 @@ protected function notify_using_webpush(): void $banned_users = phpbb_get_banned_user_ids($user_ids); // Load all the users we need - $this->user_loader->load_users(array_diff($user_ids, $banned_users), array(USER_IGNORE)); + $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 = []; + $sql = 'SELECT * FROM ' . $this->push_subscriptions_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $notify_users) . ' + GROUP BY user_id'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (isset($user_subscriptions[$row['user_id']])) + { + $user_subscription_map[$row['user_id']] += $row; + } + else + { + $user_subscription_map[$row['user_id']] = [$row]; + } + } + + $auth = [ + 'VAPID' => [ + 'subject' => generate_board_url(false), + 'publicKey' => $this->config['webpush_vapid_public'], + 'privateKey' => $this->config['webpush_vapid_private'], + ], + ]; - $web_push = new \Minishlink\WebPush\WebPush(); + $web_push = new \Minishlink\WebPush\WebPush($auth); // Time to go through the queue and send emails /** @var type_interface $notification */ @@ -158,7 +191,7 @@ protected function notify_using_webpush(): void { $user = $this->user_loader->get_user($notification->user_id); - $user_subscriptions = sanitizer::decode($this->user->data['user_push_subscriptions']); + $user_subscriptions = $user_subscription_map[$notification->user_id] ?? []; if ($user['user_type'] == USER_INACTIVE && $user['user_inactive_reason'] == INACTIVE_MANUAL || empty($user_subscriptions)) @@ -181,8 +214,17 @@ protected function notify_using_webpush(): void { try { - $push_subscription = \Minishlink\WebPush\Subscription::create($subscription); - $web_push->queueNotification($push_subscription, $json_data); + $push_subscription = \Minishlink\WebPush\Subscription::create([ + 'endpoint' => $subscription['endpoint'], + 'keys' => [ + 'p256dh' => $subscription['p256dh'], + 'auth' => $subscription['auth'], + ], + 'contentEncoding' => !empty($subscription['encoding']) ? $subscription['encoding'] : null, + ]); + //$web_push->queueNotification($push_subscription, $json_data); + $foo = $web_push->sendOneNotification($push_subscription, $json_data); + $meh = 2; } catch (\ErrorException $exception) { @@ -193,14 +235,6 @@ protected function notify_using_webpush(): void } // @todo: Try offloading to after request - try - { - $web_push->flush(); - } - catch (\ErrorException $exception) - { - // @todo: Add to error log if we can't flush ... - } // We're done, empty the queue $this->empty_queue(); From be21479b2a591e98526bb9d5862c3f0ba9e080f9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 18 Sep 2022 21:30:01 +0200 Subject: [PATCH 0012/1214] [ticket/17010] Add tests for push notification type PHPBB3-17010 --- tests/notification/base.php | 1 + .../webpush_notification.type.post.xml | 284 ++++++++++++ .../notification_method_email_test.php | 1 + .../notification_method_webpush_test.php | 433 ++++++++++++++++++ tests/notification/submit_post_base.php | 1 + 5 files changed, 720 insertions(+) create mode 100644 tests/notification/fixtures/webpush_notification.type.post.xml create mode 100644 tests/notification/notification_method_webpush_test.php diff --git a/tests/notification/base.php b/tests/notification/base.php index eb888cb0ae6..37718a85a95 100644 --- a/tests/notification/base.php +++ b/tests/notification/base.php @@ -125,6 +125,7 @@ protected function setUp(): void $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); + $phpbb_container->setParameter('tables.push_subscriptions', 'phpbb_push_subscriptions'); $this->notifications = new phpbb_notification_manager_helper( array(), diff --git a/tests/notification/fixtures/webpush_notification.type.post.xml b/tests/notification/fixtures/webpush_notification.type.post.xml new file mode 100644 index 00000000000..7654ed30aad --- /dev/null +++ b/tests/notification/fixtures/webpush_notification.type.post.xml @@ -0,0 +1,284 @@ + + + + forum_id + user_id + notify_status + + 1 + 6 + 0 + + + 1 + 7 + 0 + + + 1 + 8 + 0 + +
+ + notification_id + notification_type_id + user_id + item_id + item_parent_id + notification_read + notification_data + + 1 + 1 + 5 + 1 + 1 + 0 + + + + 2 + 1 + 8 + 1 + 1 + 0 + + +
+ +
+ + notification_type_id + notification_type_name + notification_type_enabled + + 1 + notification.type.post + 1 + + + 2 + notification.type.forum + 1 + +
+ + post_id + topic_id + forum_id + post_text + + 1 + 1 + 1 + + +
+ + subscription_id + user_id + endpoint +
+ + topic_id + forum_id + + 1 + 1 + + + 2 + 1 + +
+ + topic_id + user_id + notify_status + + 1 + 2 + 0 + + + 2 + 2 + 0 + + + 1 + 3 + 0 + + + 1 + 4 + 0 + + + 1 + 5 + 0 + + + 1 + 6 + 0 + +
+ + user_id + username_clean + user_permissions + user_sig + + 2 + poster + + + + + 3 + test + + + + + 4 + unauthorized + + + + + 5 + notified + + + + + 6 + disabled + + + + + 7 + default + + + + + 8 + latest + + + +
+ + item_type + item_id + user_id + method + notify + + notification.type.post + 0 + 2 + notification.method.webpush + 1 + + + notification.type.post + 0 + 3 + notification.method.webpush + 1 + + + notification.type.post + 0 + 4 + notification.method.webpush + 1 + + + notification.type.post + 0 + 5 + notification.method.webpush + 1 + + + notification.type.post + 0 + 6 + notification.method.webpush + 1 + + + notification.type.post + 0 + 7 + notification.method.webpush + 1 + + + notification.type.post + 0 + 8 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 2 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 3 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 4 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 5 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 6 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 7 + notification.method.webpush + 1 + + + notification.type.forum + 0 + 8 + notification.method.webpush + 1 + +
+
diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php index efcac83035f..77db505c04d 100644 --- a/tests/notification/notification_method_email_test.php +++ b/tests/notification/notification_method_email_test.php @@ -92,6 +92,7 @@ protected function setUp(): void $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); + $phpbb_container->setParameter('tables.push_subscriptions', 'phpbb_push_subscriptions'); $phpbb_container->set( 'text_formatter.s9e.mention_helper', new \phpbb\textformatter\s9e\mention_helper( diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php new file mode 100644 index 00000000000..90e1d14deb6 --- /dev/null +++ b/tests/notification/notification_method_webpush_test.php @@ -0,0 +1,433 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\notification\method\webpush; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +require_once __DIR__ . '/base.php'; + +/** + * @group + */ +class notification_method_webpush_test extends phpbb_tests_notification_base +{ + /** @var string[] VAPID keys for testing purposes */ + public const VAPID_KEYS = [ + 'publicKey' => 'BIcGkq1Ncj3a2-J0UW-1A0NETLjvxZzNLiYBiPVMKNjgwmwPi5jyK87VfS4FZn9n7S9pLMQzjV3LmFuOnRSOvmI', + 'privateKey' => 'SrlbBEVgibWmKHYbDPu4Y2XvDWPjeGcc9fC16jq01xU', + ]; + + /** @var webpush */ + protected $notification_method_webpush; + + public function getDataSet() + { + return $this->createXMLDataSet(__DIR__ . '/fixtures/webpush_notification.type.post.xml'); + } + + protected function get_notification_methods() + { + return [ + 'notification.method.webpush', + ]; + } + + public static function setUpBeforeClass(): void + { + self::start_webpush_testing(); + } + + public static function tearDownAfterClass(): void + { + self::stop_webpush_testing(); + } + + protected static function start_webpush_testing(): void + { + // Stop first to ensure port is available + self::stop_webpush_testing(); + + $process = new \Symfony\Component\Process\Process(['node_modules/.bin/web-push-testing', '--port', '9012', 'start']); + $process->run(); + } + + protected static function stop_webpush_testing(): void + { + $process = new \Symfony\Component\Process\Process(['node_modules/.bin/web-push-testing', '--port', '9012', 'stop']); + $process->run(); + } + + protected function setUp(): void + { + phpbb_database_test_case::setUp(); + + global $phpbb_root_path, $phpEx; + + include_once(__DIR__ . '/ext/test/notification/type/test.' . $phpEx); + + global $db, $config, $user, $auth, $cache, $phpbb_container, $phpbb_dispatcher; + + $avatar_helper = $this->getMockBuilder('\phpbb\avatar\helper') + ->disableOriginalConstructor() + ->getMock(); + $db = $this->db = $this->new_dbal(); + $config = $this->config = new \phpbb\config\config([ + 'allow_privmsg' => true, + 'allow_bookmarks' => true, + 'allow_topic_notify' => true, + 'allow_forum_notify' => true, + 'allow_board_notifications' => true, + 'webpush_vapid_public' => self::VAPID_KEYS['publicKey'], + 'webpush_vapid_private' => self::VAPID_KEYS['privateKey'], + ]); + $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); + $lang = new \phpbb\language\language($lang_loader); + $user = new \phpbb\user($lang, '\phpbb\datetime'); + $this->user = $user; + $this->user_loader = new \phpbb\user_loader($avatar_helper, $this->db, $phpbb_root_path, $phpEx, 'phpbb_users'); + $auth = $this->auth = new phpbb_mock_notifications_auth(); + $this->phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $phpbb_dispatcher = $this->phpbb_dispatcher; + $cache_driver = new \phpbb\cache\driver\dummy(); + $cache = $this->cache = new \phpbb\cache\service( + $cache_driver, + $this->config, + $this->db, + $this->phpbb_dispatcher, + $phpbb_root_path, + $phpEx + ); + + $phpbb_container = $this->container = new ContainerBuilder(); + $loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__ . '/fixtures')); + $loader->load('services_notification.yml'); + $phpbb_container->set('user_loader', $this->user_loader); + $phpbb_container->set('user', $user); + $phpbb_container->set('language', $lang); + $phpbb_container->set('config', $this->config); + $phpbb_container->set('dbal.conn', $this->db); + $phpbb_container->set('auth', $auth); + $phpbb_container->set('cache.driver', $cache_driver); + $phpbb_container->set('cache', $cache); + $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); + $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); + $phpbb_container->setParameter('core.root_path', $phpbb_root_path); + $phpbb_container->setParameter('core.php_ext', $phpEx); + $phpbb_container->setParameter('tables.notifications', 'phpbb_notifications'); + $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); + $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); + $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); + $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); + $phpbb_container->setParameter('tables.push_subscriptions', 'phpbb_push_subscriptions'); + $phpbb_container->set( + 'text_formatter.s9e.mention_helper', + new \phpbb\textformatter\s9e\mention_helper( + $this->db, + $auth, + $this->user, + $phpbb_root_path, + $phpEx + ) + ); + + $this->notification_method_webpush = new \phpbb\notification\method\webpush( + $phpbb_container->get('user_loader'), + $phpbb_container->get('user'), + $phpbb_container->get('config'), + $phpbb_container->get('dbal.conn'), + $phpbb_root_path, + $phpEx, + $phpbb_container->getParameter('tables.notification_push'), + $phpbb_container->getParameter('tables.push_subscriptions') + ); + + $phpbb_container->set('notification.method.webpush', $this->notification_method_webpush); + + $this->notifications = new phpbb_notification_manager_helper( + array(), + array(), + $this->container, + $this->user_loader, + $this->phpbb_dispatcher, + $this->db, + $this->cache, + $lang, + $this->user, + 'phpbb_notification_types', + 'phpbb_user_notifications' + ); + + $phpbb_container->set('notification_manager', $this->notifications); + + $phpbb_container->addCompilerPass(new phpbb\di\pass\markpublic_pass()); + + $phpbb_container->compile(); + + $this->notifications->setDependencies($this->auth, $this->config); + + $types = array(); + foreach ($this->get_notification_types() as $type) + { + $class = $this->build_type($type); + + $types[$type] = $class; + } + + $this->notifications->set_var('notification_types', $types); + + $methods = array(); + foreach ($this->get_notification_methods() as $method) + { + $class = $this->container->get($method); + + $methods[$method] = $class; + } + + $this->notifications->set_var('notification_methods', $methods); + } + + public function data_notification_webpush() + { + return [ + /** + * Normal post + * + * User => State description + * 2 => Topic id=1 and id=2 subscribed, should receive a new topics post notification + * 3 => Topic id=1 subscribed, should receive a new topic post notification + * 4 => Topic id=1 subscribed, should receive a new topic post notification + * 5 => Topic id=1 subscribed, post id=1 already notified, should receive a new topic post notification + * 6 => Topic id=1 and forum id=1 subscribed, should receive a new topic/forum post notification + * 7 => Forum id=1 subscribed, should NOT receive a new topic post but a forum post notification + * 8 => Forum id=1 subscribed, post id=1 already notified, should NOT receive a new topic post but a forum post notification + */ + [ + 'notification.type.post', + [ + 'forum_id' => '1', + 'post_id' => '2', + 'topic_id' => '1', + ], + [ + 2 => ['user_id' => '2'], + 3 => ['user_id' => '3'], + 4 => ['user_id' => '4'], + 5 => ['user_id' => '5'], + 6 => ['user_id' => '6'], + ], + ], + [ + 'notification.type.forum', + [ + 'forum_id' => '1', + 'post_id' => '3', + 'topic_id' => '1', + ], + [ + 6 => ['user_id' => '6'], + 7 => ['user_id' => '7'], + 8 => ['user_id' => '8'] + ], + ], + [ + 'notification.type.post', + [ + 'forum_id' => '1', + 'post_id' => '4', + 'topic_id' => '2', + ], + [ + 2 => ['user_id' => '2'], + ], + ], + [ + 'notification.type.forum', + [ + 'forum_id' => '1', + 'post_id' => '5', + 'topic_id' => '2', + ], + [ + 6 => ['user_id' => '6'], + 7 => ['user_id' => '7'], + 8 => ['user_id' => '8'], + ], + ], + [ + 'notification.type.post', + [ + 'forum_id' => '2', + 'post_id' => '6', + 'topic_id' => '3', + ], + [ + ], + ], + [ + 'notification.type.forum', + [ + 'forum_id' => '2', + 'post_id' => '6', + 'topic_id' => '3', + ], + [ + ], + ], + ]; + } + + /** + * @dataProvider data_notification_webpush + */ + public function test_notification_webpush($notification_type, $post_data, $expected_users) + { + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + $post_data['post_id']++; + $notification_options['item_id'] = $post_data['post_id']; + $post_data['post_time'] = 1349413323; + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users2 = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users2, 'Assert that expected users stay the same after replying to same topic'); + } + + /** + * @dataProvider data_notification_webpush + */ + public function test_get_subscription($notification_type, $post_data, $expected_users): void + { + $subscription_info = []; + foreach ($expected_users as $user_id => $user_data) + { + $subscription_info[$user_id] = $this->create_subscription_for_user($user_id); + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id]['clientHash']); + $this->assertEmpty($messages); + } + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id]['clientHash']); + $this->assertNotEmpty($messages); + } + } + + protected function create_subscription_for_user($user_id): array + { + $client = new \GuzzleHttp\Client(); + try + { + $response = $client->request('POST', 'http://localhost:9012/subscribe', ['form_params' => [ + 'applicationServerKey' => self::VAPID_KEYS['publicKey'], + ]]); + } + catch (\GuzzleHttp\Exception\GuzzleException $exception) + { + $this->fail('Failed getting subscription from web-push-testing client'); + } + + $subscription_return = \phpbb\json\sanitizer::decode((string) $response->getBody()); + $subscription_data = $subscription_return['data']; + $this->assertNotEmpty($subscription_data['endpoint']); + $this->assertStringStartsWith('http://localhost:9012/notify/', $subscription_data['endpoint']); + $this->assertIsArray($subscription_data['keys']); + + // Add subscription data to admin user (user id 2) + + + $push_subscriptions_table = $this->container->getParameter('tables.push_subscriptions'); + + $sql = 'INSERT INTO ' . $push_subscriptions_table . ' ' . $this->db->sql_build_array('INSERT', [ + 'user_id' => $user_id, + 'endpoint' => $subscription_data['endpoint'], + 'p256dh' => $subscription_data['keys']['p256dh'], + 'auth' => $subscription_data['keys']['auth'], + ]); + $this->db->sql_query($sql); + + return $subscription_data; + } + + protected function get_messages_for_subscription($client_hash): array + { + $client = new \GuzzleHttp\Client(); + try + { + $response = $client->request('POST', 'http://localhost:9012/get-notifications', ['form_params' => [ + 'clientHash' => $client_hash, + ]]); + } + catch (\GuzzleHttp\Exception\GuzzleException $exception) + { + $this->fail('Failed getting messages from web-push-testing client'); + } + + $response_data = json_decode($response->getBody()->getContents(), true); + $this->assertNotEmpty($response_data); + $this->assertArrayHasKey('data', $response_data); + $this->assertArrayHasKey('messages', $response_data['data']); + + return $response_data['data']['messages']; + } +} diff --git a/tests/notification/submit_post_base.php b/tests/notification/submit_post_base.php index cb5b8d0d2ae..3219c85e27c 100644 --- a/tests/notification/submit_post_base.php +++ b/tests/notification/submit_post_base.php @@ -155,6 +155,7 @@ protected function setUp(): void $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); $phpbb_container->setParameter('tables.notification_push', 'phpbb_notification_push'); + $phpbb_container->setParameter('tables.push_subscriptions', 'phpbb_push_subscriptions'); $phpbb_container->set('content.visibility', new \phpbb\content_visibility($auth, $config, $phpbb_dispatcher, $db, $user, $phpbb_root_path, $phpEx, FORUMS_TABLE, POSTS_TABLE, TOPICS_TABLE, USERS_TABLE)); $phpbb_container->addCompilerPass(new phpbb\di\pass\markpublic_pass()); $phpbb_container->compile(); From af29f388dab44a8409fb548c2b17dc7a31dbbe71 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 19 Sep 2022 20:34:42 +0200 Subject: [PATCH 0013/1214] [ticket/17010] Queue notifications and use flush to send them PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 0fc704e8de2..550631ec773 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -185,6 +185,7 @@ protected function notify_using_webpush(): void $web_push = new \Minishlink\WebPush\WebPush($auth); + $number_of_notifications = 0; // Time to go through the queue and send emails /** @var type_interface $notification */ foreach ($this->queue as $notification) @@ -222,9 +223,8 @@ protected function notify_using_webpush(): void ], 'contentEncoding' => !empty($subscription['encoding']) ? $subscription['encoding'] : null, ]); - //$web_push->queueNotification($push_subscription, $json_data); - $foo = $web_push->sendOneNotification($push_subscription, $json_data); - $meh = 2; + $web_push->queueNotification($push_subscription, $json_data); + $number_of_notifications++; } catch (\ErrorException $exception) { @@ -235,6 +235,21 @@ protected function notify_using_webpush(): void } // @todo: Try offloading to after request + try + { + foreach ($web_push->flush($number_of_notifications) as $report) + { + if (!$report->isSuccess()) + { + // @todo: log errors / remove subscription + } + } + } + catch (\ErrorException $exception) + { + // @todo: write to log + } + // We're done, empty the queue $this->empty_queue(); From 5fba4682c34993b8780d389afac524f3c1f0ffc5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 20 Sep 2022 17:14:15 +0200 Subject: [PATCH 0014/1214] [ticket/17010] Resolve phing sniff issues PHPBB3-17010 --- phpBB/phpbb/db/migration/data/v400/add_webpush.php | 1 - phpBB/phpbb/notification/method/webpush.php | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php index f426df5b9b8..f2214fd934b 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -13,7 +13,6 @@ namespace phpbb\db\migration\data\v400; -use Minishlink\WebPush\VAPID; use phpbb\db\migration\migration; class add_webpush extends migration diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 550631ec773..064ee4507f9 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -15,7 +15,6 @@ use phpbb\config\config; use phpbb\db\driver\driver_interface; -use phpbb\json\sanitizer; use phpbb\notification\type\type_interface; use phpbb\user; use phpbb\user_loader; @@ -54,7 +53,7 @@ class webpush extends \phpbb\notification\method\messenger_base * @param string $notification_webpush_table * @param string $push_subscriptions_table */ - public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db,string $phpbb_root_path, + public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db, string $phpbb_root_path, string $php_ext, string $notification_webpush_table, string $push_subscriptions_table) { parent::__construct($user_loader, $phpbb_root_path, $php_ext); @@ -250,7 +249,6 @@ protected function notify_using_webpush(): void // @todo: write to log } - // We're done, empty the queue $this->empty_queue(); } From c4a8e32689f624daa503109ef4045d802a460180 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 20 Sep 2022 17:20:12 +0200 Subject: [PATCH 0015/1214] [ticket/17010] Adjust query to not trigger full group by issue PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 064ee4507f9..2d015600c3c 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -158,9 +158,9 @@ protected function notify_using_webpush(): void // Get subscriptions for users $user_subscription_map = []; - $sql = 'SELECT * FROM ' . $this->push_subscriptions_table . ' - WHERE ' . $this->db->sql_in_set('user_id', $notify_users) . ' - GROUP BY user_id'; + $sql = 'SELECT user_id, endpoint, p256dh, auth, encoding + FROM ' . $this->push_subscriptions_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $notify_users); $result = $this->db->sql_query($sql); while ($row = $this->db->sql_fetchrow($result)) { From 9bd9962aeafd180fbac258a215968ebf95987ca2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 22 Sep 2022 21:08:54 +0200 Subject: [PATCH 0016/1214] [ticket/17010] Resolve test issues and add node install for webpush testing PHPBB3-17010 --- .github/workflows/tests.yml | 31 +++++++++++++++++++ .../webpush_notification.type.post.xml | 2 ++ .../notification_method_webpush_test.php | 6 +++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 982ca66a2ac..761d8e3e87a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -217,6 +217,15 @@ jobs: run: | .github/setup-ldap.sh + - name: Setup node + if: ${{ matrix.SLOWTESTS != 1 }} + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Setup node dependencies + if: ${{ matrix.SLOWTESTS != 1 }} + run: npm ci + - name: Setup SPHINX run: | .github/setup-sphinx.sh @@ -342,6 +351,13 @@ jobs: run: | .github/setup-database.sh $DB $MYISAM + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Setup node dependencies + run: npm ci + - name: Run unit tests env: DB: ${{steps.database-type.outputs.db}} @@ -447,6 +463,13 @@ jobs: run: | .github/setup-database.sh $DB $MYISAM + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Setup node dependencies + run: npm ci + - name: Run unit tests env: DB: ${{steps.database-type.outputs.db}} @@ -555,6 +578,14 @@ jobs: psql -c 'create database phpbb_tests;' -U postgres Set-MpPreference -ExclusionPath "${env:PGDATA}" # Exclude PGDATA directory from Windows Defender Set-MpPreference -DisableRealtimeMonitoring $true + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Setup node dependencies + run: npm ci + - name: Run unit tests if: ${{ matrix.type == 'unit' }} run: | diff --git a/tests/notification/fixtures/webpush_notification.type.post.xml b/tests/notification/fixtures/webpush_notification.type.post.xml index 7654ed30aad..9d3d72d531d 100644 --- a/tests/notification/fixtures/webpush_notification.type.post.xml +++ b/tests/notification/fixtures/webpush_notification.type.post.xml @@ -1,5 +1,7 @@ + +
forum_iduser_id diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 90e1d14deb6..f0e1f1cb50a 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -61,6 +61,10 @@ protected static function start_webpush_testing(): void $process = new \Symfony\Component\Process\Process(['node_modules/.bin/web-push-testing', '--port', '9012', 'start']); $process->run(); + if (!$process->isSuccessful()) + { + self::fail('Starting web push testing service failed: ' . $process->getErrorOutput()); + } } protected static function stop_webpush_testing(): void @@ -384,7 +388,7 @@ protected function create_subscription_for_user($user_id): array } catch (\GuzzleHttp\Exception\GuzzleException $exception) { - $this->fail('Failed getting subscription from web-push-testing client'); + $this->fail('Failed getting subscription from web-push-testing client: ' . $exception->getMessage()); } $subscription_return = \phpbb\json\sanitizer::decode((string) $response->getBody()); From 199bc8f964fcdb3da2be089389cdf5f0950c3dd9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 25 Sep 2022 20:23:21 +0200 Subject: [PATCH 0017/1214] [ticket/17010] Add first webpush controller and start ucp integration PHPBB3-17010 --- .../container/services_notification.yml | 2 + .../config/default/container/services_ucp.yml | 5 ++ phpBB/config/default/routing/ucp.yml | 8 +++ phpBB/includes/ucp/ucp_notifications.php | 29 +++++++- phpBB/language/en/ucp.php | 4 ++ phpBB/phpbb/ucp/controller/webpush.php | 27 +++++++ .../prosilver/template/ucp_notifications.html | 34 --------- .../template/ucp_notifications_options.html | 71 +++++++++++++++++++ 8 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 phpBB/phpbb/ucp/controller/webpush.php create mode 100644 phpBB/styles/prosilver/template/ucp_notifications_options.html diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index c018416da36..acb136641cd 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -256,3 +256,5 @@ services: - '%core.php_ext%' - '%tables.notification_push%' - '%tables.push_subscriptions%' + tags: + - { name: notification.method } diff --git a/phpBB/config/default/container/services_ucp.yml b/phpBB/config/default/container/services_ucp.yml index 69ef5ffbf88..1d0535a1168 100644 --- a/phpBB/config/default/container/services_ucp.yml +++ b/phpBB/config/default/container/services_ucp.yml @@ -15,3 +15,8 @@ services: - '%tables.users%' - '%core.root_path%' - '%core.php_ext%' + + phpbb.ucp.controller.push_worker: + class: phpbb\ucp\controller\webpush + arguments: + - '@config' diff --git a/phpBB/config/default/routing/ucp.yml b/phpBB/config/default/routing/ucp.yml index 06bd7c3a58f..a88b6fc5aeb 100644 --- a/phpBB/config/default/routing/ucp.yml +++ b/phpBB/config/default/routing/ucp.yml @@ -5,3 +5,11 @@ phpbb_ucp_reset_password_controller: phpbb_ucp_forgot_password_controller: path: /forgot_password defaults: { _controller: phpbb.ucp.controller.reset_password:request } + +phpbb_ucp_push_worker_controller: + path: /push/worker + defaults: { _controller: phpbb.ucp.controller.webpush:request } + +phpbb_ucp_push_subscribe_controller: + path: /push/subscribe + defaults: { _controller: phpbb.ucp.controller.webpush:request } diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php index 7a46d3e5aa9..7b07138595f 100644 --- a/phpBB/includes/ucp/ucp_notifications.php +++ b/phpBB/includes/ucp/ucp_notifications.php @@ -107,7 +107,12 @@ public function main($id, $mode) $this->output_notification_types($subscriptions, $phpbb_notifications, $template, $user, $phpbb_dispatcher, 'notification_types'); - $this->tpl_name = 'ucp_notifications'; + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + + $template->assign_var('U_WEBPUSH_SUBSCRIBE', $controller_helper->route('phpbb_ucp_push_subscribe_controller')); + + $this->tpl_name = 'ucp_notifications_options'; $this->page_title = 'UCP_NOTIFICATION_OPTIONS'; break; @@ -266,6 +271,11 @@ public function output_notification_methods(\phpbb\notification\manager $phpbb_n { $notification_methods = $phpbb_notifications->get_subscription_methods(); + if (isset($notification_methods['notification.method.webpush'])) + { + $this->output_webpush_data($template); + } + foreach ($notification_methods as $method => $method_data) { $template->assign_block_vars($block, array( @@ -275,4 +285,21 @@ public function output_notification_methods(\phpbb\notification\manager $phpbb_n )); } } + + /** + * Output data for webpush + * + * @param \phpbb\template\template $template + * + * @return void + */ + protected function output_webpush_data(\phpbb\template\template $template): void + { + global $config; + + $template->assign_vars([ + 'NOTIFICATIONS_WEBPUSH_ENABLE' => true, // already checked, otherwise we wouldn't be here + 'NOTIFICATIONS_WEBPUSH_VAPID_PUBLIC' => $config['webpush_vapid_public'], + ]); + } } diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index a4dcdf775d1..09bf7824e58 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -332,6 +332,7 @@ 'NOTIFICATION_METHOD_BOARD' => 'Notifications', 'NOTIFICATION_METHOD_EMAIL' => 'Email', 'NOTIFICATION_METHOD_JABBER' => 'Jabber', + 'NOTIFICATION_METHOD_WEBPUSH' => 'Web push', 'NOTIFICATION_TYPE' => 'Notification type', 'NOTIFICATION_TYPE_BOOKMARK' => 'Someone replies to a topic you have bookmarked', 'NOTIFICATION_TYPE_GROUP_REQUEST' => 'Someone requests to join a group you lead', @@ -355,6 +356,9 @@ 'NOTIFY_METHOD_EXPLAIN' => 'Method for sending messages sent via this board.', 'NOTIFY_METHOD_IM' => 'Jabber only', 'NOTIFY_ON_PM' => 'Notify me on new private messages', + 'NOTIFY_WEBPUSH_ACTIVATE' => 'Activate push notifications', + 'NOTIFY_WEBPUSH_ENABLE' => 'Enable receiving webpush notifications', + 'NOTIFY_WEBPUSH_ENABLE_EXPLAIN' => 'Enable receiving browser-based push notifications.
The notifications can be turned off at any time in your browser settings or by disabling the push notifications below.', 'NOT_ADDED_FRIENDS_ANONYMOUS' => 'You cannot add the anonymous user to your friends list.', 'NOT_ADDED_FRIENDS_BOTS' => 'You cannot add bots to your friends list.', 'NOT_ADDED_FRIENDS_FOES' => 'You cannot add users to your friends list who are on your foes list.', diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php new file mode 100644 index 00000000000..67ef237d5b5 --- /dev/null +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -0,0 +1,27 @@ +config = $config; + } + + /** + * Handle password reset request + * + * @return Response + */ + public function request(): Response + { + return new Response('foo'); + } +} diff --git a/phpBB/styles/prosilver/template/ucp_notifications.html b/phpBB/styles/prosilver/template/ucp_notifications.html index d7019dee1c6..7c5715fb41d 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications.html +++ b/phpBB/styles/prosilver/template/ucp_notifications.html @@ -12,38 +12,6 @@

{TITLE}

{TITLE_EXPLAIN}

- - -
- - - - - - - - - - - - - - - - - - - - - - - - -
{L_NOTIFICATION_TYPE}{notification_methods.NAME}
{notification_types.GROUP_NAME}
- {notification_types.NAME} -
   {notification_types.EXPLAIN} -
checked="checked" disabled="disabled" />
-
diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html new file mode 100644 index 00000000000..6536fb56cd0 --- /dev/null +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -0,0 +1,71 @@ +{% include('ucp_header.html') %} + +
+ +

{{ TITLE }}

+ {% if NOTIFICATIONS_WEBPUSH_ENABLE %} +
+
+
+
+

{{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}
+
+ {{ lang('NOTIFY_WEBPUSH_ACTIVATE') }} +
+
+
+
+
+ {% endif %} +
+
+

{{ TITLE_EXPLAIN }}

+ + + + + + {% for method in notification_methods %} + + {% endfor %} + + + + {% for notification_type in notification_types %} + {% if notification_type.GROUP_NAME %} + + + + {% else %} + + + {% for notification_method in notification_type.notification_methods %} + {% apply spaceless %} + + {% endapply %} + {% endfor %} + + {% endif %} + {% endfor %} + +
{{ lang('NOTIFICATION_TYPE') }}{{ method.NAME }}
{{ notification_type.GROUP_NAME }}
+ {{ notification_type.NAME }} + {% if notification_type.EXPLAIN %}
   {{ notification_type.EXPLAIN }}{% endif %} +
+
+
+
+ + {% if notification_types or notification_list %} +
+ + {{ S_HIDDEN_FIELDS }} + + + {{ S_FORM_TOKEN }} +
+ {% endif %} + +
+ +{% include('ucp_footer.html') %} From ff27401ed2e3eae715fc2386c39c168117023120 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 22 Oct 2022 22:20:39 +0200 Subject: [PATCH 0018/1214] [ticket/17010] Add javascript for webpush subscribe/unsubscribe PHPBB3-17010 --- phpBB/styles/all/js/webpush.js.twig | 243 ++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 phpBB/styles/all/js/webpush.js.twig diff --git a/phpBB/styles/all/js/webpush.js.twig b/phpBB/styles/all/js/webpush.js.twig new file mode 100644 index 00000000000..1287f133d9b --- /dev/null +++ b/phpBB/styles/all/js/webpush.js.twig @@ -0,0 +1,243 @@ +/* global phpbb */ + +'use strict'; + +function PhpbbWebpush() { + /** @type {string} URL to service worker */ + const serviceWorkerUrl = '{{ U_WEBPUSH_WORKER_URL }}'; + + /** @type {string} URL to subscribe to push */ + const subscribeUrl = '{{ U_WEBPUSH_SUBSCRIBE }}'; + + /** @type {string} URL to unsubscribe from push */ + const unsubscribeUrl = '{{ U_WEBPUSH_UNSUBSCRIBE }}'; + + /** @type {{creationTime: number, formToken: string}} Form tokens */ + this.formTokens = { + creationTime: {{ FORM_TOKENS.creation_time }}, + formToken: '{{ FORM_TOKENS.form_token }}' + }; + + /** @type {string} VAPID public key */ + const VAPID_PUBLIC_KEY = '{{ VAPID_PUBLIC_KEY }}'; + + let subscribeButton, + unsubscribeButton; + + /** + * Init function for phpBB webpush + */ + this.init = function() { + subscribeButton = document.querySelector('#subscribe_webpush'); + unsubscribeButton = document.querySelector('#unsubscribe_webpush'); + let serviceWorkerRegistered = false; + + // Service workers are only supported in secure context + if (window.isSecureContext !== true) { + subscribeButton.disabled = true; + return; + } + + if ('serviceWorker' in navigator && 'PushManager' in window) { + navigator.serviceWorker.register(serviceWorkerUrl) + .then(() => { + serviceWorkerRegistered = true; + }) + .catch(error => { + console.info(error); + }); + } + + if (serviceWorkerRegistered) { + subscribeButton.addEventListener('click', subscribeButtonHandler); + unsubscribeButton.addEventListener('click', unsubscribeButtonHandler); + + updateButtonState(); + } else { + // Service worker could not be registered + subscribeButton.disabled = true; + } + }; + + /** + * Update button state depending on notifications state + * + * @return void + */ + function updateButtonState() { + if (Notification.permission === 'granted') { + navigator.serviceWorker.getRegistration(serviceWorkerUrl) + .then((registration) => { + if (typeof registration === 'undefined') { + return; + } + + registration.pushManager.getSubscription() + .then((subscribed) => { + if (subscribed) { + setSubscriptionState(true); + } + }) + }); + } + } + + /** + * Set subscription state for buttons + * + * @param {boolean} subscribed True if subscribed, false if not + */ + function setSubscriptionState(subscribed) { + if (subscribed) { + subscribeButton.classList.add('hidden'); + unsubscribeButton.classList.remove('hidden'); + } else { + subscribeButton.classList.remove('hidden'); + unsubscribeButton.classList.add('hidden'); + } + } + + /** + * Handler for pushing subscribe button + * + * @param {Object} event Subscribe button push event + * @returns {Promise} + */ + async function subscribeButtonHandler(event) { + event.preventDefault(); + + subscribeButton.addEventListener('click', subscribeButtonHandler); + + // Prevent the user from clicking the subscribe button multiple times. + const result = await Notification.requestPermission(); + if (result === 'denied') { + return; + } + + const registration = await navigator.serviceWorker.getRegistration(serviceWorkerUrl); + if (typeof registration !== 'undefined') { + const subscribed = await registration.pushManager.getSubscription(); + if (subscribed) { + setSubscriptionState(true); + return; + } + } + const newSubscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY) + }); + + fetch(subscribeUrl, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + body: getFormData(newSubscription) + }) + .then((response) => response.json()) + .then(handleSubscribe) + .catch((error) => { + phpbb.alert({{ lang('AJAX_ERROR_TITLE') }}, error); + }); + } + + /** + * Handler for pushing unsubscribe button + * + * @param {Object} event Unsubscribe button push event + * @returns {Promise} + */ + async function unsubscribeButtonHandler(event) { + event.preventDefault(); + + const registration = await navigator.serviceWorker.getRegistration(serviceWorkerUrl); + if (typeof registration === 'undefined') { + return; + } + + const subscription = await registration.pushManager.getSubscription(); + fetch(unsubscribeUrl, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + body: getFormData({endpoint: subscription.endpoint}) + }).then(() => { + return subscription.unsubscribe(); + }).then((unsubscribed) => { + if (unsubscribed) { + setSubscriptionState(false); + } + }); + } + + /** + * Handle subscribe response + * + * @param {Object} response Response from subscription endpoint + */ + function handleSubscribe(response) { + if (response.success) { + setSubscriptionState(true); + if (response.hasOwnProperty('form_tokens')) { + updateFormTokens(response.form_tokens); + } + } + } + + /** + * Get form data object including form tokens + * + * @param {Object} data Data to create form data from + * @returns {FormData} Form data + */ + function getFormData(data) { + let formData = new FormData(); + formData.append('form_token', phpbb.webpush.formTokens.formToken); + formData.append('creation_time', phpbb.webpush.formTokens.creationTime); + formData.append('data', JSON.stringify(data)); + + return formData; + } + + /** + * Update form tokens with supplied ones + * + * @param {Object} formTokens + */ + function updateFormTokens(formTokens) { + phpbb.webpush.formTokens.creationTime = formTokens.creation_time; + phpbb.webpush.formTokens.formToken = formTokens.form_token; + } + + /** + * Convert a base64 string to Uint8Array + * + * @param base64String + * @returns {Uint8Array} + */ + function urlB64ToUint8Array(base64String) { + const padding = '='.repeat((4 - base64String.length % 4) % 4); + const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; + } +} + +function domReady(callBack) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callBack); + } else { + callBack(); + } +} + +phpbb.webpush = new PhpbbWebpush(); + +domReady(() => { + phpbb.webpush.init(); +}); From 0395c8dbac1f2005164afb8f425a4e3a849c1a00 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 22 Oct 2022 22:25:06 +0200 Subject: [PATCH 0019/1214] [ticket/17010] Introduce form_helper to allow using form tokens in javascript PHPBB3-17010 --- phpBB/phpbb/form/form_helper.php | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 phpBB/phpbb/form/form_helper.php diff --git a/phpBB/phpbb/form/form_helper.php b/phpBB/phpbb/form/form_helper.php new file mode 100644 index 00000000000..ca4ca14b01f --- /dev/null +++ b/phpBB/phpbb/form/form_helper.php @@ -0,0 +1,104 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\form; + +use phpbb\config\config; +use phpbb\request\request_interface; +use phpbb\user; + +class form_helper +{ + /** @var config */ + protected $config; + + /** @var request_interface */ + protected $request; + + /** @var user */ + protected $user; + + /** + * Constructor for form_helper + * + * @param config $config + * @param request_interface $request + * @param user $user + */ + public function __construct(config $config, request_interface $request, user $user) + { + $this->config = $config; + $this->request = $request; + $this->user = $user; + } + + /** + * Get form tokens for form + * + * @param string $form_name Name of form + * @param int $now Token generation time + * @param string $token_sid SID used for form token + * @param string $token Generated token + * + * @return array Array containing form_token and creation_time of form token + */ + public function get_form_tokens(string $form_name, int &$now = 0, string &$token_sid = '', string &$token = ''): array + { + $now = time(); + $token_sid = ($this->user->data['user_id'] == ANONYMOUS && !empty($this->config['form_token_sid_guests'])) ? $this->user->session_id : ''; + $token = sha1($now . $this->user->data['user_form_salt'] . $form_name . $token_sid); + + return [ + 'creation_time' => $now, + 'form_token' => $token, + ]; + } + + /** + * Check form token for form + * + * @param string $form_name Name of form + * @param int|null $timespan Lifetime of token or null if default value should be used + * @return bool True if form token is valid, false if not + */ + public function check_form_tokens(string $form_name, ?int $timespan = null): bool + { + if ($timespan === null) + { + // we enforce a minimum value of half a minute here. + $timespan = ($this->config['form_token_lifetime'] == -1) ? -1 : max(30, $this->config['form_token_lifetime']); + } + + if ($this->request->is_set_post('creation_time') && $this->request->is_set_post('form_token')) + { + $creation_time = abs($this->request->variable('creation_time', 0)); + $token = $this->request->variable('form_token', ''); + + $diff = time() - $creation_time; + + // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)... + if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1)) + { + $token_sid = ($this->user->data['user_id'] == ANONYMOUS && !empty($this->config['form_token_sid_guests'])) ? $this->user->session_id : ''; + $key = sha1($creation_time . $this->user->data['user_form_salt'] . $form_name . $token_sid); + + if (hash_equals($key, $token)) + { + return true; + } + } + } + + return false; + } +} From 35259056f0403a1ee36fdfbc85501e3eb104b6a0 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 22 Oct 2022 22:26:18 +0200 Subject: [PATCH 0020/1214] [ticket/17010] Switch to using form_helper for form tokens PHPBB3-17010 --- phpBB/config/default/container/services.yml | 7 ++++ phpBB/includes/functions.php | 45 +++++---------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index d34a1ded9f3..14c0d40263a 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -126,6 +126,13 @@ services: arguments: - '%core.root_path%' + form_helper: + class: phpbb\form\form_helper + arguments: + - '@config' + - '@request' + - '@user' + group_helper: class: phpbb\group\helper arguments: diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 453e1d31504..2f330096bf2 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2009,16 +2009,14 @@ function check_link_hash($token, $link_name) */ function add_form_key($form_name, $template_variable_suffix = '') { - global $config, $template, $user, $phpbb_dispatcher; + global $phpbb_container, $phpbb_dispatcher, $template; - $now = time(); - $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; - $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid); + /** @var \phpbb\form\form_helper $form_helper */ + $form_helper = $phpbb_container->get('form_helper'); - $s_fields = build_hidden_fields(array( - 'creation_time' => $now, - 'form_token' => $token, - )); + $form_tokens = $form_helper->get_form_tokens($form_name, $now, $token_sid, $token); + + $s_fields = build_hidden_fields($form_tokens); /** * Perform additional actions on creation of the form token @@ -2058,35 +2056,12 @@ function add_form_key($form_name, $template_variable_suffix = '') */ function check_form_key($form_name, $timespan = false) { - global $config, $request, $user; - - if ($timespan === false) - { - // we enforce a minimum value of half a minute here. - $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']); - } - - if ($request->is_set_post('creation_time') && $request->is_set_post('form_token')) - { - $creation_time = abs($request->variable('creation_time', 0)); - $token = $request->variable('form_token', ''); - - $diff = time() - $creation_time; - - // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)... - if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1)) - { - $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; - $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid); + global $phpbb_container; - if ($key === $token) - { - return true; - } - } - } + /** @var \phpbb\form\form_helper $form_helper */ + $form_helper = $phpbb_container->get('form_helper'); - return false; + return $form_helper->check_form_tokens($form_name, $timespan !== false ? $timespan : null); } // Message/Login boxes From d4b6ad562096e85783afe237b64afaa3ffd0f6ff Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 20:54:37 +0200 Subject: [PATCH 0021/1214] [ticket/17010] Add controller methods for subscribe/unsubscribe and others PHPBB3-17010 --- .../config/default/container/services_ucp.yml | 11 +- phpBB/config/default/routing/ucp.yml | 16 +- phpBB/includes/ucp/ucp_notifications.php | 4 +- phpBB/phpbb/ucp/controller/webpush.php | 246 +++++++++++++++++- .../template/ucp_notifications_options.html | 7 +- 5 files changed, 274 insertions(+), 10 deletions(-) diff --git a/phpBB/config/default/container/services_ucp.yml b/phpBB/config/default/container/services_ucp.yml index 1d0535a1168..a950ef8e354 100644 --- a/phpBB/config/default/container/services_ucp.yml +++ b/phpBB/config/default/container/services_ucp.yml @@ -16,7 +16,16 @@ services: - '%core.root_path%' - '%core.php_ext%' - phpbb.ucp.controller.push_worker: + phpbb.ucp.controller.webpush: class: phpbb\ucp\controller\webpush arguments: - '@config' + - '@controller.helper' + - '@dbal.conn' + - '@form_helper' + - '@path_helper' + - '@request' + - '@user' + - '@template.twig.environment' + - '%tables.notification_push%' + - '%tables.push_subscriptions%' diff --git a/phpBB/config/default/routing/ucp.yml b/phpBB/config/default/routing/ucp.yml index a88b6fc5aeb..dac6461ac5d 100644 --- a/phpBB/config/default/routing/ucp.yml +++ b/phpBB/config/default/routing/ucp.yml @@ -6,10 +6,22 @@ phpbb_ucp_forgot_password_controller: path: /forgot_password defaults: { _controller: phpbb.ucp.controller.reset_password:request } +phpbb_ucp_push_get_notification_controller: + path: /push/notification + defaults: { _controller: phpbb.ucp.controller.webpush:notification } + phpbb_ucp_push_worker_controller: path: /push/worker - defaults: { _controller: phpbb.ucp.controller.webpush:request } + defaults: { _controller: phpbb.ucp.controller.webpush:worker } phpbb_ucp_push_subscribe_controller: path: /push/subscribe - defaults: { _controller: phpbb.ucp.controller.webpush:request } + defaults: { _controller: phpbb.ucp.controller.webpush:subscribe } + +phpbb_ucp_push_unsubscribe_controller: + path: /push/unsubscribe + defaults: { _controller: phpbb.ucp.controller.webpush:unsubscribe } + +phpbb_ucp_push_js_controller: + path: /push/js + defaults: { _controller: phpbb.ucp.controller.webpush:js } diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php index 7b07138595f..9e03e68509c 100644 --- a/phpBB/includes/ucp/ucp_notifications.php +++ b/phpBB/includes/ucp/ucp_notifications.php @@ -110,7 +110,9 @@ public function main($id, $mode) /** @var \phpbb\controller\helper $controller_helper */ $controller_helper = $phpbb_container->get('controller.helper'); - $template->assign_var('U_WEBPUSH_SUBSCRIBE', $controller_helper->route('phpbb_ucp_push_subscribe_controller')); + $template->assign_vars([ + 'T_WEBPUSH_JS_PATH' => $controller_helper->route('phpbb_ucp_push_js_controller'), + ]); $this->tpl_name = 'ucp_notifications_options'; $this->page_title = 'UCP_NOTIFICATION_OPTIONS'; diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index 67ef237d5b5..9181ad0e837 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -3,25 +3,263 @@ namespace phpbb\ucp\controller; use phpbb\config\config; +use phpbb\controller\helper as controller_helper; +use phpbb\db\driver\driver_interface; +use phpbb\exception\http_exception; +use phpbb\form\form_helper; +use phpbb\json\sanitizer as json_sanitizer; +use phpbb\path_helper; +use phpbb\request\request_interface; +use phpbb\symfony_request; +use phpbb\user; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; class webpush { + /** @var string UCP form token name */ + private const FORM_TOKEN_UCP = 'ucp_webpush'; + + /** @var string Push worker form token name */ + private const FORM_TOKEN_WORKER = 'webpush_worker'; + /** @var config */ protected $config; - public function __construct(config $config) + /** @var controller_helper */ + protected $controller_helper; + + /** @var driver_interface */ + protected $db; + + /** @var form_helper */ + protected $form_helper; + + /** @var path_helper */ + protected $path_helper; + + /** @var request_interface */ + protected $request; + + /** @var user */ + protected $user; + + /** @var Environment */ + protected $template; + + /** @var string */ + protected $notification_webpush_table; + + /** @var string */ + protected $push_subscriptions_table; + + /** + * Constructor for webpush controller + * + * @param config $config + * @param controller_helper $controller_helper + * @param driver_interface $db + * @param form_helper $form_helper + * @param path_helper $path_helper + * @param request_interface $request + * @param user $user + * @param Environment $template + * @param string $notification_webpush_table + * @param string $push_subscriptions_table + */ + public function __construct(config $config, controller_helper $controller_helper, driver_interface $db, form_helper $form_helper, path_helper $path_helper, + request_interface $request, user $user, Environment $template, string $notification_webpush_table, string $push_subscriptions_table) { $this->config = $config; + $this->controller_helper = $controller_helper; + $this->db = $db; + $this->form_helper = $form_helper; + $this->path_helper = $path_helper; + $this->request = $request; + $this->user = $user; + $this->template = $template; + $this->notification_webpush_table = $notification_webpush_table; + $this->push_subscriptions_table = $push_subscriptions_table; + } + + /** + * Handle request to retrieve notification data + * + * @return JsonResponse + */ + public function notification(): JsonResponse + { + // Subscribe should only be available for logged-in "normal" users + if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot'] + || $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE) + { + throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden'); + } + + $item_id = $this->request->variable('item_id', 0); + $type_id = $this->request->variable('type_id', 0); + + $sql = 'SELECT push_data + FROM ' . $this->notification_webpush_table . ' + WHERE user_id = ' . $this->user->id() . ' + AND notification_type_id = ' . $type_id . ' + AND item_id = ' . $item_id; + $result = $this->db->sql_query($sql); + $notification_data = $this->db->sql_fetchfield('push_data'); + $this->db->sql_freeresult($result); + $data = json_decode($notification_data, true); + $data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : ''; + + return new JsonResponse($data); + } + + /** + * Handle request to push worker javascript + * + * @return Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ + public function worker(): Response + { + // @todo: only work for logged in users, no anonymous & bot + $content = $this->template->render('push_worker.js.twig', [ + 'U_WEBPUSH_GET_NOTIFICATION' => $this->controller_helper->route('phpbb_ucp_push_get_notification_controller'), + ]); + + $response = new Response($content); + $response->headers->set('Content-Type', 'text/javascript; charset=UTF-8'); + + if (!empty($this->user->data['is_bot'])) + { + // Let reverse proxies know we detected a bot. + $response->headers->set('X-PHPBB-IS-BOT', 'yes'); + } + + 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_ucp_push_subscribe_controller'), + 'U_WEBPUSH_UNSUBSCRIBE' => $this->controller_helper->route('phpbb_ucp_push_unsubscribe_controller'), + 'FORM_TOKENS' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), + ]; + } + + /** + * Check (un)subscribe form for valid link hash + * + * @throws http_exception If form is invalid or user should not request (un)subscription + * @return void + */ + protected function check_subscribe_requests(): void + { + if (!$this->form_helper->check_form_tokens(self::FORM_TOKEN_UCP)) + { + throw new http_exception(Response::HTTP_BAD_REQUEST, 'FORM_INVALID'); + } + + // Subscribe should only be available for logged-in "normal" users + if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot'] + || $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE) + { + throw new http_exception(Response::HTTP_FORBIDDEN, 'NO_AUTH_OPERATION'); + } } /** - * Handle password reset request + * Handle request to web push javascript * * @return Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ - public function request(): Response + public function js(): Response { - return new Response('foo'); + // @todo: return forbidden for guest & bot + + $template_data = $this->get_subscribe_vars(); + $template_data += [ + 'VAPID_PUBLIC_KEY' => $this->config['webpush_vapid_public'], + 'U_WEBPUSH_WORKER_URL' => $this->controller_helper->route('phpbb_ucp_push_worker_controller'), + ]; + + $content = $this->template->render('webpush.js.twig', $template_data); + + $response = new Response($content); + $response->headers->set('Content-Type', 'text/javascript; charset=UTF-8'); + + if (!empty($this->user->data['is_bot'])) + { + // Let reverse proxies know we detected a bot. + $response->headers->set('X-PHPBB-IS-BOT', 'yes'); + } + + return $response; + } + + /** + * Handle subscribe requests + * + * @param symfony_request $symfony_request + * @return JsonResponse + */ + public function subscribe(symfony_request $symfony_request): JsonResponse + { + $this->check_subscribe_requests(); + + $data = json_sanitizer::decode($symfony_request->get('data', '')); + + $sql = 'INSERT INTO ' . $this->push_subscriptions_table . ' ' . $this->db->sql_build_array('INSERT', [ + 'user_id' => $this->user->id(), + 'endpoint' => $data['endpoint'], + 'expiration_time' => $data['expiration_time'] ?? 0, + 'p256dh' => $data['keys']['p256dh'], + 'auth' => $data['keys']['auth'], + ]); + $this->db->sql_query($sql); + + return new JsonResponse([ + 'success' => true, + 'form_tokens' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), + ]); + } + + /** + * Handle unsubscribe requests + * + * @param symfony_request $symfony_request + * @return JsonResponse + */ + public function unsubscribe(symfony_request $symfony_request): JsonResponse + { + $this->check_subscribe_requests(); + + $data = json_sanitizer::decode($symfony_request->get('data', '')); + + $endpoint = $data['endpoint']; + + $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' + WHERE user_id = ' . $this->user->id() . " + AND endpoint = '" . $this->db->sql_escape($endpoint) . "'"; + $this->db->sql_query($sql); + + return new JsonResponse([ + 'success' => true, + 'form_tokens' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), + ]); } } diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html index 6536fb56cd0..fc85b98af2f 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications_options.html +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -1,5 +1,7 @@ {% include('ucp_header.html') %} +{% INCLUDEJS(T_WEBPUSH_JS_PATH) %} +

{{ TITLE }}

@@ -8,9 +10,10 @@

{{ TITLE }}

-

{{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}
+

{{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}
- {{ lang('NOTIFY_WEBPUSH_ACTIVATE') }} + +
From 7092f24645f9b5504379c675608711335c2834ff Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 20:55:37 +0200 Subject: [PATCH 0022/1214] [ticket/17010] Change how notification data is passed to push notifications JS PHPBB3-17010 --- .../db/migration/data/v400/add_webpush.php | 6 ++-- phpBB/phpbb/notification/method/webpush.php | 30 ++++++++++++------- .../notification/type/type_interface.php | 2 +- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php index f2214fd934b..248be387bdb 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -39,6 +39,8 @@ public function update_schema(): array 'item_id' => ['ULINT', 0], 'item_parent_id' => ['ULINT', 0], 'user_id' => ['ULINT', 0], + 'push_data' => ['MTEXT', ''], + 'notification_time' => ['TIMESTAMP', 0] ], 'PRIMARY_KEY' => ['notification_type_id', 'item_id', 'item_parent_id', 'user_id'], ], @@ -46,11 +48,11 @@ public function update_schema(): array 'COLUMNS' => [ 'subscription_id' => ['ULINT', null, 'auto_increment'], 'user_id' => ['ULINT', 0], - 'device_name' => ['VCHAR:64', ''], +// 'device_name' => ['VCHAR:64', ''], 'endpoint' => ['TEXT', ''], + 'expiration_time' => ['TIMESTAMP', 0], 'p256dh' => ['VCHAR', ''], 'auth' => ['VCHAR', ''], - 'encoding' => ['VCHAR:32', ''], ], 'PRIMARY_KEY' => ['subscription_id', 'user_id'], ] diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 2d015600c3c..6e6cc7a1eb5 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -115,7 +115,18 @@ public function notify() /** @var type_interface $notification */ foreach ($this->queue as $notification) { - $data = self::clean_data($notification->get_insert_array()); + $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' => $notification->get_url(), + 'avatar' => $notification->get_avatar(), + ]), + 'notification_time' => time(), + ]; + $data = self::clean_data($data); $insert_buffer->insert($data); } @@ -158,7 +169,7 @@ protected function notify_using_webpush(): void // Get subscriptions for users $user_subscription_map = []; - $sql = 'SELECT user_id, endpoint, p256dh, auth, encoding + $sql = 'SELECT user_id, endpoint, p256dh, auth FROM ' . $this->push_subscriptions_table . ' WHERE ' . $this->db->sql_in_set('user_id', $notify_users); $result = $this->db->sql_query($sql); @@ -200,13 +211,9 @@ protected function notify_using_webpush(): void } // add actual web push data - $data['data'] = [ - 'title' => $this->config['sitename'], - 'body' => $notification->get_title(), - 'icon' => '', // @todo: to be filled? - 'image' => '', // @todo: to be filled? - 'url' => $notification->get_url(), - 'user_id' => $notification->user_id, + $data = [ + 'item_id' => $notification->item_id, + 'type_id' => $notification->notification_type_id, ]; $json_data = json_encode($data); @@ -220,7 +227,6 @@ protected function notify_using_webpush(): void 'p256dh' => $subscription['p256dh'], 'auth' => $subscription['auth'], ], - 'contentEncoding' => !empty($subscription['encoding']) ? $subscription['encoding'] : null, ]); $web_push->queueNotification($push_subscription, $json_data); $number_of_notifications++; @@ -283,13 +289,15 @@ public function mark_notifications_by_parent($notification_type_id, $item_parent * @param array $data Notification data * @return array Cleaned notification data */ - public static function clean_data(array $data) + public static function clean_data(array $data): array { $row = [ 'notification_type_id' => null, 'item_id' => null, 'item_parent_id' => null, 'user_id' => null, + 'push_data' => null, + 'notification_time' => null, ]; return array_intersect_key($data, $row); diff --git a/phpBB/phpbb/notification/type/type_interface.php b/phpBB/phpbb/notification/type/type_interface.php index 65bd15d4aa0..4c45b634ba9 100644 --- a/phpBB/phpbb/notification/type/type_interface.php +++ b/phpBB/phpbb/notification/type/type_interface.php @@ -139,7 +139,7 @@ public function get_unsubscribe_url($method); /** * Get the user's avatar (the user who caused the notification typically) * - * @return string + * @return array */ public function get_avatar(); From 06458c95f558b9d05ef420851b4910a8d036bae3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 20:55:54 +0200 Subject: [PATCH 0023/1214] [ticket/17010] Add button disabled state CSS PHPBB3-17010 --- phpBB/styles/prosilver/theme/buttons.css | 14 ++++++++++++++ phpBB/styles/prosilver/theme/colours.css | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/phpBB/styles/prosilver/theme/buttons.css b/phpBB/styles/prosilver/theme/buttons.css index 4656128ab90..f4671ff58c9 100644 --- a/phpBB/styles/prosilver/theme/buttons.css +++ b/phpBB/styles/prosilver/theme/buttons.css @@ -32,6 +32,20 @@ outline: none; } +.button[disabled], +.button[disabled]:hover, +.button.disabled, +.button.disabled:hover { + background: #eee; + color: #aaa; + border-color: #aaa; + cursor: default; +} + +.button.hidden { + display: none; +} + .caret { border-left: 1px solid; position: relative; diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 7bdd5f4b87f..ae39635d654 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -697,6 +697,15 @@ dd.profile-warnings { color: #d41142; } +.button[disabled], +.button[disabled]:hover, +.button.disabled, +.button.disabled:hover { + background: #e0e0e0; + color: #9e9e9e; + border-color: #9e9e9e; +} + .button .icon, .button-secondary, .c-button-icon { From e177ee3750d723c9777bc3d9e6f6192558192d28 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 20:56:13 +0200 Subject: [PATCH 0024/1214] [ticket/17010] Add first version of push worker javascript PHPBB3-17010 --- phpBB/styles/all/js/push_worker.js.twig | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 phpBB/styles/all/js/push_worker.js.twig diff --git a/phpBB/styles/all/js/push_worker.js.twig b/phpBB/styles/all/js/push_worker.js.twig new file mode 100644 index 00000000000..91dbf6f4ad6 --- /dev/null +++ b/phpBB/styles/all/js/push_worker.js.twig @@ -0,0 +1,59 @@ +/** + * Event listener for push event + */ +self.addEventListener('push', event => { + if (typeof event.data === 'undefined') { + return; + } + + let itemId = 0, + typeId = 0; + try { + const notificationData = event.data.json(); + itemId = notificationData['item_id']; + typeId = notificationData['type_id']; + } catch (e) { + self.registration.showNotification(event.data.text()); + return; + } + + const getNotificationUrl = '{{ U_WEBPUSH_GET_NOTIFICATION }}'; + + let formData = new FormData(); + formData.append('item_id', itemId.toString(10)); + formData.append('type_id', typeId.toString(10)); + + fetch(getNotificationUrl, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + body: formData + }) + .then((response) => response.json()) + .then((response) => { + const responseBody = response.title + "\n" + response.text; + const options = { + body: responseBody, + data: response, + icon: response.avatar.src + // foo: '' + //icon: image + } + self.registration.showNotification( + response.heading, + options + ); + } + ); +}); + +/** + * Event listener for notification click + */ +self.addEventListener('notificationclick', event => { + event.notification.close(); + if (typeof event.notification.data !== 'undefined') { + event.waitUntil(self.clients.openWindow(event.notification.data.url)); + } +}); From 166f0deae90939f8fdd3187b7166586af71fb800 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 21:46:34 +0200 Subject: [PATCH 0025/1214] [ticket/17010] Make get_form_tokens() parameters optional PHPBB3-17010 --- phpBB/phpbb/form/form_helper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/phpbb/form/form_helper.php b/phpBB/phpbb/form/form_helper.php index ca4ca14b01f..7a266f2339d 100644 --- a/phpBB/phpbb/form/form_helper.php +++ b/phpBB/phpbb/form/form_helper.php @@ -46,13 +46,13 @@ public function __construct(config $config, request_interface $request, user $us * Get form tokens for form * * @param string $form_name Name of form - * @param int $now Token generation time - * @param string $token_sid SID used for form token - * @param string $token Generated token + * @param int|null $now Token generation time + * @param string|null $token_sid SID used for form token + * @param string|null $token Generated token * * @return array Array containing form_token and creation_time of form token */ - public function get_form_tokens(string $form_name, int &$now = 0, string &$token_sid = '', string &$token = ''): array + public function get_form_tokens(string $form_name, ?int &$now = 0, ?string &$token_sid = '', ?string &$token = ''): array { $now = time(); $token_sid = ($this->user->data['user_id'] == ANONYMOUS && !empty($this->config['form_token_sid_guests'])) ? $this->user->session_id : ''; From 98e0559a1d6205804486ad3c7c46a0f07f0e980c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 22:08:29 +0200 Subject: [PATCH 0026/1214] [ticket/17010] Remove wrong references to emails PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 6e6cc7a1eb5..ae6317a4cd9 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -149,14 +149,14 @@ protected function notify_using_webpush(): void return; } - // Load all users we want to notify (we need their email address) + // Load all users we want to notify $user_ids = []; foreach ($this->queue as $notification) { $user_ids[] = $notification->user_id; } - // We do not send emails to banned users + // 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); @@ -196,7 +196,7 @@ protected function notify_using_webpush(): void $web_push = new \Minishlink\WebPush\WebPush($auth); $number_of_notifications = 0; - // Time to go through the queue and send emails + // Time to go through the queue and send notifications /** @var type_interface $notification */ foreach ($this->queue as $notification) { From ac6512da140cc27c88ab50e23d4cf7c77e570489 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Oct 2022 23:31:49 +0200 Subject: [PATCH 0027/1214] [ticket/17010] Add missing copyright header PHPBB3-17010 --- phpBB/phpbb/ucp/controller/webpush.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index 9181ad0e837..f1f5c197be6 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -1,4 +1,15 @@ + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ namespace phpbb\ucp\controller; From b3777894cb26326d75ed71072dc1c0396aa2a718 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 30 Oct 2022 11:44:59 +0100 Subject: [PATCH 0028/1214] [ticket/17010] Fix stylelint issues PHPBB3-17010 --- phpBB/styles/prosilver/theme/buttons.css | 6 +++--- phpBB/styles/prosilver/theme/colours.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/styles/prosilver/theme/buttons.css b/phpBB/styles/prosilver/theme/buttons.css index f4671ff58c9..e8f8fbe961a 100644 --- a/phpBB/styles/prosilver/theme/buttons.css +++ b/phpBB/styles/prosilver/theme/buttons.css @@ -36,9 +36,9 @@ .button[disabled]:hover, .button.disabled, .button.disabled:hover { - background: #eee; - color: #aaa; - border-color: #aaa; + background: #eeeeee; + border-color: #aaaaaa; + color: #aaaaaa; cursor: default; } diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index ae39635d654..fe5e4d7c851 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -702,8 +702,8 @@ dd.profile-warnings { .button.disabled, .button.disabled:hover { background: #e0e0e0; - color: #9e9e9e; border-color: #9e9e9e; + color: #9e9e9e; } .button .icon, From e97313839ee19c0ca0271ac6b4b5f7934ef07691 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 31 Oct 2022 17:03:04 +0100 Subject: [PATCH 0029/1214] [ticket/17010] Add missing words & loading indicator, fix invalid twig code PHPBB3-17010 --- phpBB/language/en/ucp.php | 5 +- phpBB/styles/all/js/webpush.js.twig | 51 +++++++++++-------- .../template/ucp_notifications_options.html | 4 +- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 09bf7824e58..19d3a2c06e6 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -356,9 +356,10 @@ 'NOTIFY_METHOD_EXPLAIN' => 'Method for sending messages sent via this board.', 'NOTIFY_METHOD_IM' => 'Jabber only', 'NOTIFY_ON_PM' => 'Notify me on new private messages', - 'NOTIFY_WEBPUSH_ACTIVATE' => 'Activate push notifications', 'NOTIFY_WEBPUSH_ENABLE' => 'Enable receiving webpush notifications', - 'NOTIFY_WEBPUSH_ENABLE_EXPLAIN' => 'Enable receiving browser-based push notifications.
The notifications can be turned off at any time in your browser settings or by disabling the push notifications below.', + 'NOTIFY_WEBPUSH_ENABLE_EXPLAIN' => 'Enable receiving browser-based push notifications.
The notifications can be turned off at any time in your browser settings, by unsubscribing, or by disabling the push notifications below.', + 'NOTIFY_WEBPUSH_SUBSCRIBE' => 'Subscribe', + 'NOTIFY_WEBPUSH_UNSUBSCRIBE' => 'Unsubscribe', 'NOT_ADDED_FRIENDS_ANONYMOUS' => 'You cannot add the anonymous user to your friends list.', 'NOT_ADDED_FRIENDS_BOTS' => 'You cannot add bots to your friends list.', 'NOT_ADDED_FRIENDS_FOES' => 'You cannot add users to your friends list who are on your foes list.', diff --git a/phpBB/styles/all/js/webpush.js.twig b/phpBB/styles/all/js/webpush.js.twig index 1287f133d9b..e634f4539c2 100644 --- a/phpBB/styles/all/js/webpush.js.twig +++ b/phpBB/styles/all/js/webpush.js.twig @@ -12,7 +12,7 @@ function PhpbbWebpush() { /** @type {string} URL to unsubscribe from push */ const unsubscribeUrl = '{{ U_WEBPUSH_UNSUBSCRIBE }}'; - /** @type {{creationTime: number, formToken: string}} Form tokens */ + /** @type { {creationTime: number, formToken: string} } Form tokens */ this.formTokens = { creationTime: {{ FORM_TOKENS.creation_time }}, formToken: '{{ FORM_TOKENS.form_token }}' @@ -30,7 +30,6 @@ function PhpbbWebpush() { this.init = function() { subscribeButton = document.querySelector('#subscribe_webpush'); unsubscribeButton = document.querySelector('#unsubscribe_webpush'); - let serviceWorkerRegistered = false; // Service workers are only supported in secure context if (window.isSecureContext !== true) { @@ -41,20 +40,17 @@ function PhpbbWebpush() { if ('serviceWorker' in navigator && 'PushManager' in window) { navigator.serviceWorker.register(serviceWorkerUrl) .then(() => { - serviceWorkerRegistered = true; + subscribeButton.addEventListener('click', subscribeButtonHandler); + unsubscribeButton.addEventListener('click', unsubscribeButtonHandler); + + updateButtonState(); }) .catch(error => { console.info(error); + // Service worker could not be registered + subscribeButton.disabled = true; }); - } - - if (serviceWorkerRegistered) { - subscribeButton.addEventListener('click', subscribeButtonHandler); - unsubscribeButton.addEventListener('click', unsubscribeButtonHandler); - - updateButtonState(); } else { - // Service worker could not be registered subscribeButton.disabled = true; } }; @@ -127,6 +123,7 @@ function PhpbbWebpush() { applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY) }); + const loadingIndicator = phpbb.loadingIndicator(); fetch(subscribeUrl, { method: 'POST', headers: { @@ -134,10 +131,14 @@ function PhpbbWebpush() { }, body: getFormData(newSubscription) }) - .then((response) => response.json()) + .then((response) => { + loadingIndicator.fadeOut(phpbb.alertTime); + return response.json(); + }) .then(handleSubscribe) .catch((error) => { - phpbb.alert({{ lang('AJAX_ERROR_TITLE') }}, error); + loadingIndicator.fadeOut(phpbb.alertTime); + phpbb.alert('{{ lang('AJAX_ERROR_TITLE') }}', error); }); } @@ -156,19 +157,27 @@ function PhpbbWebpush() { } const subscription = await registration.pushManager.getSubscription(); + const loadingIndicator = phpbb.loadingIndicator(); fetch(unsubscribeUrl, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: getFormData({endpoint: subscription.endpoint}) - }).then(() => { - return subscription.unsubscribe(); - }).then((unsubscribed) => { - if (unsubscribed) { - setSubscriptionState(false); - } - }); + }) + .then(() => { + loadingIndicator.fadeOut(phpbb.alertTime); + return subscription.unsubscribe(); + }) + .then((unsubscribed) => { + if (unsubscribed) { + setSubscriptionState(false); + } + }) + .catch((error) => { + loadingIndicator.fadeOut(phpbb.alertTime); + phpbb.alert('{{ lang('AJAX_ERROR_TITLE') }}', error); + }); } /** @@ -194,7 +203,7 @@ function PhpbbWebpush() { function getFormData(data) { let formData = new FormData(); formData.append('form_token', phpbb.webpush.formTokens.formToken); - formData.append('creation_time', phpbb.webpush.formTokens.creationTime); + formData.append('creation_time', phpbb.webpush.formTokens.creationTime.toString()); formData.append('data', JSON.stringify(data)); return formData; diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html index fc85b98af2f..205e8d43940 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications_options.html +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -12,8 +12,8 @@

{{ TITLE }}


{{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}
- - + +
From edaff6cd2d8f1b30c7ef66e69acc0277d357a4d2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 1 Nov 2022 12:44:22 +0100 Subject: [PATCH 0030/1214] [ticket/17010] Remove undeliverable subscriptions PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index ae6317a4cd9..994ccc35acd 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -13,6 +13,7 @@ namespace phpbb\notification\method; +use Minishlink\WebPush\Subscription; use phpbb\config\config; use phpbb\db\driver\driver_interface; use phpbb\notification\type\type_interface; @@ -24,7 +25,7 @@ * This class handles sending push messages for notifications */ -class webpush extends \phpbb\notification\method\messenger_base +class webpush extends messenger_base { /** @var config */ protected $config; @@ -169,7 +170,7 @@ protected function notify_using_webpush(): void // Get subscriptions for users $user_subscription_map = []; - $sql = 'SELECT user_id, endpoint, p256dh, auth + $sql = 'SELECT subscription_id, user_id, endpoint, p256dh, auth FROM ' . $this->push_subscriptions_table . ' WHERE ' . $this->db->sql_in_set('user_id', $notify_users); $result = $this->db->sql_query($sql); @@ -196,6 +197,8 @@ protected function notify_using_webpush(): void $web_push = new \Minishlink\WebPush\WebPush($auth); $number_of_notifications = 0; + $remove_subscriptions = []; + // Time to go through the queue and send notifications /** @var type_interface $notification */ foreach ($this->queue as $notification) @@ -221,7 +224,7 @@ protected function notify_using_webpush(): void { try { - $push_subscription = \Minishlink\WebPush\Subscription::create([ + $push_subscription = Subscription::create([ 'endpoint' => $subscription['endpoint'], 'keys' => [ 'p256dh' => $subscription['p256dh'], @@ -233,13 +236,19 @@ protected function notify_using_webpush(): void } catch (\ErrorException $exception) { - // @todo: decide whether we want to remove invalid subscriptions directly? - // Might need too many resources ... + $remove_subscriptions[] = $subscription['subscription_id']; } } } - // @todo: Try offloading to after request + // Remove any subscriptions that couldn't be queued, i.e. that have invalid data + if (count($remove_subscriptions)) + { + $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' + WHERE ' . $this->db->sql_in_set('subscription_id', $remove_subscriptions); + $this->db->sql_query($sql); + } + try { foreach ($web_push->flush($number_of_notifications) as $report) From ad196d1dba96cf8222a2e71438578094f7d95de2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 29 Jan 2023 09:48:23 +0100 Subject: [PATCH 0031/1214] [ticket/17010] Move web push testing to slow test group PHPBB3-17010 --- tests/notification/notification_method_webpush_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index f0e1f1cb50a..ce79f578383 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -19,7 +19,7 @@ require_once __DIR__ . '/base.php'; /** - * @group + * @group slow */ class notification_method_webpush_test extends phpbb_tests_notification_base { From a8dc08a21903b48fb2e43f60b4d71acea3f3ae32 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 29 Jan 2023 09:54:01 +0100 Subject: [PATCH 0032/1214] [ticket/17010] Add prune notification support to webpush PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 994ccc35acd..d11959319ca 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -292,6 +292,19 @@ public function mark_notifications_by_parent($notification_type_id, $item_parent $this->db->sql_query($sql); } + /** + * {@inheritDoc} + */ + public function prune_notifications($timestamp, $only_read = true): void + { + $sql = 'DELETE FROM ' . $this->notification_webpush_table . ' + WHERE notification_time < ' . (int) $timestamp . + (($only_read) ? ' AND notification_read = 1' : ''); + $this->db->sql_query($sql); + + $this->config->set('read_notification_last_gc', (string) time(), false); + } + /** * Clean data to contain only what we need for webpush notifications table * From 974b7a9184ff5764eca2531b8c34a465a00208da Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 30 Jan 2023 21:39:49 +0100 Subject: [PATCH 0033/1214] [ticket/17010] Resolve use of undefined variable and possibly wrong type PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index d11959319ca..ea71e1cab83 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -176,7 +176,7 @@ protected function notify_using_webpush(): void $result = $this->db->sql_query($sql); while ($row = $this->db->sql_fetchrow($result)) { - if (isset($user_subscriptions[$row['user_id']])) + if (isset($user_subscription_map[$row['user_id']])) { $user_subscription_map[$row['user_id']] += $row; } @@ -274,7 +274,7 @@ protected function notify_using_webpush(): void public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) { $sql = 'DELETE FROM ' . $this->notification_webpush_table . ' - WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : '1=1') . + WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', is_array($notification_type_id) ? $notification_type_id : [$notification_type_id]) : '1=1') . ($user_id !== false ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . ($item_id !== false ? ' AND ' . $this->db->sql_in_set('item_id', $item_id) : ''); $this->db->sql_query($sql); @@ -286,7 +286,7 @@ public function mark_notifications($notification_type_id, $item_id, $user_id, $t public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) { $sql = 'DELETE FROM ' . $this->notification_webpush_table . ' - WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : '1=1') . + WHERE ' . ($notification_type_id !== false ? $this->db->sql_in_set('notification_type_id', is_array($notification_type_id) ? $notification_type_id : [$notification_type_id]) : '1=1') . ($user_id !== false ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . ($item_parent_id !== false ? ' AND ' . $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : ''); $this->db->sql_query($sql); From 3513c85ee61bdb88adffc3b732ac3e8cb9309adc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 30 Jan 2023 23:09:57 +0100 Subject: [PATCH 0034/1214] [ticket/17010] Stop skipping node dependency setup on slow tests PHPBB3-17010 --- .github/workflows/tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 761d8e3e87a..1c4de83e508 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -218,12 +218,10 @@ jobs: .github/setup-ldap.sh - name: Setup node - if: ${{ matrix.SLOWTESTS != 1 }} uses: actions/setup-node@v3 with: node-version: 16 - name: Setup node dependencies - if: ${{ matrix.SLOWTESTS != 1 }} run: npm ci - name: Setup SPHINX From 7410c3be6f31d566451caca8650d8f4adea67273 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 23 Jun 2023 17:44:21 +0200 Subject: [PATCH 0035/1214] [ticket/17010] Generate VAPID keys with javascript in ACP PHPBB3-17010 --- phpBB/adm/style/ajax.js | 62 +++++++++++++++++++++++++++++ phpBB/includes/acp/acp_board.php | 68 +++++++++++++++++++++----------- phpBB/language/en/acp/board.php | 1 + 3 files changed, 109 insertions(+), 22 deletions(-) diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index 5949c73920d..25a65274810 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -157,6 +157,68 @@ phpbb.addAjaxCallback('row_delete', function(res) { } }); +/** + * This callback generates the VAPID keys for the web push notification service. + */ +phpbb.addAjaxCallback('generate_vapid_keys', (res) => { + function rawKeyToBase64(rawKey) { + const keyBuffer = new Uint8Array(rawKey); + let keyText = ''; + const keyLength = keyBuffer.byteLength; + for (let i = 0; i < keyLength; i++) { + keyText += String.fromCharCode(keyBuffer[i]); + } + + keyText = window.btoa(keyText); + return keyText; + } + + function base64SafeEncode(base64String) { + const base64URL = base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + return base64URL; + } + + async function generateVAPIDKeys() { + try { + // Generate a new key pair using the Subtle Crypto API + const keyPair = await crypto.subtle.generateKey( + { + name: 'ECDH', + namedCurve: 'P-256', + }, + true, + ['deriveKey', 'deriveBits'] + ); + + // Export the private key as a JWK (JSON Web Key) object + const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey); + console.log(privateKeyJwk.d); + + const privateKeyString = privateKeyJwk.d; + + // Export the public key as a JWK object + const publicKeyBuffer = await crypto.subtle.exportKey('raw', keyPair.publicKey); + const publicKeyString = base64SafeEncode(rawKeyToBase64(publicKeyBuffer)); + console.log(publicKeyString); + + return { + privateKey: privateKeyString, + publicKey: publicKeyString + }; + } catch (error) { + console.error('Error generating private key:', error); + return null; + } + } + + generateVAPIDKeys().then(keyPair => { + const publicKeyInput = document.querySelector('#webpush_vapid_public'); + const privateKeyInput = document.querySelector('#webpush_vapid_private'); + publicKeyInput.value = keyPair.publicKey; + privateKeyInput.value = keyPair.privateKey; + }) +}) + /** * Handler for submitting permissions form in chunks * This call will submit permissions forms in chunks of 5 fieldsets. diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index ceb7f47d58f..d0ced6788f3 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -491,7 +491,7 @@ function main($id, $mode) 'title' => 'ACP_WEBPUSH_SETTINGS', 'vars' => [ 'legend1' => 'GENERAL_SETTINGS', - 'webpush_enable' => ['lang' => 'WEBPUSH_ENABLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], + 'webpush_enable' => ['lang' => 'WEBPUSH_ENABLE', 'validate' => 'bool', 'type' => 'custom', 'method' => 'webpush_enable', 'explain' => true], 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], @@ -539,27 +539,6 @@ function main($id, $mode) } } - if ($mode == 'webpush') - { - // Create VAPID keys if keys are empty and web push is enabled - if ($submit && $cfg_array['webpush_enable'] && $cfg_array['webpush_enable'] != $config['webpush_enable'] - && empty($cfg_array['webpush_vapid_public']) && empty($cfg_array['webpush_vapid_private']) - && empty($config['webpush_vapid_public']) && empty($config['webpush_vapid_private'])) - { - try - { - $vapid_keys = VAPID::createVapidKeys(); - $cfg_array['webpush_vapid_public'] = $vapid_keys['publicKey']; - $cfg_array['webpush_vapid_private'] = $vapid_keys['privateKey']; - } - catch (\ErrorException $exception) - { - // Nothing we can do about this, user will have to follow the - // documentation and manually create these. - } - } - } - // We validate the complete config if wished validate_config_vars($display_vars['vars'], $cfg_array, $error); @@ -1383,4 +1362,49 @@ function send_test_email($value, $key) return ' '; } + + /** + * Generate form data for web push enable + * + * @param string $value Webpush enable value + * @param string $key Webpush enable config key + * + * @return array[] Form data + */ + public function webpush_enable($value, $key): array + { + return [ + [ + 'tag' => 'radio', + 'buttons' => [ + [ + 'name' => "config[$key]", + 'label' => $this->language->lang('YES'), + 'type' => 'radio', + 'class' => 'radio', + 'value' => 1, + 'checked' => $value, + ], + [ + 'name' => "config[$key]", + 'label' => $this->language->lang('NO'), + 'type' => 'radio', + 'class' => 'radio', + 'value' => 0, + 'checked' => !$value, + ], + ], + ], + [ + 'tag' => 'input', + 'class' => 'button2', + 'name' => "config[$key]", + 'type' => 'button', + 'value' => $this->language->lang('WEBPUSH_GENERATE_VAPID_KEYS'), + 'data' => [ + 'ajax' => 'generate_vapid_keys', + ] + ], + ]; + } } diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 8fe9f832f2e..d2905d94e8f 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -603,6 +603,7 @@ 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Webpush for board notifications. Webpush is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', 'WEBPUSH_ENABLE' => 'Enable Webpush', 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush.
Note: If VAPID keys have not been set, phpBB will try to automatically create them when enabling Webpush.', + 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate VAPID keys', 'WEBPUSH_VAPID_PUBLIC' => 'VAPID public key', 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The VAPID public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all webpush subscriptions.', 'WEBPUSH_VAPID_PRIVATE' => 'VAPID private key', From 3cbe14eb4a3fc2b4c69216cda41a508f1e405398 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 24 Jun 2023 09:40:35 +0200 Subject: [PATCH 0036/1214] [ticket/17010] Clean up code and move some functions to core js PHPBB3-17010 --- phpBB/adm/style/ajax.js | 35 +++++++++++---------------------- phpBB/assets/javascript/core.js | 27 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index 25a65274810..67956e946a2 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -160,24 +160,13 @@ phpbb.addAjaxCallback('row_delete', function(res) { /** * This callback generates the VAPID keys for the web push notification service. */ -phpbb.addAjaxCallback('generate_vapid_keys', (res) => { - function rawKeyToBase64(rawKey) { - const keyBuffer = new Uint8Array(rawKey); - let keyText = ''; - const keyLength = keyBuffer.byteLength; - for (let i = 0; i < keyLength; i++) { - keyText += String.fromCharCode(keyBuffer[i]); - } - - keyText = window.btoa(keyText); - return keyText; - } - - function base64SafeEncode(base64String) { - const base64URL = base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); - return base64URL; - } +phpbb.addAjaxCallback('generate_vapid_keys', () => { + /** + * Generate VAPID keypair with public and private key string + * + * @returns {Promise<{privateKey: string, publicKey: string}|null>} + */ async function generateVAPIDKeys() { try { // Generate a new key pair using the Subtle Crypto API @@ -190,28 +179,26 @@ phpbb.addAjaxCallback('generate_vapid_keys', (res) => { ['deriveKey', 'deriveBits'] ); - // Export the private key as a JWK (JSON Web Key) object const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey); - console.log(privateKeyJwk.d); - const privateKeyString = privateKeyJwk.d; - // Export the public key as a JWK object const publicKeyBuffer = await crypto.subtle.exportKey('raw', keyPair.publicKey); - const publicKeyString = base64SafeEncode(rawKeyToBase64(publicKeyBuffer)); - console.log(publicKeyString); + const publicKeyString = phpbb.base64UrlEncode(phpbb.rawKeyToBase64(publicKeyBuffer)); return { privateKey: privateKeyString, publicKey: publicKeyString }; } catch (error) { - console.error('Error generating private key:', error); + console.error('Error generating keys with SubtleCrypto:', error); return null; } } generateVAPIDKeys().then(keyPair => { + if (!keyPair) { + return; + } const publicKeyInput = document.querySelector('#webpush_vapid_public'); const privateKeyInput = document.querySelector('#webpush_vapid_private'); publicKeyInput.value = keyPair.publicKey; diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index d301cc8da87..c0b225214dd 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -1677,6 +1677,33 @@ phpbb.getFunctionByName = function (functionName) { return context[func]; }; +/** + * Convert raw key ArrayBuffer to base64 string. + * + * @param {ArrayBuffer} rawKey Raw key array buffer as exported by SubtleCrypto exportKey() + * @returns {string} Base64 encoded raw key string + */ +phpbb.rawKeyToBase64 = (rawKey) => { + const keyBuffer = new Uint8Array(rawKey); + let keyText = ''; + const keyLength = keyBuffer.byteLength; + for (let i = 0; i < keyLength; i++) { + keyText += String.fromCharCode(keyBuffer[i]); + } + + return window.btoa(keyText); +}; + +/** + * Base64URL encode base64 encoded string + * + * @param {string} base64String Base64 encoded string + * @returns {string} Base64URL encoded string + */ +phpbb.base64UrlEncode = (base64String) => { + return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +}; + /** * Register page dropdowns. */ From 21ccb804d3765d1fe2afbe9d06ee6bd875d35c44 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 24 Jun 2023 09:42:30 +0200 Subject: [PATCH 0037/1214] [ticket/17010] Update explain string in ACP for webpush enable PHPBB3-17010 --- phpBB/language/en/acp/board.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index d2905d94e8f..1c65cccbc4f 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -602,7 +602,7 @@ $lang = array_merge($lang, [ 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Webpush for board notifications. Webpush is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', 'WEBPUSH_ENABLE' => 'Enable Webpush', - 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush.
Note: If VAPID keys have not been set, phpBB will try to automatically create them when enabling Webpush.', + 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush.', 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate VAPID keys', 'WEBPUSH_VAPID_PUBLIC' => 'VAPID public key', 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The VAPID public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all webpush subscriptions.', From 6cc8df5d95ec54394f72c06862ed17d018aa4894 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 25 Jun 2023 09:26:06 +0200 Subject: [PATCH 0038/1214] [ticket/17010] Adjust wording on ACP page for webpush PHPBB3-17010 --- phpBB/language/en/acp/board.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 1c65cccbc4f..7a9b81ef1eb 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -602,12 +602,12 @@ $lang = array_merge($lang, [ 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Webpush for board notifications. Webpush is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', 'WEBPUSH_ENABLE' => 'Enable Webpush', - 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush.', - 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate VAPID keys', - 'WEBPUSH_VAPID_PUBLIC' => 'VAPID public key', - 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The VAPID public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all webpush subscriptions.', - 'WEBPUSH_VAPID_PRIVATE' => 'VAPID private key', - 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The VAPID private key will be used to create authenticated push messages sent by your site. The VAPID private key must be a valid public-private key pair with the VAPID public key.
Warning: Changing the VAPID private key will automatically invalidate all webpush subscriptions.', + 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush. It is required to enter or generate valid VAPID identification keys to be able to use Webpush.', + 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate Identification keys', + 'WEBPUSH_VAPID_PUBLIC' => 'Server identification public key', + 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all Webpush subscriptions.', + 'WEBPUSH_VAPID_PRIVATE' => 'Server identification private key', + 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key will be used to create authenticated push messages sent by your site. The VAPID private key must be a valid public-private key pair with the VAPID public key.
Warning: Changing the VAPID private key will automatically invalidate all Webpush subscriptions.', ]); // Jabber settings From c35e9c2438e5978ef8589906422e189853641807 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 25 Jun 2023 16:51:29 +0200 Subject: [PATCH 0039/1214] [ticket/17010] Correctly fill subscription map for multiple subscriptions PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index ea71e1cab83..94e2e95c694 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -176,14 +176,7 @@ protected function notify_using_webpush(): void $result = $this->db->sql_query($sql); while ($row = $this->db->sql_fetchrow($result)) { - if (isset($user_subscription_map[$row['user_id']])) - { - $user_subscription_map[$row['user_id']] += $row; - } - else - { - $user_subscription_map[$row['user_id']] = [$row]; - } + $user_subscription_map[$row['user_id']][] = $row; } $auth = [ From b779ce5910b88cd995a97c4efac97797d52eee61 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 25 Jun 2023 21:03:08 +0200 Subject: [PATCH 0040/1214] [ticket/17010] Remove optional column check for non-existent column PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 94e2e95c694..5ec857af052 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -291,8 +291,7 @@ public function mark_notifications_by_parent($notification_type_id, $item_parent public function prune_notifications($timestamp, $only_read = true): void { $sql = 'DELETE FROM ' . $this->notification_webpush_table . ' - WHERE notification_time < ' . (int) $timestamp . - (($only_read) ? ' AND notification_read = 1' : ''); + WHERE notification_time < ' . (int) $timestamp; $this->db->sql_query($sql); $this->config->set('read_notification_last_gc', (string) time(), false); From 79ff21fdf583d2d71c78007170172ff2cbc0b9bb Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 26 Jun 2023 08:53:26 +0200 Subject: [PATCH 0041/1214] [ticket/17010] Move get subscription map to separate function and extend tests Unit tests will also now ensure there are no special surprises with more than one subscription for users. PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 35 ++++++++++++++----- .../notification_method_webpush_test.php | 13 +++++-- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 5ec857af052..5091c2823d6 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -169,15 +169,7 @@ protected function notify_using_webpush(): void $this->user_loader->load_users($notify_users, array(USER_IGNORE)); // Get subscriptions for users - $user_subscription_map = []; - $sql = 'SELECT subscription_id, user_id, endpoint, p256dh, auth - FROM ' . $this->push_subscriptions_table . ' - WHERE ' . $this->db->sql_in_set('user_id', $notify_users); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $user_subscription_map[$row['user_id']][] = $row; - } + $user_subscription_map = $this->get_user_subscription_map($notify_users); $auth = [ 'VAPID' => [ @@ -316,4 +308,29 @@ public static function clean_data(array $data): array return array_intersect_key($data, $row); } + + /** + * Get subscriptions for notify users + * + * @param array $notify_users Users to notify + * + * @return array Subscription map + */ + protected function get_user_subscription_map(array $notify_users): array + { + // Get subscriptions for users + $user_subscription_map = []; + + $sql = 'SELECT subscription_id, user_id, endpoint, p256dh, auth + FROM ' . $this->push_subscriptions_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $notify_users); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_subscription_map[$row['user_id']][] = $row; + } + $this->db->sql_freeresult($result); + + return $user_subscription_map; + } } diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index ce79f578383..888ad11a3b0 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -338,7 +338,14 @@ public function test_get_subscription($notification_type, $post_data, $expected_ $subscription_info = []; foreach ($expected_users as $user_id => $user_data) { - $subscription_info[$user_id] = $this->create_subscription_for_user($user_id); + $subscription_info[$user_id][] = $this->create_subscription_for_user($user_id); + } + + // Create second subscription for first user ID passed + if (count($expected_users)) + { + $first_user_id = array_key_first($expected_users); + $subscription_info[$first_user_id][] = $this->create_subscription_for_user($first_user_id); } $post_data = array_merge([ @@ -361,7 +368,7 @@ public function test_get_subscription($notification_type, $post_data, $expected_ foreach ($expected_users as $user_id => $data) { - $messages = $this->get_messages_for_subscription($subscription_info[$user_id]['clientHash']); + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); $this->assertEmpty($messages); } @@ -372,7 +379,7 @@ public function test_get_subscription($notification_type, $post_data, $expected_ foreach ($expected_users as $user_id => $data) { - $messages = $this->get_messages_for_subscription($subscription_info[$user_id]['clientHash']); + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); $this->assertNotEmpty($messages); } } From 5098f315fd6968ced4eadbcb3cdc14f2caa70b91 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 2 Jul 2023 17:27:50 +0200 Subject: [PATCH 0042/1214] [ticket/17010] Use special chars decode to have valid URL with amp PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 5091c2823d6..3298ee2e3c1 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -122,7 +122,7 @@ public function notify() 'heading' => $this->config['sitename'], 'title' => strip_tags($notification->get_title()), 'text' => strip_tags($notification->get_reference()), - 'url' => $notification->get_url(), + 'url' => htmlspecialchars_decode($notification->get_url()), 'avatar' => $notification->get_avatar(), ]), 'notification_time' => time(), From 6b00e9fe09c464b55a23076da684b744607dbd8a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 3 Jul 2023 14:38:01 +0200 Subject: [PATCH 0043/1214] [ticket/17010] Handle already existing subscriptions PHPBB3-17010 --- .../db/migration/data/v400/add_webpush.php | 1 - phpBB/phpbb/ucp/controller/webpush.php | 26 +++++++++-- phpBB/styles/all/js/webpush.js.twig | 45 ++++++++++++++++--- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php index 248be387bdb..351e02a3611 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -48,7 +48,6 @@ public function update_schema(): array 'COLUMNS' => [ 'subscription_id' => ['ULINT', null, 'auto_increment'], 'user_id' => ['ULINT', 0], -// 'device_name' => ['VCHAR:64', ''], 'endpoint' => ['TEXT', ''], 'expiration_time' => ['TIMESTAMP', 0], 'p256dh' => ['VCHAR', ''], diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index f1f5c197be6..e292f35fac0 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -35,9 +35,6 @@ class webpush /** @var string UCP form token name */ private const FORM_TOKEN_UCP = 'ucp_webpush'; - /** @var string Push worker form token name */ - private const FORM_TOKEN_WORKER = 'webpush_worker'; - /** @var config */ protected $config; @@ -206,6 +203,7 @@ public function js(): Response $template_data += [ 'VAPID_PUBLIC_KEY' => $this->config['webpush_vapid_public'], 'U_WEBPUSH_WORKER_URL' => $this->controller_helper->route('phpbb_ucp_push_worker_controller'), + 'SUBSCRIPTIONS' => $this->get_subscriptions(), ]; $content = $this->template->render('webpush.js.twig', $template_data); @@ -273,4 +271,26 @@ public function unsubscribe(symfony_request $symfony_request): JsonResponse 'form_tokens' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), ]); } + + /** + * Get subscriptions for current user + * + * @return array Subscriptions for user + */ + protected function get_subscriptions(): array + { + $subscriptions = []; + + $sql = 'SELECT endpoint, expiration_time + FROM ' . $this->push_subscriptions_table . ' + WHERE user_id = ' . $this->user->id(); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $subscriptions[] = $row; + } + $this->db->sql_freeresult($result); + + return $subscriptions; + } } diff --git a/phpBB/styles/all/js/webpush.js.twig b/phpBB/styles/all/js/webpush.js.twig index e634f4539c2..b9c776d5490 100644 --- a/phpBB/styles/all/js/webpush.js.twig +++ b/phpBB/styles/all/js/webpush.js.twig @@ -18,6 +18,13 @@ function PhpbbWebpush() { formToken: '{{ FORM_TOKENS.form_token }}' }; + /** @type [{endpoint: string, expiration: string}[]] Subscriptions */ + let subscriptions = [ + {% for sub in SUBSCRIPTIONS %} + {endpoint: '{{ sub.endpoint }}', expiration: '{{ sub.expiration }}' }, + {% endfor %} + ]; + /** @type {string} VAPID public key */ const VAPID_PUBLIC_KEY = '{{ VAPID_PUBLIC_KEY }}'; @@ -70,7 +77,7 @@ function PhpbbWebpush() { registration.pushManager.getSubscription() .then((subscribed) => { - if (subscribed) { + if (isValidSubscription(subscribed)) { setSubscriptionState(true); } }) @@ -78,6 +85,31 @@ function PhpbbWebpush() { } } + /** + * Check whether subscription is valid + * + * @param {PushSubscription} subscription + * @returns {boolean} + */ + const isValidSubscription = (subscription) => { + if (!subscription) { + return false; + } + + if (subscription.expirationTime && subscription.expirationTime <= Date.now()) { + return false; + } + + for (const curSubscription of subscriptions) { + if (subscription.endpoint === curSubscription.endpoint) { + return true; + } + } + + // Subscription is not in valid subscription list for user + return false; + }; + /** * Set subscription state for buttons * @@ -111,16 +143,19 @@ function PhpbbWebpush() { } const registration = await navigator.serviceWorker.getRegistration(serviceWorkerUrl); + + // We might already have a subscription that is unknown to this instance of phpBB. + // Unsubscribe before trying to subscribe again. if (typeof registration !== 'undefined') { const subscribed = await registration.pushManager.getSubscription(); if (subscribed) { - setSubscriptionState(true); - return; + await subscribed.unsubscribe(); } } - const newSubscription = await registration.pushManager.subscribe({ + + let newSubscription = await registration.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY) + applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY), }); const loadingIndicator = phpbb.loadingIndicator(); From f3eb774abdb181f2dafc985186ba3b12590754b3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 30 Jul 2023 09:02:19 +0200 Subject: [PATCH 0044/1214] [ticket/17010] Add missing language variable PHPBB3-17010 --- phpBB/language/en/acp/common.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index 84c047af54b..85418652dad 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -593,6 +593,7 @@ 'LOG_CONFIG_SETTINGS' => 'Altered board settings', 'LOG_CONFIG_SIGNATURE' => 'Altered signature settings', 'LOG_CONFIG_VISUAL' => 'Altered anti-spambot settings', + 'LOG_CONFIG_WEBPUSH' => 'Altered webpush settings', 'LOG_APPROVE_TOPIC' => 'Approved topic
» %s', 'LOG_BUMP_TOPIC' => 'User bumped topic
» %s', From 479e54db932d09e748eef07d08386e1613ba546c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 15 Aug 2023 08:12:33 +0200 Subject: [PATCH 0045/1214] [ticket/17010] Add logging to webpush notifications PHPBB3-17010 --- .../container/services_notification.yml | 5 ++-- phpBB/language/en/acp/common.php | 3 +++ phpBB/phpbb/notification/method/webpush.php | 23 ++++++++++++++----- tests/notification/base.php | 1 + .../notification_method_email_test.php | 1 + tests/notification/submit_post_base.php | 1 + 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index acb136641cd..92592850f9c 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -248,10 +248,11 @@ services: class: phpbb\notification\method\webpush shared: false arguments: - - '@user_loader' - - '@user' - '@config' - '@dbal.conn' + - '@log' + - '@user_loader' + - '@user' - '%core.root_path%' - '%core.php_ext%' - '%tables.notification_push%' diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index 85418652dad..b9e24163419 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -814,6 +814,9 @@ ), 'LOG_WARNINGS_DELETED_ALL' => 'Deleted all user warnings
» %s', + 'LOG_WEBPUSH_MESSAGE_FAIL' => 'WebPush message could not be sent: %s', + 'LOG_WEBPUSH_SUBSCRIPTION_REMOVED' => 'Removed WebPush subscription:» %s', + 'LOG_WORD_ADD' => 'Added word censor
» %s', 'LOG_WORD_DELETE' => 'Deleted word censor
» %s', 'LOG_WORD_EDIT' => 'Edited word censor
» %s', diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 3298ee2e3c1..c4d44946edb 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -16,6 +16,7 @@ use Minishlink\WebPush\Subscription; use phpbb\config\config; use phpbb\db\driver\driver_interface; +use phpbb\log\log_interface; use phpbb\notification\type\type_interface; use phpbb\user; use phpbb\user_loader; @@ -33,6 +34,9 @@ class webpush extends messenger_base /** @var driver_interface */ protected $db; + /** @var log_interface */ + protected $log; + /** @var user */ protected $user; @@ -45,23 +49,25 @@ class webpush extends messenger_base /** * Notification Method web push constructor * - * @param user_loader $user_loader - * @param user $user * @param config $config * @param driver_interface $db + * @param log_interface $log + * @param user_loader $user_loader + * @param user $user * @param string $phpbb_root_path * @param string $php_ext * @param string $notification_webpush_table * @param string $push_subscriptions_table */ - public function __construct(user_loader $user_loader, user $user, config $config, driver_interface $db, string $phpbb_root_path, + public function __construct(config $config, driver_interface $db, log_interface $log, user_loader $user_loader, user $user, 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->user = $user; $this->config = $config; $this->db = $db; + $this->log = $log; + $this->user = $user; $this->notification_webpush_table = $notification_webpush_table; $this->push_subscriptions_table = $push_subscriptions_table; } @@ -222,6 +228,10 @@ protected function notify_using_webpush(): void catch (\ErrorException $exception) { $remove_subscriptions[] = $subscription['subscription_id']; + $this->log->add('user', $user['user_id'], $user['user_ip'] ?? '', 'LOG_WEBPUSH_SUBSCRIPTION_REMOVED', false, [ + 'reportee_id' => $user['user_id'], + $user['username'], + ]); } } } @@ -240,13 +250,14 @@ protected function notify_using_webpush(): void { if (!$report->isSuccess()) { - // @todo: log errors / remove subscription + $report_data = \phpbb\json\sanitizer::sanitize($report->jsonSerialize()); + $this->log->add('admin', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$report_data['reason']]); } } } catch (\ErrorException $exception) { - // @todo: write to log + $this->log->add('critical', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$exception->getMessage()]); } // We're done, empty the queue diff --git a/tests/notification/base.php b/tests/notification/base.php index 37718a85a95..8e6f67c2198 100644 --- a/tests/notification/base.php +++ b/tests/notification/base.php @@ -106,6 +106,7 @@ protected function setUp(): void $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); + $phpbb_container->set('log', new \phpbb\log\dummy()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set( 'text_formatter.s9e.mention_helper', diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php index 77db505c04d..6b464b1c42d 100644 --- a/tests/notification/notification_method_email_test.php +++ b/tests/notification/notification_method_email_test.php @@ -83,6 +83,7 @@ protected function setUp(): void $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); + $phpbb_container->set('log', new \phpbb\log\dummy()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('event_dispatcher', $this->phpbb_dispatcher); $phpbb_container->setParameter('core.root_path', $phpbb_root_path); diff --git a/tests/notification/submit_post_base.php b/tests/notification/submit_post_base.php index 3219c85e27c..124ac4d1b4d 100644 --- a/tests/notification/submit_post_base.php +++ b/tests/notification/submit_post_base.php @@ -135,6 +135,7 @@ protected function setUp(): void $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); + $phpbb_container->set('log', new \phpbb\log\dummy()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set( 'text_formatter.s9e.mention_helper', From 2da2211898e8931ac68977438f0d11b24881a531 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 15 Aug 2023 09:47:11 +0200 Subject: [PATCH 0046/1214] [ticket/17010] Update webpush test PHPBB3-17010 --- tests/notification/notification_method_webpush_test.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 888ad11a3b0..9cf374da1a3 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -125,6 +125,7 @@ protected function setUp(): void $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); + $phpbb_container->set('log', new \phpbb\log\dummy()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); $phpbb_container->setParameter('core.root_path', $phpbb_root_path); @@ -147,10 +148,11 @@ protected function setUp(): void ); $this->notification_method_webpush = new \phpbb\notification\method\webpush( - $phpbb_container->get('user_loader'), - $phpbb_container->get('user'), $phpbb_container->get('config'), $phpbb_container->get('dbal.conn'), + $phpbb_container->get('log'), + $phpbb_container->get('user_loader'), + $phpbb_container->get('user'), $phpbb_root_path, $phpEx, $phpbb_container->getParameter('tables.notification_push'), From d85da61c9a9b9203d0058bee10bf1312f926fa63 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 12:55:07 +0100 Subject: [PATCH 0047/1214] [ticket/17010] Move webpush js to assets folder PHPBB3-17010 --- .../all/js/webpush.js.twig => assets/javascript/webpush.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename phpBB/{styles/all/js/webpush.js.twig => assets/javascript/webpush.js} (100%) diff --git a/phpBB/styles/all/js/webpush.js.twig b/phpBB/assets/javascript/webpush.js similarity index 100% rename from phpBB/styles/all/js/webpush.js.twig rename to phpBB/assets/javascript/webpush.js From fa91bf791f125e63ef467dac760c34c778cbd881 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 12:56:01 +0100 Subject: [PATCH 0048/1214] [ticket/17010] Make webpush js not depend on twig compilation PHPBB3-17010 --- phpBB/assets/javascript/webpush.js | 93 +++++++++++-------- .../template/ucp_notifications_options.html | 4 +- .../template/ucp_notifications_webpush.html | 21 +++++ 3 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 phpBB/styles/prosilver/template/ucp_notifications_webpush.html diff --git a/phpBB/assets/javascript/webpush.js b/phpBB/assets/javascript/webpush.js index b9c776d5490..c326fe1cff3 100644 --- a/phpBB/assets/javascript/webpush.js +++ b/phpBB/assets/javascript/webpush.js @@ -4,37 +4,48 @@ function PhpbbWebpush() { /** @type {string} URL to service worker */ - const serviceWorkerUrl = '{{ U_WEBPUSH_WORKER_URL }}'; + let serviceWorkerUrl = ''; /** @type {string} URL to subscribe to push */ - const subscribeUrl = '{{ U_WEBPUSH_SUBSCRIBE }}'; + let subscribeUrl = ''; /** @type {string} URL to unsubscribe from push */ - const unsubscribeUrl = '{{ U_WEBPUSH_UNSUBSCRIBE }}'; + let unsubscribeUrl = ''; /** @type { {creationTime: number, formToken: string} } Form tokens */ this.formTokens = { - creationTime: {{ FORM_TOKENS.creation_time }}, - formToken: '{{ FORM_TOKENS.form_token }}' + creationTime: 0, + formToken: '', }; - /** @type [{endpoint: string, expiration: string}[]] Subscriptions */ - let subscriptions = [ - {% for sub in SUBSCRIPTIONS %} - {endpoint: '{{ sub.endpoint }}', expiration: '{{ sub.expiration }}' }, - {% endfor %} - ]; + /** @type {{endpoint: string, expiration: string}[]} Subscriptions */ + let subscriptions; + + /** @type {string} Title of error message */ + let ajaxErrorTitle = ''; /** @type {string} VAPID public key */ - const VAPID_PUBLIC_KEY = '{{ VAPID_PUBLIC_KEY }}'; + let vapidPublicKey = ''; + + /** @type {HTMLElement} Subscribe button */ + let subscribeButton; - let subscribeButton, - unsubscribeButton; + /** @type {HTMLElement} Unsubscribe button */ + let unsubscribeButton; /** - * Init function for phpBB webpush + * Init function for phpBB Web Push + * @type {array} options */ - this.init = function() { + this.init = function(options) { + serviceWorkerUrl = options.serviceWorkerUrl; + subscribeUrl = options.subscribeUrl; + unsubscribeUrl = options.unsubscribeUrl; + this.formTokens = options.formTokens; + subscriptions = options.subscriptions; + ajaxErrorTitle = options.ajaxErrorTitle; + vapidPublicKey = options.vapidPublicKey; + subscribeButton = document.querySelector('#subscribe_webpush'); unsubscribeButton = document.querySelector('#unsubscribe_webpush'); @@ -70,17 +81,17 @@ function PhpbbWebpush() { function updateButtonState() { if (Notification.permission === 'granted') { navigator.serviceWorker.getRegistration(serviceWorkerUrl) - .then((registration) => { + .then(registration => { if (typeof registration === 'undefined') { return; } registration.pushManager.getSubscription() - .then((subscribed) => { + .then(subscribed => { if (isValidSubscription(subscribed)) { setSubscriptionState(true); } - }) + }); }); } } @@ -91,7 +102,7 @@ function PhpbbWebpush() { * @param {PushSubscription} subscription * @returns {boolean} */ - const isValidSubscription = (subscription) => { + const isValidSubscription = subscription => { if (!subscription) { return false; } @@ -153,27 +164,27 @@ function PhpbbWebpush() { } } - let newSubscription = await registration.pushManager.subscribe({ + const newSubscription = await registration.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY), + applicationServerKey: urlB64ToUint8Array(vapidPublicKey), }); const loadingIndicator = phpbb.loadingIndicator(); fetch(subscribeUrl, { - method: 'POST', - headers: { - 'X-Requested-With': 'XMLHttpRequest' - }, - body: getFormData(newSubscription) - }) - .then((response) => { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + body: getFormData(newSubscription), + }) + .then(response => { loadingIndicator.fadeOut(phpbb.alertTime); return response.json(); }) .then(handleSubscribe) - .catch((error) => { + .catch(error => { loadingIndicator.fadeOut(phpbb.alertTime); - phpbb.alert('{{ lang('AJAX_ERROR_TITLE') }}', error); + phpbb.alert(ajaxErrorTitle, error); }); } @@ -196,22 +207,22 @@ function PhpbbWebpush() { fetch(unsubscribeUrl, { method: 'POST', headers: { - 'X-Requested-With': 'XMLHttpRequest' + 'X-Requested-With': 'XMLHttpRequest', }, - body: getFormData({endpoint: subscription.endpoint}) + body: getFormData({ endpoint: subscription.endpoint }), }) .then(() => { loadingIndicator.fadeOut(phpbb.alertTime); return subscription.unsubscribe(); }) - .then((unsubscribed) => { + .then(unsubscribed => { if (unsubscribed) { setSubscriptionState(false); } }) - .catch((error) => { + .catch(error => { loadingIndicator.fadeOut(phpbb.alertTime); - phpbb.alert('{{ lang('AJAX_ERROR_TITLE') }}', error); + phpbb.alert(ajaxErrorTitle, error); }); } @@ -223,7 +234,7 @@ function PhpbbWebpush() { function handleSubscribe(response) { if (response.success) { setSubscriptionState(true); - if (response.hasOwnProperty('form_tokens')) { + if ('form_tokens' in response) { updateFormTokens(response.form_tokens); } } @@ -236,7 +247,7 @@ function PhpbbWebpush() { * @returns {FormData} Form data */ function getFormData(data) { - let formData = new FormData(); + const formData = new FormData(); formData.append('form_token', phpbb.webpush.formTokens.formToken); formData.append('creation_time', phpbb.webpush.formTokens.creationTime.toString()); formData.append('data', JSON.stringify(data)); @@ -261,13 +272,14 @@ function PhpbbWebpush() { * @returns {Uint8Array} */ function urlB64ToUint8Array(base64String) { - const padding = '='.repeat((4 - base64String.length % 4) % 4); + const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } + return outputArray; } } @@ -283,5 +295,6 @@ function domReady(callBack) { phpbb.webpush = new PhpbbWebpush(); domReady(() => { - phpbb.webpush.init(); + /* global phpbbWebpushOptions */ + phpbb.webpush.init(phpbbWebpushOptions); }); diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html index 205e8d43940..a0d9caad12e 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications_options.html +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -1,6 +1,8 @@ {% include('ucp_header.html') %} -{% INCLUDEJS(T_WEBPUSH_JS_PATH) %} +{% if NOTIFICATIONS_WEBPUSH_ENABLE %} + {% include('ucp_notifications_webpush.html') %} +{% endif %} diff --git a/phpBB/styles/prosilver/template/ucp_notifications_webpush.html b/phpBB/styles/prosilver/template/ucp_notifications_webpush.html new file mode 100644 index 00000000000..31c4790b33d --- /dev/null +++ b/phpBB/styles/prosilver/template/ucp_notifications_webpush.html @@ -0,0 +1,21 @@ + + +{% INCLUDEJS(T_ASSETS_PATH ~ '/javascript/webpush.js') %} + From 8d9a7aa62c3002c0545b856691f85677769aee15 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 12:56:39 +0100 Subject: [PATCH 0049/1214] [ticket/17010] Add new interface and create template data in type PHPBB3-17010 --- phpBB/includes/ucp/ucp_notifications.php | 59 +++++++++---------- .../method/extended_method_interface.php | 29 +++++++++ phpBB/phpbb/notification/method/webpush.php | 33 ++++++++++- phpBB/phpbb/ucp/controller/webpush.php | 2 +- 4 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 phpBB/phpbb/notification/method/extended_method_interface.php diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php index 9e03e68509c..62d7fbd112e 100644 --- a/phpBB/includes/ucp/ucp_notifications.php +++ b/phpBB/includes/ucp/ucp_notifications.php @@ -14,6 +14,11 @@ /** * @ignore */ + +use phpbb\controller\helper; +use phpbb\form\form_helper; +use phpbb\notification\method\extended_method_interface; + if (!defined('IN_PHPBB')) { exit; @@ -23,17 +28,29 @@ class ucp_notifications { public $u_action; + private const FORM_TOKEN_NAME = 'ucp_notification'; + + /** @var helper */ + private helper $controller_helper; + + /** @var form_helper */ + private form_helper $form_helper; + public function main($id, $mode) { global $config, $template, $user, $request, $phpbb_container, $phpbb_dispatcher; global $phpbb_root_path, $phpEx; - add_form_key('ucp_notification'); + add_form_key(self::FORM_TOKEN_NAME); $start = $request->variable('start', 0); $form_time = $request->variable('form_time', 0); $form_time = ($form_time <= 0 || $form_time > time()) ? time() : $form_time; + + $this->controller_helper = $phpbb_container->get('controller.helper'); + $this->form_helper = $phpbb_container->get('form_helper'); + /* @var $phpbb_notifications \phpbb\notification\manager */ $phpbb_notifications = $phpbb_container->get('notification_manager'); @@ -48,7 +65,7 @@ public function main($id, $mode) // Add/remove subscriptions if ($request->is_set_post('submit')) { - if (!check_form_key('ucp_notification')) + if (!check_form_key(self::FORM_TOKEN_NAME)) { trigger_error('FORM_INVALID'); } @@ -103,15 +120,12 @@ public function main($id, $mode) trigger_error($message); } - $this->output_notification_methods($phpbb_notifications, $template, $user, 'notification_methods'); + $this->output_notification_methods($phpbb_notifications, $template, $user); $this->output_notification_types($subscriptions, $phpbb_notifications, $template, $user, $phpbb_dispatcher, 'notification_types'); - /** @var \phpbb\controller\helper $controller_helper */ - $controller_helper = $phpbb_container->get('controller.helper'); - $template->assign_vars([ - 'T_WEBPUSH_JS_PATH' => $controller_helper->route('phpbb_ucp_push_js_controller'), + 'FORM_TOKENS' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_NAME), ]); $this->tpl_name = 'ucp_notifications_options'; @@ -145,7 +159,7 @@ public function main($id, $mode) // Mark specific notifications read if ($request->is_set_post('submit')) { - if (!check_form_key('ucp_notification')) + if (!check_form_key(self::FORM_TOKEN_NAME)) { trigger_error('FORM_INVALID'); } @@ -273,35 +287,18 @@ public function output_notification_methods(\phpbb\notification\manager $phpbb_n { $notification_methods = $phpbb_notifications->get_subscription_methods(); - if (isset($notification_methods['notification.method.webpush'])) + foreach ($notification_methods as $method_data) { - $this->output_webpush_data($template); - } + if ($method_data['method'] instanceof extended_method_interface) + { + $ucp_template_data = $method_data['method']->get_ucp_template_data($this->controller_helper, $this->form_helper); + $template->assign_vars($ucp_template_data); + } - foreach ($notification_methods as $method => $method_data) - { $template->assign_block_vars($block, array( 'METHOD' => $method_data['id'], - 'NAME' => $user->lang($method_data['lang']), )); } } - - /** - * Output data for webpush - * - * @param \phpbb\template\template $template - * - * @return void - */ - protected function output_webpush_data(\phpbb\template\template $template): void - { - global $config; - - $template->assign_vars([ - 'NOTIFICATIONS_WEBPUSH_ENABLE' => true, // already checked, otherwise we wouldn't be here - 'NOTIFICATIONS_WEBPUSH_VAPID_PUBLIC' => $config['webpush_vapid_public'], - ]); - } } diff --git a/phpBB/phpbb/notification/method/extended_method_interface.php b/phpBB/phpbb/notification/method/extended_method_interface.php new file mode 100644 index 00000000000..866288ec3e2 --- /dev/null +++ b/phpBB/phpbb/notification/method/extended_method_interface.php @@ -0,0 +1,29 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\notification\method; + +use phpbb\controller\helper; +use phpbb\form\form_helper; + +interface extended_method_interface extends method_interface +{ + /** + * Get UCP template data for type + * + * @param helper $controller_helper + * @param form_helper $form_helper + * @return array Template data + */ + public function get_ucp_template_data(helper $controller_helper, form_helper $form_helper): array; +} diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index c4d44946edb..273ddcddd51 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -15,7 +15,9 @@ use Minishlink\WebPush\Subscription; use phpbb\config\config; +use phpbb\controller\helper; use phpbb\db\driver\driver_interface; +use phpbb\form\form_helper; use phpbb\log\log_interface; use phpbb\notification\type\type_interface; use phpbb\user; @@ -26,7 +28,7 @@ * This class handles sending push messages for notifications */ -class webpush extends messenger_base +class webpush extends messenger_base implements extended_method_interface { /** @var config */ protected $config; @@ -320,6 +322,33 @@ public static function clean_data(array $data): array return array_intersect_key($data, $row); } + public function get_ucp_template_data(helper $controller_helper, form_helper $form_helper): array + { + $subscription_map = $this->get_user_subscription_map([$this->user->id()]); + $subscriptions = []; + + if (isset($subscription_map[$this->user->id()])) + { + foreach ($subscription_map[$this->user->id()] as $subscription) + { + $subscriptions[] = [ + 'endpoint' => $subscription['endpoint'], + 'expirationTime' => $subscription['expiration_time'], + ]; + } + } + + return [ + 'NOTIFICATIONS_WEBPUSH_ENABLE' => true, + 'U_WEBPUSH_SUBSCRIBE' => $controller_helper->route('phpbb_ucp_push_subscribe_controller'), + 'U_WEBPUSH_UNSUBSCRIBE' => $controller_helper->route('phpbb_ucp_push_unsubscribe_controller'), + 'VAPID_PUBLIC_KEY' => $this->config['webpush_vapid_public'], + 'U_WEBPUSH_WORKER_URL' => $controller_helper->route('phpbb_ucp_push_worker_controller'), + 'SUBSCRIPTIONS' => $subscriptions, + 'WEBPUSH_FORM_TOKENS' => $form_helper->get_form_tokens(\phpbb\ucp\controller\webpush::FORM_TOKEN_UCP), + ]; + } + /** * Get subscriptions for notify users * @@ -332,7 +361,7 @@ protected function get_user_subscription_map(array $notify_users): array // Get subscriptions for users $user_subscription_map = []; - $sql = 'SELECT subscription_id, user_id, endpoint, p256dh, auth + $sql = 'SELECT subscription_id, user_id, endpoint, p256dh, auth, expiration_time FROM ' . $this->push_subscriptions_table . ' WHERE ' . $this->db->sql_in_set('user_id', $notify_users); $result = $this->db->sql_query($sql); diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index e292f35fac0..a9b5e133cf3 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -33,7 +33,7 @@ class webpush { /** @var string UCP form token name */ - private const FORM_TOKEN_UCP = 'ucp_webpush'; + public const FORM_TOKEN_UCP = 'ucp_webpush'; /** @var config */ protected $config; From bfc3a969eeabc410074ee82eec1f74ae63bc08ef Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 14:38:52 +0100 Subject: [PATCH 0050/1214] [ticket/17010] Remove no longer used route & handler PHPBB3-17010 --- phpBB/config/default/routing/ucp.yml | 4 ---- phpBB/phpbb/ucp/controller/webpush.php | 33 -------------------------- 2 files changed, 37 deletions(-) diff --git a/phpBB/config/default/routing/ucp.yml b/phpBB/config/default/routing/ucp.yml index dac6461ac5d..772910bfe13 100644 --- a/phpBB/config/default/routing/ucp.yml +++ b/phpBB/config/default/routing/ucp.yml @@ -21,7 +21,3 @@ phpbb_ucp_push_subscribe_controller: phpbb_ucp_push_unsubscribe_controller: path: /push/unsubscribe defaults: { _controller: phpbb.ucp.controller.webpush:unsubscribe } - -phpbb_ucp_push_js_controller: - path: /push/js - defaults: { _controller: phpbb.ucp.controller.webpush:js } diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index a9b5e133cf3..79f6b110cc0 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -187,39 +187,6 @@ protected function check_subscribe_requests(): void } } - /** - * Handle request to web push javascript - * - * @return Response - * @throws LoaderError - * @throws RuntimeError - * @throws SyntaxError - */ - public function js(): Response - { - // @todo: return forbidden for guest & bot - - $template_data = $this->get_subscribe_vars(); - $template_data += [ - 'VAPID_PUBLIC_KEY' => $this->config['webpush_vapid_public'], - 'U_WEBPUSH_WORKER_URL' => $this->controller_helper->route('phpbb_ucp_push_worker_controller'), - 'SUBSCRIPTIONS' => $this->get_subscriptions(), - ]; - - $content = $this->template->render('webpush.js.twig', $template_data); - - $response = new Response($content); - $response->headers->set('Content-Type', 'text/javascript; charset=UTF-8'); - - if (!empty($this->user->data['is_bot'])) - { - // Let reverse proxies know we detected a bot. - $response->headers->set('X-PHPBB-IS-BOT', 'yes'); - } - - return $response; - } - /** * Handle subscribe requests * From d79e10e032a474e072a540e719dcbf57d2141039 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 14:39:12 +0100 Subject: [PATCH 0051/1214] [ticket/17010] Unify naming of Web Push PHPBB3-17010 --- phpBB/language/en/acp/board.php | 6 +++--- phpBB/language/en/acp/common.php | 8 ++++---- phpBB/language/en/ucp.php | 4 ++-- phpBB/phpbb/notification/method/webpush.php | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 7a9b81ef1eb..58a256bb2c9 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -600,9 +600,9 @@ )); $lang = array_merge($lang, [ - 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Webpush for board notifications. Webpush is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', - 'WEBPUSH_ENABLE' => 'Enable Webpush', - 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Webpush. It is required to enter or generate valid VAPID identification keys to be able to use Webpush.', + 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Web Push for board notifications. Web Push is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', + 'WEBPUSH_ENABLE' => 'Enable Web Push', + 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Web Push. It is required to enter or generate valid VAPID identification keys to be able to use Web Push.', 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate Identification keys', 'WEBPUSH_VAPID_PUBLIC' => 'Server identification public key', 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all Webpush subscriptions.', diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index b9e24163419..ae2b54f62fa 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -219,7 +219,7 @@ 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS' => 'View global moderation permissions', 'ACP_VIEW_USER_PERMISSIONS' => 'View user-based permissions', - 'ACP_WEBPUSH_SETTINGS' => 'Webpush settings', + 'ACP_WEBPUSH_SETTINGS' => 'Web Push settings', 'ACP_WORDS' => 'Word censoring', 'ACTION' => 'Action', @@ -593,7 +593,7 @@ 'LOG_CONFIG_SETTINGS' => 'Altered board settings', 'LOG_CONFIG_SIGNATURE' => 'Altered signature settings', 'LOG_CONFIG_VISUAL' => 'Altered anti-spambot settings', - 'LOG_CONFIG_WEBPUSH' => 'Altered webpush settings', + 'LOG_CONFIG_WEBPUSH' => 'Altered Web Push settings', 'LOG_APPROVE_TOPIC' => 'Approved topic
» %s', 'LOG_BUMP_TOPIC' => 'User bumped topic
» %s', @@ -814,8 +814,8 @@ ), 'LOG_WARNINGS_DELETED_ALL' => 'Deleted all user warnings
» %s', - 'LOG_WEBPUSH_MESSAGE_FAIL' => 'WebPush message could not be sent: %s', - 'LOG_WEBPUSH_SUBSCRIPTION_REMOVED' => 'Removed WebPush subscription:» %s', + 'LOG_WEBPUSH_MESSAGE_FAIL' => 'Web Push message could not be sent: %s', + 'LOG_WEBPUSH_SUBSCRIPTION_REMOVED' => 'Removed Web Push subscription:» %s', 'LOG_WORD_ADD' => 'Added word censor
» %s', 'LOG_WORD_DELETE' => 'Deleted word censor
» %s', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 19d3a2c06e6..3d90f15742d 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -332,7 +332,7 @@ 'NOTIFICATION_METHOD_BOARD' => 'Notifications', 'NOTIFICATION_METHOD_EMAIL' => 'Email', 'NOTIFICATION_METHOD_JABBER' => 'Jabber', - 'NOTIFICATION_METHOD_WEBPUSH' => 'Web push', + 'NOTIFICATION_METHOD_WEBPUSH' => 'Web Push', 'NOTIFICATION_TYPE' => 'Notification type', 'NOTIFICATION_TYPE_BOOKMARK' => 'Someone replies to a topic you have bookmarked', 'NOTIFICATION_TYPE_GROUP_REQUEST' => 'Someone requests to join a group you lead', @@ -356,7 +356,7 @@ 'NOTIFY_METHOD_EXPLAIN' => 'Method for sending messages sent via this board.', 'NOTIFY_METHOD_IM' => 'Jabber only', 'NOTIFY_ON_PM' => 'Notify me on new private messages', - 'NOTIFY_WEBPUSH_ENABLE' => 'Enable receiving webpush notifications', + 'NOTIFY_WEBPUSH_ENABLE' => 'Enable receiving Web Push notifications', 'NOTIFY_WEBPUSH_ENABLE_EXPLAIN' => 'Enable receiving browser-based push notifications.
The notifications can be turned off at any time in your browser settings, by unsubscribing, or by disabling the push notifications below.', 'NOTIFY_WEBPUSH_SUBSCRIBE' => 'Subscribe', 'NOTIFY_WEBPUSH_UNSUBSCRIBE' => 'Unsubscribe', diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 273ddcddd51..386dfd25251 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -24,7 +24,7 @@ use phpbb\user_loader; /** -* Web push notification method class +* Web Push notification method class * This class handles sending push messages for notifications */ @@ -42,14 +42,14 @@ class webpush extends messenger_base implements extended_method_interface /** @var user */ protected $user; - /** @var string Notification web push table */ + /** @var string Notification Web Push table */ protected $notification_webpush_table; /** @var string Notification push subscriptions table */ protected $push_subscriptions_table; /** - * Notification Method web push constructor + * Notification Method Web Push constructor * * @param config $config * @param driver_interface $db @@ -147,7 +147,7 @@ public function notify() } /** - * Notify using web push + * Notify using Web Push * * @return void */ @@ -206,7 +206,7 @@ protected function notify_using_webpush(): void continue; } - // add actual web push data + // Add actual Web Push data $data = [ 'item_id' => $notification->item_id, 'type_id' => $notification->notification_type_id, From a86d222ab04459746843698bf1431c127ae377c4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 14:59:10 +0100 Subject: [PATCH 0052/1214] [ticket/17010] Remove not needed empty line PHPBB3-17010 --- phpBB/includes/ucp/ucp_notifications.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php index 62d7fbd112e..94a1586f9fd 100644 --- a/phpBB/includes/ucp/ucp_notifications.php +++ b/phpBB/includes/ucp/ucp_notifications.php @@ -47,7 +47,6 @@ public function main($id, $mode) $form_time = $request->variable('form_time', 0); $form_time = ($form_time <= 0 || $form_time > time()) ? time() : $form_time; - $this->controller_helper = $phpbb_container->get('controller.helper'); $this->form_helper = $phpbb_container->get('form_helper'); From adf639e871275da79265b18ba2bc29af187695dc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 15:15:11 +0100 Subject: [PATCH 0053/1214] [ticket/17010] Clean up code and remove unused method PHPBB3-17010 --- phpBB/phpbb/ucp/controller/webpush.php | 32 ++++---------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index 79f6b110cc0..f1cd513df43 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -113,9 +113,9 @@ public function notification(): JsonResponse $sql = 'SELECT push_data FROM ' . $this->notification_webpush_table . ' - WHERE user_id = ' . $this->user->id() . ' - AND notification_type_id = ' . $type_id . ' - AND item_id = ' . $item_id; + WHERE user_id = ' . (int) $this->user->id() . ' + AND notification_type_id = ' . (int) $type_id . ' + AND item_id = ' . (int) $item_id; $result = $this->db->sql_query($sql); $notification_data = $this->db->sql_fetchfield('push_data'); $this->db->sql_freeresult($result); @@ -229,8 +229,8 @@ public function unsubscribe(symfony_request $symfony_request): JsonResponse $endpoint = $data['endpoint']; $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' - WHERE user_id = ' . $this->user->id() . " - AND endpoint = '" . $this->db->sql_escape($endpoint) . "'"; + WHERE user_id = ' . (int) $this->user->id() . " + AND endpoint = '" . (int) $this->db->sql_escape($endpoint) . "'"; $this->db->sql_query($sql); return new JsonResponse([ @@ -238,26 +238,4 @@ public function unsubscribe(symfony_request $symfony_request): JsonResponse 'form_tokens' => $this->form_helper->get_form_tokens(self::FORM_TOKEN_UCP), ]); } - - /** - * Get subscriptions for current user - * - * @return array Subscriptions for user - */ - protected function get_subscriptions(): array - { - $subscriptions = []; - - $sql = 'SELECT endpoint, expiration_time - FROM ' . $this->push_subscriptions_table . ' - WHERE user_id = ' . $this->user->id(); - $result = $this->db->sql_query($sql); - while ($row = $this->db->sql_fetchrow($result)) - { - $subscriptions[] = $row; - } - $this->db->sql_freeresult($result); - - return $subscriptions; - } } From 4e9fb6ed4f71b9f84e52524a2ddfaffc9bf06f9e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 15:35:04 +0100 Subject: [PATCH 0054/1214] [ticket/17010] Clean up push worker PHPBB3-17010 --- phpBB/styles/all/js/push_worker.js.twig | 44 +++++++++++-------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/phpBB/styles/all/js/push_worker.js.twig b/phpBB/styles/all/js/push_worker.js.twig index 91dbf6f4ad6..8d6ec3c6af2 100644 --- a/phpBB/styles/all/js/push_worker.js.twig +++ b/phpBB/styles/all/js/push_worker.js.twig @@ -6,46 +6,40 @@ self.addEventListener('push', event => { return; } - let itemId = 0, - typeId = 0; + let itemId = 0; + let typeId = 0; try { const notificationData = event.data.json(); - itemId = notificationData['item_id']; - typeId = notificationData['type_id']; - } catch (e) { + itemId = notificationData.item_id; + typeId = notificationData.type_id; + } catch { self.registration.showNotification(event.data.text()); return; } const getNotificationUrl = '{{ U_WEBPUSH_GET_NOTIFICATION }}'; - let formData = new FormData(); + const formData = new FormData(); formData.append('item_id', itemId.toString(10)); formData.append('type_id', typeId.toString(10)); fetch(getNotificationUrl, { - method: 'POST', - headers: { - 'X-Requested-With': 'XMLHttpRequest' - }, - body: formData - }) - .then((response) => response.json()) - .then((response) => { - const responseBody = response.title + "\n" + response.text; + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + }, + body: formData, + }) + .then(response => response.json()) + .then(response => { + const responseBody = response.title + '\n' + response.text; const options = { body: responseBody, data: response, - icon: response.avatar.src - // foo: '' - //icon: image - } - self.registration.showNotification( - response.heading, - options - ); - } - ); + icon: response.avatar.src, + }; + self.registration.showNotification(response.heading, options); + }); }); /** From 5ada5728ee40814840a96d4d5a3e1d5673888f2a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Nov 2023 15:41:17 +0100 Subject: [PATCH 0055/1214] [ticket/17010] Also check .js.twig files with eslint PHPBB3-17010 --- .github/check-js.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/check-js.sh b/.github/check-js.sh index 702ec03cbf1..03d040d25ef 100755 --- a/.github/check-js.sh +++ b/.github/check-js.sh @@ -15,4 +15,5 @@ sudo npm install -g > /dev/null npm ci > /dev/null set -x node_modules/eslint/bin/eslint.js "phpBB/**/*.js" +node_modules/eslint/bin/eslint.js "phpBB/**/*.js.twig" node_modules/eslint/bin/eslint.js "gulpfile.js" From 44861c1f45cf1d69e040cae1517062f3fb471c7a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 24 Feb 2024 17:49:44 +0100 Subject: [PATCH 0056/1214] [ticket/17010] Add ban manager to webpush test PHPBB3-17010 --- .../webpush_notification.type.post.xml | 2 -- .../notification_method_webpush_test.php | 24 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/notification/fixtures/webpush_notification.type.post.xml b/tests/notification/fixtures/webpush_notification.type.post.xml index 9d3d72d531d..7654ed30aad 100644 --- a/tests/notification/fixtures/webpush_notification.type.post.xml +++ b/tests/notification/fixtures/webpush_notification.type.post.xml @@ -1,7 +1,5 @@ - -
forum_iduser_id diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 9cf374da1a3..0faff80576b 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -97,8 +97,8 @@ protected function setUp(): void 'webpush_vapid_private' => self::VAPID_KEYS['privateKey'], ]); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); - $lang = new \phpbb\language\language($lang_loader); - $user = new \phpbb\user($lang, '\phpbb\datetime'); + $language = new \phpbb\language\language($lang_loader); + $user = new \phpbb\user($language, '\phpbb\datetime'); $this->user = $user; $this->user_loader = new \phpbb\user_loader($avatar_helper, $this->db, $phpbb_root_path, $phpEx, 'phpbb_users'); $auth = $this->auth = new phpbb_mock_notifications_auth(); @@ -113,19 +113,20 @@ protected function setUp(): void $phpbb_root_path, $phpEx ); + $phpbb_log = new \phpbb\log\dummy(); $phpbb_container = $this->container = new ContainerBuilder(); $loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__ . '/fixtures')); $loader->load('services_notification.yml'); $phpbb_container->set('user_loader', $this->user_loader); $phpbb_container->set('user', $user); - $phpbb_container->set('language', $lang); + $phpbb_container->set('language', $language); $phpbb_container->set('config', $this->config); $phpbb_container->set('dbal.conn', $this->db); $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); - $phpbb_container->set('log', new \phpbb\log\dummy()); + $phpbb_container->set('log', $phpbb_log); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); $phpbb_container->setParameter('core.root_path', $phpbb_root_path); @@ -147,6 +148,19 @@ protected function setUp(): void ) ); + $ban_type_email = new \phpbb\ban\type\email($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = new \phpbb\ban\type\ip($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + $phpbb_container->set('ban.manager', $ban_manager); + $this->notification_method_webpush = new \phpbb\notification\method\webpush( $phpbb_container->get('config'), $phpbb_container->get('dbal.conn'), @@ -169,7 +183,7 @@ protected function setUp(): void $this->phpbb_dispatcher, $this->db, $this->cache, - $lang, + $language, $this->user, 'phpbb_notification_types', 'phpbb_user_notifications' From e993d82bc57f613b9555ac67738a818f93fd283e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 24 Feb 2024 20:53:03 +0100 Subject: [PATCH 0057/1214] [ticket/17010] Update language keys based on review recommendations PHPBB3-17010 --- phpBB/language/en/acp/board.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 58a256bb2c9..8dde2c72529 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -600,14 +600,14 @@ )); $lang = array_merge($lang, [ - 'ACP_WEBPUSH_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Web Push for board notifications. Web Push is a simple protocol for the delivery of real-time events to user agents, more commonly known as push messages. It is supported by most modern browsers on desktop and mobile devices.', + '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.', 'WEBPUSH_ENABLE' => 'Enable Web Push', - 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow receiving notifications via Web Push. It is required to enter or generate valid VAPID identification keys to be able to use Web Push.', + 'WEBPUSH_ENABLE_EXPLAIN' => 'Allow users to receive notifications in their browser or device via Web Push. To utilize Web Push, you must input or generate valid VAPID identification keys.', 'WEBPUSH_GENERATE_VAPID_KEYS' => 'Generate Identification keys', 'WEBPUSH_VAPID_PUBLIC' => 'Server identification public key', - 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key will be shared to authenticate push messages sent by your site.
Warning: Changing the VAPID public key will automatically invalidate all Webpush subscriptions.', + 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key is shared to authenticate push messages from your site.
Caution: Modifying the VAPID public key will automatically render all Web Push subscriptions invalid.', 'WEBPUSH_VAPID_PRIVATE' => 'Server identification private key', - 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key will be used to create authenticated push messages sent by your site. The VAPID private key must be a valid public-private key pair with the VAPID public key.
Warning: Changing the VAPID private key will automatically invalidate all Webpush subscriptions.', + 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key is used to generate authenticated push messages dispatched from your site. The VAPID private key must form a valid public-private key pair alongside the VAPID public key.
Caution: Modifying the VAPID private key will automatically render all Web Push subscriptions invalid.', ]); // Jabber settings From a3f656356836e3b6436f6b136102d2a3dc22301b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 24 Feb 2024 22:29:48 +0100 Subject: [PATCH 0058/1214] [ticket/17010] Remove duplicated css for buttons PHPBB3-17010 --- phpBB/styles/prosilver/theme/colours.css | 9 --------- 1 file changed, 9 deletions(-) diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index fe5e4d7c851..7bdd5f4b87f 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -697,15 +697,6 @@ dd.profile-warnings { color: #d41142; } -.button[disabled], -.button[disabled]:hover, -.button.disabled, -.button.disabled:hover { - background: #e0e0e0; - border-color: #9e9e9e; - color: #9e9e9e; -} - .button .icon, .button-secondary, .c-button-icon { From f36542c661a498dca9733cbb7a2189f95eff1e8f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 27 Feb 2024 17:30:46 +0100 Subject: [PATCH 0059/1214] [ticket/17010] Update web-push-testing to 1.1.0 PHPBB3-17010 --- package-lock.json | 55 ++++++++++++++++------------------------------- package.json | 2 +- 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5801393ff31..4f0ec060d51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "postcss-sorting": "^7.0.1", "stylelint": "^14.7.0", "stylelint-order": "^5.0.0", - "web-push-testing": "^1.0.0" + "web-push-testing": "^1.1.0" } }, "node_modules/@babel/code-frame": { @@ -4208,9 +4208,9 @@ "dev": true }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dev": true, "dependencies": { "jws": "^3.2.2", @@ -4222,20 +4222,11 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" + "node": ">=12", + "npm": ">=6" } }, "node_modules/just-debounce": { @@ -8750,15 +8741,15 @@ } }, "node_modules/web-push-testing": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.0.0.tgz", - "integrity": "sha512-p14iWiQSPY4kjpbaz59QfczaxEyD4uWg1aBjUS+Y9LAzQPU4Z95Jnq3FiOIazrvrfY0/1uDBbQkjXVxHq6wI/Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.1.0.tgz", + "integrity": "sha512-VcQK/2u79/OKFgXtg8nxwC6CZCpS6eRyPF3TSy3mWIMmySv7s7yB+ugTGWiKOe+oKsR7ZdvpM+Nvbt/PhrTt5A==", "dev": true, "dependencies": { "arg": "^5.0.1", "express": "^4.17.2", "http_ece": "^1.1.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "node-persist": "^2.1.0", "ps-node": "^0.1.6", "urlsafe-base64": "^1.0.0" @@ -12402,9 +12393,9 @@ "dev": true }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dev": true, "requires": { "jws": "^3.2.2", @@ -12416,15 +12407,7 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "semver": "^7.5.4" } }, "just-debounce": { @@ -15963,15 +15946,15 @@ } }, "web-push-testing": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.0.0.tgz", - "integrity": "sha512-p14iWiQSPY4kjpbaz59QfczaxEyD4uWg1aBjUS+Y9LAzQPU4Z95Jnq3FiOIazrvrfY0/1uDBbQkjXVxHq6wI/Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.1.0.tgz", + "integrity": "sha512-VcQK/2u79/OKFgXtg8nxwC6CZCpS6eRyPF3TSy3mWIMmySv7s7yB+ugTGWiKOe+oKsR7ZdvpM+Nvbt/PhrTt5A==", "dev": true, "requires": { "arg": "^5.0.1", "express": "^4.17.2", "http_ece": "^1.1.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "node-persist": "^2.1.0", "ps-node": "^0.1.6", "urlsafe-base64": "^1.0.0" diff --git a/package.json b/package.json index 22103522f3f..ecaa5d3c4f3 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,6 @@ "postcss-sorting": "^7.0.1", "stylelint": "^14.7.0", "stylelint-order": "^5.0.0", - "web-push-testing": "^1.0.0" + "web-push-testing": "^1.1.0" } } From fc14c5fd0b19c344717e3c01898224dbe70bd507 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 27 Feb 2024 20:43:27 +0100 Subject: [PATCH 0060/1214] [ticket/17010] Increase test coverage PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 69 +++++++- .../webpush_notification.type.post.xml | 6 + .../notification_method_webpush_test.php | 147 ++++++++++++++++-- 3 files changed, 204 insertions(+), 18 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 386dfd25251..c71256a29d1 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -239,12 +239,10 @@ protected function notify_using_webpush(): void } // Remove any subscriptions that couldn't be queued, i.e. that have invalid data - if (count($remove_subscriptions)) - { - $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' - WHERE ' . $this->db->sql_in_set('subscription_id', $remove_subscriptions); - $this->db->sql_query($sql); - } + $this->remove_subscriptions($remove_subscriptions); + + // List to fill with expired subscriptions based on return + $expired_endpoints = []; try { @@ -252,8 +250,16 @@ protected function notify_using_webpush(): void { if (!$report->isSuccess()) { - $report_data = \phpbb\json\sanitizer::sanitize($report->jsonSerialize()); - $this->log->add('admin', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$report_data['reason']]); + // Fill array of endpoints to remove if subscription has expired + if ($report->isSubscriptionExpired()) + { + $expired_endpoints = $report->getEndpoint(); + } + else + { + $report_data = \phpbb\json\sanitizer::sanitize($report->jsonSerialize()); + $this->log->add('admin', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$report_data['reason']]); + } } } } @@ -262,6 +268,8 @@ protected function notify_using_webpush(): void $this->log->add('critical', ANONYMOUS, '', 'LOG_WEBPUSH_MESSAGE_FAIL', false, [$exception->getMessage()]); } + $this->clean_expired_subscriptions($user_subscription_map, $expired_endpoints); + // We're done, empty the queue $this->empty_queue(); } @@ -373,4 +381,49 @@ protected function get_user_subscription_map(array $notify_users): array return $user_subscription_map; } + + /** + * Remove subscriptions + * + * @param array $subscription_ids Subscription ids to remove + * @return void + */ + public function remove_subscriptions(array $subscription_ids): void + { + if (count($subscription_ids)) + { + $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' + WHERE ' . $this->db->sql_in_set('subscription_id', $subscription_ids); + $this->db->sql_query($sql); + } + } + + /** + * Clean expired subscriptions from the database + * + * @param array $user_subscription_map User subscription map + * @param array $expired_endpoints Expired endpoints + * @return void + */ + protected function clean_expired_subscriptions(array $user_subscription_map, array $expired_endpoints): void + { + if (!count($expired_endpoints)) + { + return; + } + + $remove_subscriptions = []; + foreach ($expired_endpoints as $endpoint) + { + foreach ($user_subscription_map as $user_id => $subscriptions) + { + if (isset($subscriptions['endpoint']) && $subscriptions['endpoint'] == $endpoint) + { + $remove_subscriptions[] = $subscriptions[$endpoint]['subscription_id']; + } + } + } + + $this->remove_subscriptions($remove_subscriptions); + } } diff --git a/tests/notification/fixtures/webpush_notification.type.post.xml b/tests/notification/fixtures/webpush_notification.type.post.xml index 7654ed30aad..896df4e1c1a 100644 --- a/tests/notification/fixtures/webpush_notification.type.post.xml +++ b/tests/notification/fixtures/webpush_notification.type.post.xml @@ -133,6 +133,12 @@ username_clean user_permissions user_sig + + 1 + Anonymous + + + 2 poster diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 0faff80576b..3a0a48064c0 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -32,6 +32,12 @@ class notification_method_webpush_test extends phpbb_tests_notification_base /** @var webpush */ protected $notification_method_webpush; + /** @var \phpbb\language\language */ + protected $language; + + /** @var \phpbb\log\log_interface */ + protected $log; + public function getDataSet() { return $this->createXMLDataSet(__DIR__ . '/fixtures/webpush_notification.type.post.xml'); @@ -97,9 +103,11 @@ protected function setUp(): void 'webpush_vapid_private' => self::VAPID_KEYS['privateKey'], ]); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); - $language = new \phpbb\language\language($lang_loader); - $user = new \phpbb\user($language, '\phpbb\datetime'); + $this->language = new \phpbb\language\language($lang_loader); + $this->language->add_lang('acp/common'); + $user = new \phpbb\user($this->language, '\phpbb\datetime'); $this->user = $user; + $this->user->data['user_options'] = 230271; $this->user_loader = new \phpbb\user_loader($avatar_helper, $this->db, $phpbb_root_path, $phpEx, 'phpbb_users'); $auth = $this->auth = new phpbb_mock_notifications_auth(); $this->phpbb_dispatcher = new phpbb_mock_event_dispatcher(); @@ -113,20 +121,22 @@ protected function setUp(): void $phpbb_root_path, $phpEx ); - $phpbb_log = new \phpbb\log\dummy(); + + $log_table = 'phpbb_log'; + $this->log = new \phpbb\log\log($this->db, $user, $auth, $this->phpbb_dispatcher, $phpbb_root_path, 'adm/', $phpEx, $log_table); $phpbb_container = $this->container = new ContainerBuilder(); $loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__ . '/fixtures')); $loader->load('services_notification.yml'); $phpbb_container->set('user_loader', $this->user_loader); $phpbb_container->set('user', $user); - $phpbb_container->set('language', $language); + $phpbb_container->set('language', $this->language); $phpbb_container->set('config', $this->config); $phpbb_container->set('dbal.conn', $this->db); $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); - $phpbb_container->set('log', $phpbb_log); + $phpbb_container->set('log', $this->log); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); $phpbb_container->setParameter('core.root_path', $phpbb_root_path); @@ -158,7 +168,7 @@ protected function setUp(): void $collection->add('ban.type.email'); $collection->add('ban.type.user'); $collection->add('ban.type.ip'); - $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $this->language, $this->log, $user, 'phpbb_bans', 'phpbb_users'); $phpbb_container->set('ban.manager', $ban_manager); $this->notification_method_webpush = new \phpbb\notification\method\webpush( @@ -183,7 +193,7 @@ protected function setUp(): void $this->phpbb_dispatcher, $this->db, $this->cache, - $language, + $this->language, $this->user, 'phpbb_notification_types', 'phpbb_user_notifications' @@ -400,7 +410,122 @@ public function test_get_subscription($notification_type, $post_data, $expected_ } } - protected function create_subscription_for_user($user_id): array + /** + * @dataProvider data_notification_webpush + */ + public function test_notify_empty_queue($notification_type, $post_data, $expected_users): void + { + foreach ($expected_users as $user_id => $user_data) + { + $this->create_subscription_for_user($user_id); + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + $post_data['post_id']++; + $notification_options['item_id'] = $post_data['post_id']; + $post_data['post_time'] = 1349413323; + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users2 = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users2, 'Assert that expected users stay the same after replying to same topic'); + } + + /** + * @dataProvider data_notification_webpush + */ + public function test_notify_invalid_endpoint($notification_type, $post_data, $expected_users): void + { + $subscription_info = []; + foreach ($expected_users as $user_id => $user_data) + { + $subscription_info[$user_id][] = $this->create_subscription_for_user($user_id); + } + + // Create second subscription for first user ID passed + if (count($expected_users)) + { + $first_user_id = array_key_first($expected_users); + $first_user_sub = $this->create_subscription_for_user($first_user_id, true); + $subscription_info[$first_user_id][] = $first_user_sub; + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertEmpty($messages); + } + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertNotEmpty($messages); + } + + if (isset($first_user_sub)) + { + $admin_logs = $this->log->get_logs('admin'); + $this->db->sql_query('DELETE FROM phpbb_log'); // Clear logs + $this->assertCount(1, $admin_logs, 'Assert that an admin log was created for invalid endpoint'); + + $log_entry = $admin_logs[0]; + + $this->assertStringStartsWith('Web Push message could not be sent:', $log_entry['action']); + $this->assertStringContainsString('400', $log_entry['action']); + } + } + + public function test_get_type(): void + { + $this->assertEquals('notification.method.webpush', $this->notification_method_webpush->get_type()); + } + + protected function create_subscription_for_user($user_id, bool $invalidate_endpoint = false): array { $client = new \GuzzleHttp\Client(); try @@ -420,8 +545,10 @@ protected function create_subscription_for_user($user_id): array $this->assertStringStartsWith('http://localhost:9012/notify/', $subscription_data['endpoint']); $this->assertIsArray($subscription_data['keys']); - // Add subscription data to admin user (user id 2) - + if ($invalidate_endpoint) + { + $subscription_data['endpoint'] .= 'invalid'; + } $push_subscriptions_table = $this->container->getParameter('tables.push_subscriptions'); From 93a6b8d87a46f3248d710b90b483da83bd13a57b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 29 Feb 2024 22:16:35 +0100 Subject: [PATCH 0061/1214] [ticket/17010] Update web-push-testing to 1.1.1 PHPBB3-17010 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f0ec060d51..1bcd4497011 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "postcss-sorting": "^7.0.1", "stylelint": "^14.7.0", "stylelint-order": "^5.0.0", - "web-push-testing": "^1.1.0" + "web-push-testing": "^1.1.1" } }, "node_modules/@babel/code-frame": { @@ -8741,9 +8741,9 @@ } }, "node_modules/web-push-testing": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.1.0.tgz", - "integrity": "sha512-VcQK/2u79/OKFgXtg8nxwC6CZCpS6eRyPF3TSy3mWIMmySv7s7yB+ugTGWiKOe+oKsR7ZdvpM+Nvbt/PhrTt5A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.1.1.tgz", + "integrity": "sha512-q+FcdmPeePLI7kwxIngl9jzOfqnbp8t5DD17tQ8j1VxdEtsYb8HAbaieZH5q7uudQn+LFniAn0V9xsOnFctV7Q==", "dev": true, "dependencies": { "arg": "^5.0.1", @@ -15946,9 +15946,9 @@ } }, "web-push-testing": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.1.0.tgz", - "integrity": "sha512-VcQK/2u79/OKFgXtg8nxwC6CZCpS6eRyPF3TSy3mWIMmySv7s7yB+ugTGWiKOe+oKsR7ZdvpM+Nvbt/PhrTt5A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/web-push-testing/-/web-push-testing-1.1.1.tgz", + "integrity": "sha512-q+FcdmPeePLI7kwxIngl9jzOfqnbp8t5DD17tQ8j1VxdEtsYb8HAbaieZH5q7uudQn+LFniAn0V9xsOnFctV7Q==", "dev": true, "requires": { "arg": "^5.0.1", diff --git a/package.json b/package.json index ecaa5d3c4f3..58e6fe6ae7d 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,6 @@ "postcss-sorting": "^7.0.1", "stylelint": "^14.7.0", "stylelint-order": "^5.0.0", - "web-push-testing": "^1.1.0" + "web-push-testing": "^1.1.1" } } From fcfed793858d44473cc242d73d0ba044cf0786ad Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 1 Mar 2024 19:40:26 +0100 Subject: [PATCH 0062/1214] [ticket/17010] Properly handle expired subscriptions and extend tests PHPBB3-17010 --- phpBB/phpbb/notification/method/webpush.php | 11 +- .../webpush_notification.type.post.xml | 2 + .../notification_method_webpush_test.php | 165 +++++++++++++++++- 3 files changed, 169 insertions(+), 9 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index c71256a29d1..e5b8910a074 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -253,7 +253,7 @@ protected function notify_using_webpush(): void // Fill array of endpoints to remove if subscription has expired if ($report->isSubscriptionExpired()) { - $expired_endpoints = $report->getEndpoint(); + $expired_endpoints[] = $report->getEndpoint(); } else { @@ -415,11 +415,14 @@ protected function clean_expired_subscriptions(array $user_subscription_map, arr $remove_subscriptions = []; foreach ($expired_endpoints as $endpoint) { - foreach ($user_subscription_map as $user_id => $subscriptions) + foreach ($user_subscription_map as $subscriptions) { - if (isset($subscriptions['endpoint']) && $subscriptions['endpoint'] == $endpoint) + foreach ($subscriptions as $subscription) { - $remove_subscriptions[] = $subscriptions[$endpoint]['subscription_id']; + if (isset($subscription['endpoint']) && $subscription['endpoint'] == $endpoint) + { + $remove_subscriptions[] = $subscription['subscription_id']; + } } } } diff --git a/tests/notification/fixtures/webpush_notification.type.post.xml b/tests/notification/fixtures/webpush_notification.type.post.xml index 896df4e1c1a..d59d5b92c4b 100644 --- a/tests/notification/fixtures/webpush_notification.type.post.xml +++ b/tests/notification/fixtures/webpush_notification.type.post.xml @@ -1,5 +1,7 @@ +
+
forum_iduser_id diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index 3a0a48064c0..ff95c74b093 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -406,7 +406,7 @@ public function test_get_subscription($notification_type, $post_data, $expected_ foreach ($expected_users as $user_id => $data) { $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); - $this->assertNotEmpty($messages); + $this->assertNotEmpty($messages, 'Failed asserting that user ' . $user_id . ' has received messages.'); } } @@ -438,10 +438,10 @@ public function test_notify_empty_queue($notification_type, $post_data, $expecte $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); - $this->notifications->add_notifications($notification_type, $post_data); + $this->notification_method_webpush->notify(); // should have no effect $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); - $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); $post_data['post_id']++; $notification_options['item_id'] = $post_data['post_id']; @@ -504,7 +504,7 @@ public function test_notify_invalid_endpoint($notification_type, $post_data, $ex foreach ($expected_users as $user_id => $data) { $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); - $this->assertNotEmpty($messages); + $this->assertNotEmpty($messages, 'Failed asserting that user ' . $user_id . ' has received messages.'); } if (isset($first_user_sub)) @@ -520,11 +520,139 @@ public function test_notify_invalid_endpoint($notification_type, $post_data, $ex } } + /** + * @dataProvider data_notification_webpush + */ + public function test_notify_expired($notification_type, $post_data, $expected_users) + { + $subscription_info = []; + foreach ($expected_users as $user_id => $user_data) + { + $subscription_info[$user_id][] = $this->create_subscription_for_user($user_id); + } + + $expected_delivered_users = $expected_users; + + // Expire subscriptions for first user + if (count($expected_users)) + { + + $first_user_id = array_key_first($expected_users); + $first_user_subs = $subscription_info[$first_user_id]; + unset($expected_delivered_users[$first_user_id]); + $this->expire_subscription($first_user_subs[0]['clientHash']); + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + foreach ($expected_delivered_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertEmpty($messages); + } + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + foreach ($expected_delivered_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertNotEmpty($messages, 'Failed asserting that user ' . $user_id . ' has received messages.'); + } + } + public function test_get_type(): void { $this->assertEquals('notification.method.webpush', $this->notification_method_webpush->get_type()); } + /** + * @dataProvider data_notification_webpush + */ + public function test_prune_notifications($notification_type, $post_data, $expected_users): void + { + $subscription_info = []; + foreach ($expected_users as $user_id => $user_data) + { + $subscription_info[$user_id][] = $this->create_subscription_for_user($user_id); + } + + // Create second subscription for first user ID passed + if (count($expected_users)) + { + $first_user_id = array_key_first($expected_users); + $subscription_info[$first_user_id][] = $this->create_subscription_for_user($first_user_id); + } + + $post_data = array_merge([ + 'post_time' => 1349413322, + 'poster_id' => 1, + 'topic_title' => '', + 'post_subject' => '', + 'post_username' => '', + 'forum_name' => '', + ], + + $post_data); + $notification_options = [ + 'item_id' => $post_data['post_id'], + 'item_parent_id' => $post_data['topic_id'], + ]; + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals(0, count($notified_users), 'Assert no user has been notified yet'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertEmpty($messages); + } + + $this->notifications->add_notifications($notification_type, $post_data); + + $notified_users = $this->notification_method_webpush->get_notified_users($this->notifications->get_notification_type_id($notification_type), $notification_options); + $this->assertEquals($expected_users, $notified_users, 'Assert that expected users have been notified'); + + foreach ($expected_users as $user_id => $data) + { + $messages = $this->get_messages_for_subscription($subscription_info[$user_id][0]['clientHash']); + $this->assertNotEmpty($messages, 'Failed asserting that user ' . $user_id . ' has received messages.'); + } + + // Prune notifications with 0 time, shouldn't change anything + $prune_time = time(); + $this->notification_method_webpush->prune_notifications(0); + $this->assertGreaterThanOrEqual($prune_time, $this->config->offsetGet('read_notification_last_gc'), 'Assert that prune time was set'); + + $cur_notifications = $this->get_notifications(); + $this->assertSameSize($cur_notifications, $expected_users, 'Assert that no notifications have been pruned'); + + // Prune only read not supported, will prune all + $this->notification_method_webpush->prune_notifications($prune_time); + $this->assertGreaterThanOrEqual($prune_time, $this->config->offsetGet('read_notification_last_gc'), 'Assert that prune time was set'); + + $cur_notifications = $this->get_notifications(); + $this->assertCount(0, $cur_notifications, 'Assert that no notifications have been pruned'); + } + protected function create_subscription_for_user($user_id, bool $invalidate_endpoint = false): array { $client = new \GuzzleHttp\Client(); @@ -563,6 +691,22 @@ protected function create_subscription_for_user($user_id, bool $invalidate_endpo return $subscription_data; } + protected function expire_subscription(string $client_hash): void + { + $client = new \GuzzleHttp\Client(); + try + { + $response = $client->request('POST', 'http://localhost:9012/expire-subscription/' . $client_hash); + } + catch (\GuzzleHttp\Exception\GuzzleException $exception) + { + $this->fail('Failed expiring subscription with web-push-testing client: ' . $exception->getMessage()); + } + + $subscription_return = \phpbb\json\sanitizer::decode((string) $response->getBody()); + $this->assertEquals(200, $response->getStatusCode(), 'Expected response status to be 200'); + } + protected function get_messages_for_subscription($client_hash): array { $client = new \GuzzleHttp\Client(); @@ -574,7 +718,7 @@ protected function get_messages_for_subscription($client_hash): array } catch (\GuzzleHttp\Exception\GuzzleException $exception) { - $this->fail('Failed getting messages from web-push-testing client'); + $this->fail('Failed getting messages from web-push-testing client: ' . $exception->getMessage()); } $response_data = json_decode($response->getBody()->getContents(), true); @@ -584,4 +728,15 @@ protected function get_messages_for_subscription($client_hash): array return $response_data['data']['messages']; } + + protected function get_notifications(): array + { + $webpush_table = $this->container->getParameter('tables.notification_push'); + $sql = 'SELECT * FROM ' . $webpush_table; + $result = $this->db->sql_query($sql); + $sql_ary = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $sql_ary; + } } From bda99e1c359ddc6072ba5eaf8469769a22d12382 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 7 Mar 2024 20:57:19 +0100 Subject: [PATCH 0063/1214] [ticket/17296] Reduce complexity of relative paths on adm pages PHPBB3-17296 --- phpBB/adm/index.php | 4 ++-- phpBB/phpbb/path_helper.php | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/phpBB/adm/index.php b/phpBB/adm/index.php index c648fdf1005..088f2f24dd1 100644 --- a/phpBB/adm/index.php +++ b/phpBB/adm/index.php @@ -61,8 +61,8 @@ ), ), $phpbb_admin_path . 'style'); -$template->assign_var('T_ASSETS_PATH', $phpbb_root_path . 'assets'); -$template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style'); +$template->assign_var('T_ASSETS_PATH', $phpbb_path_helper->update_web_root_path($phpbb_root_path . 'assets')); +$template->assign_var('T_TEMPLATE_PATH', $phpbb_path_helper->update_web_root_path($phpbb_root_path . 'style')); // Instantiate new module $module = new p_master(); diff --git a/phpBB/phpbb/path_helper.php b/phpBB/phpbb/path_helper.php index 7112412ec33..bcfb7381447 100644 --- a/phpBB/phpbb/path_helper.php +++ b/phpBB/phpbb/path_helper.php @@ -39,6 +39,9 @@ class path_helper /** @var string */ protected $web_root_path; + /** @var bool Flag whether we're in adm path */ + protected $in_adm_path = false; + /** * Constructor * @@ -117,7 +120,13 @@ public function update_web_root_path($path) $path = substr($path, 8); } - return $this->filesystem->clean_path($web_root_path . $path); + $path = $this->filesystem->clean_path($web_root_path . $path); + + // Further clean path if we're in adm + if ($this->in_adm_path && strpos($path, $this->phpbb_root_path . $this->adm_relative_path) === 0) + { + $path = substr($path, strlen($this->phpbb_root_path . $this->adm_relative_path)); + } } return $path; @@ -181,6 +190,11 @@ public function get_web_root_path() return $this->web_root_path = $this->filesystem->clean_path('./../' . $this->phpbb_root_path); } + if ($path_info === '/' && defined('ADMIN_START') && preg_match('/\/' . preg_quote($this->adm_relative_path, '/') . 'index\.' . $this->php_ext . '$/', $script_name)) + { + $this->in_adm_path = true; + } + /* * If the path info is empty (single /), then we're not using * a route like app.php/foo/bar From 51b3d9de639bf7b49dd5fcd8fb8a1693406e8c31 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 7 Mar 2024 21:11:26 +0100 Subject: [PATCH 0064/1214] [ticket/17296] Ensure session page update test actually runs PHPBB3-17296 --- tests/functional/session_page_update_test.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/functional/session_page_update_test.php b/tests/functional/session_page_update_test.php index b2e04c0deff..35a1647a7de 100644 --- a/tests/functional/session_page_update_test.php +++ b/tests/functional/session_page_update_test.php @@ -17,7 +17,16 @@ class phpbb_functional_session_page_update_test extends phpbb_functional_test_case { - protected function test_session_page_update() + public function setUp(): void + { + parent::setUp(); + + global $db; + + $db = $this->db; + } + + public function test_session_page_update() { $this->login(); $db = $this->get_db(); @@ -32,7 +41,7 @@ protected function test_session_page_update() } $user_ids = []; - $username = [$this->get_logged_in_user()]; + $username = ['admin']; user_get_id_name($user_ids, $username); $user_id = (int) $user_ids[0]; @@ -45,7 +54,7 @@ protected function test_session_page_update() $this->assertEquals('index.php', $db->sql_fetchfield('session_page')); // Request non-existent url - self::request('GET', 'nonexistent.jpg'); + self::request('GET', 'nonexistent.jpg', [], false); $this->assertEquals(404, self::$client->getResponse()->getStatus()); $db->sql_query_limit($sql, 1); From 9c51a7866b90f2429f1fe02e08083a5d931c81b7 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 9 Mar 2024 09:54:57 +0100 Subject: [PATCH 0065/1214] [ticket/17296] Fix session page update test PHPBB3-17296 --- .../event/kernel_exception_subscriber.php | 9 ++++--- tests/functional/session_page_update_test.php | 26 ++++++------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/phpBB/phpbb/event/kernel_exception_subscriber.php b/phpBB/phpbb/event/kernel_exception_subscriber.php index 0dc9ccaf9ca..ccc3107d82e 100644 --- a/phpBB/phpbb/event/kernel_exception_subscriber.php +++ b/phpBB/phpbb/event/kernel_exception_subscriber.php @@ -90,12 +90,15 @@ public function on_kernel_exception(GetResponseForExceptionEvent $event) } else if (!$this->debug && $exception instanceof NotFoundHttpException) { - // Do not update user session page if it does not exist - $this->user->update_session_page = false; - $message = $this->language->lang('PAGE_NOT_FOUND'); } + // Do not update user session page if it does not exist + if ($exception instanceof NotFoundHttpException) + { + $this->user->update_session_page = false; + } + // Show text in bold $message = preg_replace('#<(/?strong)>#i', '<$1>', $message); diff --git a/tests/functional/session_page_update_test.php b/tests/functional/session_page_update_test.php index 35a1647a7de..00f857223d2 100644 --- a/tests/functional/session_page_update_test.php +++ b/tests/functional/session_page_update_test.php @@ -24,41 +24,31 @@ public function setUp(): void global $db; $db = $this->db; + + $this->login(); } public function test_session_page_update() { - $this->login(); $db = $this->get_db(); - if (!function_exists('utf_clean_string')) - { - require_once(__DIR__ . '/../../phpBB/includes/utf/utf_tools.php'); - } - if (!function_exists('user_get_id_name')) - { - require_once(__DIR__ . '/../../phpBB/includes/functions_user.php'); - } - - $user_ids = []; - $username = ['admin']; - user_get_id_name($user_ids, $username); - $user_id = (int) $user_ids[0]; + // Sleep for 2 seconds to ensure we don't have session time race condition + sleep(2); // Request index page self::request('GET', 'index.php'); $this->assertEquals(200, self::$client->getResponse()->getStatus()); - $sql = 'SELECT session_page FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $user_id . ' ORDER BY session_time DESC'; + $sql = 'SELECT session_page FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = 2 ORDER BY session_time DESC'; $db->sql_query_limit($sql, 1); - $this->assertEquals('index.php', $db->sql_fetchfield('session_page')); + $this->assertEquals('index.php', $db->sql_fetchfield('session_page'), 'Failed asserting that session_page is index.php for admin user'); // Request non-existent url self::request('GET', 'nonexistent.jpg', [], false); - $this->assertEquals(404, self::$client->getResponse()->getStatus()); + $this->assertEquals(404, self::$client->getResponse()->getStatus(), 'Failed asserting that status of non-existent image is 404'); $db->sql_query_limit($sql, 1); // User page should not be updated to non-existent one - $this->assertEquals('index.php', $db->sql_fetchfield('session_page')); + $this->assertEquals('index.php', $db->sql_fetchfield('session_page'), 'Failed asserting that session page has not changed after 404'); } } From 6ee64c94a21f69bd2568f62f379bb6beedd596a5 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 13 Mar 2024 23:25:02 +0700 Subject: [PATCH 0066/1214] [ticket/17284] Add viewtopic_body_online_list_after template event PHPBB3-17284 --- phpBB/docs/events.md | 7 +++++++ phpBB/styles/prosilver/template/viewtopic_body.html | 2 ++ 2 files changed, 9 insertions(+) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 95d4a9515c0..d05cb876fdb 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -3200,6 +3200,13 @@ viewtopic_body_footer_before * Purpose: Add content to the bottom of the View topic screen below the posts and quick reply, directly before the jumpbox in Prosilver. +viewtopic_body_online_list_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.3.12-RC1 +* Purpose: Add content after the online users list + viewtopic_body_online_list_before === * Locations: diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index ea65c7eac82..db4e51d772d 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -467,4 +467,6 @@

{L_WHO_IS_ONLINE}

+{% EVENT viewtopic_body_online_list_after %} + From 1ec51c99e57be2e6d34f6dab6138722d3edc999b Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 19 Mar 2024 08:46:07 -0700 Subject: [PATCH 0067/1214] [ticket/17297] Fix broken no avatar source tag in HTML PHPBB3-17297 Signed-off-by: Matt Friedman --- phpBB/phpbb/avatar/helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/avatar/helper.php b/phpBB/phpbb/avatar/helper.php index 51592b584fe..135bf22c88a 100644 --- a/phpBB/phpbb/avatar/helper.php +++ b/phpBB/phpbb/avatar/helper.php @@ -252,7 +252,7 @@ private function get_avatar_html(array $data): string { if ($data['lazy']) { - $data['src'] = $this->get_no_avatar_source() . ' data-src="' . $data['src']; + $data['src'] = $this->get_no_avatar_source() . '" data-src="' . $data['src']; } $src = ' src="' . $data['src'] . '"'; From b4a343bdc92343867470462ac925a0b90b61bde8 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 19 Mar 2024 20:36:38 +0100 Subject: [PATCH 0068/1214] [ticket/17296] Enforce unique session entry for admin user with delete PHPBB3-17296 --- tests/functional/session_page_update_test.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/functional/session_page_update_test.php b/tests/functional/session_page_update_test.php index 00f857223d2..a967c771892 100644 --- a/tests/functional/session_page_update_test.php +++ b/tests/functional/session_page_update_test.php @@ -25,6 +25,10 @@ public function setUp(): void $db = $this->db; + // Delete previous session info for admin user + $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = 2'; + $db->sql_query($sql); + $this->login(); } @@ -32,9 +36,6 @@ public function test_session_page_update() { $db = $this->get_db(); - // Sleep for 2 seconds to ensure we don't have session time race condition - sleep(2); - // Request index page self::request('GET', 'index.php'); $this->assertEquals(200, self::$client->getResponse()->getStatus()); From b38e8a2c84a7697feaf286a2e5883370ca058399 Mon Sep 17 00:00:00 2001 From: rxu Date: Sun, 31 Mar 2024 09:55:57 +0700 Subject: [PATCH 0069/1214] [ticket/17299] Allow event to modify email core vars before sending a message PHPBB3-17299 --- phpBB/includes/functions_messenger.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index fa47c50b49a..f25ce82b8fa 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -582,6 +582,11 @@ function msg_email() ); extract($phpbb_dispatcher->trigger_event('core.notification_message_email', compact($vars))); + $this->addresses = $addresses; + $this->subject = $subject; + $this->msg = $msg; + unset($addresses, $subject, $msg); + if ($break) { return true; From 7c36c0c5bdf429feebe2cf3571661fa3179af5f4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 2 Apr 2024 21:26:15 +0200 Subject: [PATCH 0070/1214] [ticket/17010] Remove invalid int cast PHPBB3-17010 --- phpBB/phpbb/ucp/controller/webpush.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index f1cd513df43..a7ebbffae8e 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -230,7 +230,7 @@ public function unsubscribe(symfony_request $symfony_request): JsonResponse $sql = 'DELETE FROM ' . $this->push_subscriptions_table . ' WHERE user_id = ' . (int) $this->user->id() . " - AND endpoint = '" . (int) $this->db->sql_escape($endpoint) . "'"; + AND endpoint = '" . $this->db->sql_escape($endpoint) . "'"; $this->db->sql_query($sql); return new JsonResponse([ From 12e9c3d08233eea033276c9830c33b394b770f0a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 25 Mar 2024 21:34:44 +0100 Subject: [PATCH 0071/1214] [ticket/12960] Remove broken captchas and prepare for incomplete captcha PHPBB3-12960 --- phpBB/adm/style/captcha_gd_acp.html | 74 - .../default/container/services_captcha.yml | 22 +- phpBB/install/schemas/schema_data.sql | 2 +- phpBB/language/en/acp/board.php | 20 +- phpBB/phpbb/captcha/char_cube3d.php | 277 --- phpBB/phpbb/captcha/colour_manager.php | 527 ----- phpBB/phpbb/captcha/gd.php | 1846 ----------------- phpBB/phpbb/captcha/gd_wave.php | 847 -------- phpBB/phpbb/captcha/non_gd.php | 387 ---- phpBB/phpbb/captcha/plugins/gd.php | 123 -- phpBB/phpbb/captcha/plugins/gd_wave.php | 42 - phpBB/phpbb/captcha/plugins/nogd.php | 42 - tests/auth/provider_db_test.php | 6 +- tests/functions/user_delete_test.php | 6 +- tests/session/garbage_collection_test.php | 10 +- 15 files changed, 16 insertions(+), 4215 deletions(-) delete mode 100644 phpBB/adm/style/captcha_gd_acp.html delete mode 100644 phpBB/phpbb/captcha/char_cube3d.php delete mode 100644 phpBB/phpbb/captcha/colour_manager.php delete mode 100644 phpBB/phpbb/captcha/gd.php delete mode 100644 phpBB/phpbb/captcha/gd_wave.php delete mode 100644 phpBB/phpbb/captcha/non_gd.php delete mode 100644 phpBB/phpbb/captcha/plugins/gd.php delete mode 100644 phpBB/phpbb/captcha/plugins/gd_wave.php delete mode 100644 phpBB/phpbb/captcha/plugins/nogd.php diff --git a/phpBB/adm/style/captcha_gd_acp.html b/phpBB/adm/style/captcha_gd_acp.html deleted file mode 100644 index 43d54adc0be..00000000000 --- a/phpBB/adm/style/captcha_gd_acp.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - « {L_BACK} - -

{L_ACP_VC_SETTINGS}

- -

{L_ACP_VC_SETTINGS_EXPLAIN}

- - - - -
-{L_GENERAL_OPTIONS} - -
-

{L_CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN}
-
-
-
-
-

{L_CAPTCHA_GD_X_GRID_EXPLAIN}
-
-
-
-

{L_CAPTCHA_GD_Y_GRID_EXPLAIN}
-
-
-
-

{L_CAPTCHA_GD_WAVE_EXPLAIN}
-
- -
-
-
-

{L_CAPTCHA_GD_3D_NOISE_EXPLAIN}
-
- -
-
-
-

{L_CAPTCHA_GD_FONTS_EXPLAIN}
-
- - - -
-
- -
-
- {L_PREVIEW} - - - - -
- -
- {L_ACP_SUBMIT_CHANGES} -

-   -   -   -

- - - - - {S_FORM_TOKEN} -
- - - diff --git a/phpBB/config/default/container/services_captcha.yml b/phpBB/config/default/container/services_captcha.yml index ba102640930..f3f9476f861 100644 --- a/phpBB/config/default/container/services_captcha.yml +++ b/phpBB/config/default/container/services_captcha.yml @@ -14,27 +14,11 @@ services: tags: - { name: service_collection, tag: captcha.plugins } - core.captcha.plugins.gd: - class: phpbb\captcha\plugins\gd + core.captcha.plugins.incomplete: + class: phpbb\captcha\plugins\incomplete shared: false calls: - - [set_name, [core.captcha.plugins.gd]] - tags: - - { name: captcha.plugins } - - core.captcha.plugins.gd_wave: - class: phpbb\captcha\plugins\gd_wave - shared: false - calls: - - [set_name, [core.captcha.plugins.gd_wave]] - tags: - - { name: captcha.plugins } - - core.captcha.plugins.nogd: - class: phpbb\captcha\plugins\nogd - shared: false - calls: - - [set_name, [core.captcha.plugins.nogd]] + - [ set_name, [ core.captcha.plugins.incomplete ] ] tags: - { name: captcha.plugins } diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index b924f706035..4b66815993f 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -76,7 +76,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_foregro INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_wave', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_x_grid', '25'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_y_grid', '25'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_plugin', 'core.captcha.plugins.nogd'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_plugin', 'core.captcha.plugins.incomplete'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('check_attachment_content', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('check_dnsbl', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('chg_passforce', '0'); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 8dde2c72529..b86e0b6335c 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -308,25 +308,7 @@ 'ACP_VC_EXT_GET_MORE' => 'For additional (and possibly better) anti-spam plugins, visit the phpBB.com Extensions Database. For more information on preventing spam on your board, visit the phpBB.com Knowledge Base.', 'AVAILABLE_CAPTCHAS' => 'Available plugins', 'CAPTCHA_UNAVAILABLE' => 'The plugin cannot be selected as its requirements are not met.', - 'CAPTCHA_GD' => 'GD image', - 'CAPTCHA_GD_3D' => 'GD 3D image', - 'CAPTCHA_GD_FOREGROUND_NOISE' => 'Foreground noise', - 'CAPTCHA_GD_EXPLAIN' => 'Uses GD to make a more advanced anti-spambot image.', - 'CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN' => 'Use foreground noise to make the image harder to read.', - 'CAPTCHA_GD_X_GRID' => 'Background noise x-axis', - 'CAPTCHA_GD_X_GRID_EXPLAIN' => 'Use lower settings of this to make the image harder to read. 0 will disable x-axis background noise.', - 'CAPTCHA_GD_Y_GRID' => 'Background noise y-axis', - 'CAPTCHA_GD_Y_GRID_EXPLAIN' => 'Use lower settings of this to make the image harder to read. 0 will disable y-axis background noise.', - 'CAPTCHA_GD_WAVE' => 'Wave distortion', - 'CAPTCHA_GD_WAVE_EXPLAIN' => 'This applies a wave distortion to the image.', - 'CAPTCHA_GD_3D_NOISE' => 'Add 3D-noise objects', - 'CAPTCHA_GD_3D_NOISE_EXPLAIN' => 'This adds additional objects to the image, over the letters.', - 'CAPTCHA_GD_FONTS' => 'Use different fonts', - 'CAPTCHA_GD_FONTS_EXPLAIN' => 'This setting controls how many different letter shapes are used. You can just use the default shapes or introduce altered letters. Adding lowercase letters is also possible.', - 'CAPTCHA_FONT_DEFAULT' => 'Default', - 'CAPTCHA_FONT_NEW' => 'New Shapes', - 'CAPTCHA_FONT_LOWER' => 'Also use lowercase', - 'CAPTCHA_NO_GD' => 'Simple image', + 'CAPTCHA_INCOMPLETE' => 'Incomplete Captcha (Disabled Registration)', 'CAPTCHA_PREVIEW_MSG' => 'Your changes have not been saved, this is just a preview.', 'CAPTCHA_PREVIEW_EXPLAIN' => 'The plugin as it would look like using the current selection.', diff --git a/phpBB/phpbb/captcha/char_cube3d.php b/phpBB/phpbb/captcha/char_cube3d.php deleted file mode 100644 index 0255259ac4f..00000000000 --- a/phpBB/phpbb/captcha/char_cube3d.php +++ /dev/null @@ -1,277 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha; - -class char_cube3d -{ - var $bitmap; - var $bitmap_width; - var $bitmap_height; - - var $basis_matrix = array(array(1, 0, 0), array(0, 1, 0), array(0, 0, 1)); - var $abs_x = array(1, 0); - var $abs_y = array(0, 1); - var $x = 0; - var $y = 1; - var $z = 2; - var $letter = ''; - - /** - */ - function __construct(&$bitmaps, $letter) - { - $this->bitmap = $bitmaps['data'][$letter]; - $this->bitmap_width = $bitmaps['width']; - $this->bitmap_height = $bitmaps['height']; - - $this->basis_matrix[0][0] = mt_rand(-600, 600); - $this->basis_matrix[0][1] = mt_rand(-600, 600); - $this->basis_matrix[0][2] = (mt_rand(0, 1) * 2000) - 1000; - $this->basis_matrix[1][0] = mt_rand(-1000, 1000); - $this->basis_matrix[1][1] = mt_rand(-1000, 1000); - $this->basis_matrix[1][2] = mt_rand(-1000, 1000); - - $this->normalize($this->basis_matrix[0]); - $this->normalize($this->basis_matrix[1]); - $this->basis_matrix[2] = $this->cross_product($this->basis_matrix[0], $this->basis_matrix[1]); - $this->normalize($this->basis_matrix[2]); - - // $this->basis_matrix[1] might not be (probably isn't) orthogonal to $basis_matrix[0] - $this->basis_matrix[1] = $this->cross_product($this->basis_matrix[0], $this->basis_matrix[2]); - $this->normalize($this->basis_matrix[1]); - - // Make sure our cube is facing into the canvas (assuming +z == in) - for ($i = 0; $i < 3; ++$i) - { - if ($this->basis_matrix[$i][2] < 0) - { - $this->basis_matrix[$i][0] *= -1; - $this->basis_matrix[$i][1] *= -1; - $this->basis_matrix[$i][2] *= -1; - } - } - - // Force our "z" basis vector to be the one with greatest absolute z value - $this->x = 0; - $this->y = 1; - $this->z = 2; - - // Swap "y" with "z" - if ($this->basis_matrix[1][2] > $this->basis_matrix[2][2]) - { - $this->z = 1; - $this->y = 2; - } - - // Swap "x" with "z" - if ($this->basis_matrix[0][2] > $this->basis_matrix[$this->z][2]) - { - $this->x = $this->z; - $this->z = 0; - } - - // Still need to determine which of $x,$y are which. - // wrong orientation if y's y-component is less than it's x-component - // likewise if x's x-component is less than it's y-component - // if they disagree, go with the one with the greater weight difference. - // rotate if positive - $weight = (abs($this->basis_matrix[$this->x][1]) - abs($this->basis_matrix[$this->x][0])) + (abs($this->basis_matrix[$this->y][0]) - abs($this->basis_matrix[$this->y][1])); - - // Swap "x" with "y" - if ($weight > 0) - { - list($this->x, $this->y) = array($this->y, $this->x); - } - - $this->abs_x = array($this->basis_matrix[$this->x][0], $this->basis_matrix[$this->x][1]); - $this->abs_y = array($this->basis_matrix[$this->y][0], $this->basis_matrix[$this->y][1]); - - if ($this->abs_x[0] < 0) - { - $this->abs_x[0] *= -1; - $this->abs_x[1] *= -1; - } - - if ($this->abs_y[1] > 0) - { - $this->abs_y[0] *= -1; - $this->abs_y[1] *= -1; - } - - $this->letter = $letter; - } - - /** - * Draw a character - */ - function drawchar($scale, $xoff, $yoff, $img, $background, $colours) - { - $width = $this->bitmap_width; - $height = $this->bitmap_height; - $bitmap = $this->bitmap; - - $colour1 = $colours[array_rand($colours)]; - $colour2 = $colours[array_rand($colours)]; - - $swapx = ($this->basis_matrix[$this->x][0] > 0); - $swapy = ($this->basis_matrix[$this->y][1] < 0); - - for ($y = 0; $y < $height; ++$y) - { - for ($x = 0; $x < $width; ++$x) - { - $xp = ($swapx) ? ($width - $x - 1) : $x; - $yp = ($swapy) ? ($height - $y - 1) : $y; - - if ($bitmap[$height - $yp - 1][$xp]) - { - $dx = $this->scale($this->abs_x, ($xp - ($swapx ? ($width / 2) : ($width / 2) - 1)) * $scale); - $dy = $this->scale($this->abs_y, ($yp - ($swapy ? ($height / 2) : ($height / 2) - 1)) * $scale); - $xo = $xoff + $dx[0] + $dy[0]; - $yo = $yoff + $dx[1] + $dy[1]; - - $origin = array(0, 0, 0); - $xvec = $this->scale($this->basis_matrix[$this->x], $scale); - $yvec = $this->scale($this->basis_matrix[$this->y], $scale); - $face_corner = $this->sum2($xvec, $yvec); - - $zvec = $this->scale($this->basis_matrix[$this->z], $scale); - $x_corner = $this->sum2($xvec, $zvec); - $y_corner = $this->sum2($yvec, $zvec); - - imagefilledpolygon($img, $this->gen_poly($xo, $yo, $origin, $xvec, $x_corner,$zvec), 4, $colour1); - imagefilledpolygon($img, $this->gen_poly($xo, $yo, $origin, $yvec, $y_corner,$zvec), 4, $colour2); - - $face = $this->gen_poly($xo, $yo, $origin, $xvec, $face_corner, $yvec); - - imagefilledpolygon($img, $face, 4, $background); - imagepolygon($img, $face, 4, $colour1); - } - } - } - } - - /* - * return a roughly acceptable range of sizes for rendering with this texttype - */ - function range() - { - return array(3, 4); - } - - /** - * Vector length - */ - function vectorlen($vector) - { - return sqrt(pow($vector[0], 2) + pow($vector[1], 2) + pow($vector[2], 2)); - } - - /** - * Normalize - */ - function normalize(&$vector, $length = 1) - { - $length = (( $length < 1) ? 1 : $length); - $length /= $this->vectorlen($vector); - $vector[0] *= $length; - $vector[1] *= $length; - $vector[2] *= $length; - } - - /** - */ - function cross_product($vector1, $vector2) - { - $retval = array(0, 0, 0); - $retval[0] = (($vector1[1] * $vector2[2]) - ($vector1[2] * $vector2[1])); - $retval[1] = -(($vector1[0] * $vector2[2]) - ($vector1[2] * $vector2[0])); - $retval[2] = (($vector1[0] * $vector2[1]) - ($vector1[1] * $vector2[0])); - - return $retval; - } - - /** - */ - function sum($vector1, $vector2) - { - return array($vector1[0] + $vector2[0], $vector1[1] + $vector2[1], $vector1[2] + $vector2[2]); - } - - /** - */ - function sum2($vector1, $vector2) - { - return array($vector1[0] + $vector2[0], $vector1[1] + $vector2[1]); - } - - /** - */ - function scale($vector, $length) - { - if (count($vector) == 2) - { - return array($vector[0] * $length, $vector[1] * $length); - } - - return array($vector[0] * $length, $vector[1] * $length, $vector[2] * $length); - } - - /** - */ - function gen_poly($xoff, $yoff, &$vec1, &$vec2, &$vec3, &$vec4) - { - $poly = array(); - $poly[0] = $xoff + $vec1[0]; - $poly[1] = $yoff + $vec1[1]; - $poly[2] = $xoff + $vec2[0]; - $poly[3] = $yoff + $vec2[1]; - $poly[4] = $xoff + $vec3[0]; - $poly[5] = $yoff + $vec3[1]; - $poly[6] = $xoff + $vec4[0]; - $poly[7] = $yoff + $vec4[1]; - - return $poly; - } - - /** - * dimensions - */ - function dimensions($size) - { - $xn = $this->scale($this->basis_matrix[$this->x], -($this->bitmap_width / 2) * $size); - $xp = $this->scale($this->basis_matrix[$this->x], ($this->bitmap_width / 2) * $size); - $yn = $this->scale($this->basis_matrix[$this->y], -($this->bitmap_height / 2) * $size); - $yp = $this->scale($this->basis_matrix[$this->y], ($this->bitmap_height / 2) * $size); - - $p = array(); - $p[0] = $this->sum2($xn, $yn); - $p[1] = $this->sum2($xp, $yn); - $p[2] = $this->sum2($xp, $yp); - $p[3] = $this->sum2($xn, $yp); - - $min_x = $max_x = $p[0][0]; - $min_y = $max_y = $p[0][1]; - - for ($i = 1; $i < 4; ++$i) - { - $min_x = ($min_x > $p[$i][0]) ? $p[$i][0] : $min_x; - $min_y = ($min_y > $p[$i][1]) ? $p[$i][1] : $min_y; - $max_x = ($max_x < $p[$i][0]) ? $p[$i][0] : $max_x; - $max_y = ($max_y < $p[$i][1]) ? $p[$i][1] : $max_y; - } - - return array($min_x, $min_y, $max_x, $max_y); - } -} diff --git a/phpBB/phpbb/captcha/colour_manager.php b/phpBB/phpbb/captcha/colour_manager.php deleted file mode 100644 index 82332da8103..00000000000 --- a/phpBB/phpbb/captcha/colour_manager.php +++ /dev/null @@ -1,527 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha; - -class colour_manager -{ - var $img; - var $mode; - var $colours; - var $named_colours; - - /** - * Create the colour manager, link it to the image resource - */ - function __construct($img, $background = false, $mode = 'ahsv') - { - $this->img = $img; - $this->mode = $mode; - $this->colours = array(); - $this->named_colours = array(); - - if ($background !== false) - { - $bg = $this->allocate_named('background', $background); - imagefill($this->img, 0, 0, $bg); - } - } - - /** - * Lookup a named colour resource - */ - function get_resource($named_colour) - { - if (isset($this->named_colours[$named_colour])) - { - return $this->named_colours[$named_colour]; - } - - if (isset($this->named_rgb[$named_colour])) - { - return $this->allocate_named($named_colour, $this->named_rgb[$named_colour], 'rgb'); - } - - return false; - } - - /** - * Assign a name to a colour resource - */ - function name_colour($name, $resource) - { - $this->named_colours[$name] = $resource; - } - - /** - * names and allocates a colour resource - */ - function allocate_named($name, $colour, $mode = false) - { - $resource = $this->allocate($colour, $mode); - - if ($resource !== false) - { - $this->name_colour($name, $resource); - } - return $resource; - } - - /** - * allocates a specified colour into the image - */ - function allocate($colour, $mode = false) - { - if ($mode === false) - { - $mode = $this->mode; - } - - if (!is_array($colour)) - { - if (isset($this->named_rgb[$colour])) - { - return $this->allocate_named($colour, $this->named_rgb[$colour], 'rgb'); - } - - if (!is_int($colour)) - { - return false; - } - - $mode = 'rgb'; - $colour = array(255 & ($colour >> 16), 255 & ($colour >> 8), 255 & $colour); - } - - if (isset($colour['mode'])) - { - $mode = $colour['mode']; - unset($colour['mode']); - } - - if (isset($colour['random'])) - { - unset($colour['random']); - // everything else is params - return $this->random_colour($colour, $mode); - } - - $rgb = $this->model_convert($colour, $mode, 'rgb'); - $store = ($this->mode == 'rgb') ? $rgb : $this->model_convert($colour, $mode, $this->mode); - $resource = imagecolorallocate($this->img, $rgb[0], $rgb[1], $rgb[2]); - $this->colours[$resource] = $store; - - return $resource; - } - - /** - * randomly generates a colour, with optional params - */ - function random_colour($params = array(), $mode = false) - { - if ($mode === false) - { - $mode = $this->mode; - } - - switch ($mode) - { - case 'rgb': - // @TODO random rgb generation. do we intend to do this, or is it just too tedious? - break; - - case 'ahsv': - case 'hsv': - default: - - $default_params = array( - 'hue_bias' => false, // degree / 'r'/'g'/'b'/'c'/'m'/'y' /'o' - 'hue_range' => false, // if hue bias, then difference range +/- from bias - 'min_saturation' => 30, // 0 - 100 - 'max_saturation' => 80, // 0 - 100 - 'min_value' => 30, // 0 - 100 - 'max_value' => 80, // 0 - 100 - ); - - $alt = ($mode == 'ahsv') ? true : false; - $params = array_merge($default_params, $params); - - $min_hue = 0; - $max_hue = 359; - $min_saturation = max(0, $params['min_saturation']); - $max_saturation = min(100, $params['max_saturation']); - $min_value = max(0, $params['min_value']); - $max_value = min(100, $params['max_value']); - - if ($params['hue_bias'] !== false) - { - if (is_numeric($params['hue_bias'])) - { - $h = intval($params['hue_bias']) % 360; - } - else - { - switch ($params['hue_bias']) - { - case 'o': - $h = $alt ? 60 : 30; - break; - - case 'y': - $h = $alt ? 120 : 60; - break; - - case 'g': - $h = $alt ? 180 : 120; - break; - - case 'c': - $h = $alt ? 210 : 180; - break; - - case 'b': - $h = 240; - break; - - case 'm': - $h = 300; - break; - - case 'r': - default: - $h = 0; - break; - } - } - - $min_hue = $h + 360; - $max_hue = $h + 360; - - if ($params['hue_range']) - { - $min_hue -= min(180, $params['hue_range']); - $max_hue += min(180, $params['hue_range']); - } - } - - $h = mt_rand($min_hue, $max_hue); - $s = mt_rand($min_saturation, $max_saturation); - $v = mt_rand($min_value, $max_value); - - return $this->allocate(array($h, $s, $v), $mode); - - break; - } - } - - /** - */ - function colour_scheme($resource, $include_original = true) - { - $mode = 'hsv'; - - if (($pre = $this->get_resource($resource)) !== false) - { - $resource = $pre; - } - - $colour = $this->model_convert($this->colours[$resource], $this->mode, $mode); - $results = ($include_original) ? array($resource) : array(); - $colour2 = $colour3 = $colour4 = $colour; - $colour2[0] += 150; - $colour3[0] += 180; - $colour4[0] += 210; - - $results[] = $this->allocate($colour2, $mode); - $results[] = $this->allocate($colour3, $mode); - $results[] = $this->allocate($colour4, $mode); - - return $results; - } - - /** - */ - function mono_range($resource, $count = 5, $include_original = true) - { - if (is_array($resource)) - { - $results = array(); - for ($i = 0, $size = count($resource); $i < $size; ++$i) - { - $results = array_merge($results, $this->mono_range($resource[$i], $count, $include_original)); - } - return $results; - } - - $mode = (in_array($this->mode, array('hsv', 'ahsv'), true) ? $this->mode : 'ahsv'); - if (($pre = $this->get_resource($resource)) !== false) - { - $resource = $pre; - } - - $colour = $this->model_convert($this->colours[$resource], $this->mode, $mode); - - $results = array(); - if ($include_original) - { - $results[] = $resource; - $count--; - } - - // This is a hard problem. I chicken out and try to maintain readability at the cost of less randomness. - - while ($count > 0) - { - $colour[1] = ($colour[1] + mt_rand(40,60)) % 99; - $colour[2] = ($colour[2] + mt_rand(40,60)); - $results[] = $this->allocate($colour, $mode); - $count--; - } - return $results; - } - - /** - * Convert from one colour model to another - */ - function model_convert($colour, $from_model, $to_model) - { - if ($from_model == $to_model) - { - return $colour; - } - - switch ($to_model) - { - case 'hsv': - - switch ($from_model) - { - case 'ahsv': - return $this->ah2h($colour); - break; - - case 'rgb': - return $this->rgb2hsv($colour); - break; - } - break; - - case 'ahsv': - - switch ($from_model) - { - case 'hsv': - return $this->h2ah($colour); - break; - - case 'rgb': - return $this->h2ah($this->rgb2hsv($colour)); - break; - } - break; - - case 'rgb': - switch ($from_model) - { - case 'hsv': - return $this->hsv2rgb($colour); - break; - - case 'ahsv': - return $this->hsv2rgb($this->ah2h($colour)); - break; - } - break; - } - return false; - } - - /** - * Slightly altered from wikipedia's algorithm - */ - function hsv2rgb($hsv) - { - $this->normalize_hue($hsv[0]); - - $h = $hsv[0]; - $s = min(1, max(0, $hsv[1] / 100)); - $v = min(1, max(0, $hsv[2] / 100)); - - // calculate hue sector - $hi = floor($hsv[0] / 60); - - // calculate opposite colour - $p = $v * (1 - $s); - - // calculate distance between hex vertices - $f = ($h / 60) - $hi; - - // coming in or going out? - if (!($hi & 1)) - { - $f = 1 - $f; - } - - // calculate adjacent colour - $q = $v * (1 - ($f * $s)); - - switch ($hi) - { - case 0: - $rgb = array($v, $q, $p); - break; - - case 1: - $rgb = array($q, $v, $p); - break; - - case 2: - $rgb = array($p, $v, $q); - break; - - case 3: - $rgb = array($p, $q, $v); - break; - - case 4: - $rgb = array($q, $p, $v); - break; - - case 5: - $rgb = array($v, $p, $q); - break; - - default: - return array(0, 0, 0); - break; - } - - return array(255 * $rgb[0], 255 * $rgb[1], 255 * $rgb[2]); - } - - /** - * (more than) Slightly altered from wikipedia's algorithm - */ - function rgb2hsv($rgb) - { - $r = min(255, max(0, $rgb[0])); - $g = min(255, max(0, $rgb[1])); - $b = min(255, max(0, $rgb[2])); - $max = max($r, $g, $b); - $min = min($r, $g, $b); - - $v = $max / 255; - $s = (!$max) ? 0 : 1 - ($min / $max); - - // if max - min is 0, we want hue to be 0 anyway. - $h = $max - $min; - - if ($h) - { - switch ($max) - { - case $g: - $h = 120 + (60 * ($b - $r) / $h); - break; - - case $b: - $h = 240 + (60 * ($r - $g) / $h); - break; - - case $r: - $h = 360 + (60 * ($g - $b) / $h); - break; - } - } - $this->normalize_hue($h); - - return array($h, $s * 100, $v * 100); - } - - /** - */ - function normalize_hue(&$hue) - { - $hue %= 360; - - if ($hue < 0) - { - $hue += 360; - } - } - - /** - * Alternate hue to hue - */ - function ah2h($ahue) - { - if (is_array($ahue)) - { - $ahue[0] = $this->ah2h($ahue[0]); - return $ahue; - } - $this->normalize_hue($ahue); - - // blue through red is already ok - if ($ahue >= 240) - { - return $ahue; - } - - // ahue green is at 180 - if ($ahue >= 180) - { - // return (240 - (2 * (240 - $ahue))); - return (2 * $ahue) - 240; // equivalent - } - - // ahue yellow is at 120 (RYB rather than RGB) - if ($ahue >= 120) - { - return $ahue - 60; - } - - return $ahue / 2; - } - - /** - * hue to Alternate hue - */ - function h2ah($hue) - { - if (is_array($hue)) - { - $hue[0] = $this->h2ah($hue[0]); - return $hue; - } - $this->normalize_hue($hue); - - // blue through red is already ok - if ($hue >= 240) - { - return $hue; - } - else if ($hue <= 60) - { - return $hue * 2; - } - else if ($hue <= 120) - { - return $hue + 60; - } - else - { - return ($hue + 240) / 2; - } - } -} diff --git a/phpBB/phpbb/captcha/gd.php b/phpBB/phpbb/captcha/gd.php deleted file mode 100644 index 308bc73e0ba..00000000000 --- a/phpBB/phpbb/captcha/gd.php +++ /dev/null @@ -1,1846 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha; - -class gd -{ - var $width = 360; - var $height = 96; - - /** - * Create the image containing $code with a seed of $seed - */ - function execute($code, $seed) - { - global $config; - - mt_srand($seed); - - // Create image - $img = imagecreatetruecolor($this->width, $this->height); - - // Generate colours - $colour = new colour_manager($img, array( - 'random' => true, - 'min_value' => 60, - ), 'hsv'); - - $scheme = $colour->colour_scheme('background', false); - $scheme = $colour->mono_range($scheme, 10, false); - shuffle($scheme); - - $bg_colours = array_splice($scheme, mt_rand(6, 12)); - - // Generate code characters - $characters = $sizes = $bounding_boxes = $noise = array(); - $width_avail = $this->width - 15; - $code_len = strlen($code); - $captcha_bitmaps = $this->captcha_bitmaps(); - - for ($i = 0; $i < $code_len; ++$i) - { - $characters[$i] = new char_cube3d($captcha_bitmaps, $code[$i]); - - list($min, $max) = $characters[$i]->range(); - $sizes[$i] = mt_rand($min, $max); - - $box = $characters[$i]->dimensions($sizes[$i]); - $width_avail -= ($box[2] - $box[0]); - $bounding_boxes[$i] = $box; - } - - // Redistribute leftover x-space - $offset = array(); - for ($i = 0; $i < $code_len; ++$i) - { - $denom = ($code_len - $i); - $denom = max(1.3, $denom); - $offset[$i] = phpbb_mt_rand(0, (int) round((1.5 * $width_avail) / $denom)); - $width_avail -= $offset[$i]; - } - - if ($config['captcha_gd_x_grid']) - { - $grid = (int) $config['captcha_gd_x_grid']; - for ($y = 0; $y < $this->height; $y += mt_rand($grid - 2, $grid + 2)) - { - $current_colour = $scheme[array_rand($scheme)]; - imageline($img, mt_rand(0,4), mt_rand($y - 3, $y), mt_rand($this->width - 5, $this->width), mt_rand($y - 3, $y), $current_colour); - } - } - - if ($config['captcha_gd_y_grid']) - { - $grid = (int) $config['captcha_gd_y_grid']; - for ($x = 0; $x < $this->width; $x += mt_rand($grid - 2, $grid + 2)) - { - $current_colour = $scheme[array_rand($scheme)]; - imagedashedline($img, mt_rand($x -3, $x + 3), mt_rand(0, 4), mt_rand($x -3, $x + 3), mt_rand($this->height - 5, $this->height), $current_colour); - } - } - - if ($config['captcha_gd_wave'] && ($config['captcha_gd_x_grid'] || $config['captcha_gd_y_grid'])) - { - $this->wave($img); - } - - if ($config['captcha_gd_3d_noise']) - { - $noise_bitmaps = $this->captcha_noise_bg_bitmaps(); - for ($i = 0; $i < $code_len; ++$i) - { - $noise[$i] = new char_cube3d($noise_bitmaps, mt_rand(1, count($noise_bitmaps['data']))); - - $noise[$i]->range(); - //$box = $noise[$i]->dimensions($sizes[$i]); - } - $xoffset = 0; - for ($i = 0; $i < $code_len; ++$i) - { - $dimm = $bounding_boxes[$i]; - $xoffset += ($offset[$i] - $dimm[0]); - $yoffset = mt_rand(-$dimm[1], $this->height - $dimm[3]); - - $noise[$i]->drawchar($sizes[$i], $xoffset, $yoffset, $img, $colour->get_resource('background'), $scheme); - $xoffset += $dimm[2]; - } - } - - $xoffset = 5; - for ($i = 0; $i < $code_len; ++$i) - { - $dimm = $bounding_boxes[$i]; - $xoffset += ($offset[$i] - $dimm[0]); - $yoffset = mt_rand(-$dimm[1], $this->height - $dimm[3]); - - $characters[$i]->drawchar($sizes[$i], $xoffset, $yoffset, $img, $colour->get_resource('background'), $scheme); - $xoffset += $dimm[2]; - } - - if ($config['captcha_gd_wave']) - { - $this->wave($img); - } - - if ($config['captcha_gd_foreground_noise']) - { - $this->noise_line($img, 0, 0, $this->width, $this->height, $colour->get_resource('background'), $scheme, $bg_colours); - } - - // Send image - header('Content-Type: image/png'); - header('Cache-control: no-cache, no-store'); - imagepng($img); - imagedestroy($img); - } - - /** - * Sinus - */ - function wave($img) - { - $period_x = mt_rand(12,18); - $period_y = mt_rand(7,14); - $amp_x = mt_rand(5,10); - $amp_y = mt_rand(2,4); - $socket = mt_rand(0,100); - - $dampen_x = mt_rand($this->width/5, $this->width/2); - $dampen_y = mt_rand($this->height/5, $this->height/2); - $direction_x = (mt_rand (0, 1)); - $direction_y = (mt_rand (0, 1)); - - for ($i = 0; $i < $this->width; $i++) - { - $dir = ($direction_x) ? $i : ($this->width - $i); - imagecopy($img, $img, $i-1, sin($socket+ $i/($period_x + $dir/$dampen_x)) * $amp_x, $i, 0, 1, $this->height); - } - $socket = mt_rand(0,100); - for ($i = 0; $i < $this->height; $i++) - { - $dir = ($direction_y) ? $i : ($this->height - $i); - imagecopy($img, $img ,sin($socket + $i/($period_y + ($dir)/$dampen_y)) * $amp_y, $i-1, 0, $i, $this->width, 1); - } - return $img; - } - - /** - * Noise line - */ - function noise_line($img, $min_x, $min_y, $max_x, $max_y, $bg, $font, $non_font) - { - imagesetthickness($img, 2); - - $x1 = $min_x; - $x2 = $max_x; - $y1 = $min_y; - $y2 = $min_y; - - do - { - $line = array_merge( - array_fill(0, mt_rand(30, 60), $non_font[array_rand($non_font)]), - array_fill(0, mt_rand(30, 60), $bg) - ); - - imagesetstyle($img, $line); - imageline($img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); - - $y1 += mt_rand(12, 35); - $y2 += mt_rand(12, 35); - } - while ($y1 < $max_y && $y2 < $max_y); - - $x1 = $min_x; - $x2 = $min_x; - $y1 = $min_y; - $y2 = $max_y; - - do - { - $line = array_merge( - array_fill(0, mt_rand(30, 60), $non_font[array_rand($non_font)]), - array_fill(0, mt_rand(30, 60), $bg) - ); - - imagesetstyle($img, $line); - imageline($img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); - - $x1 += mt_rand(20, 35); - $x2 += mt_rand(20, 35); - } - while ($x1 < $max_x && $x2 < $max_x); - imagesetthickness($img, 1); - } - - function captcha_noise_bg_bitmaps() - { - return array( - 'width' => 15, - 'height' => 5, - 'data' => array( - - 1 => array( - array(1,0,0,0,1,0,0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,1,0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,1,0,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0,0,0,1,0,0,0), - ), - 2 => array( - array(1,1,mt_rand(0,1),1,0,1,1,1,1,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,1,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,1,1,0,1,1,1), - ), - 3 => array( - array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0,0,0,0,0,1,0), - array(0,0,0,0,1,0,0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,1), - ), - 4 => array( - array(1,0,1,0,1,0,0,1,1,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,1,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(1,0,1,0,0,0,0,0,0,0,0,0,0,0,0), - ), - 5 => array( - array(1,1,1,1,0,0,0,1,1,1,0,0,1,0,1), - array(0,0,0,0,0,0,0,1,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(1,0,1,0,0,0,0,0,0,0,0,0,0,0,0), - ), - 6 => array( - array(mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),0,mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),0,mt_rand(0,1),mt_rand(0,1),mt_rand(0,1)), - array(0,0,0,0,0,0,0,mt_rand(0,1),0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(mt_rand(0,1),0,mt_rand(0,1),0,0,0,0,0,0,0,0,0,0,0,0), - ), - 7 => array( - array(0,0,0,0,0,0,0,0,0,0,1,1,0,1,1), - array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - array(0,0,1,1,0,0,0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,1,0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0), - ), - )); - } - - /** - * Return bitmaps - */ - function captcha_bitmaps() - { - global $config; - - $chars = array( - 'A' => array( - array( - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,1,1,1,1,1,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,1,1,0,1,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,1,1,1,1,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,0,0,0,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,1,1,0,0,0,1,1,0), - array(1,1,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,1), - array(0,0,0,0,0,1,1,1,1), - array(0,0,0,1,1,1,0,0,1), - array(0,1,1,1,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,1,0,0,0,0,1,1,1), - array(0,1,1,1,1,1,1,0,1), - ), - ), - 'B' => array( - array( - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - ), - array( - array(1,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - ), - array( - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,1,1,1,1,0,0), - ), - ), - 'C' => array( - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,1,1,1,1,1,0,1), - array(0,1,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,1), - array(0,0,1,1,1,1,1,0,1), - ), - ), - 'D' => array( - array( - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - ), - array( - array(1,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,1,1,1,1,1,0,1), - array(0,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,1,0,0,0,1,1,1), - array(0,0,1,1,1,1,1,0,1), - ), - ), - 'E' => array( - array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,1,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,1,1,0,0,0,1,1,0), - array(1,1,0,0,0,0,0,1,1), - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(1,1,0,0,0,0,0,1,1), - array(0,1,1,1,1,1,1,1,0), - ), - ), - 'F' => array( - array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - ), - array( - array(0,1,1,1,1,1,1,1,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,0,0,0), - ), - array( - array(0,0,0,1,1,0,0,0,0), - array(0,0,1,1,0,0,0,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,1,1,1,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - ), - ), - 'G' => array( - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,1,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,1,1,1,1,1,0,1), - array(0,1,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,1,1,1,1,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,1), - array(0,0,1,1,1,1,1,0,1), - ), - array( - array(0,0,1,1,1,1,1,0,1), - array(0,1,1,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,1,1,0,0,0,0,0,1), - array(0,0,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,1), - array(1,1,1,1,1,1,1,1,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - ), - 'H' => array( - array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - ), - array( - array(1,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,1,1,1,1,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,0,0,0,1,1,1), - ), - array( - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,1,1,1,0,0,0), - array(1,1,1,1,0,1,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - ), - ), - 'I' => array( - array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(0,0,0,1,1,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,1,1,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,1,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,1,1,0,0,0), - ), - ), - 'J' => array( - array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,0,1,1,0,0,0,0,0), - ), - array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,1,0,0,1,0,0,0,0), - array(1,0,1,1,0,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,0,1,1,0,0,0,0,0), - ), - ), - 'K' => array( - array( // New 'K', supplied by NeoThermic - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,1,0,0,0,0,0), - array(1,0,1,0,0,0,0,0,0), - array(1,1,0,0,0,0,0,0,0), - array(1,0,1,0,0,0,0,0,0), - array(1,0,0,1,0,0,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - ), - array( - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,1,0,0), - array(0,1,0,0,0,1,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,1,0,1,0,0,0,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,1,0,1,0,0,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,1,0,0,0,1,0,0,0), - array(0,1,0,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,0,0,0,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,1,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,1,0,1,0,0,0,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,1,0,1,0,0,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,1,0,0,0,1,0,0,0), - array(0,1,0,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - ), - ), - 'L' => array( - array( - array(0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,1), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,0,1,1,1,0,0,0,0), - ), - ), - 'M' => array( - array( - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,0,1,0,0,0,1,0,1), - array(1,0,1,0,0,0,1,0,1), - array(1,0,1,0,0,0,1,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,1,0,1,0,1,0,1,0), - array(0,1,0,1,0,1,0,1,0), - array(0,1,0,1,0,1,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,0,0,0,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,1,1,1,0,1,1,1,0), - array(1,1,0,1,1,1,0,1,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - ), - ), - 'N' => array( - array( - array(1,1,0,0,0,0,0,0,1), - array(1,1,0,0,0,0,0,0,1), - array(1,0,1,0,0,0,0,0,1), - array(1,0,1,0,0,0,0,0,1), - array(1,0,0,1,0,0,0,0,1), - array(1,0,0,1,0,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,0,1,0,0,1), - array(1,0,0,0,0,1,0,0,1), - array(1,0,0,0,0,0,1,0,1), - array(1,0,0,0,0,0,1,0,1), - array(1,0,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,0,0,0,0,1,0), - array(0,1,1,0,0,0,0,1,0), - array(0,1,1,0,0,0,0,1,0), - array(0,1,0,1,0,0,0,1,0), - array(0,1,0,1,0,0,0,1,0), - array(0,1,0,1,0,0,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,0,1,1,0,1,0), - array(0,1,0,0,0,1,0,1,0), - array(0,1,0,0,0,1,1,1,0), - array(0,1,0,0,0,0,1,1,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,0,0,0,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(1,0,1,1,1,1,0,0,0), - array(1,1,1,0,0,1,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - ), - ), - 'O' => array( - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,1,1,1,1,1,0,0,0), - array(1,1,1,0,0,1,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,1,0,0,0,1,1,0,0), - array(0,1,1,1,1,1,0,0,0), - ), - ), - 'P' => array( - array( - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - ), - array( - array(1,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,1,1,0,0,0,0,0), - array(1,1,0,1,1,0,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,1,1,0,0,0,0), - array(1,1,1,1,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - ), - ), - 'Q' => array( - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,1,0,0,1), - array(1,0,0,0,0,0,1,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,1), - ), - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,1,0,0,1,1,0,1,1), - array(0,1,1,1,1,1,1,1,0), - array(0,0,0,0,0,0,1,1,0), - array(0,0,0,0,0,0,0,1,1), - array(0,0,0,0,0,0,0,0,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,1,1,1,1), - array(0,0,0,0,1,1,0,0,1), - array(0,0,0,0,1,0,0,0,1), - array(0,0,0,0,1,0,0,0,1), - array(0,0,0,0,1,1,0,1,1), - array(0,0,0,0,0,1,1,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - ), - ), - 'R' => array( - array( - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(1,1,1,0,0,0,0,0,0), - array(1,0,0,1,0,0,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - ), - array( - array(1,1,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(0,1,1,0,0,0,0,0,0), - array(0,1,1,1,0,0,0,0,0), - array(0,1,0,1,1,0,0,0,0), - array(0,1,0,0,1,1,0,0,0), - array(0,1,0,0,0,1,1,0,0), - array(0,1,0,0,0,0,1,1,0), - array(1,1,1,0,0,0,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,0,0,0,0), - array(1,1,0,0,1,1,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - ), - ), - 'S' => array( - array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,1,1,1,1,1,0,1), - array(0,1,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,1,0,0,0,0,0,1,0), - array(1,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,1,1,1,1,0,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,0,0,0,0,0,0,0), - array(0,1,1,1,1,0,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(1,0,0,0,1,1,0,0,0), - array(0,1,1,1,1,0,0,0,0), - ), - ), - 'T' => array( - array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - ), - array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,1,0,0,0,1), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,1,1,0,0,0), - ), - array( - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,1,1,1,1,1,1,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,1,0,0,0), - array(0,0,0,0,0,1,1,1,0), - ), - ), - 'U' => array( - array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,1,1), - array(0,0,1,1,0,0,1,1,1), - array(0,0,0,1,1,1,1,0,1), - ), - ), - 'V' => array( - array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - ), - ), - 'W' => array( - array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,1,0,0,0,1,0,1), - array(1,0,1,0,0,0,1,0,1), - array(1,0,1,0,0,0,1,0,1), - array(1,1,0,0,0,0,0,1,1), - array(1,1,0,0,0,0,0,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,1,1,1,0,1,0), - array(0,1,0,1,0,1,0,1,0), - array(0,1,1,1,0,1,1,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,1,1,1,0,1,0), - array(0,1,0,1,0,1,0,1,0), - array(0,1,1,1,0,1,1,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,0), - ), - ), - 'X' => array( - array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,1,1,0,0,0,1,1,1), - array(0,0,0,0,0,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,0,1,1,0,1,1,0,0), - array(0,0,0,1,1,1,0,0,0), - array(0,0,0,1,1,1,0,0,0), - array(0,0,1,1,0,1,1,0,0), - array(0,1,1,0,0,0,1,1,0), - array(0,0,0,0,0,0,0,0,0), - ), - ), - 'Y' => array( - array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(1,1,1,0,0,0,1,1,1), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,1,1,0,0,0), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,1,0,0,0,0,1), - array(0,0,0,1,1,0,0,0,1), - array(0,0,0,0,1,0,0,1,1), - array(0,0,0,0,1,1,0,1,0), - array(0,0,0,0,0,1,1,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,1,0,0,0), - array(0,0,1,1,1,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - ), - 'Z' => array( - array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,1,1), - ), - array( - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,1,1,1,1,1,1,1,0), - array(0,0,0,0,0,1,1,0,0), - array(0,0,0,0,1,1,0,0,0), - array(0,0,0,1,1,0,0,0,0), - array(0,0,1,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,1,1,1,1,1,1,0), - ), - ), - ); - - /** @var 1|2|3 $config['captcha_gd_fonts'] */ - return array( - 'width' => 9, - 'height' => 15, - 'data' => array( - - 'A' => $chars['A'][mt_rand(0, min(count($chars['A']), $config['captcha_gd_fonts']) -1)], - 'B' => $chars['B'][mt_rand(0, min(count($chars['B']), $config['captcha_gd_fonts']) -1)], - 'C' => $chars['C'][mt_rand(0, min(count($chars['C']), $config['captcha_gd_fonts']) -1)], - 'D' => $chars['D'][mt_rand(0, min(count($chars['D']), $config['captcha_gd_fonts']) -1)], - 'E' => $chars['E'][mt_rand(0, min(count($chars['E']), $config['captcha_gd_fonts']) -1)], - 'F' => $chars['F'][mt_rand(0, min(count($chars['F']), $config['captcha_gd_fonts']) -1)], - 'G' => $chars['G'][mt_rand(0, min(count($chars['G']), $config['captcha_gd_fonts']) -1)], - 'H' => $chars['H'][mt_rand(0, min(count($chars['H']), $config['captcha_gd_fonts']) -1)], - 'I' => $chars['I'][mt_rand(0, min(count($chars['I']), $config['captcha_gd_fonts']) -1)], - 'J' => $chars['J'][mt_rand(0, min(count($chars['J']), $config['captcha_gd_fonts']) -1)], - 'K' => $chars['K'][mt_rand(0, min(count($chars['K']), $config['captcha_gd_fonts']) -1)], - 'L' => $chars['L'][mt_rand(0, min(count($chars['L']), $config['captcha_gd_fonts']) -1)], - 'M' => $chars['M'][mt_rand(0, min(count($chars['M']), $config['captcha_gd_fonts']) -1)], - 'N' => $chars['N'][mt_rand(0, min(count($chars['N']), $config['captcha_gd_fonts']) -1)], - 'O' => $chars['O'][mt_rand(0, min(count($chars['O']), $config['captcha_gd_fonts']) -1)], - 'P' => $chars['P'][mt_rand(0, min(count($chars['P']), $config['captcha_gd_fonts']) -1)], - 'Q' => $chars['Q'][mt_rand(0, min(count($chars['Q']), $config['captcha_gd_fonts']) -1)], - 'R' => $chars['R'][mt_rand(0, min(count($chars['R']), $config['captcha_gd_fonts']) -1)], - 'S' => $chars['S'][mt_rand(0, min(count($chars['S']), $config['captcha_gd_fonts']) -1)], - 'T' => $chars['T'][mt_rand(0, min(count($chars['T']), $config['captcha_gd_fonts']) -1)], - 'U' => $chars['U'][mt_rand(0, min(count($chars['U']), $config['captcha_gd_fonts']) -1)], - 'V' => $chars['V'][mt_rand(0, min(count($chars['V']), $config['captcha_gd_fonts']) -1)], - 'W' => $chars['W'][mt_rand(0, min(count($chars['W']), $config['captcha_gd_fonts']) -1)], - 'X' => $chars['X'][mt_rand(0, min(count($chars['X']), $config['captcha_gd_fonts']) -1)], - 'Y' => $chars['Y'][mt_rand(0, min(count($chars['Y']), $config['captcha_gd_fonts']) -1)], - 'Z' => $chars['Z'][mt_rand(0, min(count($chars['Z']), $config['captcha_gd_fonts']) -1)], - - '1' => array( - array(0,0,0,1,1,0,0,0,0), - array(0,0,1,0,1,0,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,1,1,1,1,1,1,1,0), - ), - '2' => array( // New '2' supplied by Anon - array(0,0,0,1,1,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,1,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,0), - ), - '3' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - '4' => array( - array(0,0,0,0,0,0,1,1,0), - array(0,0,0,0,0,1,0,1,0), - array(0,0,0,0,1,0,0,1,0), - array(0,0,0,1,0,0,0,1,0), - array(0,0,1,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - ), - '5' => array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - '6' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,1,1,1,1,0,0), - array(1,0,1,0,0,0,0,1,0), - array(1,1,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - '7' => array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - ), - '8' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - '9' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,1), - array(0,1,0,0,0,0,1,0,1), - array(0,0,1,1,1,1,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - ), - ) - ); - } -} diff --git a/phpBB/phpbb/captcha/gd_wave.php b/phpBB/phpbb/captcha/gd_wave.php deleted file mode 100644 index e0801e7c504..00000000000 --- a/phpBB/phpbb/captcha/gd_wave.php +++ /dev/null @@ -1,847 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha; - -/** -* Wave3D CAPTCHA -*/ -class gd_wave -{ - var $width = 360; - var $height = 96; - - function execute($code, $seed) - { - // seed the random generator - mt_srand($seed); - - // set height and width - $img_x = $this->width; - $img_y = $this->height; - - // Generate image - $img = imagecreatetruecolor($img_x, $img_y); - $x_grid = mt_rand(6, 10); - $y_grid = mt_rand(6, 10); - - // Ok, so lets cut to the chase. We could accurately represent this in 3d and - // do all the appropriate linear transforms. my questions is... why bother? - // The computational overhead is unnecessary when you consider the simple fact: - // we're not here to accurately represent a model, but to just show off some random-ish - // polygons - - // Conceive of 3 spaces. - // 1) planar-space (discrete "pixel" grid) - // 2) 3-space. (planar-space with z/height aspect) - // 3) image space (pixels on the screen) - // resolution of the planar-space we're embedding the text code in - $plane_x = 100; - $plane_y = 30; - - $subdivision_factor = 3; - - // $box is the 4 points in img_space that correspond to the corners of the plane in 3-space - $box = array( - 'upper_left' => array( - 'x' => mt_rand(5, 15), - 'y' => mt_rand(10, 15) - ), - 'upper_right' => array( - 'x' => mt_rand($img_x - 35, $img_x - 19), - 'y' => mt_rand(10, 17) - ), - 'lower_left' => array( - 'x' => mt_rand($img_x - 45, $img_x - 5), - 'y' => mt_rand($img_y - 15, $img_y - 0), - ), - ); - - $box['lower_right'] = array( - 'x' => $box['lower_left']['x'] + $box['upper_left']['x'] - $box['upper_right']['x'], - 'y' => $box['lower_left']['y'] + $box['upper_left']['y'] - $box['upper_right']['y'], - ); - - // TODO - $background = imagecolorallocate($img, mt_rand(155, 255), mt_rand(155, 255), mt_rand(155, 255)); - imagefill($img, 0, 0, $background); - - $random = array(); - $fontcolors = array(); - - for ($i = 0; $i < 15; ++$i) - { - $random[$i] = imagecolorallocate($img, mt_rand(120, 255), mt_rand(120, 255), mt_rand(120, 255)); - } - - $fontcolors[0] = imagecolorallocate($img, mt_rand(0, 120), mt_rand(0, 120), mt_rand(0, 120)); - - $colors = array(); - - $minr = mt_rand(20, 30); - $ming = mt_rand(20, 30); - $minb = mt_rand(20, 30); - - $maxr = mt_rand(150, 230); - $maxg = mt_rand(150, 230); - $maxb = mt_rand(150, 230); - - for ($i = -30; $i <= 30; ++$i) - { - $coeff1 = ($i + 12) / 45; - $coeff2 = 1 - $coeff1; - - $red = ($coeff2 * $maxr) + ($coeff1 * $minr); - $green = ($coeff2 * $maxg) + ($coeff1 * $ming); - $blue = ($coeff2 * $maxb) + ($coeff1 * $minb); - - $colors[$i] = imagecolorallocate($img, min([255, (int) $red]), min([255, (int) $green]), min([255, (int) $blue])); - } - - // $img_buffer is the last row of 3-space positions (converted to img-space), cached - // (using this means we don't need to recalculate all 4 positions for each new polygon, - // merely the newest point that we're adding, which is then cached. - $img_buffer = array(array(), array()); - - // In image-space, the x- and y-offset necessary to move one unit in the x-direction in planar-space - $dxx = ($box['upper_right']['x'] - $box['upper_left']['x']) / ($subdivision_factor * $plane_x); - $dxy = ($box['upper_right']['y'] - $box['upper_left']['y']) / ($subdivision_factor * $plane_x); - - // In image-space, the x- and y-offset necessary to move one unit in the y-direction in planar-space - $dyx = ($box['lower_right']['x'] - $box['upper_left']['x']) / ($subdivision_factor * $plane_y); - $dyy = ($box['lower_right']['y'] - $box['upper_left']['y']) / ($subdivision_factor * $plane_y); - - // Initial captcha-letter offset in planar-space - $plane_offset_x = mt_rand(3, 8); - $plane_offset_y = mt_rand( 12, 15); - - // character map - $map = $this->captcha_bitmaps(); - - // matrix - $plane = array(); - - // for each character, we'll silkscreen it into our boolean pixel plane - for ($c = 0, $code_num = strlen($code); $c < $code_num; ++$c) - { - $letter = $code[$c]; - - for ($x = $map['width'] - 1; $x >= 0; --$x) - { - for ($y = $map['height'] - 1; $y >= 0; --$y) - { - if ($map['data'][$letter][$y][$x]) - { - $plane[$y + $plane_offset_y + (($c & 1) ? 1 : -1)][$x + $plane_offset_x] = true; - } - } - } - $plane_offset_x += 11; - } - - // calculate our first buffer, we can't actually draw polys with these yet - // img_pos_prev == screen x,y location to our immediate left. - // img_pos_cur == current screen x,y location - // we calculate screen position of our - // current cell based on the difference from the previous cell - // rather than recalculating from absolute coordinates - // What we cache into the $img_buffer contains the raised text coordinates. - $img_pos_prev = $img_buffer[0][0] = array($box['upper_left']['x'], $box['upper_left']['y']); - $prev_height = $this->wave_height(0, 0, $subdivision_factor); - $full_x = $plane_x * $subdivision_factor; - $full_y = $plane_y * $subdivision_factor; - - for ($x = 1; $x <= $full_x; ++$x) - { - $cur_height = $this->wave_height($x, 0, $subdivision_factor); - $offset = $cur_height - $prev_height; - $img_pos_cur = array($img_pos_prev[0] + $dxx, $img_pos_prev[1] + $dxy + $offset); - - $img_buffer[0][$x] = $img_pos_cur; - $img_pos_prev = $img_pos_cur; - $prev_height = $cur_height; - } - - for ($y = 1; $y <= $full_y; ++$y) - { - // swap buffers - $buffer_cur = $y & 1; - $buffer_prev = 1 - $buffer_cur; - - $prev_height = $this->wave_height(0, $y, $subdivision_factor); - $offset = $prev_height - $this->wave_height(0, $y - 1, $subdivision_factor); - $img_pos_cur = array($img_buffer[$buffer_prev][0][0] + $dyx, min($img_buffer[$buffer_prev][0][1] + $dyy + $offset, $img_y - 1)); - - // make sure we don't try to write off the page - $img_pos_prev = $img_pos_cur; - - $img_buffer[$buffer_cur][0] = $img_pos_cur; - - for ($x = 1; $x <= $full_x; ++$x) - { - $cur_height = $this->wave_height($x, $y, $subdivision_factor) + $this->grid_height($x, $y, $x_grid, $y_grid, 1); - - // height is a z-factor, not a y-factor - $offset = $cur_height - $prev_height; - $img_pos_cur = array($img_pos_prev[0] + $dxx, $img_pos_prev[1] + $dxy + $offset); - - // height is float, index it to an int, get closest color - $color = $colors[intval($cur_height)]; - $img_pos_prev = $img_pos_cur; - $prev_height = $cur_height; - - $y_index_old = intval(($y - 1) / $subdivision_factor); - $y_index_new = intval($y / $subdivision_factor); - $x_index_old = intval(($x - 1) / $subdivision_factor); - $x_index_new = intval($x / $subdivision_factor); - - if ($plane[$y_index_new][$x_index_new]) - { - $img_pos_cur[1] += $this->wave_height($x, $y, $subdivision_factor, 1) - 30 - $cur_height; - $color = $colors[20]; - } - $img_pos_cur[1] = min($img_pos_cur[1], $img_y - 1); - $img_buffer[$buffer_cur][$x] = $img_pos_cur; - - // Smooth the edges as much as possible by having not more than one low<->high triangle per square - // Otherwise, just - $diag_down = !$plane[$y_index_old][$x_index_old] && !$plane[$y_index_new][$x_index_new]; - $diag_up = !$plane[$y_index_old][$x_index_new] && !$plane[$y_index_new][$x_index_old]; - - // natural switching - $mode = ($x + $y) & 1; - - // override if it requires it - if ($diag_down != $diag_up) - { - $mode = $diag_up; - } - - if ($mode) - { - // +-/ / - // 1 |/ 2 /| - // / /-+ - $poly1 = array_merge($img_buffer[$buffer_cur][$x - 1], $img_buffer[$buffer_prev][$x - 1], $img_buffer[$buffer_prev][$x]); - $poly2 = array_merge($img_buffer[$buffer_cur][$x - 1], $img_buffer[$buffer_cur][$x], $img_buffer[$buffer_prev][$x]); - } - else - { - // \ \-+ - // 1 |\ 2 \| - // +-\ \ - $poly1 = array_merge($img_buffer[$buffer_cur][$x - 1], $img_buffer[$buffer_prev][$x - 1], $img_buffer[$buffer_cur][$x]); - $poly2 = array_merge($img_buffer[$buffer_prev][$x - 1], $img_buffer[$buffer_prev][$x], $img_buffer[$buffer_cur][$x]); - } - - imagefilledpolygon($img, $poly1, 3, $color); - imagefilledpolygon($img, $poly2, 3, $color); - } - } - - // Output image - header('Content-Type: image/png'); - header('Cache-control: no-cache, no-store'); - //$mtime = explode(' ', microtime()); - //$totaltime = $mtime[0] + $mtime[1] - $starttime; - - //echo $totaltime . "
\n"; - //echo memory_get_usage() - $tmp; - imagepng($img); - imagedestroy($img); - } - - function wave_height($x, $y, $factor = 1, $tweak = 0.7) - { - // stretch the wave. TODO: pretty it up - $x = $x/5 + 180; - $y = $y/4; - return ((sin($x / (3 * $factor)) + sin($y / (3 * $factor))) * 10 * $tweak); - } - - function grid_height($x, $y, $x_grid, $y_grid, $factor = 1) - { - return ((!($x % ($x_grid * $factor)) || !($y % ($y_grid * $factor))) ? 3 : 0); - } - - function captcha_bitmaps() - { - return array( - 'width' => 9, - 'height' => 13, - 'data' => array( - 'A' => array( - array(0,0,1,1,1,1,0,0,0), - array(0,1,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'B' => array( - array(1,1,1,1,1,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,1,0,0), - array(1,1,1,1,1,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,1,0,0), - array(1,1,1,1,1,1,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'C' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'D' => array( - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'E' => array( - array(0,0,1,1,1,1,1,1,1), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'F' => array( - array(0,0,1,1,1,1,1,1,0), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,1,1,1,1,1,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'G' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'H' => array( - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,1,1,1,1,1,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'I' => array( - array(0,1,1,1,1,1,1,1,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,1,1,1,1,1,1,1,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'J' => array( - array(0,0,0,0,0,0,1,1,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,1,0), - array(0,0,0,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'K' => array( - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,1,0,0,0,0,0), - array(1,0,1,0,0,0,0,0,0), - array(1,1,0,0,0,0,0,0,0), - array(1,0,1,0,0,0,0,0,0), - array(1,0,0,1,0,0,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'L' => array( - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'M' => array( - array(0,1,0,0,0,0,0,1,0), - array(0,1,1,0,0,0,1,1,0), - array(0,1,0,1,0,1,0,1,0), - array(0,1,0,0,1,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'N' => array( - array(1,0,0,0,0,0,0,0,1), - array(1,1,0,0,0,0,0,0,1), - array(1,0,1,0,0,0,0,0,1), - array(1,0,0,1,0,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,0,0,1,0,0,1), - array(1,0,0,0,0,0,1,0,1), - array(1,0,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'O' => array( - array(0,0,0,1,1,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,1,1,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'P' => array( - array(1,1,1,1,1,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,1,0,0), - array(1,1,1,1,1,1,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'Q' => array( - array(0,0,1,1,1,1,0,0,0), - array(0,1,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,1,0,0,1,0), - array(1,0,0,0,0,1,0,1,0), - array(0,1,0,0,0,0,1,0,0), - array(0,0,1,1,1,1,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'R' => array( - array(1,1,1,1,1,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,1,0,0), - array(1,1,1,1,1,1,0,0,0), - array(1,0,1,0,0,0,0,0,0), - array(1,0,0,1,0,0,0,0,0), - array(1,0,0,0,1,0,0,0,0), - array(1,0,0,0,0,1,0,0,0), - array(1,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'S' => array( - array(0,0,1,1,1,1,1,1,1), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(1,1,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'T' => array( - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'U' => array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'V' => array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'W' => array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,1,0,0,0,1), - array(1,0,0,1,0,1,0,0,1), - array(1,0,1,0,0,0,1,0,1), - array(1,1,0,0,0,0,0,1,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'X' => array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'Y' => array( - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,0,0,0,1,0,0), - array(0,0,0,1,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - 'Z' => array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,1), - array(1,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '1' => array( - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,1,0,0,0,0), - array(0,0,1,0,1,0,0,0,0), - array(0,1,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,1,1,1,1,1,1,1,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '2' => array( - array(0,0,0,1,1,1,0,0,0), - array(0,0,1,0,0,0,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,1,0,0,0,0,0), - array(0,0,1,0,0,0,0,0,0), - array(0,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,0,0), - ), - '3' => array( - array(0,0,0,1,1,1,1,0,0), - array(0,0,1,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,1,0), - array(0,0,0,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '4' => array( - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,1,0), - array(0,0,0,0,0,1,0,1,0), - array(0,0,0,0,1,0,0,1,0), - array(0,0,0,1,0,0,0,1,0), - array(0,0,1,0,0,0,0,1,0), - array(0,1,1,1,1,1,1,1,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '5' => array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(0,1,0,0,0,0,0,0,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '6' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,0,0,0,0,0,0), - array(1,0,0,1,1,1,1,0,0), - array(1,0,1,0,0,0,0,1,0), - array(1,1,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '7' => array( - array(1,1,1,1,1,1,1,1,1), - array(1,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,1,0), - array(0,0,0,0,0,0,1,0,0), - array(0,0,0,0,0,1,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,1,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '8' => array( - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,1,0,0,0,0,0,1,0), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(1,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,0), - array(0,0,1,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - '9' => array( - array(0,0,0,1,1,1,1,0,0), - array(0,0,1,0,0,0,0,1,0), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,1,1), - array(0,0,1,1,1,1,1,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,0,0,0,0,0,0,0,1), - array(0,1,0,0,0,0,0,0,1), - array(0,0,1,0,0,0,0,1,0), - array(0,0,0,1,1,1,1,0,0), - array(0,0,0,0,0,0,0,0,0), - array(0,0,0,0,0,0,0,0,0), - ), - ) - ); - } -} diff --git a/phpBB/phpbb/captcha/non_gd.php b/phpBB/phpbb/captcha/non_gd.php deleted file mode 100644 index 881701c7d39..00000000000 --- a/phpBB/phpbb/captcha/non_gd.php +++ /dev/null @@ -1,387 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha; - -/** -* Main non-gd captcha class -* @ignore -*/ -class non_gd -{ - var $filtered_pngs; - var $width = 320; - var $height = 50; - - /** - * Define filtered pngs on init - */ - function __construct() - { - // If we can we will generate a single filtered png, we avoid nastiness via emulation of some Zlib stuff - $this->define_filtered_pngs(); - } - - /** - * Create the image containing $code with a seed of $seed - */ - function execute($code, $seed) - { - $img_height = $this->height - 10; - $img_width = 0; - - mt_srand($seed); - - $char_widths = $hold_chars = array(); - $code_len = strlen($code); - - for ($i = 0; $i < $code_len; $i++) - { - $char = $code[$i]; - - $width = mt_rand(0, 4); - $raw_width = $this->filtered_pngs[$char]['width']; - $char_widths[$i] = $width; - $img_width += $raw_width - $width; - - // Split the char into chunks of $raw_width + 1 length - if (empty($hold_chars[$char])) - { - $hold_chars[$char] = str_split(base64_decode($this->filtered_pngs[$char]['data']), $raw_width + 1); - } - } - - $offset_x = mt_rand(0, $this->width - $img_width); - $offset_y = mt_rand(0, $this->height - $img_height); - - $image = ''; - for ($i = 0; $i < $this->height; $i++) - { - $image .= chr(0); - - if ($i > $offset_y && $i < $offset_y + $img_height) - { - for ($j = 0; $j < $offset_x; $j++) - { - $image .= chr(mt_rand(140, 255)); - } - - for ($j = 0; $j < $code_len; $j++) - { - /** @psalm-suppress InvalidArrayOffset */ - $image .= $this->randomise(substr($hold_chars[$code[$j]][$i - $offset_y - 1], 1), $char_widths[$j]); - } - - for ($j = $offset_x + $img_width; $j < $this->width; $j++) - { - $image .= chr(mt_rand(140, 255)); - } - } - else - { - for ($j = 0; $j < $this->width; $j++) - { - $image .= chr(mt_rand(140, 255)); - } - } - } - unset($hold_chars); - - $image = $this->create_png($image, $this->width, $this->height); - - // Output image - header('Content-Type: image/png'); - header('Cache-control: no-cache, no-store'); - echo $image; - exit; - } - - /** - * This is designed to randomise the pixels of the image data within - * certain limits so as to keep it readable. It also varies the image - * width a little - */ - function randomise($scanline, $width) - { - $new_line = ''; - - $end = strlen($scanline) - ceil($width/2); - for ($i = (int) floor($width / 2); $i < $end; $i++) - { - $pixel = ord($scanline[$i]); - - if ($pixel < 190) - { - $new_line .= chr(mt_rand(0, 205)); - } - else if ($pixel > 190) - { - $new_line .= chr(mt_rand(145, 255)); - } - else - { - $new_line .= $scanline[$i]; - } - } - - return $new_line; - } - - /** - * This creates a chunk of the given type, with the given data - * of the given length adding the relevant crc - */ - function png_chunk($length, $type, $data) - { - $raw = $type . $data; - - return pack('N', $length) . $raw . pack('N', crc32($raw)); - } - - /** - * Creates greyscale 8bit png - The PNG spec can be found at - * http://www.libpng.org/pub/png/spec/PNG-Contents.html we use - * png because it's a fully recognised open standard and supported - * by practically all modern browsers and OSs - */ - function create_png($raw_image, $width, $height) - { - // SIG - $image = pack('C8', 137, 80, 78, 71, 13, 10, 26, 10); - - // IHDR - $raw = pack('N2', $width, $height); - $raw .= pack('C5', 8, 0, 0, 0, 0); - $image .= $this->png_chunk(13, 'IHDR', $raw); - - // IDAT - if (@extension_loaded('zlib')) - { - $raw_image = gzcompress($raw_image); - $length = strlen($raw_image); - } - else - { - // The total length of this image, uncompressed, is just a calculation of pixels - $length = ($width + 1) * $height; - - // Adler-32 hash generation - // Note: The hash is _backwards_ so we must reverse it - - if (@extension_loaded('hash')) - { - $adler_hash = strrev(hash('adler32', $raw_image, true)); - } - else if (@extension_loaded('mhash')) - { - $adler_hash = strrev(mhash(MHASH_ADLER32, $raw_image)); - } - else - { - // Optimized Adler-32 loop ported from the GNU Classpath project - $temp_length = $length; - $s1 = 1; - $s2 = $index = 0; - - while ($temp_length > 0) - { - // We can defer the modulo operation: - // s1 maximally grows from 65521 to 65521 + 255 * 3800 - // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 - $substract_value = ($temp_length < 3800) ? $temp_length : 3800; - $temp_length -= $substract_value; - - while (--$substract_value >= 0) - { - $s1 += ord($raw_image[$index]); - $s2 += $s1; - - $index++; - } - - $s1 %= 65521; - $s2 %= 65521; - } - $adler_hash = pack('N', ($s2 << 16) | $s1); - } - - // This is the same thing as gzcompress($raw_image, 0) but does not need zlib - $raw_image = pack('C3v2', 0x78, 0x01, 0x01, $length, ~$length) . $raw_image . $adler_hash; - - // The Zlib header + Adler hash make us add on 11 - $length += 11; - } - - // IDAT - $image .= $this->png_chunk($length, 'IDAT', $raw_image); - - // IEND - $image .= $this->png_chunk(0, 'IEND', ''); - - return $image; - } - - /** - * png image data - * Each 'data' element is base64_encoded uncompressed IDAT - */ - function define_filtered_pngs() - { - $this->filtered_pngs = array( - '0' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A///////////////////olFAkBAAAGDyA4P///M31/////////////wD////////////////0dAgAAAAAAAAAAAAEcPipFGHn////////////AP//////////////6DAAAAAAAAAAAAAAAAAALSEAN+T///////////8A//////////////xAAAAAAAAAAAAAAAAAAAAAACPA/////////////wD/////////////oAAAAAAAAAAAAAAAAAAAAAAAev//////////////AP////////////8oAAAAAAAAPNj/zDAAAAAAAABD//////////////8A////////////1AAAAAAAABjw////5BAAAAAAAADo/////////////wD///////////+QAAAAAAAAbP//////QgAAAAAAAKj/////////////AP///////////1wAAAAAAACs/////8AXAAAAAAAAcP////////////8A////////////OAAAAAAAAND////dNwAAAAAAAABI/////////////wD///////////8gAAAAAAAA4P//7koACwAAAAAAACT/////////////AP///////////wgAAAAAAAD///VqAwaPAAAAAAAAEP////////////8A////////////AAAAAAAAAP/8kQYDavUAAAAAAAAA/////////////wD///////////8AAAAAAAAA/6kNAEru/wAAAAAAAAD/////////////AP///////////wAAAAAAAADAIwA33f//AAAAAAAAAP////////////8A////////////FAAAAAAAADYAI8D///8AAAAAAAAQ/////////////wD///////////8kAAAAAAAAAA2p////5AAAAAAAACD/////////////AP///////////0gAAAAAAAAFkfz////UAAAAAAAAQP////////////8A////////////cAAAAAAAAET1/////7AAAAAAAABo/////////////wD///////////+oAAAAAAAAXfX/////sAAAAAAAAGj/////////////AAAAALgAAAAAAAAwAAAAAAAAAAAAAAD////////////oAAAAAAAACOT////oEAAAAAAAAOD/////////////AP////////////8+AAAAAAAAKMz/zDQAAAAAAAA0//////////////8A////////////7jgAAAAAAAAAAAAAAAAAAAAAAKT//////////////wD///////////VqAwIAAAAAAAAAAAAAAAAAAAA8////////////////AP//////////rQcDaVEAAAAAAAAAAAAAAAAAKOj///////////////8A///////////nblnu/IAIAAAAAAAAAAAAAFzw/////////////////wD////////////79////+iITCAAAAAgSITg////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////w==', - 'width' => 40 - ), - '1' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////8BAAAAAAAP//////////////////AP////////////////////////9sAAAAAAAA//////////////////8A////////////////////////pAAAAAAAAAD//////////////////wD//////////////////////6wEAAAAAAAAAP//////////////////AP////////////////////h4AAAAAAAAAAAA//////////////////8A//////////////////ygJAAAAAAAAAAAAAD//////////////////wD//////////////9x8HAAAAAAAAAAAAAAAAP//////////////////AP//////////////AAAAAAAAAAAAAAAAAAAA//////////////////8A//////////////8AAAAAAAAAAAAAAAAAAAD//////////////////wD//////////////wAAAAAAAAR4AAAAAAAAAP//////////////////AP//////////////AAAAAAA4zP8AAAAAAAAA//////////////////8A//////////////8AAAA4sP///wAAAAAAAAD//////////////////wD//////////////yR80P//////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '2' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP/////////////////okFAkCAAABCBIfNT///////////////////8A///////////////8hAgAAAAAAAAAAAAAAFTo/////////////////wD//////////////1QAAAAAAAAAAAAAAAAAACjo////////////////AP////////////+MAAAAAAAAAAAAAAAAAAAAADj///////////////8A////////////9BAAAAAAAAAAAAAAAAAAAAAAALD//////////////wD///////////+gAAAAAAAAAHjs+KwMAAAAAAAAVP//////////////AP///////////1gAAAAAAABM/////6QAAAAAAAAU//////////////8A////////////KAAAAAAAALj/////+AAAAAAAAAD//////////////wD///////////+MfGBMOCAI8P/////wAAAAAAAACP//////////////AP///////////////////////////5wAAAAAAAAw//////////////8A///////////////////////////oFAAAAAAAAHz//////////////wD/////////////////////////6CgAAAAAAAAE3P//////////////AP///////////////////////9ggAAAAAAAAAHT///////////////8A//////////////////////+0DAAAAAAAAAA8+P///////////////wD/////////////////////gAAAAAAAAAAAKOj/////////////////AP//////////////////9FAAAAAAAAAAADzw//////////////////8A/////////////////+g4AAAAAAAAAABk/P///////////////////wD////////////////oKAAAAAAAAAAMqP//////////////////////AP//////////////6CgAAAAAAAAAMNz///////////////////////8A//////////////g4AAAAAAAAAFT0/////////////////////////wD/////////////bAAAAAAAAABU/P//////////////////////////AP///////////8wAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A////////////SAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////9wAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////hAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////9AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////xAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '3' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD////////////////8sGg0FAAAACA4cLz8////////////////////AP//////////////rBgAAAAAAAAAAAAAACTA//////////////////8A/////////////3QAAAAAAAAAAAAAAAAAAASs/////////////////wD///////////+YAAAAAAAAAAAAAAAAAAAAAAjc////////////////AP//////////6AwAAAAAAAAAAAAAAAAAAAAAAGT///////////////8A//////////94AAAAAAAABJDw/8g4AAAAAAAAHP///////////////wD//////////yAAAAAAAACE/////9gAAAAAAAAA////////////////AP///////////NSwiGQ4FOT//////AAAAAAAABD///////////////8A//////////////////////////+YAAAAAAAAVP///////////////wD//////////////////////P/ggAQAAAAAAATM////////////////AP////////////////////9gAAAAAAAAAAAElP////////////////8A/////////////////////0AAAAAAAAAAHLj//////////////////wD/////////////////////OAAAAAAAAAAwkPj/////////////////AP////////////////////8gAAAAAAAAAAAAINj///////////////8A/////////////////////xAAAAAAAAAAAAAAIPD//////////////wD/////////////////////uOz/4HgEAAAAAAAAhP//////////////AP///////////////////////////3wAAAAAAAAw//////////////8A////////////////////////////6AAAAAAAAAj//////////////wD/////////////////////////////AAAAAAAAAP//////////////AP//////////tJh8YEQoDNz//////+AAAAAAAAAY//////////////8A//////////88AAAAAAAAaP//////dAAAAAAAAEz//////////////wD//////////6QAAAAAAAAAdOD/5HQAAAAAAAAApP//////////////AP///////////CgAAAAAAAAAAAAAAAAAAAAAACD4//////////////8A////////////yAQAAAAAAAAAAAAAAAAAAAAEuP///////////////wD/////////////rAQAAAAAAAAAAAAAAAAABJD/////////////////AP//////////////zDQAAAAAAAAAAAAAACTA//////////////////8A/////////////////8BwOCAAAAAUNGi0/P///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '4' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////////////////nAAAAAAAAAD///////////////8A/////////////////////////8AEAAAAAAAAAP///////////////wD////////////////////////gGAAAAAAAAAAA////////////////AP//////////////////////9DAAAAAAAAAAAAD///////////////8A//////////////////////9UAAAAAAAAAAAAAP///////////////wD/////////////////////hAAAAAAAAAAAAAAA////////////////AP///////////////////7QAAAAAAAAAAAAAAAD///////////////8A///////////////////UDAAAAAAUAAAAAAAAAP///////////////wD/////////////////7CQAAAAABMAAAAAAAAAA////////////////AP////////////////xEAAAAAACU/wAAAAAAAAD///////////////8A////////////////cAAAAAAAZP//AAAAAAAAAP///////////////wD//////////////6AAAAAAADz8//8AAAAAAAAA////////////////AP/////////////IBAAAAAAc6P///wAAAAAAAAD///////////////8A////////////5BgAAAAADMz/////AAAAAAAAAP///////////////wD///////////g0AAAAAACk//////8AAAAAAAAA////////////////AP//////////XAAAAAAAfP///////wAAAAAAAAD///////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A////////////////////////////AAAAAAAAAP///////////////wD///////////////////////////8AAAAAAAAA////////////////AP///////////////////////////wAAAAAAAAD///////////////8A////////////////////////////AAAAAAAAAP///////////////wD///////////////////////////8AAAAAAAAA////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '5' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////8AAAAAAAAAAAAAAAAAAAAAAA//////////////8A///////////////MAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////////6wAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////////iAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////////9kAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////////0QAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////////IAAAAAAAYP////////////////////////////8A//////////////wAAAAAAAB8/////////////////////////////wD/////////////3AAAAAAAAIj/////////////////////////////AP////////////+4AAAAAAAAoLRYHAAEKGTE//////////////////8A/////////////5QAAAAAAAAQAAAAAAAAAABY9P///////////////wD/////////////dAAAAAAAAAAAAAAAAAAAAAA89P//////////////AP////////////9QAAAAAAAAAAAAAAAAAAAAAABg//////////////8A/////////////zAAAAAAAAAAAAAAAAAAAAAAAADQ/////////////wD/////////////IAAAAAAAAGjY/+h4BAAAAAAAAGz/////////////AP//////////////9NS0lHSc//////90AAAAAAAALP////////////8A/////////////////////////////9QAAAAAAAAE/////////////wD//////////////////////////////wAAAAAAAAD/////////////AP/////////////////////////////8AAAAAAAAEP////////////8A////////////pIRwWEAgDOD//////8wAAAAAAAA8/////////////wD///////////9EAAAAAAAAaP//////ZAAAAAAAAHz/////////////AP///////////6QAAAAAAAAAaOD/4GQAAAAAAAAE4P////////////8A/////////////CQAAAAAAAAAAAAAAAAAAAAAAGD//////////////wD/////////////yAQAAAAAAAAAAAAAAAAAAAAc7P//////////////AP//////////////rAwAAAAAAAAAAAAAAAAAGNj///////////////8A////////////////0EAAAAAAAAAAAAAAAFTo/////////////////wD//////////////////8h4QCAAAAAcQHzU////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '6' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////////////+0ZCwMAAAUNGjI////////////////////AP/////////////////EMAAAAAAAAAAAAABM6P////////////////8A////////////////lAQAAAAAAAAAAAAAAAAo6P///////////////wD//////////////6wAAAAAAAAAAAAAAAAAAABI////////////////AP/////////////oEAAAAAAAAAAAAAAAAAAAAACw//////////////8A/////////////3AAAAAAAAAoxP/YPAAAAAAAAEj//////////////wD////////////4EAAAAAAACOD////YDCBAVGiAoP//////////////AP///////////7gAAAAAAABY//////////////////////////////8A////////////eAAAAAAAAJT//////////////////////////////wD///////////9MAAAAAAAAvP/IXBgABCx03P//////////////////AP///////////ygAAAAAAADcdAAAAAAAAAAEiP////////////////8A////////////FAAAAAAAAFAAAAAAAAAAAAAAcP///////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAlP//////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAQ8P////////////8A////////////AAAAAAAAAABAyP/kZAAAAAAAAACQ/////////////wD///////////8MAAAAAAAALPj/////WAAAAAAAAET/////////////AP///////////yQAAAAAAACY///////MAAAAAAAAFP////////////8A////////////SAAAAAAAAMD///////wAAAAAAAAA/////////////wD///////////9wAAAAAAAAvP///////wAAAAAAAAD/////////////AP///////////7QAAAAAAACI///////UAAAAAAAAJP////////////8A////////////+AwAAAAAACDw/////2wAAAAAAABY/////////////wD/////////////cAAAAAAAADC8/Ox4AAAAAAAAAKj/////////////AP/////////////oEAAAAAAAAAAAAAAAAAAAAAAk/P////////////8A//////////////+oAAAAAAAAAAAAAAAAAAAABLj//////////////wD///////////////+QAAAAAAAAAAAAAAAAAACQ////////////////AP////////////////+0JAAAAAAAAAAAAAAkuP////////////////8A///////////////////8sGg0FAAADCxgqPz//////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '7' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAABP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAy4/////////////wD//////////////////////////+QUAAAAAAAEuP//////////////AP/////////////////////////8QAAAAAAAAKT///////////////8A/////////////////////////4wAAAAAAAB0/////////////////wD////////////////////////cCAAAAAAANPz/////////////////AP///////////////////////0QAAAAAAATY//////////////////8A//////////////////////+0AAAAAAAAeP///////////////////wD//////////////////////CQAAAAAABTw////////////////////AP////////////////////+gAAAAAAAAkP////////////////////8A/////////////////////ywAAAAAABDw/////////////////////wD///////////////////+4AAAAAAAAbP//////////////////////AP///////////////////1wAAAAAAADQ//////////////////////8A///////////////////4DAAAAAAAMP///////////////////////wD//////////////////7QAAAAAAAB8////////////////////////AP//////////////////aAAAAAAAAMj///////////////////////8A//////////////////8oAAAAAAAM/P///////////////////////wD/////////////////8AAAAAAAAET/////////////////////////AP////////////////+0AAAAAAAAcP////////////////////////8A/////////////////4wAAAAAAACY/////////////////////////wD/////////////////WAAAAAAAAMD/////////////////////////AP////////////////80AAAAAAAA4P////////////////////////8A/////////////////xAAAAAAAAD4/////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '8' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD////////////////////IdDQUAAAEIEiA1P//////////////////AP/////////////////gRAAAAAAAAAAAAAAAROD///////////////8A////////////////0BgAAAAAAAAAAAAAAAAAEMj//////////////wD///////////////AcAAAAAAAAAAAAAAAAAAAAHPD/////////////AP//////////////hAAAAAAAAAAAAAAAAAAAAAAAhP////////////8A//////////////8sAAAAAAAAKMz/zCgAAAAAAAAs/////////////wD//////////////wAAAAAAAADM////zAAAAAAAAAD/////////////AP//////////////BAAAAAAAAP//////AAAAAAAABP////////////8A//////////////8sAAAAAAAAzP///9QAAAAAAAAw/////////////wD//////////////3wAAAAAAAAoyP/YNAAAAAAAAIT/////////////AP//////////////7BgAAAAAAAAAAAAAAAAAAAAc8P////////////8A////////////////xBgAAAAAAAAAAAAAAAAAGNj//////////////wD/////////////////tAQAAAAAAAAAAAAAAACo////////////////AP///////////////HAAAAAAAAAAAAAAAAAAAAB8//////////////8A//////////////9gAAAAAAAAAAAAAAAAAAAAAAB8/////////////wD/////////////wAAAAAAAAABk4P/UWAAAAAAAAATQ////////////AP////////////9UAAAAAAAAaP//////XAAAAAAAAGT///////////8A/////////////xgAAAAAAADg///////cAAAAAAAAJP///////////wD/////////////AAAAAAAAAP////////8AAAAAAAAA////////////AP////////////8AAAAAAAAA4P//////3AAAAAAAAAT///////////8A/////////////ygAAAAAAABg//////9cAAAAAAAALP///////////wD/////////////ZAAAAAAAAABY1P/cXAAAAAAAAABw////////////AP/////////////QAAAAAAAAAAAAAAAAAAAAAAAABNz///////////8A//////////////9gAAAAAAAAAAAAAAAAAAAAAAB0/////////////wD///////////////Q8AAAAAAAAAAAAAAAAAAAAUPz/////////////AP////////////////x4CAAAAAAAAAAAAAAAEIT8//////////////8A///////////////////smFQwGAAAABg0ZKT0/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - '9' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////////////ysYCwMAAAUNGiw/P//////////////////AP////////////////+4JAAAAAAAAAAAAAAkuP////////////////8A////////////////lAQAAAAAAAAAAAAAAAAAkP///////////////wD//////////////8AEAAAAAAAAAAAAAAAAAAAAqP//////////////AP/////////////8JAAAAAAAAAAAAAAAAAAAAAAQ7P////////////8A/////////////6wAAAAAAAAAfOz8vCwAAAAAAABw/////////////wD/////////////WAAAAAAAAHD/////7BgAAAAAAAz4////////////AP////////////8kAAAAAAAA1P//////hAAAAAAAALT///////////8A/////////////wAAAAAAAAD///////+4AAAAAAAAcP///////////wD/////////////AAAAAAAAAPz//////8AAAAAAAABI////////////AP////////////8UAAAAAAAAzP//////lAAAAAAAACT///////////8A/////////////0QAAAAAAABY//////gsAAAAAAAADP///////////wD/////////////kAAAAAAAAABw5P/IPAAAAAAAAAAA////////////AP/////////////wEAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A//////////////+UAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD///////////////9wAAAAAAAAAAAAAFAAAAAAAAAU////////////AP////////////////+IBAAAAAAAAABw3AAAAAAAACj///////////8A///////////////////cdCwEABhcxP+8AAAAAAAATP///////////wD//////////////////////////////5AAAAAAAAB4////////////AP//////////////////////////////UAAAAAAAALj///////////8A//////////////+kgGxUQCAM2P///+AIAAAAAAAQ+P///////////wD//////////////0gAAAAAAAA42P/EKAAAAAAAAHD/////////////AP//////////////sAAAAAAAAAAAAAAAAAAAAAAQ6P////////////8A////////////////TAAAAAAAAAAAAAAAAAAAAKz//////////////wD////////////////oKAAAAAAAAAAAAAAAAASU////////////////AP/////////////////sUAAAAAAAAAAAAAAwxP////////////////8A////////////////////yHA0FAAADCxktP///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'A' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////////////////+QAAAAAAAAAAAAAAOT/////////////////AP//////////////////kAAAAAAAAAAAAAAAkP////////////////8A//////////////////88AAAAAAAAAAAAAAA8/////////////////wD/////////////////5AAAAAAAAAAAAAAAAADk////////////////AP////////////////+QAAAAAAAAAAAAAAAAAJD///////////////8A/////////////////zwAAAAAAAAAAAAAAAAAPP///////////////wD////////////////kAAAAAAAAAAgAAAAAAAAA5P//////////////AP///////////////5AAAAAAAAAAgAAAAAAAAACQ//////////////8A////////////////PAAAAAAAAAz8HAAAAAAAADz//////////////wD//////////////+QAAAAAAAAAWP9kAAAAAAAAANz/////////////AP//////////////kAAAAAAAAACk/7wAAAAAAAAAhP////////////8A//////////////88AAAAAAAABOz//BQAAAAAAAAw/////////////wD/////////////4AAAAAAAAAA8////ZAAAAAAAAADc////////////AP////////////+EAAAAAAAAAIj///+8AAAAAAAAAIT///////////8A/////////////zAAAAAAAAAA2P////wQAAAAAAAAMP///////////wD////////////cAAAAAAAAACT//////1wAAAAAAAAA3P//////////AP///////////4QAAAAAAAAAAAAAAAAAAAAAAAAAAACE//////////8A////////////MAAAAAAAAAAAAAAAAAAAAAAAAAAAADD//////////wD//////////9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANz/////////AP//////////hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhP////////8A//////////8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw/////////wD/////////3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADc////////AP////////+EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIT///////8A/////////zAAAAAAAAAAhP///////////2QAAAAAAAAAMP///////wD////////cAAAAAAAAAADM////////////vAAAAAAAAAAA3P//////AP///////4QAAAAAAAAAHP/////////////4DAAAAAAAAACE//////8A////////MAAAAAAAAABk//////////////9cAAAAAAAAADD//////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'B' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAEDh83P///////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAEhP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAeP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAxP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAABY////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAABT///////////8A//////////8AAAAAAAAAAP/////4zEwAAAAAAAAAAP///////////wD//////////wAAAAAAAAAA////////7AAAAAAAAAAQ////////////AP//////////AAAAAAAAAAD////////sAAAAAAAAAEj///////////8A//////////8AAAAAAAAAAP/////4zEQAAAAAAAAAtP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAFz/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAiA/P////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAIjPj//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAGKz/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJT///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAABNz//////////wD//////////wAAAAAAAAAA///////sqCAAAAAAAAAAbP//////////AP//////////AAAAAAAAAAD/////////yAAAAAAAAAAs//////////8A//////////8AAAAAAAAAAP//////////AAAAAAAAAAT//////////wD//////////wAAAAAAAAAA/////////7wAAAAAAAAAAP//////////AP//////////AAAAAAAAAAD//////+ikGAAAAAAAAAAY//////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFT//////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsP//////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAADj///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAc6P///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAATOj/////////////AP//////////AAAAAAAAAAAAAAAAAAAEIEBkkNj///////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'C' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////////5JRULBAAAAgkTIDQ//////////////////8A////////////////1FAAAAAAAAAAAAAAAABAyP///////////////wD//////////////4gEAAAAAAAAAAAAAAAAAAAElP//////////////AP////////////9wAAAAAAAAAAAAAAAAAAAAAAAAlP////////////8A////////////kAAAAAAAAAAAAAAAAAAAAAAAAAAEyP///////////wD//////////9wIAAAAAAAAAAAAAAAAAAAAAAAAAAAw////////////AP//////////WAAAAAAAAAAAWMz/8JwQAAAAAAAAAACw//////////8A/////////+wEAAAAAAAAAID//////9QMAAAAAAAAAET//////////wD/////////nAAAAAAAAAAo/P///////3wAAAAABDBspP//////////AP////////9gAAAAAAAAAIz/////////3BxQjMT0//////////////8A/////////zQAAAAAAAAAzP///////////////////////////////wD/////////GAAAAAAAAADo////////////////////////////////AP////////8AAAAAAAAAAP////////////////////////////////8A/////////wAAAAAAAAAA/////////////////////////////////wD/////////AAAAAAAAAAD/////////////////////////////////AP////////8cAAAAAAAAAOj///////////////////////////////8A/////////zgAAAAAAAAA0P/////////kIGio7P///////////////wD/////////bAAAAAAAAACg/////////5wAAAAAMHS49P//////////AP////////+oAAAAAAAAAEz/////////PAAAAAAAAAAc//////////8A//////////QIAAAAAAAAALz//////6QAAAAAAAAAAGT//////////wD//////////3AAAAAAAAAADIzo/+SEBAAAAAAAAAAAyP//////////AP//////////7BAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A////////////rAAAAAAAAAAAAAAAAAAAAAAAAAAE0P///////////wD/////////////fAAAAAAAAAAAAAAAAAAAAAAAAJz/////////////AP//////////////iAQAAAAAAAAAAAAAAAAAAASY//////////////8A////////////////yEAAAAAAAAAAAAAAAAA8yP///////////////wD//////////////////9yIUCwQAAAAIEB4yP//////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'D' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////8AAAAAAAAAAAAAAAAADChQkOT/////////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAABGjw//////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAACDY/////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAABjk////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKj//////////wD///////////8AAAAAAAAAAP///+isSAAAAAAAAAAANP//////////AP///////////wAAAAAAAAAA////////hAAAAAAAAAAA2P////////8A////////////AAAAAAAAAAD/////////MAAAAAAAAACQ/////////wD///////////8AAAAAAAAAAP////////+MAAAAAAAAAFj/////////AP///////////wAAAAAAAAAA/////////8gAAAAAAAAAMP////////8A////////////AAAAAAAAAAD/////////5AAAAAAAAAAY/////////wD///////////8AAAAAAAAAAP//////////AAAAAAAAAAD/////////AP///////////wAAAAAAAAAA//////////8AAAAAAAAAAP////////8A////////////AAAAAAAAAAD//////////wAAAAAAAAAA/////////wD///////////8AAAAAAAAAAP/////////wAAAAAAAAABD/////////AP///////////wAAAAAAAAAA/////////9QAAAAAAAAAJP////////8A////////////AAAAAAAAAAD/////////qAAAAAAAAABI/////////wD///////////8AAAAAAAAAAP////////9QAAAAAAAAAHj/////////AP///////////wAAAAAAAAAA////////uAAAAAAAAAAAvP////////8A////////////AAAAAAAAAAD////w0HwEAAAAAAAAACT8/////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAADz8//////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAY6P///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAKNz/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAACHT0//////////////8A////////////AAAAAAAAAAAAAAAAABg4bKj0/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'E' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'F' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'G' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////////////////MB8TCgQAAAACCA4YJzs////////////////AP///////////////JQcAAAAAAAAAAAAAAAAAAhw8P////////////8A/////////////9gwAAAAAAAAAAAAAAAAAAAAAAAk2P///////////wD////////////EDAAAAAAAAAAAAAAAAAAAAAAAAAAc7P//////////AP//////////2AwAAAAAAAAAAAAAAAAAAAAAAAAAAABY//////////8A//////////wwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQ/////////wD/////////kAAAAAAAAAAAEHzQ/P/gmCAAAAAAAAAAAFz/////////AP////////wcAAAAAAAAACjg////////8CwAAAAAAAAgWP////////8A////////vAAAAAAAAAAI2P//////////yBRAcJjI8P///////////wD///////94AAAAAAAAAGD/////////////////////////////////AP///////0AAAAAAAAAAsP////////////////////////////////8A////////IAAAAAAAAADc/////////////////////////////////wD///////8AAAAAAAAAAP///////wAAAAAAAAAAAAAAAAD/////////AP///////wAAAAAAAAAA////////AAAAAAAAAAAAAAAAAP////////8A////////AAAAAAAAAAD///////8AAAAAAAAAAAAAAAAA/////////wD///////8gAAAAAAAAAOD//////wAAAAAAAAAAAAAAAAD/////////AP///////0AAAAAAAAAAtP//////AAAAAAAAAAAAAAAAAP////////8A////////cAAAAAAAAABw//////8AAAAAAAAAAAAAAAAA/////////wD///////+8AAAAAAAAABDs////////////AAAAAAAAAAD/////////AP////////wYAAAAAAAAADz0//////////AAAAAAAAAAAP////////8A/////////5AAAAAAAAAAACCY4P//3KhcCAAAAAAAAAAA/////////wD/////////+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////AP//////////xAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIP////////8A////////////rAQAAAAAAAAAAAAAAAAAAAAAAAAAAGTw/////////wD/////////////vBQAAAAAAAAAAAAAAAAAAAAAADjI////////////AP//////////////8HAQAAAAAAAAAAAAAAAAAEiw//////////////8A//////////////////iwcEAgBAAABCA4aKDk/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'H' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'I' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'J' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAj//////////////wD//////////+zMrIxwUDAQ//////wAAAAAAAAAIP//////////////AP//////////DAAAAAAAAADo////2AAAAAAAAAA0//////////////8A//////////8wAAAAAAAAAKj///+YAAAAAAAAAFj//////////////wD//////////2gAAAAAAAAAIND/yBgAAAAAAAAAkP//////////////AP//////////vAAAAAAAAAAAAAAAAAAAAAAAAADc//////////////8A////////////MAAAAAAAAAAAAAAAAAAAAAAAUP///////////////wD////////////EBAAAAAAAAAAAAAAAAAAAABjk////////////////AP////////////+sBAAAAAAAAAAAAAAAAAAY2P////////////////8A///////////////EMAAAAAAAAAAAAAAAVOj//////////////////wD/////////////////vHBAIAAAABg8fNT/////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'K' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////8AAAAAAAAAAP//////////wAQAAAAAAAAAAABw////////AP///////wAAAAAAAAAA/////////9AMAAAAAAAAAAAAcP////////8A////////AAAAAAAAAAD////////cGAAAAAAAAAAAAHD//////////wD///////8AAAAAAAAAAP//////6CgAAAAAAAAAAABs////////////AP///////wAAAAAAAAAA//////Q0AAAAAAAAAAAAVPz///////////8A////////AAAAAAAAAAD////8RAAAAAAAAAAAAFT8/////////////wD///////8AAAAAAAAAAP///1gAAAAAAAAAAABU/P//////////////AP///////wAAAAAAAAAA//9wAAAAAAAAAAAASPz///////////////8A////////AAAAAAAAAAD/jAAAAAAAAAAAADz0/////////////////wD///////8AAAAAAAAAAKQAAAAAAAAAAAA89P//////////////////AP///////wAAAAAAAAAABAAAAAAAAAAAFPT///////////////////8A////////AAAAAAAAAAAAAAAAAAAAAAAApP///////////////////wD///////8AAAAAAAAAAAAAAAAAAAAAAAAU8P//////////////////AP///////wAAAAAAAAAAAAAAAAAAAAAAAABk//////////////////8A////////AAAAAAAAAAAAAAAAAAAAAAAAAADE/////////////////wD///////8AAAAAAAAAAAAAAAAoEAAAAAAAACz8////////////////AP///////wAAAAAAAAAAAAAAGNiAAAAAAAAAAIj///////////////8A////////AAAAAAAAAAAAABjY//gYAAAAAAAACOD//////////////wD///////8AAAAAAAAAAAAY2P///5wAAAAAAAAASP//////////////AP///////wAAAAAAAAAAGNj//////CgAAAAAAAAAqP////////////8A////////AAAAAAAAAADI////////sAAAAAAAAAAc8P///////////wD///////8AAAAAAAAAAP//////////QAAAAAAAAABs////////////AP///////wAAAAAAAAAA///////////IAAAAAAAAAATI//////////8A////////AAAAAAAAAAD///////////9YAAAAAAAAADD8/////////wD///////8AAAAAAAAAAP///////////9wEAAAAAAAAAJD/////////AP///////wAAAAAAAAAA/////////////3AAAAAAAAAADOT///////8A////////AAAAAAAAAAD/////////////7BAAAAAAAAAAUP///////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'L' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'M' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////8AAAAAAAAAAAAAAHz//////3wAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAAAAAATP//////UAAAAAAAAAAAAAAA////////AP//////AAAAAAAAAAAAAAAc//////8cAAAAAAAAAAAAAAD///////8A//////8AAAAAAAAAAAAAAADw////8AAAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAAAAAAALz////AAAAAAAAAAAAAAAAA////////AP//////AAAAAAAAAAAAAAAAkP///5AAAAAAAAAAAAAAAAD///////8A//////8AAAAAAAAAAAAAAABc////ZAAAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAoAAAAADD///8wAAAAACQAAAAAAAAA////////AP//////AAAAAAAAAFwAAAAABPz//AgAAAAAXAAAAAAAAAD///////8A//////8AAAAAAAAAkAAAAAAA0P/UAAAAAACQAAAAAAAAAP///////wD//////wAAAAAAAADMAAAAAACg/6gAAAAAAMQAAAAAAAAA////////AP//////AAAAAAAAAPgEAAAAAHD/dAAAAAAE+AAAAAAAAAD///////8A//////8AAAAAAAAA/zQAAAAAQP9IAAAAADD/AAAAAAAAAP///////wD//////wAAAAAAAAD/bAAAAAAQ/xQAAAAAaP8AAAAAAAAA////////AP//////AAAAAAAAAP+gAAAAAADQAAAAAACc/wAAAAAAAAD///////8A//////8AAAAAAAAA/9QAAAAAAGgAAAAAAND/AAAAAAAAAP///////wD//////wAAAAAAAAD//wwAAAAAFAAAAAAM/P8AAAAAAAAA////////AP//////AAAAAAAAAP//RAAAAAAAAAAAADz//wAAAAAAAAD///////8A//////8AAAAAAAAA//94AAAAAAAAAAAAcP//AAAAAAAAAP///////wD//////wAAAAAAAAD//7AAAAAAAAAAAACo//8AAAAAAAAA////////AP//////AAAAAAAAAP//5AAAAAAAAAAAANz//wAAAAAAAAD///////8A//////8AAAAAAAAA////HAAAAAAAAAAQ////AAAAAAAAAP///////wD//////wAAAAAAAAD///9QAAAAAAAAAEz///8AAAAAAAAA////////AP//////AAAAAAAAAP///4gAAAAAAAAAfP///wAAAAAAAAD///////8A//////8AAAAAAAAA////vAAAAAAAAACw////AAAAAAAAAP///////wD//////wAAAAAAAAD////wAAAAAAAAAOz///8AAAAAAAAA////////AP//////AAAAAAAAAP////8sAAAAAAAc/////wAAAAAAAAD///////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'N' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////AAAAAAAAALD/////////////AAAAAAAAAP//////////AP////////8AAAAAAAAAFOj///////////8AAAAAAAAA//////////8A/////////wAAAAAAAAAASP///////////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAkP//////////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAI1P////////8AAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAw+P///////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAABw////////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAC8//////8AAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAABzs/////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAFD/////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAJz///8AAAAAAAAA//////////8A/////////wAAAAAAAAAUAAAAAAAADNz//wAAAAAAAAD//////////wD/////////AAAAAAAAALQAAAAAAAAANPz/AAAAAAAAAP//////////AP////////8AAAAAAAAA/2wAAAAAAAAAfP8AAAAAAAAA//////////8A/////////wAAAAAAAAD/+CwAAAAAAAAExAAAAAAAAAD//////////wD/////////AAAAAAAAAP//0AQAAAAAAAAgAAAAAAAAAP//////////AP////////8AAAAAAAAA////jAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD/////RAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP/////kFAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAA//////+sAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD///////9kAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP////////QkAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAA/////////8wEAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD//////////4QAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP///////////DwAAAAAAAAAAP//////////AP////////8AAAAAAAAA////////////4BAAAAAAAAAA//////////8A/////////wAAAAAAAAD/////////////qAAAAAAAAAD//////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'O' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A///////////////////0qGw4HAAAABw4aKT0/////////////////wD////////////////wcAwAAAAAAAAAAAAAAAho6P//////////////AP//////////////uBQAAAAAAAAAAAAAAAAAAAAMoP////////////8A/////////////6AEAAAAAAAAAAAAAAAAAAAAAAAAkP///////////wD///////////+4BAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP//////////8BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAM5P////////8A//////////9wAAAAAAAAAAAsrPD/7KQsAAAAAAAAAABg/////////wD/////////+BAAAAAAAAAAUPj///////hQAAAAAAAAAAjs////////AP////////+sAAAAAAAAABDw//////////AYAAAAAAAAAKD///////8A/////////2wAAAAAAAAAdP///////////3wAAAAAAAAAYP///////wD/////////OAAAAAAAAAC4////////////xAAAAAAAAAAw////////AP////////8cAAAAAAAAAOD////////////oAAAAAAAAABT///////8A/////////wAAAAAAAAAA//////////////8AAAAAAAAAAP///////wD/////////AAAAAAAAAAD//////////////wAAAAAAAAAA////////AP////////8AAAAAAAAAAP/////////////8AAAAAAAAAAD///////8A/////////xwAAAAAAAAA5P///////////+AAAAAAAAAAHP///////wD/////////NAAAAAAAAAC8////////////uAAAAAAAAAA4////////AP////////9oAAAAAAAAAHj///////////98AAAAAAAAAGT///////8A/////////6gAAAAAAAAAGPD/////////+BgAAAAAAAAApP///////wD/////////9AwAAAAAAAAAUPz///////xcAAAAAAAAAAjs////////AP//////////cAAAAAAAAAAALKjs//CwOAAAAAAAAAAAYP////////8A///////////wFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzk/////////wD///////////+4BAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP////////////+QAAAAAAAAAAAAAAAAAAAAAAAAAJD///////////8A//////////////+sEAAAAAAAAAAAAAAAAAAAAAyg/////////////wD////////////////oZAgAAAAAAAAAAAAAAARg4P//////////////AP//////////////////9KhsOCAAAAAUMFyc7P////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'P' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////////wAAAAAAAAAAAAAAAAAACCxguP////////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAOOD//////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAGOD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAARP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAxP///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAABo////////////AP///////////wAAAAAAAAAA////6JwMAAAAAAAAADD///////////8A////////////AAAAAAAAAAD//////6AAAAAAAAAADP///////////wD///////////8AAAAAAAAAAP//////9AAAAAAAAAAA////////////AP///////////wAAAAAAAAAA///////0AAAAAAAAAAD///////////8A////////////AAAAAAAAAAD//////5gAAAAAAAAAHP///////////wD///////////8AAAAAAAAAAP///9iICAAAAAAAAABI////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAJD///////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAI6P///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAIT/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAABU/P////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAIhPz//////////////wD///////////8AAAAAAAAAAAAAAAAABCRMkOz/////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'Q' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////SoaDQcAAAAHDhoqPT///////////////////8A//////////////BwDAAAAAAAAAAAAAAACHDo/////////////////wD///////////+4FAAAAAAAAAAAAAAAAAAAABCo////////////////AP//////////nAQAAAAAAAAAAAAAAAAAAAAAAACQ//////////////8A/////////7gEAAAAAAAAAAAAAAAAAAAAAAAAAACg/////////////wD////////wFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzo////////////AP///////3AAAAAAAAAAACyo8P/sqCwAAAAAAAAAAGT///////////8A///////4EAAAAAAAAABM+P///////FQAAAAAAAAACPT//////////wD//////7AAAAAAAAAAFPD/////////9BgAAAAAAAAApP//////////AP//////bAAAAAAAAAB4////////////fAAAAAAAAABk//////////8A//////84AAAAAAAAALz///////////+8AAAAAAAAADT//////////wD//////xwAAAAAAAAA6P///////////+QAAAAAAAAAHP//////////AP//////AAAAAAAAAAD//////////////wAAAAAAAAAA//////////8A//////8AAAAAAAAAAP//////////////AAAAAAAAAAD//////////wD//////wAAAAAAAAAA/P////////////8AAAAAAAAAAP//////////AP//////GAAAAAAAAADg////////////4AAAAAAAAAAc//////////8A//////84AAAAAAAAALT////MJHTo//+8AAAAAAAAADT//////////wD//////2wAAAAAAAAAdP///2AAABCg/3wAAAAAAAAAZP//////////AP//////rAAAAAAAAAAY9P/sCAAAAABMGAAAAAAAAACk//////////8A///////4EAAAAAAAAABU/P+0OAAAAAAAAAAAAAAACPT//////////wD///////94AAAAAAAAAAA4sPD/gAAAAAAAAAAAAABk////////////AP////////AcAAAAAAAAAAAAAAAAAAAAAAAAAAAADOT///////////8A/////////7wEAAAAAAAAAAAAAAAAAAAAAAAAAACQ/////////////wD//////////6wEAAAAAAAAAAAAAAAAAAAAAAAAABSs////////////AP///////////7gUAAAAAAAAAAAAAAAAAAAAAAAAAABAwP////////8A//////////////BwDAAAAAAAAAAAAAAABAgAAAAAAAA8/////////wD////////////////0qGg0GAAAABgwXJjkxBgAAAAAALD/////////AP//////////////////////////////////5DQAAAAk/P////////8A////////////////////////////////////+GwAAJD//////////wD//////////////////////////////////////8A49P//////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'R' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////wAAAAAAAAAAAAAAAAAAAAQgOGSk+P///////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAcuP//////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAEsP////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ6P///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD///////////8A/////////wAAAAAAAAAA///////svDgAAAAAAAAACP///////////wD/////////AAAAAAAAAAD/////////7AAAAAAAAAAA////////////AP////////8AAAAAAAAAAP/////////cAAAAAAAAABD///////////8A/////////wAAAAAAAAAA//////DQoCQAAAAAAAAAQP///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAACU////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIPj///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAzU/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAA02P//////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAxctPz///////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAEDY/////////////////wD/////////AAAAAAAAAAD/9LAsAAAAAAAAAAzc////////////////AP////////8AAAAAAAAAAP///+wkAAAAAAAAADD8//////////////8A/////////wAAAAAAAAAA/////8QAAAAAAAAAAJD//////////////wD/////////AAAAAAAAAAD//////1QAAAAAAAAAFPD/////////////AP////////8AAAAAAAAAAP//////3AQAAAAAAAAAgP////////////8A/////////wAAAAAAAAAA////////aAAAAAAAAAAM6P///////////wD/////////AAAAAAAAAAD////////oCAAAAAAAAABs////////////AP////////8AAAAAAAAAAP////////+AAAAAAAAAAATc//////////8A/////////wAAAAAAAAAA//////////AUAAAAAAAAAFj//////////wD/////////AAAAAAAAAAD//////////5AAAAAAAAAAAND/////////AP////////8AAAAAAAAAAP//////////+CQAAAAAAAAAQP////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'S' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP/////////////////8vHBEIAgAAAQgQHC8/P////////////////8A////////////////pCQAAAAAAAAAAAAAAAAcoP///////////////wD//////////////FwAAAAAAAAAAAAAAAAAAAAAXP//////////////AP////////////9oAAAAAAAAAAAAAAAAAAAAAAAAhP////////////8A////////////zAAAAAAAAAAAAAAAAAAAAAAAAAAI6P///////////wD///////////9cAAAAAAAAAAAAAAAAAAAAAAAAAACA////////////AP///////////xgAAAAAAAAAUOD/8KwkAAAAAAAAADj///////////8A////////////AAAAAAAAAAD0/////8wABCAgICxASP///////////wD///////////8MAAAAAAAAAMz/////////////////////////////AP///////////0AAAAAAAAAACFiQxPT///////////////////////8A////////////oAAAAAAAAAAAAAAAADBwtPT//////////////////wD////////////8QAAAAAAAAAAAAAAAAAAACFTA////////////////AP/////////////oOAAAAAAAAAAAAAAAAAAAAABM6P////////////8A///////////////4fAgAAAAAAAAAAAAAAAAAAAAY2P///////////wD/////////////////7IwwAAAAAAAAAAAAAAAAAAAo+P//////////AP/////////////////////koGw0BAAAAAAAAAAAAACU//////////8A///////////////////////////4uFgAAAAAAAAAADz//////////wD//////////2BgSEA0IBwA6P///////5QAAAAAAAAADP//////////AP//////////JAAAAAAAAACc/////////AAAAAAAAAAA//////////8A//////////9YAAAAAAAAACDo///////AAAAAAAAAABT//////////wD//////////6QAAAAAAAAAACCk7P/snBQAAAAAAAAAUP//////////AP//////////+BAAAAAAAAAAAAAAAAAAAAAAAAAAAACs//////////8A////////////kAAAAAAAAAAAAAAAAAAAAAAAAAAAOP///////////wD////////////8RAAAAAAAAAAAAAAAAAAAAAAAABjc////////////AP/////////////0PAAAAAAAAAAAAAAAAAAAAAAg2P////////////8A///////////////8hBQAAAAAAAAAAAAAAAAMdPT//////////////wD/////////////////+LRwSCAMAAAAHDhoqPT/////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'T' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'U' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////JAAAAAAAAADk/////////+gAAAAAAAAAHP//////////AP////////9MAAAAAAAAAJz/////////nAAAAAAAAABE//////////8A/////////4gAAAAAAAAAHOj//////+ggAAAAAAAAAHz//////////wD/////////0AAAAAAAAAAAIJzs/+ykIAAAAAAAAAAA0P//////////AP//////////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A///////////IBAAAAAAAAAAAAAAAAAAAAAAAAAAE0P///////////wD///////////+YAAAAAAAAAAAAAAAAAAAAAAAAAJj/////////////AP////////////+UBAAAAAAAAAAAAAAAAAAAAASU//////////////8A///////////////IPAAAAAAAAAAAAAAAAAAwyP///////////////wD/////////////////0IxYOCAIAAAEIEiAyP//////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'V' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////zAAAAAAAAAAYP//////////////ZAAAAAAAAAAw////////AP//////kAAAAAAAAAAU/P////////////8UAAAAAAAAAJD///////8A///////oBAAAAAAAAADE////////////xAAAAAAAAAAE7P///////wD///////9MAAAAAAAAAHD///////////94AAAAAAAAAEz/////////AP///////6gAAAAAAAAAJP///////////yQAAAAAAAAArP////////8A////////+BAAAAAAAAAA1P/////////YAAAAAAAAABT4/////////wD/////////aAAAAAAAAACE/////////4QAAAAAAAAAbP//////////AP/////////EAAAAAAAAADT/////////OAAAAAAAAADM//////////8A//////////8kAAAAAAAAAOT//////+QAAAAAAAAAKP///////////wD//////////4QAAAAAAAAAmP//////nAAAAAAAAACI////////////AP//////////5AAAAAAAAABE//////9EAAAAAAAABOT///////////8A////////////QAAAAAAAAAT0////9AgAAAAAAABI/////////////wD///////////+gAAAAAAAAAKT///+kAAAAAAAAAKj/////////////AP////////////QIAAAAAAAAXP///1wAAAAAAAAM+P////////////8A/////////////1wAAAAAAAAM+P/8DAAAAAAAAGT//////////////wD/////////////vAAAAAAAAAC8/7wAAAAAAAAAxP//////////////AP//////////////HAAAAAAAAGj/aAAAAAAAACT///////////////8A//////////////94AAAAAAAAHP8cAAAAAAAAhP///////////////wD//////////////9gAAAAAAAAAkAAAAAAAAADk////////////////AP///////////////zgAAAAAAAAQAAAAAAAAQP////////////////8A////////////////lAAAAAAAAAAAAAAAAACg/////////////////wD////////////////sCAAAAAAAAAAAAAAADPT/////////////////AP////////////////9QAAAAAAAAAAAAAABg//////////////////8A/////////////////7AAAAAAAAAAAAAAAMD//////////////////wD//////////////////BQAAAAAAAAAAAAc////////////////////AP//////////////////cAAAAAAAAAAAAHz///////////////////8A///////////////////MAAAAAAAAAAAA3P///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'W' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//8cAAAAAAAAALz/////4AAAAAAAAAAA6P////+8AAAAAAAAABz//wD//1QAAAAAAAAAjP////+gAAAAAAAAAACo/////4wAAAAAAAAAUP//AP//jAAAAAAAAABU/////2AAAAAAAAAAAGj/////VAAAAAAAAACM//8A///EAAAAAAAAACT/////IAAAAAAAAAAAKP////8kAAAAAAAAAMT//wD///gEAAAAAAAAAPD//+AAAAAAAAAAAAAA6P//8AAAAAAAAAAE9P//AP///zAAAAAAAAAAvP//oAAAAAAAAAAAAACo//+8AAAAAAAAADD///8A////bAAAAAAAAACM//9gAAAAAAAAAAAAAGT//4wAAAAAAAAAaP///wD///+kAAAAAAAAAFT//yAAAAAAAAAAAAAAIP//VAAAAAAAAACc////AP///9gAAAAAAAAAJP/gAAAAAAAAAAAAAAAA4P8kAAAAAAAAANT///8A/////xAAAAAAAAAA8KAAAAAAAAAAAAAAAACg8AAAAAAAAAAQ/////wD/////TAAAAAAAAAC8YAAAAAAAAAAAAAAAAGC8AAAAAAAAAET/////AP////+AAAAAAAAAAIwgAAAAAAAAAAAAAAAAIIwAAAAAAAAAfP////8A/////7gAAAAAAAAANAAAAAAAACwwAAAAAAAANAAAAAAAAACw/////wD/////8AAAAAAAAAAAAAAAAAAAdHgAAAAAAAAAAAAAAAAAAOz/////AP//////KAAAAAAAAAAAAAAAAAC4vAAAAAAAAAAAAAAAAAAg//////8A//////9gAAAAAAAAAAAAAAAACPj4CAAAAAAAAAAAAAAAAFj//////wD//////5QAAAAAAAAAAAAAAABE//9IAAAAAAAAAAAAAAAAkP//////AP//////0AAAAAAAAAAAAAAAAIj//4wAAAAAAAAAAAAAAADI//////8A///////8DAAAAAAAAAAAAAAAzP//1AAAAAAAAAAAAAAABPj//////wD///////88AAAAAAAAAAAAABT/////GAAAAAAAAAAAAAA0////////AP///////3QAAAAAAAAAAAAAWP////9gAAAAAAAAAAAAAHD///////8A////////sAAAAAAAAAAAAACg/////6QAAAAAAAAAAAAApP///////wD////////kAAAAAAAAAAAAAOT/////6AAAAAAAAAAAAADc////////AP////////8cAAAAAAAAAAAo////////MAAAAAAAAAAAEP////////8A/////////1QAAAAAAAAAAHD///////94AAAAAAAAAABM/////////wD/////////jAAAAAAAAAAAtP///////7wAAAAAAAAAAID/////////AP/////////EAAAAAAAAAAT0////////+AgAAAAAAAAAuP////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'X' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////9UAAAAAAAAAKz///////////+sAAAAAAAAAFD/////////AP///////+QQAAAAAAAAFOT/////////8BwAAAAAAAAM5P////////8A/////////5gAAAAAAAAATP////////9kAAAAAAAAAJD//////////wD//////////0AAAAAAAAAAoP//////wAAAAAAAAAA0/P//////////AP//////////2AgAAAAAAAAQ4P////gkAAAAAAAABMz///////////8A////////////iAAAAAAAAABA////dAAAAAAAAABw/////////////wD////////////8MAAAAAAAAACU/9AEAAAAAAAAHPD/////////////AP/////////////IBAAAAAAAAAzYMAAAAAAAAACs//////////////8A//////////////90AAAAAAAAABAAAAAAAAAATP///////////////wD///////////////QgAAAAAAAAAAAAAAAAAAzg////////////////AP///////////////7wAAAAAAAAAAAAAAAAAjP////////////////8A/////////////////2AAAAAAAAAAAAAAADD8/////////////////wD/////////////////7BQAAAAAAAAAAAAEyP//////////////////AP/////////////////gDAAAAAAAAAAAAAjY//////////////////8A/////////////////0AAAAAAAAAAAAAAADj8/////////////////wD///////////////+UAAAAAAAAAAAAAAAAAJD/////////////////AP//////////////4AwAAAAAAAAAAAAAAAAADOD///////////////8A//////////////9AAAAAAAAAAAAAAAAAAAAAQP///////////////wD/////////////nAAAAAAAAAAAWAAAAAAAAAAAlP//////////////AP///////////+QQAAAAAAAAAGD/YAAAAAAAAAAM4P////////////8A////////////TAAAAAAAAAAs9P/0LAAAAAAAAABM/////////////wD//////////6AAAAAAAAAADNT////UDAAAAAAAAACg////////////AP/////////kEAAAAAAAAACg//////+gAAAAAAAAABDk//////////8A/////////0wAAAAAAAAAYP////////9gAAAAAAAAAEz//////////wD///////+oAAAAAAAAACz0//////////QsAAAAAAAAAKT/////////AP//////7BQAAAAAAAAM1P///////////9QMAAAAAAAAFOz///////8A//////9UAAAAAAAAAKD//////////////6AAAAAAAAAAVP///////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'Y' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////1QAAAAAAAAAAGj//////////2gAAAAAAAAAAFT///////8A////////5BAAAAAAAAAAAMT////////EAAAAAAAAAAAQ5P///////wD/////////mAAAAAAAAAAAKPj/////+CgAAAAAAAAAAJj/////////AP//////////PAAAAAAAAAAAgP////+AAAAAAAAAAAA8//////////8A///////////YCAAAAAAAAAAE2P//2AQAAAAAAAAACNj//////////wD///////////+AAAAAAAAAAAA4//84AAAAAAAAAACA////////////AP////////////woAAAAAAAAAACUlAAAAAAAAAAAKPz///////////8A/////////////8gAAAAAAAAAABAQAAAAAAAAAADI/////////////wD//////////////2wAAAAAAAAAAAAAAAAAAAAAbP//////////////AP//////////////8BwAAAAAAAAAAAAAAAAAABzw//////////////8A////////////////tAAAAAAAAAAAAAAAAAAAtP///////////////wD/////////////////VAAAAAAAAAAAAAAAAFT/////////////////AP/////////////////oEAAAAAAAAAAAAAAQ6P////////////////8A//////////////////+cAAAAAAAAAAAAAJz//////////////////wD///////////////////9AAAAAAAAAAABA////////////////////AP///////////////////9gAAAAAAAAAANj///////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - 'Z' => array( - 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAQ//////////////8A/////////////////////////1AAAAAAAAAABLz//////////////wD///////////////////////98AAAAAAAAAACY////////////////AP//////////////////////pAAAAAAAAAAAaP////////////////8A/////////////////////8QIAAAAAAAAAET8/////////////////wD////////////////////gGAAAAAAAAAAo9P//////////////////AP//////////////////9CwAAAAAAAAAFNz///////////////////8A//////////////////xMAAAAAAAAAATA/////////////////////wD/////////////////eAAAAAAAAAAAnP//////////////////////AP///////////////5wAAAAAAAAAAHT///////////////////////8A///////////////ABAAAAAAAAABM/P///////////////////////wD/////////////3BQAAAAAAAAALPT/////////////////////////AP////////////QoAAAAAAAAABjg//////////////////////////8A///////////8SAAAAAAAAAAExP///////////////////////////wD//////////2wAAAAAAAAAAKD/////////////////////////////AP////////+YAAAAAAAAAAB8//////////////////////////////8A/////////wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', - 'width' => 40 - ), - ); - } -} diff --git a/phpBB/phpbb/captcha/plugins/gd.php b/phpBB/phpbb/captcha/plugins/gd.php deleted file mode 100644 index 6d3c9bb3d28..00000000000 --- a/phpBB/phpbb/captcha/plugins/gd.php +++ /dev/null @@ -1,123 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha\plugins; - -class gd extends captcha_abstract -{ - var $captcha_vars = array( - 'captcha_gd_x_grid' => 'CAPTCHA_GD_X_GRID', - 'captcha_gd_y_grid' => 'CAPTCHA_GD_Y_GRID', - 'captcha_gd_foreground_noise' => 'CAPTCHA_GD_FOREGROUND_NOISE', -// 'captcha_gd' => 'CAPTCHA_GD_PREVIEWED', - 'captcha_gd_wave' => 'CAPTCHA_GD_WAVE', - 'captcha_gd_3d_noise' => 'CAPTCHA_GD_3D_NOISE', - 'captcha_gd_fonts' => 'CAPTCHA_GD_FONTS', - ); - - public function is_available() - { - return @extension_loaded('gd'); - } - - /** - * @return string the name of the class used to generate the captcha - */ - function get_generator_class() - { - return '\\phpbb\\captcha\\gd'; - } - - /** - * API function - */ - function has_config() - { - return true; - } - - public function get_name() - { - return 'CAPTCHA_GD'; - } - - function acp_page($id, $module) - { - global $user, $template, $phpbb_log, $request; - global $config; - - $user->add_lang('acp/board'); - - $module->tpl_name = 'captcha_gd_acp'; - $module->page_title = 'ACP_VC_SETTINGS'; - $form_key = 'acp_captcha'; - add_form_key($form_key); - - $submit = $request->variable('submit', ''); - - if ($submit && check_form_key($form_key)) - { - $captcha_vars = array_keys($this->captcha_vars); - foreach ($captcha_vars as $captcha_var) - { - $value = $request->variable($captcha_var, 0); - if ($value >= 0) - { - $config->set($captcha_var, $value); - } - } - - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); - trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($module->u_action)); - } - else if ($submit) - { - trigger_error($user->lang['FORM_INVALID'] . adm_back_link($module->u_action)); - } - else - { - foreach ($this->captcha_vars as $captcha_var => $template_var) - { - $var = (isset($_REQUEST[$captcha_var])) ? $request->variable($captcha_var, 0) : $config[$captcha_var]; - $template->assign_var($template_var, $var); - } - - $template->assign_vars(array( - 'CAPTCHA_PREVIEW' => $this->get_demo_template($id), - 'CAPTCHA_NAME' => $this->get_service_name(), - 'U_ACTION' => $module->u_action, - )); - } - } - - function execute_demo() - { - global $config, $request; - - $config_old = $config; - - $config = new \phpbb\config\config(array()); - foreach ($config_old as $key => $value) - { - $config->set($key, $value); - } - - foreach ($this->captcha_vars as $captcha_var => $template_var) - { - $config->set($captcha_var, $request->variable($captcha_var, (int) $config[$captcha_var])); - } - parent::execute_demo(); - $config = $config_old; - } - -} diff --git a/phpBB/phpbb/captcha/plugins/gd_wave.php b/phpBB/phpbb/captcha/plugins/gd_wave.php deleted file mode 100644 index 4ac26ed2b72..00000000000 --- a/phpBB/phpbb/captcha/plugins/gd_wave.php +++ /dev/null @@ -1,42 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha\plugins; - -class gd_wave extends captcha_abstract -{ - public function is_available() - { - return @extension_loaded('gd'); - } - - public function get_name() - { - return 'CAPTCHA_GD_3D'; - } - - /** - * @return string the name of the class used to generate the captcha - */ - function get_generator_class() - { - return '\\phpbb\\captcha\\gd_wave'; - } - - function acp_page($id, $module) - { - global $user; - - trigger_error($user->lang['CAPTCHA_NO_OPTIONS'] . adm_back_link($module->u_action)); - } -} diff --git a/phpBB/phpbb/captcha/plugins/nogd.php b/phpBB/phpbb/captcha/plugins/nogd.php deleted file mode 100644 index da67cd2bf41..00000000000 --- a/phpBB/phpbb/captcha/plugins/nogd.php +++ /dev/null @@ -1,42 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\captcha\plugins; - -class nogd extends captcha_abstract -{ - public function is_available() - { - return true; - } - - public function get_name() - { - return 'CAPTCHA_NO_GD'; - } - - /** - * @return string the name of the class used to generate the captcha - */ - function get_generator_class() - { - return '\\phpbb\\captcha\\non_gd'; - } - - function acp_page($id, $module) - { - global $user; - - trigger_error($user->lang['CAPTCHA_NO_OPTIONS'] . adm_back_link($module->u_action)); - } -} diff --git a/tests/auth/provider_db_test.php b/tests/auth/provider_db_test.php index c3a0743e144..145e3c47cda 100644 --- a/tests/auth/provider_db_test.php +++ b/tests/auth/provider_db_test.php @@ -45,14 +45,14 @@ public function test_login() $phpbb_container = new phpbb_mock_container_builder(); $plugins = new \phpbb\di\service_collection($phpbb_container); - $plugins->add('core.captcha.plugins.nogd'); + $plugins->add('core.captcha.plugins.qa'); $phpbb_container->set( 'captcha.factory', new \phpbb\captcha\factory($phpbb_container, $plugins) ); $phpbb_container->set( - 'core.captcha.plugins.nogd', - new \phpbb\captcha\plugins\nogd() + 'core.captcha.plugins.qa', + new \phpbb\captcha\plugins\qa('', '', '') ); /** @var \phpbb\captcha\factory $captcha_factory */ $captcha_factory = $phpbb_container->get('captcha.factory'); diff --git a/tests/functions/user_delete_test.php b/tests/functions/user_delete_test.php index 41951c29bc4..956c61b15dd 100644 --- a/tests/functions/user_delete_test.php +++ b/tests/functions/user_delete_test.php @@ -62,14 +62,14 @@ protected function setUp(): void $passwords_manager = new \phpbb\passwords\manager($config, $passwords_drivers, $passwords_helper, array_keys($passwords_drivers)); $plugins = new \phpbb\di\service_collection($phpbb_container); - $plugins->add('core.captcha.plugins.nogd'); + $plugins->add('core.captcha.plugins.qa'); $phpbb_container->set( 'captcha.factory', new \phpbb\captcha\factory($phpbb_container, $plugins) ); $phpbb_container->set( - 'core.captcha.plugins.nogd', - new \phpbb\captcha\plugins\nogd() + 'core.captcha.plugins.qa', + new \phpbb\captcha\plugins\qa('', '', '') ); // Set up passwords manager $db_auth_provider = new \phpbb\auth\provider\db( diff --git a/tests/session/garbage_collection_test.php b/tests/session/garbage_collection_test.php index 9080478a285..e8117f08c57 100644 --- a/tests/session/garbage_collection_test.php +++ b/tests/session/garbage_collection_test.php @@ -30,14 +30,14 @@ protected function setUp(): void global $phpbb_container; $plugins = new \phpbb\di\service_collection($phpbb_container); - $plugins->add('core.captcha.plugins.nogd'); + $plugins->add('core.captcha.plugins.recaptcha'); $phpbb_container->set( 'captcha.factory', new \phpbb\captcha\factory($phpbb_container, $plugins) ); $phpbb_container->set( - 'core.captcha.plugins.nogd', - new \phpbb\captcha\plugins\nogd() + 'core.captcha.plugins.recaptcha', + new \phpbb\captcha\plugins\recaptcha() ); } @@ -77,7 +77,7 @@ public function test_session_gc() ); // There is an error unless the captcha plugin is set - $config['captcha_plugin'] = 'core.captcha.plugins.nogd'; + $config['captcha_plugin'] = 'core.captcha.plugins.recaptcha'; $this->session->session_gc(); $this->check_expired_sessions_recent( [], @@ -132,7 +132,7 @@ public function test_cleanup_all() global $config; $config['session_length'] = 0; // There is an error unless the captcha plugin is set - $config['captcha_plugin'] = 'core.captcha.plugins.nogd'; + $config['captcha_plugin'] = 'core.captcha.plugins.recaptcha'; $this->session->session_gc(); $this->check_sessions_equals( [], From 5e9d8662d18cc743c978d7f5ef288fb26b6b6729 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 26 Mar 2024 17:28:33 +0100 Subject: [PATCH 0072/1214] [ticket/12960] Use test api key for recaptcha v2 demo PHPBB3-12960 --- phpBB/adm/style/captcha_recaptcha.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/phpBB/adm/style/captcha_recaptcha.html b/phpBB/adm/style/captcha_recaptcha.html index 563bd988354..fe274d5ca13 100644 --- a/phpBB/adm/style/captcha_recaptcha.html +++ b/phpBB/adm/style/captcha_recaptcha.html @@ -1,4 +1,3 @@ -
- -{L_RECAPTCHA_NOT_AVAILABLE} - From 716c1ca8eb397609857792f360dd3083326197a0 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 26 Mar 2024 21:00:31 +0100 Subject: [PATCH 0073/1214] [ticket/12960] Add incomplete captcha to prevent registration PHPBB3-12960 --- .../default/container/services_captcha.yml | 5 ++ phpBB/language/en/ucp.php | 1 + phpBB/phpbb/captcha/plugins/incomplete.php | 66 +++++++++++++++++++ .../template/captcha_incomplete.html | 7 ++ 4 files changed, 79 insertions(+) create mode 100644 phpBB/phpbb/captcha/plugins/incomplete.php create mode 100644 phpBB/styles/prosilver/template/captcha_incomplete.html diff --git a/phpBB/config/default/container/services_captcha.yml b/phpBB/config/default/container/services_captcha.yml index f3f9476f861..8e9d829b47e 100644 --- a/phpBB/config/default/container/services_captcha.yml +++ b/phpBB/config/default/container/services_captcha.yml @@ -17,6 +17,11 @@ services: core.captcha.plugins.incomplete: class: phpbb\captcha\plugins\incomplete shared: false + arguments: + - '@config' + - '@template' + - '%core.root_path%' + - '%core.php_ext%' calls: - [ set_name, [ core.captcha.plugins.incomplete ] ] tags: diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 3d90f15742d..75964fde884 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -136,6 +136,7 @@ 'CONFIRMATION' => 'Confirmation of registration', 'CONFIRM_CHANGES' => 'Confirm changes', 'CONFIRM_EXPLAIN' => 'To prevent automated registrations the board requires you to enter a confirmation code. The code is displayed in the image you should see below. If you are visually impaired or cannot otherwise read this code please contact the %sBoard Administrator%s.', + 'CONFIRM_INCOMPLETE' => 'To prevent automated registrations the board requires you to enter a confirmation code. This has not been configured yet and hence cannot be used. Please try again later or contact the %sBoard Administrator%s.', 'VC_REFRESH' => 'Refresh confirmation code', 'VC_REFRESH_EXPLAIN' => 'If you cannot read the code you can request a new one by clicking the button.', diff --git a/phpBB/phpbb/captcha/plugins/incomplete.php b/phpBB/phpbb/captcha/plugins/incomplete.php new file mode 100644 index 00000000000..9669931761f --- /dev/null +++ b/phpBB/phpbb/captcha/plugins/incomplete.php @@ -0,0 +1,66 @@ +config, $this->phpbb_root_path, $this->phpEx); + + $this->template->assign_vars([ + 'CONFIRM_LANG' => $this->type != CONFIRM_POST ? 'CONFIRM_INCOMPLETE' : 'POST_CONFIRM_INCOMPLETE', + 'CONTACT_LINK' => $contact_link, + ]); + + return 'captcha_incomplete.html'; + } + + public function validate() + { + return false; + } + + public function is_solved() + { + return false; + } +} \ No newline at end of file diff --git a/phpBB/styles/prosilver/template/captcha_incomplete.html b/phpBB/styles/prosilver/template/captcha_incomplete.html new file mode 100644 index 00000000000..ed6b0af156b --- /dev/null +++ b/phpBB/styles/prosilver/template/captcha_incomplete.html @@ -0,0 +1,7 @@ +
+
+ +

{{ lang('CONFIRMATION') }}

+

{{ lang(CONFIRM_LANG, '', '') }}

+
+
From e333d96eef40e90b1f954a4eeda7c1fdf269bfa4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 4 Apr 2024 21:02:52 +0200 Subject: [PATCH 0074/1214] [ticket/12960] Adjust incomplete captcha to fix tests PHPBB3-12960 --- phpBB/language/en/acp/board.php | 4 ++-- phpBB/language/en/common.php | 1 + phpBB/language/en/ucp.php | 1 - phpBB/styles/prosilver/template/captcha_incomplete.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index b86e0b6335c..c57cefe6857 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -304,11 +304,11 @@ // Visual Confirmation Settings $lang = array_merge($lang, array( - 'ACP_VC_SETTINGS_EXPLAIN' => 'Here you can select and configure plugins, which are designed to block automated form submissions by spambots. These plugins typically work by challenging the user with a CAPTCHA, a test which is designed to be difficult for computers to solve.', + 'ACP_VC_SETTINGS_EXPLAIN' => 'Here you can select and configure plugins, which are designed to block automated form submissions by spambots. These plugins typically work by challenging the user with a CAPTCHA, a test which is designed to be difficult for computers to solve. phpBB defaults to "Incomplete Captcha," disabling verification and preventing functionality like registration that require CAPTCHA verification.', 'ACP_VC_EXT_GET_MORE' => 'For additional (and possibly better) anti-spam plugins, visit the phpBB.com Extensions Database. For more information on preventing spam on your board, visit the phpBB.com Knowledge Base.', 'AVAILABLE_CAPTCHAS' => 'Available plugins', 'CAPTCHA_UNAVAILABLE' => 'The plugin cannot be selected as its requirements are not met.', - 'CAPTCHA_INCOMPLETE' => 'Incomplete Captcha (Disabled Registration)', + 'CAPTCHA_INCOMPLETE' => 'Incomplete Captcha (Not configured)', 'CAPTCHA_PREVIEW_MSG' => 'Your changes have not been saved, this is just a preview.', 'CAPTCHA_PREVIEW_EXPLAIN' => 'The plugin as it would look like using the current selection.', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 5799137f5fb..79128293e25 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -173,6 +173,7 @@ 'CONFIRM_CODE' => 'Confirmation code', 'CONFIRM_CODE_EXPLAIN' => 'Enter the code exactly as it appears. All letters are case insensitive.', 'CONFIRM_CODE_WRONG' => 'The confirmation code you entered was incorrect.', + 'CONFIRM_INCOMPLETE' => 'To prevent automated registrations the board requires you to enter a confirmation code. This has not been configured yet and hence cannot be used. Please try again later or contact the %sBoard Administrator%s.', 'CONFIRM_OPERATION' => 'Are you sure you wish to carry out this operation?', 'CONFIRM_AVATAR_DELETE' => 'Are you sure you wish to delete this avatar?', 'CONGRATULATIONS' => 'Congratulations to', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 75964fde884..3d90f15742d 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -136,7 +136,6 @@ 'CONFIRMATION' => 'Confirmation of registration', 'CONFIRM_CHANGES' => 'Confirm changes', 'CONFIRM_EXPLAIN' => 'To prevent automated registrations the board requires you to enter a confirmation code. The code is displayed in the image you should see below. If you are visually impaired or cannot otherwise read this code please contact the %sBoard Administrator%s.', - 'CONFIRM_INCOMPLETE' => 'To prevent automated registrations the board requires you to enter a confirmation code. This has not been configured yet and hence cannot be used. Please try again later or contact the %sBoard Administrator%s.', 'VC_REFRESH' => 'Refresh confirmation code', 'VC_REFRESH_EXPLAIN' => 'If you cannot read the code you can request a new one by clicking the button.', diff --git a/phpBB/styles/prosilver/template/captcha_incomplete.html b/phpBB/styles/prosilver/template/captcha_incomplete.html index ed6b0af156b..6a9078a37bc 100644 --- a/phpBB/styles/prosilver/template/captcha_incomplete.html +++ b/phpBB/styles/prosilver/template/captcha_incomplete.html @@ -1,7 +1,7 @@
-

{{ lang('CONFIRMATION') }}

+

{{ lang('CONFIRM_CODE') }}

{{ lang(CONFIRM_LANG, '', '') }}

From c07c6816fca9b82d7009d1ab6b8d94f95f7876e5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 8 Apr 2024 21:50:27 +0200 Subject: [PATCH 0075/1214] [ticket/17077] Improve handling of accidental double submission of posts PHPBB3-17077 --- .../default/container/services_content.yml | 8 ++ phpBB/phpbb/lock/posting.php | 82 +++++++++++++++++++ phpBB/posting.php | 10 ++- phpBB/styles/prosilver/template/ajax.js | 23 ++++++ .../prosilver/template/posting_editor.html | 2 +- 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 phpBB/phpbb/lock/posting.php diff --git a/phpBB/config/default/container/services_content.yml b/phpBB/config/default/container/services_content.yml index 8b240067e41..e2d53784c24 100644 --- a/phpBB/config/default/container/services_content.yml +++ b/phpBB/config/default/container/services_content.yml @@ -67,6 +67,14 @@ services: - '@controller.helper' - '@dispatcher' + posting.lock: + class: phpbb\lock\posting + shared: false + arguments: + - '@cache.driver' + - '@config' + - '@request' + viewonline_helper: class: phpbb\viewonline_helper arguments: diff --git a/phpBB/phpbb/lock/posting.php b/phpBB/phpbb/lock/posting.php new file mode 100644 index 00000000000..d9124758902 --- /dev/null +++ b/phpBB/phpbb/lock/posting.php @@ -0,0 +1,82 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\lock; + +use phpbb\cache\driver\driver_interface as cache_interface; +use phpbb\config\config; +use phpbb\request\request_interface; + +class posting +{ + /** @var cache_interface */ + private $cache; + + /** @var config */ + private $config; + + /** @var request_interface */ + private $request; + + /** @var string */ + private $lock_name = ''; + + /** + * Constructor for posting lock + * + * @param cache_interface $cache + * @param config $config + * @param request_interface $request + */ + public function __construct(cache_interface $cache, config $config, request_interface $request) + { + $this->cache = $cache; + $this->config = $config; + $this->request = $request; + } + + /** + * Get lock name + * @return string Lock name + */ + private function lock_name(): string + { + if ($this->lock_name) + { + return $this->lock_name; + } + + $creation_time = abs($this->request->variable('creation_time', 0)); + $token = $this->request->variable('form_token', ''); + + return sha1(((string) $creation_time) . $token) . '_posting_lock'; + } + + /** + * Acquire lock for current posting form submission + * + * @return bool True if lock could be acquired, false if not + */ + public function acquire(): bool + { + // Lock is held for session, cannot acquire it + if ($this->cache->_exists($this->lock_name())) + { + return false; + } + + $this->cache->put($this->lock_name(), true, $this->config['flood_interval']); + + return true; + } +} \ No newline at end of file diff --git a/phpBB/posting.php b/phpBB/posting.php index 724d8a8df18..7b3d2243ab6 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1429,7 +1429,10 @@ // Store message, sync counters if (!count($error) && $submit) { - if ($submit) + /** @var \phpbb\lock\posting $posting_lock */ + $posting_lock = $phpbb_container->get('posting.lock'); + + if ($posting_lock->acquire()) { // Lock/Unlock Topic $change_topic_status = $post_data['topic_status']; @@ -1620,6 +1623,11 @@ redirect($redirect_url); } + else + { + // Posting was already locked before, hence form submission was already attempted once and is now invalid + $error[] = $language->lang('FORM_INVALID'); + } } } diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js index 79187470d81..e01e7eb4d49 100644 --- a/phpBB/styles/prosilver/template/ajax.js +++ b/phpBB/styles/prosilver/template/ajax.js @@ -337,6 +337,29 @@ $('[data-ajax]').each(function() { } }); +// Prevent accidental double submission of form +$('[data-prevent-flood] input[type=submit]').click(function(event) { + const $submitButton = $(this); // Store the button element + const $form = $submitButton.closest('form'); + + // Always add the disabled class for visual feedback + $submitButton.addClass('disabled'); + + // Submit form if it hasn't been submitted yet + if (!$form.prop('data-form-submitted')) { + $form.prop('data-form-submitted', true); + + return; + } + + // Prevent default submission for subsequent clicks within 5 seconds + event.preventDefault(); + + setTimeout(() => { + $form.prop('removeProp', 'data-form-submitted'); + $submitButton.removeClass('disabled'); // Re-enable after 5 seconds + }, 5000); +}); /** * This simply appends #preview to the action of the diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index 12790360d64..d3b2ecd68ed 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -100,7 +100,7 @@
-
+
{S_HIDDEN_ADDRESS_FIELD} {S_HIDDEN_FIELDS} From 6943fbba890a3ad3f315cf64f97901f88419036c Mon Sep 17 00:00:00 2001 From: LukeWCS Date: Tue, 9 Apr 2024 19:06:09 +0200 Subject: [PATCH 0076/1214] [ticket/17302] Fix password reset function Password reset function does not update all necessary data PHPBB3-17302 --- phpBB/phpbb/ucp/controller/reset_password.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/phpbb/ucp/controller/reset_password.php b/phpBB/phpbb/ucp/controller/reset_password.php index 007258094bf..442de50ec77 100644 --- a/phpBB/phpbb/ucp/controller/reset_password.php +++ b/phpBB/phpbb/ucp/controller/reset_password.php @@ -407,6 +407,7 @@ public function reset() { $sql_ary = [ 'user_password' => $this->passwords_manager->hash($data['new_password']), + 'user_passchg' => time(), 'user_login_attempts' => 0, 'reset_token' => '', 'reset_token_expiration' => 0, From 3b5777f9008b49b7ceb4b80b75a091782a6e9b86 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 9 Apr 2024 20:53:05 +0200 Subject: [PATCH 0077/1214] [ticket/17077] Move request handling outside locking and add release PHPBB3-17077 --- .../default/container/services_content.yml | 1 - phpBB/phpbb/lock/posting.php | 59 +++++++++++-------- phpBB/posting.php | 9 ++- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/phpBB/config/default/container/services_content.yml b/phpBB/config/default/container/services_content.yml index e2d53784c24..0dc0829d9e7 100644 --- a/phpBB/config/default/container/services_content.yml +++ b/phpBB/config/default/container/services_content.yml @@ -73,7 +73,6 @@ services: arguments: - '@cache.driver' - '@config' - - '@request' viewonline_helper: class: phpbb\viewonline_helper diff --git a/phpBB/phpbb/lock/posting.php b/phpBB/phpbb/lock/posting.php index d9124758902..1fee36e63f1 100644 --- a/phpBB/phpbb/lock/posting.php +++ b/phpBB/phpbb/lock/posting.php @@ -15,7 +15,6 @@ use phpbb\cache\driver\driver_interface as cache_interface; use phpbb\config\config; -use phpbb\request\request_interface; class posting { @@ -25,58 +24,72 @@ class posting /** @var config */ private $config; - /** @var request_interface */ - private $request; - /** @var string */ private $lock_name = ''; + /** @var bool Lock state */ + private $locked = false; + /** * Constructor for posting lock * * @param cache_interface $cache * @param config $config - * @param request_interface $request */ - public function __construct(cache_interface $cache, config $config, request_interface $request) + public function __construct(cache_interface $cache, config $config) { $this->cache = $cache; $this->config = $config; - $this->request = $request; } /** - * Get lock name - * @return string Lock name + * Set lock name + * + * @param int $creation_time Creation time of form, must be checked already + * @param string $form_token Form token used for form, must be checked already + * + * @return void */ - private function lock_name(): string + private function set_lock_name(int $creation_time, string $form_token): void { - if ($this->lock_name) - { - return $this->lock_name; - } - - $creation_time = abs($this->request->variable('creation_time', 0)); - $token = $this->request->variable('form_token', ''); - - return sha1(((string) $creation_time) . $token) . '_posting_lock'; + $this->lock_name = sha1(((string) $creation_time) . $form_token) . '_posting_lock'; } /** * Acquire lock for current posting form submission * + * @param int $creation_time Creation time of form, must be checked already + * @param string $form_token Form token used for form, must be checked already + * * @return bool True if lock could be acquired, false if not */ - public function acquire(): bool + public function acquire(int $creation_time, string $form_token): bool { + $this->set_lock_name($creation_time, $form_token); + // Lock is held for session, cannot acquire it - if ($this->cache->_exists($this->lock_name())) + if ($this->cache->_exists($this->lock_name)) { return false; } - $this->cache->put($this->lock_name(), true, $this->config['flood_interval']); + $this->locked = true; + + $this->cache->put($this->lock_name, true, $this->config['flood_interval']); return true; } -} \ No newline at end of file + + /** + * Release lock + * + * @return void + */ + public function release(): void + { + if ($this->locked) + { + $this->cache->destroy($this->lock_name); + } + } +} diff --git a/phpBB/posting.php b/phpBB/posting.php index 7b3d2243ab6..bea75081c14 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1432,7 +1432,11 @@ /** @var \phpbb\lock\posting $posting_lock */ $posting_lock = $phpbb_container->get('posting.lock'); - if ($posting_lock->acquire()) + // Get creation time and form token, must be already checked at this point + $creation_time = abs($request->variable('creation_time', 0)); + $form_token = $request->variable('form_token', ''); + + if ($posting_lock->acquire($creation_time, $form_token)) { // Lock/Unlock Topic $change_topic_status = $post_data['topic_status']; @@ -1561,6 +1565,9 @@ // The last parameter tells submit_post if search indexer has to be run $redirect_url = submit_post($mode, $post_data['post_subject'], $post_author_name, $post_data['topic_type'], $poll, $data, $update_message, ($update_message || $update_subject) ? true : false); + // Release lock after submitting post + $posting_lock->release(); + /** * This event allows you to define errors after the post action is performed * From 98929ca9838a60d5dc8a5f454ab101170f1975bc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 9 Apr 2024 21:13:28 +0200 Subject: [PATCH 0078/1214] [ticket/17077] Add test for posting lock PHPBB3-17077 --- tests/lock/posting_test.php | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/lock/posting_test.php diff --git a/tests/lock/posting_test.php b/tests/lock/posting_test.php new file mode 100644 index 00000000000..e9a9cdfffb8 --- /dev/null +++ b/tests/lock/posting_test.php @@ -0,0 +1,54 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\cache\driver\file as file_cache; +use phpbb\config\config; +use phpbb\lock\posting; + +class phpbb_lock_posting_test extends phpbb_test_case +{ + /** @var \phpbb\cache\driver\file */ + protected $cache; + + /** @var config */ + protected $config; + + /** @var posting */ + protected $lock; + + public function setUp(): void + { + $this->cache = new file_cache(__DIR__ . '/../tmp/'); + $this->cache->purge(); // ensure cache is clean + $this->config = new config([ + 'flood_interval' => 15, + ]); + $this->lock = new posting($this->cache, $this->config); + } + + public function test_lock_acquire() + { + $this->assertTrue($this->lock->acquire(100, 'foo')); + $this->assertFalse($this->lock->acquire(100, 'foo')); + + $this->assertTrue($this->cache->_exists(sha1('100foo') . '_posting_lock')); + $this->lock->release(); + $this->assertFalse($this->cache->_exists(sha1('100foo') . '_posting_lock')); + + $this->assertTrue($this->lock->acquire(100, 'foo')); + $this->assertTrue($this->cache->_exists(sha1('100foo') . '_posting_lock')); + $this->lock->release(); + $this->lock->release(); // double release has no effect + $this->assertFalse($this->cache->_exists(sha1('100foo') . '_posting_lock')); + } +} \ No newline at end of file From abffd0f6622c2c662aa914cbaa564d7549d71d19 Mon Sep 17 00:00:00 2001 From: rxu Date: Mon, 15 Apr 2024 00:19:52 +0700 Subject: [PATCH 0079/1214] [ticket/17303] Update jQuery to the version 3.7.1 PHPBB3-17303 --- phpBB/adm/style/installer_footer.html | 2 +- phpBB/adm/style/overall_footer.html | 2 +- phpBB/adm/style/simple_footer.html | 2 +- phpBB/assets/javascript/jquery-3.6.0.min.js | 2 -- phpBB/assets/javascript/jquery-3.7.1.min.js | 2 ++ phpBB/includes/functions.php | 2 +- phpBB/includes/functions_acp.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- .../migration/data/v33x/jquery_update_v3.php | 36 +++++++++++++++++++ phpBB/phpbb/install/controller/helper.php | 2 +- .../prosilver/template/overall_footer.html | 2 +- .../prosilver/template/simple_footer.html | 2 +- 12 files changed, 47 insertions(+), 11 deletions(-) delete mode 100644 phpBB/assets/javascript/jquery-3.6.0.min.js create mode 100644 phpBB/assets/javascript/jquery-3.7.1.min.js create mode 100644 phpBB/phpbb/db/migration/data/v33x/jquery_update_v3.php diff --git a/phpBB/adm/style/installer_footer.html b/phpBB/adm/style/installer_footer.html index 24c80b69e51..ce06f93223f 100644 --- a/phpBB/adm/style/installer_footer.html +++ b/phpBB/adm/style/installer_footer.html @@ -23,7 +23,7 @@ - + {$SCRIPTS} diff --git a/phpBB/adm/style/overall_footer.html b/phpBB/adm/style/overall_footer.html index 7f7f2f82561..5f17eaf28ed 100644 --- a/phpBB/adm/style/overall_footer.html +++ b/phpBB/adm/style/overall_footer.html @@ -34,7 +34,7 @@

 

- + diff --git a/phpBB/adm/style/simple_footer.html b/phpBB/adm/style/simple_footer.html index 50dba2f9108..706bd987d95 100644 --- a/phpBB/adm/style/simple_footer.html +++ b/phpBB/adm/style/simple_footer.html @@ -17,7 +17,7 @@
- + diff --git a/phpBB/assets/javascript/jquery-3.6.0.min.js b/phpBB/assets/javascript/jquery-3.6.0.min.js deleted file mode 100644 index c4c6022f298..00000000000 --- a/phpBB/assets/javascript/jquery-3.6.0.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"
","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 !empty($config['allow_cdn']) && !empty($config['load_font_awesome_url']) ? $config['load_font_awesome_url'] : "{$web_path}assets/css/font-awesome.min.css?assets_version=" . $config['assets_version'], - 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery-3.6.0.min.js?assets_version=" . $config['assets_version'], + 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery-3.7.1.min.js?assets_version=" . $config['assets_version'], 'S_ALLOW_CDN' => !empty($config['allow_cdn']), 'S_COOKIE_NOTICE' => !empty($config['cookie_notice']), diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index a1c60c2807f..2155ae0519f 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -178,7 +178,7 @@ function adm_page_footer($copyright_html = true) 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '', 'S_COPYRIGHT_HTML' => $copyright_html, 'CREDIT_LINE' => $user->lang('POWERED_BY', 'phpBB® Forum Software © phpBB Limited'), - 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$phpbb_root_path}assets/javascript/jquery-3.6.0.min.js", + 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$phpbb_root_path}assets/javascript/jquery-3.7.1.min.js", 'S_ALLOW_CDN' => !empty($config['allow_cdn']), 'VERSION' => $config['version']) ); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 24580c414be..97ff395ac90 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -200,7 +200,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_viewtopic INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_db_lastread', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_db_track', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_font_awesome_url', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jumpbox', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_moderators', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_notifications', '1'); diff --git a/phpBB/phpbb/db/migration/data/v33x/jquery_update_v3.php b/phpBB/phpbb/db/migration/data/v33x/jquery_update_v3.php new file mode 100644 index 00000000000..f1f96071268 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/jquery_update_v3.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +use phpbb\db\migration\migration; + +class jquery_update_v3 extends migration +{ + public function effectively_installed() + { + return $this->config['load_jquery_url'] === '//ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js'; + } + + public static function depends_on() + { + return ['\phpbb\db\migration\data\v33x\v3311']; + } + + public function update_data() + { + return [ + ['config.update', ['load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js']], + ]; + } +} diff --git a/phpBB/phpbb/install/controller/helper.php b/phpBB/phpbb/install/controller/helper.php index 1f936dda86f..0d3b48d76dd 100644 --- a/phpBB/phpbb/install/controller/helper.php +++ b/phpBB/phpbb/install/controller/helper.php @@ -267,7 +267,7 @@ protected function page_header($page_title, $selected_language = false) 'L_SKIP' => $this->language->lang('SKIP'), 'PAGE_TITLE' => $this->language->lang($page_title), 'T_IMAGE_PATH' => $this->path_helper->get_web_root_path() . $path . 'images', - 'T_JQUERY_LINK' => $this->path_helper->get_web_root_path() . $path . '../assets/javascript/jquery-3.6.0.min.js', + 'T_JQUERY_LINK' => $this->path_helper->get_web_root_path() . $path . '../assets/javascript/jquery-3.7.1.min.js', 'T_TEMPLATE_PATH' => $this->path_helper->get_web_root_path() . $path . 'style', 'T_ASSETS_PATH' => $this->path_helper->get_web_root_path() . $path . '../assets', diff --git a/phpBB/styles/prosilver/template/overall_footer.html b/phpBB/styles/prosilver/template/overall_footer.html index 5d872e24150..465fe44f2a3 100644 --- a/phpBB/styles/prosilver/template/overall_footer.html +++ b/phpBB/styles/prosilver/template/overall_footer.html @@ -64,7 +64,7 @@

 

- + diff --git a/phpBB/styles/prosilver/template/simple_footer.html b/phpBB/styles/prosilver/template/simple_footer.html index 98a43b89b90..06dba0f1621 100644 --- a/phpBB/styles/prosilver/template/simple_footer.html +++ b/phpBB/styles/prosilver/template/simple_footer.html @@ -36,7 +36,7 @@

 

- + From d0c48f3fde0886b7e4f010d8f8306ba2cbdf7b85 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 16 Apr 2024 20:46:40 +0200 Subject: [PATCH 0080/1214] [ticket/17305] Improve queries for unanswered posts/topics PHPBB3-17305 --- phpBB/search.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/phpBB/search.php b/phpBB/search.php index cd2d65a4822..af4f8dcb6cc 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -337,7 +337,13 @@ } // define some variables needed for retrieving post_id/topic_id information - $sort_by_sql = array('a' => 'u.username_clean', 't' => (($show_results == 'posts') ? 'p.post_time' : 't.topic_last_post_time'), 'f' => 'f.forum_id', 'i' => 't.topic_title', 's' => (($show_results == 'posts') ? 'p.post_subject' : 't.topic_title')); + $sort_by_sql = [ + 'a' => 'u.username_clean', + 't' => (($show_results == 'posts') ? 'p.post_time' : 't.topic_last_post_time'), + 'f' => 'f.forum_id', + 'i' => 't.topic_title', + 's' => (($show_results == 'posts') ? 'p.post_subject' : 't.topic_title') + ]; /** * Event to modify the SQL parameters before pre-made searches @@ -403,11 +409,11 @@ $sql_sort = 'ORDER BY ' . $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); $sort_join = ($sort_key == 'f') ? FORUMS_TABLE . ' f, ' : ''; - $sql_sort = ($sort_key == 'f') ? ' AND f.forum_id = p.forum_id ' . $sql_sort : $sql_sort; + $sql_sort = ($sort_key == 'f') ? ' AND f.forum_id = t.forum_id ' . $sql_sort : $sql_sort; if ($sort_days) { - $last_post_time = 'AND p.post_time > ' . (time() - ($sort_days * 24 * 3600)); + $last_post_time = 'AND ' . ($show_results == 'posts' ? 'p.post_time' : 't.topic_last_post_time') . ' > ' . (time() - ($sort_days * 24 * 3600)); } else { @@ -417,7 +423,7 @@ if ($sort_key == 'a') { $sort_join = USERS_TABLE . ' u, '; - $sql_sort = ' AND u.user_id = p.poster_id ' . $sql_sort; + $sql_sort = ' AND u.user_id = t.topic_last_poster_id ' . $sql_sort; } if ($show_results == 'posts') { @@ -433,14 +439,13 @@ } else { - $sql = 'SELECT DISTINCT ' . $sort_by_sql[$sort_key] . ", p.topic_id - FROM $sort_join" . POSTS_TABLE . ' p, ' . TOPICS_TABLE . " t + $sql = 'SELECT DISTINCT ' . $sort_by_sql[$sort_key] . ", t.topic_id + FROM $sort_join" . TOPICS_TABLE . " t WHERE t.topic_posts_approved = 1 AND t.topic_moved_id = 0 - AND p.topic_id = t.topic_id $last_post_time AND $m_approve_topics_fid_sql - " . ((count($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : '') . " + " . ((count($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('t.forum_id', $ex_fid_ary, true) : '') . " $sql_sort"; $field = 'topic_id'; } From 3e23839c0491f2ff6303330245c28817f1d89d0f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 17 Apr 2024 19:55:06 +0200 Subject: [PATCH 0081/1214] [ticket/12960] Add warning messages when captcha is disabled or incomplete PHPBB3-12960 --- phpBB/adm/style/acp_main.html | 9 +++++++++ phpBB/includes/acp/acp_main.php | 26 ++++++++++++++++++++++++++ phpBB/language/en/acp/common.php | 3 +++ 3 files changed, 38 insertions(+) diff --git a/phpBB/adm/style/acp_main.html b/phpBB/adm/style/acp_main.html index 05ccd106054..2fe84656dbe 100644 --- a/phpBB/adm/style/acp_main.html +++ b/phpBB/adm/style/acp_main.html @@ -35,6 +35,15 @@

{L_WELCOME_PHPBB}

{UPGRADE_INSTRUCTIONS}

+ {% if S_CAPTCHA_UNSAFE %} +
+

{{ lang('CAPTCHA_UNSAFE_WARNING') }}

+
+ {% elseif S_CAPTCHA_INCOMPLETE %} +
+

{{ lang('CAPTCHA_INCOMPLETE_WARNING') }}

+
+ {% endif %}
diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index 8b34b5fea83..cdf5895def4 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -641,6 +641,9 @@ function main($id, $mode) } } + // Warn if incomplete captcha is enabled + $this->check_captcha_type($config, $template); + if (!defined('PHPBB_DISABLE_CONFIG_CHECK')) { // World-Writable? (000x) @@ -673,4 +676,27 @@ function main($id, $mode) $this->tpl_name = 'acp_main'; $this->page_title = 'ACP_MAIN'; } + + /** + * Check CAPTCHA type and output warning if incomplete type or unsafe config is used + * + * @param \phpbb\config\config $config + * @param \phpbb\template\template $template + * @return void + */ + protected function check_captcha_type(\phpbb\config\config $config, \phpbb\template\template $template): void + { + $template_vars = []; + + if (!$config['enable_confirm']) + { + $template_vars['S_CAPTCHA_UNSAFE'] = true; + } + else if ($config['captcha_plugin'] == 'core.captcha.plugins.incomplete') + { + $template_vars['S_CAPTCHA_INCOMPLETE'] = true; + } + + $template->assign_vars($template_vars); + } } diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index ae2b54f62fa..bcbbe5d931a 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -235,6 +235,9 @@ 'BACK' => 'Back', + 'CAPTCHA_UNSAFE_WARNING' => 'Your board is currently vulnerable to spam submissions because the Spambot countermeasures are either disabled or not configured correctly.', + 'CAPTCHA_INCOMPLETE_WARNING' => '“Incomplete Captcha“ is currently enabled. This placeholder CAPTCHA will prevent all form submissions requiring CAPTCHA verification, e.g. user registration. Please configure a proper CAPTCHA solution like Q&A or reCaptcha to ensure intended functionality.', + 'CONTAINER_EXCEPTION' => 'phpBB encountered an error building the container due to an installed extension. For this reason, all extensions have been temporarily disabled. Please try purging your forum cache. All extensions will automatically be re-enabled once the container error is resolved. If this error continues, please visit phpBB.com for support.', 'EXCEPTION' => 'Exception', From 1baf5c295e98ace3e4ea06d986820b445aa81448 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 17 Apr 2024 20:08:16 +0200 Subject: [PATCH 0082/1214] [ticket/12960] Add missing docblocks to incomplete captcha PHPBB3-12960 --- phpBB/phpbb/captcha/plugins/incomplete.php | 86 ++++++++++++++++++++-- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/phpBB/phpbb/captcha/plugins/incomplete.php b/phpBB/phpbb/captcha/plugins/incomplete.php index 9669931761f..5c2b7f9a66c 100644 --- a/phpBB/phpbb/captcha/plugins/incomplete.php +++ b/phpBB/phpbb/captcha/plugins/incomplete.php @@ -1,48 +1,108 @@ + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ namespace phpbb\captcha\plugins; use phpbb\config\config; use phpbb\template\template; -class incomplete extends \phpbb\captcha\plugins\captcha_abstract +class incomplete extends captcha_abstract { + /** + * Constructor for incomplete captcha + * + * @param config $config + * @param template $template + * @param string $phpbb_root_path + * @param string $phpEx + */ public function __construct(protected config $config, protected template $template, protected string $phpbb_root_path, protected string $phpEx) {} - public function is_available() + /** + * @return bool True if captcha is available, false if not + */ + public function is_available(): bool { return true; } + /** + * Dummy implementation + * + * @return void + */ public function get_generator_class() { } - public static function get_name() + /** + * Get CAPTCHA name language variable + * + * @return string Language variable + */ + public static function get_name(): string { return 'CAPTCHA_INCOMPLETE'; } + /** + * Init CAPTCHA + * + * @param int $type CAPTCHA type + * @return void + */ public function init($type) { } + /** + * Execute demo + * + * @return void + */ public function execute_demo() { } + /** + * Execute CAPTCHA + * + * @return void + */ public function execute() { } - public function get_demo_template($id) + /** + * Get template data for demo + * + * @param int|string $id ACP module ID + * + * @return string Demo template file name + */ + public function get_demo_template($id): string { return ''; } - public function get_template() + /** + * Get template data for CAPTCHA + * + * @return string CAPTCHA template file name + */ + public function get_template(): string { $contact_link = phpbb_get_board_contact_link($this->config, $this->phpbb_root_path, $this->phpEx); @@ -54,13 +114,23 @@ public function get_template() return 'captcha_incomplete.html'; } - public function validate() + /** + * Validate CAPTCHA + * + * @return false Incomplete CAPTCHA will never validate + */ + public function validate(): bool { return false; } - public function is_solved() + /** + * Check whether CAPTCHA is solved + * + * @return false Incomplete CAPTCHA will never be solved + */ + public function is_solved(): bool { return false; } -} \ No newline at end of file +} From 5d11098753977688892a43c8918e08bdccd91c3f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 17 Apr 2024 20:42:41 +0200 Subject: [PATCH 0083/1214] [ticket/12960] Do not try to enable removed captcha during install PHPBB3-12960 --- .../module/install_database/task/add_config_settings.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpBB/phpbb/install/module/install_database/task/add_config_settings.php b/phpBB/phpbb/install/module/install_database/task/add_config_settings.php index 5d5b7043256..bd3f0852e60 100644 --- a/phpBB/phpbb/install/module/install_database/task/add_config_settings.php +++ b/phpBB/phpbb/install/module/install_database/task/add_config_settings.php @@ -166,12 +166,6 @@ public function run() 'site_desc' => $this->install_config->get('board_description'), ]; - if (@extension_loaded('gd')) - { - $updates['captcha_plugin'] = 'core.captcha.plugins.gd'; - $updates['captcha_gd'] = '1'; - } - $ref = substr($referer, strpos($referer, '://') + 3); if (!(stripos($ref, $server_name) === 0)) { From 9d51ca3eb7edcedf141528636c1c936130eed0b0 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 18 Apr 2024 22:14:52 +0200 Subject: [PATCH 0084/1214] [ticket/12960] Add migration for removing broken captcha PHPBB3-12960 --- .../data/v400/remove_broken_captcha.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php diff --git a/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php b/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php new file mode 100644 index 00000000000..5e901a9193d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php @@ -0,0 +1,67 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class remove_broken_captcha extends migration +{ + private $removed_captchas = [ + 'core.captcha.plugins.gd', + 'core.captcha.plugins.gd_wave', + 'core.captcha.plugins.nogd' + ]; + + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\dev', + ]; + } + + public function update_data(): array + { + return [ + ['config.remove', ['captcha_gd']], + ['config.remove', ['captcha_gd_3d_noise']], + ['config.remove', ['captcha_gd_fonts']], + ['config.remove', ['captcha_gd_foreground_noise']], + ['config.remove', ['captcha_gd_wave']], + ['config.remove', ['captcha_gd_x_grid']], + ['config.remove', ['captcha_gd_y_grid']], + ['custom', [[$this, 'replace_broken_captcha']]], + ]; + } + + public function revert_data(): array + { + return [ + ['config.add', ['captcha_gd', 0]], + ['config.add', ['captcha_gd_3d_noise', 1]], + ['config.add', ['captcha_gd_fonts', 1]], + ['config.add', ['captcha_gd_foreground_noise', 1]], + ['config.add', ['captcha_gd_wave', 0]], + ['config.add', ['captcha_gd_x_grid', 25]], + ['config.add', ['captcha_gd_y_grid', 25]], + ]; + } + + public function replace_broken_captcha(): void + { + if (in_array($this->config['captcha_plugin'], $this->removed_captchas)) + { + $this->config->set('captcha_plugin', 'core.captcha.plugins.incomplete'); + } + } +} From 7da461eac428d1681c75a4e9531e6f41f590b521 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 18 Apr 2024 22:16:36 +0200 Subject: [PATCH 0085/1214] [ticket/12960] Add docblock for removed_captchas array PHPBB3-12960 --- phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php b/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php index 5e901a9193d..1fb79f8f4b7 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_broken_captcha.php @@ -17,7 +17,8 @@ class remove_broken_captcha extends migration { - private $removed_captchas = [ + /** @var array List of broken captcha that have been removed */ + private array $removed_captchas = [ 'core.captcha.plugins.gd', 'core.captcha.plugins.gd_wave', 'core.captcha.plugins.nogd' From d1e2e39469ada88ddd219a8d306f42a6c3425f55 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 19 Apr 2024 08:43:49 +0200 Subject: [PATCH 0086/1214] [ticket/12960] Add tests for incomplete captcha PHPBB3-12960 --- phpBB/language/en/common.php | 1 + phpBB/phpbb/captcha/plugins/incomplete.php | 8 +- phpBB/phpbb/captcha/plugins/recaptcha.php | 5 +- phpBB/phpbb/captcha/plugins/recaptcha_v3.php | 6 +- tests/captcha/incomplete_test.php | 86 ++++++++++++++++++++ 5 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 tests/captcha/incomplete_test.php diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 79128293e25..25851d738e2 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -525,6 +525,7 @@ 'NO_EMAIL_SUBJECT' => 'No email subject specified.', 'NO_FORUM' => 'The forum you selected does not exist.', 'NO_FORUMS' => 'This board has no forums.', + 'NO_GENERATOR_CLASS' => 'No generator class given.', 'NO_GROUP' => 'The requested usergroup does not exist.', 'NO_GROUP_MEMBERS' => 'This group currently has no members.', 'NO_IPS_DEFINED' => 'No IP addresses or hostnames defined', diff --git a/phpBB/phpbb/captcha/plugins/incomplete.php b/phpBB/phpbb/captcha/plugins/incomplete.php index 5c2b7f9a66c..37301c45f57 100644 --- a/phpBB/phpbb/captcha/plugins/incomplete.php +++ b/phpBB/phpbb/captcha/plugins/incomplete.php @@ -14,6 +14,7 @@ namespace phpbb\captcha\plugins; use phpbb\config\config; +use phpbb\exception\runtime_exception; use phpbb\template\template; class incomplete extends captcha_abstract @@ -39,12 +40,14 @@ public function is_available(): bool } /** - * Dummy implementation + * Dummy implementation, not supported by this captcha * + * @throws runtime_exception * @return void */ - public function get_generator_class() + public function get_generator_class(): void { + throw new runtime_exception('NO_GENERATOR_CLASS'); } /** @@ -65,6 +68,7 @@ public static function get_name(): string */ public function init($type) { + $this->type = (int) $type; } /** diff --git a/phpBB/phpbb/captcha/plugins/recaptcha.php b/phpBB/phpbb/captcha/plugins/recaptcha.php index 399eb34b6b9..ef30baaa9bb 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha.php @@ -13,6 +13,8 @@ namespace phpbb\captcha\plugins; +use phpbb\exception\runtime_exception; + class recaptcha extends captcha_abstract { private $response; @@ -55,10 +57,11 @@ public static function get_name() /** * This function is implemented because required by the upper class, but is never used for reCaptcha. + * @throws runtime_exception */ function get_generator_class() { - throw new \Exception('No generator class given.'); + throw new runtime_exception('NO_GENERATOR_CLASS'); } function acp_page($id, $module) diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php index 02dacb4b51d..67035ab1c62 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -13,6 +13,8 @@ namespace phpbb\captcha\plugins; +use phpbb\exception\runtime_exception; + /** * Google reCAPTCHA v3 plugin. */ @@ -86,12 +88,12 @@ public function execute_demo() * * Not needed by this CAPTCHA plugin. * - * @throws \Exception + * @throws runtime_exception * @return void */ public function get_generator_class() { - throw new \Exception('No generator class given.'); + throw new runtime_exception('NO_GENERATOR_CLASS'); } /** diff --git a/tests/captcha/incomplete_test.php b/tests/captcha/incomplete_test.php new file mode 100644 index 00000000000..88b6825fa93 --- /dev/null +++ b/tests/captcha/incomplete_test.php @@ -0,0 +1,86 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\captcha\plugins\incomplete; +use phpbb\config\config; +use phpbb\template\template; + +class phpbb_captcha_incomplete_test extends phpbb_test_case +{ + protected config $config; + + protected template $template; + + + /** @var incomplete */ + protected incomplete $incomplete_captcha; + + protected array $assigned_vars = []; + + public function assign_vars(array $vars): void + { + $this->assigned_vars = array_merge($this->assigned_vars, $vars); + } + + protected function setUp(): void + { + global $phpbb_root_path, $phpEx; + + $this->config = new config([]); + $this->template = $this->getMockBuilder('\phpbb\template\twig\twig') + ->setMethods(['assign_vars']) + ->disableOriginalConstructor() + ->getMock(); + $this->template->method('assign_vars') + ->willReturnCallback([$this, 'assign_vars']); + + $this->incomplete_captcha = new incomplete( + $this->config, + $this->template, + $phpbb_root_path, + $phpEx + ); + } + + public function test_miscellaneous_incomplete(): void + { + $this->assertTrue($this->incomplete_captcha->is_available()); + $this->assertFalse($this->incomplete_captcha->is_solved()); + $this->assertFalse($this->incomplete_captcha->validate()); + $this->assertSame('CAPTCHA_INCOMPLETE', incomplete::get_name()); + $this->incomplete_captcha->init(0); + $this->incomplete_captcha->execute(); + $this->incomplete_captcha->execute_demo(); + $this->assertEmpty($this->assigned_vars); + $this->assertEmpty($this->incomplete_captcha->get_demo_template(0)); + } + + public function test_get_generator_class(): void + { + $this->expectException(\phpbb\exception\runtime_exception::class); + $this->incomplete_captcha->get_generator_class(); + } + + public function test_get_tempate(): void + { + $this->incomplete_captcha->init(CONFIRM_REG); + $this->assertSame('captcha_incomplete.html', $this->incomplete_captcha->get_template()); + $this->assertEquals('CONFIRM_INCOMPLETE', $this->assigned_vars['CONFIRM_LANG']); + + $this->assigned_vars = []; + + $this->incomplete_captcha->init(CONFIRM_POST); + $this->assertSame('captcha_incomplete.html', $this->incomplete_captcha->get_template()); + $this->assertEquals('POST_CONFIRM_INCOMPLETE', $this->assigned_vars['CONFIRM_LANG']); + } +} From e83f25a448f256515c0bf4967ef53bba77524962 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 19 Apr 2024 08:49:57 +0200 Subject: [PATCH 0087/1214] [ticket/12960] Remove not needed extra language variable PHPBB3-12960 --- phpBB/phpbb/captcha/plugins/incomplete.php | 3 +-- tests/captcha/incomplete_test.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/captcha/plugins/incomplete.php b/phpBB/phpbb/captcha/plugins/incomplete.php index 37301c45f57..ec3376c0a55 100644 --- a/phpBB/phpbb/captcha/plugins/incomplete.php +++ b/phpBB/phpbb/captcha/plugins/incomplete.php @@ -68,7 +68,6 @@ public static function get_name(): string */ public function init($type) { - $this->type = (int) $type; } /** @@ -111,7 +110,7 @@ public function get_template(): string $contact_link = phpbb_get_board_contact_link($this->config, $this->phpbb_root_path, $this->phpEx); $this->template->assign_vars([ - 'CONFIRM_LANG' => $this->type != CONFIRM_POST ? 'CONFIRM_INCOMPLETE' : 'POST_CONFIRM_INCOMPLETE', + 'CONFIRM_LANG' => 'CONFIRM_INCOMPLETE', 'CONTACT_LINK' => $contact_link, ]); diff --git a/tests/captcha/incomplete_test.php b/tests/captcha/incomplete_test.php index 88b6825fa93..c1da2b6c486 100644 --- a/tests/captcha/incomplete_test.php +++ b/tests/captcha/incomplete_test.php @@ -81,6 +81,6 @@ public function test_get_tempate(): void $this->incomplete_captcha->init(CONFIRM_POST); $this->assertSame('captcha_incomplete.html', $this->incomplete_captcha->get_template()); - $this->assertEquals('POST_CONFIRM_INCOMPLETE', $this->assigned_vars['CONFIRM_LANG']); + $this->assertEquals('CONFIRM_INCOMPLETE', $this->assigned_vars['CONFIRM_LANG']); } } From 1c8128292caff906b21637fb8c5c246ac51b5be3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 21 Apr 2024 21:01:22 +0200 Subject: [PATCH 0088/1214] [ticket/17305] Change sql_sort to be more in line with previous code PHPBB3-17305 --- phpBB/search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/search.php b/phpBB/search.php index af4f8dcb6cc..abd177f9d90 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -423,7 +423,7 @@ if ($sort_key == 'a') { $sort_join = USERS_TABLE . ' u, '; - $sql_sort = ' AND u.user_id = t.topic_last_poster_id ' . $sql_sort; + $sql_sort = ' AND u.user_id = ' . ($show_results == 'posts' ? 'p.poster_id' : 't.topic_last_poster_id ') . $sql_sort; } if ($show_results == 'posts') { From 17a50027b14689075f119a84b203d7137531d4a7 Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Sat, 20 Apr 2024 19:06:18 +0200 Subject: [PATCH 0089/1214] [ticket/17306] Change type description for get_context() PHPBB3-17306 --- phpBB/includes/functions_content.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index 1824533f22f..40e1b642395 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -320,7 +320,7 @@ function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_po * Generates a text with approx. the specified length which contains the specified words and their context * * @param string $text The full text from which context shall be extracted -* @param string $words An array of words which should be contained in the result, has to be a valid part of a PCRE pattern (escape with preg_quote!) +* @param array $words An array of words which should be contained in the result, has to be a valid part of a PCRE pattern (escape with preg_quote!) * @param int $length The desired length of the resulting text, however the result might be shorter or longer than this value * * @return string Context of the specified words separated by "..." From 8f1a777c5f32434ce1eaee9a79738bb71f4ad108 Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Sat, 20 Apr 2024 19:06:18 +0200 Subject: [PATCH 0090/1214] [ticket/17306] Add type declaration to get_context and array for $words PHPBB3-17306 --- phpBB/includes/functions_content.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index 2f81c0db8da..941b2eb2462 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -324,7 +324,7 @@ function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_po * * @return string Context of the specified words separated by "..." */ -function get_context($text, $words, $length = 400) +function get_context(string $text, array $words, int $length = 400) { // first replace all whitespaces with single spaces $text = preg_replace('/ +/', ' ', strtr($text, "\t\n\r\x0C ", ' ')); From 18a672c16e0b81b9881e0ab987274765d0e4bab4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 23 Apr 2024 20:17:59 +0200 Subject: [PATCH 0091/1214] [ticket/17305] Add missing space to sql_sort PHPBB3-17305 --- phpBB/search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/search.php b/phpBB/search.php index abd177f9d90..a81c42e6f00 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -423,7 +423,7 @@ if ($sort_key == 'a') { $sort_join = USERS_TABLE . ' u, '; - $sql_sort = ' AND u.user_id = ' . ($show_results == 'posts' ? 'p.poster_id' : 't.topic_last_poster_id ') . $sql_sort; + $sql_sort = ' AND u.user_id = ' . ($show_results == 'posts' ? 'p.poster_id ' : 't.topic_last_poster_id ') . $sql_sort; } if ($show_results == 'posts') { From 6c45c1ff6bbdb4d000c804e7e2d4ba36c30c0fdc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 25 Sep 2023 22:04:02 +0200 Subject: [PATCH 0092/1214] [ticket/15325] Do not show non-local permissions for local data PHPBB3-15325 --- phpBB/includes/acp/auth.php | 2 +- phpBB/phpbb/auth/auth.php | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index efe0f0e5d17..85062896556 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -95,7 +95,7 @@ function get_mask($mode, $user_id = false, $group_id = false, $forum_id = false, } else { - $hold_ary = ($group_id !== false) ? $this->acl_group_raw_data($group_id, $auth_option . '%', ($scope == 'global') ? 0 : false) : $this->$acl_user_function($user_id, $auth_option . '%', ($scope == 'global') ? 0 : false); + $hold_ary = ($group_id !== false) ? $this->acl_group_raw_data($group_id, $auth_option . '%') : $this->$acl_user_function($user_id, $auth_option . '%', ($scope == 'global') ? 0 : false); } } diff --git a/phpBB/phpbb/auth/auth.php b/phpBB/phpbb/auth/auth.php index 55917ea9675..7e866bc20cd 100644 --- a/phpBB/phpbb/auth/auth.php +++ b/phpBB/phpbb/auth/auth.php @@ -776,6 +776,7 @@ function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : ''; $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + $is_local = $forum_id !== false ? 'AND ao.is_local <> 0' : ''; $sql_opts = ''; $hold_ary = $sql_ary = array(); @@ -787,9 +788,10 @@ function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) // Grab group settings - non-role specific... $sql_ary[] = 'SELECT a.group_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option - FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao + FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . " ao WHERE a.auth_role_id = 0 - AND a.auth_option_id = ao.auth_option_id ' . + AND a.auth_option_id = ao.auth_option_id + $is_local " . (($sql_group) ? 'AND a.' . $sql_group : '') . " $sql_forum $sql_opts @@ -797,9 +799,10 @@ function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) // Now grab group settings - role specific... $sql_ary[] = 'SELECT a.group_id, a.forum_id, r.auth_setting, r.auth_option_id, ao.auth_option - FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao + FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . " ao WHERE a.auth_role_id = r.role_id - AND r.auth_option_id = ao.auth_option_id ' . + $is_local + AND r.auth_option_id = ao.auth_option_id " . (($sql_group) ? 'AND a.' . $sql_group : '') . " $sql_forum $sql_opts From 8dd6b5373dcde42c881a0c88821a04f3428b41af Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 23 Apr 2024 21:33:09 +0200 Subject: [PATCH 0093/1214] [ticket/15325] Add test for displayed permissions PHPBB3-15325 --- tests/functional/acp_permissions_test.php | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/functional/acp_permissions_test.php b/tests/functional/acp_permissions_test.php index ddd519506fe..92980dbfd7f 100644 --- a/tests/functional/acp_permissions_test.php +++ b/tests/functional/acp_permissions_test.php @@ -124,4 +124,29 @@ public function test_change_permission($description, $permission_type, $permissi $auth->acl($user_data); $this->assertEquals(0, $auth->acl_get($permission)); } + + public function test_forum_permissions_misc() + { + // Open forum moderators permissions page + $crawler = self::request('GET', "adm/index.php?i=acp_permissions&icat=16&mode=setting_mod_local&sid=" . $this->sid); + + // Select "Your first forum" + $form = $crawler->filter('#select_victim')->form(['forum_id' => [2]]); + $crawler = self::submit($form); + + // Select "Global moderators" + $form = $crawler->filter('#add_groups')->form(['group_id' => [4]]); + $crawler = self::submit($form); + + // Check that global permissions are not displayed + $this->add_lang('acp/permissions_phpbb'); + $page_text = $crawler->text(); + $this->assertNotContainsLang('ACL_M_BAN', $page_text); + $this->assertNotContainsLang('ACL_M_PM_REPORT', $page_text); + $this->assertNotContainsLang('ACL_M_WARN', $page_text); + + // Check that other permissions exist + $this->assertContainsLang('ACL_M_EDIT', $page_text); + $this->assertContainsLang('ACL_M_MOVE', $page_text); + } } From 6f45b467466c963d874bad1a442ce3c26b2e34dd Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 1 May 2024 11:22:29 +0200 Subject: [PATCH 0094/1214] [ticket/17077] Add proper locking in PHP without releasing form tokens PHPBB3-17077 --- phpBB/phpbb/lock/posting.php | 22 ++----------------- phpBB/posting.php | 3 --- tests/lock/posting_test.php | 14 +++++++----- .../phpbb_functional_test_case.php | 16 ++++++++++++++ .../phpbb_test_case_helpers.php | 1 - 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/phpBB/phpbb/lock/posting.php b/phpBB/phpbb/lock/posting.php index 1fee36e63f1..569cad9971b 100644 --- a/phpBB/phpbb/lock/posting.php +++ b/phpBB/phpbb/lock/posting.php @@ -27,9 +27,6 @@ class posting /** @var string */ private $lock_name = ''; - /** @var bool Lock state */ - private $locked = false; - /** * Constructor for posting lock * @@ -67,29 +64,14 @@ public function acquire(int $creation_time, string $form_token): bool { $this->set_lock_name($creation_time, $form_token); - // Lock is held for session, cannot acquire it - if ($this->cache->_exists($this->lock_name)) + // Lock is held for session, cannot acquire it unless special flag for testing is set + if ($this->cache->_exists($this->lock_name) && !$this->config->offsetExists('ci_tests_no_lock_posting')) { return false; } - $this->locked = true; - $this->cache->put($this->lock_name, true, $this->config['flood_interval']); return true; } - - /** - * Release lock - * - * @return void - */ - public function release(): void - { - if ($this->locked) - { - $this->cache->destroy($this->lock_name); - } - } } diff --git a/phpBB/posting.php b/phpBB/posting.php index bea75081c14..f99b0db0061 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1565,9 +1565,6 @@ // The last parameter tells submit_post if search indexer has to be run $redirect_url = submit_post($mode, $post_data['post_subject'], $post_author_name, $post_data['topic_type'], $poll, $data, $update_message, ($update_message || $update_subject) ? true : false); - // Release lock after submitting post - $posting_lock->release(); - /** * This event allows you to define errors after the post action is performed * diff --git a/tests/lock/posting_test.php b/tests/lock/posting_test.php index e9a9cdfffb8..75716dd2bac 100644 --- a/tests/lock/posting_test.php +++ b/tests/lock/posting_test.php @@ -42,13 +42,15 @@ public function test_lock_acquire() $this->assertFalse($this->lock->acquire(100, 'foo')); $this->assertTrue($this->cache->_exists(sha1('100foo') . '_posting_lock')); - $this->lock->release(); - $this->assertFalse($this->cache->_exists(sha1('100foo') . '_posting_lock')); + $this->assertFalse($this->lock->acquire(100, 'foo')); + $this->cache->put(sha1('100foo') . '_posting_lock', 'foo', -30); $this->assertTrue($this->lock->acquire(100, 'foo')); $this->assertTrue($this->cache->_exists(sha1('100foo') . '_posting_lock')); - $this->lock->release(); - $this->lock->release(); // double release has no effect - $this->assertFalse($this->cache->_exists(sha1('100foo') . '_posting_lock')); + $this->config->offsetSet('ci_tests_no_lock_posting', true); + $this->assertTrue($this->lock->acquire(100, 'foo')); + $this->assertTrue($this->cache->_exists(sha1('100foo') . '_posting_lock')); + // Multiple acquires possible due to special ci test flag + $this->assertTrue($this->lock->acquire(100, 'foo')); } -} \ No newline at end of file +} diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index a90008c22ed..9e4cb364d0d 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -96,6 +96,14 @@ protected function setUp(): void $db = $this->get_db(); + // Special flag for testing without possibility to run into lock scenario. + // Unset entry and add it back if lock behavior for posting should be tested. + // Unset ci_tests_no_lock_posting from config + $db->sql_return_on_error(true); + $sql = 'INSERT INTO ' . CONFIG_TABLE . " (config_name, config_value) VALUES ('ci_tests_no_lock_posting', '1')"; + $this->db->sql_query($sql); + $db->sql_return_on_error(false); + foreach (static::setup_extensions() as $extension) { $this->purge_cache(); @@ -120,6 +128,11 @@ protected function tearDown(): void if ($this->db instanceof \phpbb\db\driver\driver_interface) { + // Unset ci_tests_no_lock_posting from config + $sql = 'DELETE FROM ' . CONFIG_TABLE . " + WHERE config_name = 'ci_tests_no_lock_posting'"; + $this->db->sql_query($sql); + // Close the database connections again this test $this->db->sql_close(); } @@ -193,6 +206,9 @@ public function __construct($name = NULL, array $data = [], $dataName = '') $this->excludeBackupStaticAttributes($backupStaticAttributesBlacklist); } + /** + * @return \phpbb\db\driver\driver_interface + */ protected function get_db() { global $phpbb_root_path, $phpEx; diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index c8cddb4352a..d22b1cb8fad 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -123,7 +123,6 @@ static public function get_test_config() { $config = array(); - if (extension_loaded('sqlite3')) { $config = array_merge($config, array( From 3f73ae8545fa0746519935ebe75b92988fb969fc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 3 May 2024 20:25:44 +0200 Subject: [PATCH 0095/1214] [ticket/17130] Reparse magic urls in posts based on actual content PHPBB3-17130 --- phpBB/phpbb/textreparser/base.php | 7 ++- .../text_reparser/plugins/post_text_test.php | 63 +++++++++++++++++++ .../plugins/test_row_based_plugin.php | 1 + 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/textreparser/base.php b/phpBB/phpbb/textreparser/base.php index 2ee6ea2cb36..82a59a075d6 100644 --- a/phpBB/phpbb/textreparser/base.php +++ b/phpBB/phpbb/textreparser/base.php @@ -198,8 +198,8 @@ protected function guess_bbcodes(array $record) */ protected function guess_magic_url(array $record) { - // Look for or for a URL tag that's not immediately followed by - return (strpos($record['text'], '') !== false || preg_match('(]++>(?!))', $record['text'])); + // Look for magic URL markers or for a URL tag that's not immediately followed by + return preg_match('#.*?#', $record['text']) || preg_match('(]++>(?!))', $record['text']); } /** @@ -231,7 +231,10 @@ public function reparse_range($min_id, $max_id) */ protected function reparse_record(array $record) { + // Guess magic URL state based on actual record content before adding fields + $record['enable_magic_url'] = $this->guess_magic_url($record); $record = $this->add_missing_fields($record); + $flags = ($record['enable_bbcode']) ? OPTION_FLAG_BBCODE : 0; $flags |= ($record['enable_smilies']) ? OPTION_FLAG_SMILIES : 0; $flags |= ($record['enable_magic_url']) ? OPTION_FLAG_LINKS : 0; diff --git a/tests/text_reparser/plugins/post_text_test.php b/tests/text_reparser/plugins/post_text_test.php index 8ea71e65f50..ecd06ac9bf0 100644 --- a/tests/text_reparser/plugins/post_text_test.php +++ b/tests/text_reparser/plugins/post_text_test.php @@ -23,4 +23,67 @@ protected function get_reparser() { return new \phpbb\textreparser\plugins\post_text($this->db, POSTS_TABLE); } + + public function data_reparse_url(): array + { + return [ + [ // Reparse the same + 'https://www.example.com and some more text test included', + 'https://www.example.com and some more text test included', + ], + [ // Reparse without magic URL, shouldn't change + 'https://www.example.com and some more text test included', + 'https://www.example.com and some more text test included', + ], + [ // Reparse new format without magic URL, shouldn't change + 'https://www.example.com and some more text test included', + 'https://www.example.com and some more text test included', + ], + [ // Reparse with magic URL, should update to text formatter format + 'Foo is https://symfony.com/doc/current/service_container.html good', + 'Foo is https://symfony.com/doc/current/service_container.html good', + ], + [ // Reparse new format with magic URL, shouldn't change + 'Foo is https://symfony.com/doc/current/service_container.html good', + 'Foo is https://symfony.com/doc/current/service_container.html good', + ] + ]; + } + + /** + * @dataProvider data_reparse_url + */ + public function test_reparse_url(string $input_text, string $expected_text) + { + foreach ([true, false] as $enable_magic_url) + { + $record = [ + 'enable_bbcode' => true, + 'enable_smilies' => true, + 'enable_magic_url' => $enable_magic_url, + 'post_text' => $input_text, + 'bbcode_uid' => '', + ]; + + $sql = 'INSERT INTO ' . POSTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $record); + $this->db->sql_query($sql); + + $record['id'] = $this->db->sql_last_inserted_id(); + $record['text'] = $record['post_text']; + + // Call reparse_record via reflection + $reparser = $this->get_reparser(); + $reparser_reflection = new \ReflectionMethod($reparser, 'reparse_record'); + $reparser_reflection->setAccessible(true); + $reparser_reflection->invoke($reparser, $record); + + // Retrieve reparsed post text and compare with expectec + $sql = 'SELECT post_id, post_text FROM ' . POSTS_TABLE . ' WHERE post_id = ' . (int) $record['id']; + $result = $this->db->sql_query($sql); + $actual_text = $this->db->sql_fetchfield('post_text'); + $this->db->sql_freeresult($result); + + $this->assertSame($expected_text, $actual_text); + } + } } diff --git a/tests/text_reparser/plugins/test_row_based_plugin.php b/tests/text_reparser/plugins/test_row_based_plugin.php index 700bead7e53..eca6b2dd67e 100644 --- a/tests/text_reparser/plugins/test_row_based_plugin.php +++ b/tests/text_reparser/plugins/test_row_based_plugin.php @@ -15,6 +15,7 @@ abstract class phpbb_textreparser_test_row_based_plugin extends phpbb_database_test_case { + /** @var \phpbb\db\driver\driver_interface */ protected $db; abstract protected function get_reparser(); From 42108b5e119efb149294cde85181aeecf483dae9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 4 May 2024 09:00:30 +0200 Subject: [PATCH 0096/1214] [ticket/15325] Remove no longer needed note about permissions being global PHPBB3-15325 --- phpBB/language/en/acp/permissions_phpbb.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index ab8939932b1..cd84dc1e968 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -159,9 +159,9 @@ 'ACL_M_MERGE' => 'Can merge topics', 'ACL_M_INFO' => 'Can view post details', - 'ACL_M_WARN' => 'Can issue warnings
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) - 'ACL_M_PM_REPORT' => 'Can close and delete reports of private messages
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) - 'ACL_M_BAN' => 'Can manage bans
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) + 'ACL_M_WARN' => 'Can issue warnings', + 'ACL_M_PM_REPORT' => 'Can close and delete reports of private messages', + 'ACL_M_BAN' => 'Can manage bans', )); // Admin Permissions From 4573e2eae02d8b8172c622c56f78034c3171392b Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 4 May 2024 08:32:51 -0700 Subject: [PATCH 0097/1214] [ticket/17300] Replace Sticky and Announcement Icons PHPBB3-17300 Signed-off-by: Matt Friedman --- phpBB/phpbb/template/twig/extension/icon.php | 2 +- phpBB/styles/prosilver/imgs/svg/bullhorn.svg | 1 + phpBB/styles/prosilver/imgs/svg/globe.svg | 1 + phpBB/styles/prosilver/imgs/svg/thumbtack.svg | 1 + phpBB/styles/prosilver/template/mcp_forum.html | 6 +++--- phpBB/styles/prosilver/template/search_results.html | 6 +++--- phpBB/styles/prosilver/template/ucp_main_bookmarks.html | 6 +++--- phpBB/styles/prosilver/template/ucp_main_front.html | 6 +++--- phpBB/styles/prosilver/template/ucp_main_subscribed.html | 6 +++--- phpBB/styles/prosilver/template/viewforum_body.html | 6 +++--- tests/template/extension_test.php | 4 ++-- 11 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 phpBB/styles/prosilver/imgs/svg/bullhorn.svg create mode 100644 phpBB/styles/prosilver/imgs/svg/globe.svg create mode 100644 phpBB/styles/prosilver/imgs/svg/thumbtack.svg diff --git a/phpBB/phpbb/template/twig/extension/icon.php b/phpBB/phpbb/template/twig/extension/icon.php index 8edd33bbc7d..37888538370 100644 --- a/phpBB/phpbb/template/twig/extension/icon.php +++ b/phpBB/phpbb/template/twig/extension/icon.php @@ -280,7 +280,7 @@ protected function prepare_svg(\Twig\TemplateWrapper $file, &$view_box = '') * * {{ Icon('font', { * 'bullhorn': topicrow.S_POST_GLOBAL or topicrow.S_POST_ANNOUNCE, - * 'star': topicrow.S_POST_STICKY, + * 'thumbtack': topicrow.S_POST_STICKY, * 'lock': topicrow.S_TOPIC_LOCKED, * 'fire': topicrow.S_TOPIC_HOT, * 'file': true, diff --git a/phpBB/styles/prosilver/imgs/svg/bullhorn.svg b/phpBB/styles/prosilver/imgs/svg/bullhorn.svg new file mode 100644 index 00000000000..2755c3b78bb --- /dev/null +++ b/phpBB/styles/prosilver/imgs/svg/bullhorn.svg @@ -0,0 +1 @@ + diff --git a/phpBB/styles/prosilver/imgs/svg/globe.svg b/phpBB/styles/prosilver/imgs/svg/globe.svg new file mode 100644 index 00000000000..c3405615094 --- /dev/null +++ b/phpBB/styles/prosilver/imgs/svg/globe.svg @@ -0,0 +1 @@ + diff --git a/phpBB/styles/prosilver/imgs/svg/thumbtack.svg b/phpBB/styles/prosilver/imgs/svg/thumbtack.svg new file mode 100644 index 00000000000..9a0adc18fa9 --- /dev/null +++ b/phpBB/styles/prosilver/imgs/svg/thumbtack.svg @@ -0,0 +1 @@ + diff --git a/phpBB/styles/prosilver/template/mcp_forum.html b/phpBB/styles/prosilver/template/mcp_forum.html index cff04e89bcc..786c09c5317 100644 --- a/phpBB/styles/prosilver/template/mcp_forum.html +++ b/phpBB/styles/prosilver/template/mcp_forum.html @@ -40,9 +40,9 @@

{L_FORUM}{L_COLON} {FORUM_NAME}

{{ Icon('svg', { - 'info-variant' : topicrow.S_POST_GLOBAL, - 'info-variant' : topicrow.S_POST_ANNOUNCE, - 'star' : topicrow.S_POST_STICKY, + 'globe' : topicrow.S_POST_GLOBAL, + 'bullhorn' : topicrow.S_POST_ANNOUNCE, + 'thumbtack' : topicrow.S_POST_STICKY, 'lock' : topicrow.S_TOPIC_LOCKED, 'arrow-right-bold' : topicrow.S_TOPIC_MOVED, 'fire' : topicrow.S_TOPIC_HOT, diff --git a/phpBB/styles/prosilver/template/search_results.html b/phpBB/styles/prosilver/template/search_results.html index 8ea1bd05182..e7cd42ff4f9 100644 --- a/phpBB/styles/prosilver/template/search_results.html +++ b/phpBB/styles/prosilver/template/search_results.html @@ -82,9 +82,9 @@

{SEARCH_TITLE} - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 4de56d8fdbd..f5d8122628e 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '3.3.12-dev'); +@define('PHPBB_VERSION', '3.3.12-RC1'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/phpbbcli.php b/phpBB/install/phpbbcli.php index b7e85890e32..31072dbd6b8 100755 --- a/phpBB/install/phpbbcli.php +++ b/phpBB/install/phpbbcli.php @@ -23,7 +23,7 @@ define('IN_PHPBB', true); define('IN_INSTALL', true); define('PHPBB_ENVIRONMENT', 'production'); -define('PHPBB_VERSION', '3.3.12-dev'); +define('PHPBB_VERSION', '3.3.12-RC1'); $phpbb_root_path = __DIR__ . '/../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 97ff395ac90..7ec43e3b547 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -316,7 +316,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.12-dev'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.12-RC1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); From 74845a6b3a9215519888b712fe335a09ae6ae9e1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 9 May 2024 12:59:36 +0200 Subject: [PATCH 0105/1214] [prep-release-3.3.12] Update version numbers to 3.3.12 --- phpBB/install/convertors/convert_phpbb20.php | 2 +- phpBB/styles/prosilver/style.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 54d7c7f8d69..2d6a868938d 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -38,7 +38,7 @@ $convertor_data = array( 'forum_name' => 'phpBB 2.0.x', 'version' => '1.0.3', - 'phpbb_version' => '3.3.11', + 'phpbb_version' => '3.3.12', 'author' => 'phpBB Limited', 'dbms' => $dbms, 'dbhost' => $dbhost, diff --git a/phpBB/styles/prosilver/style.cfg b/phpBB/styles/prosilver/style.cfg index 8d7af0133e1..3f6f8a35d6f 100644 --- a/phpBB/styles/prosilver/style.cfg +++ b/phpBB/styles/prosilver/style.cfg @@ -21,8 +21,8 @@ # General Information about this style name = prosilver copyright = © phpBB Limited, 2007 -style_version = 3.3.11 -phpbb_version = 3.3.11 +style_version = 3.3.12 +phpbb_version = 3.3.12 # Defining a different template bitfield # template_bitfield = //g= From d2295b82f17ee8ed2da5a1584ebb3997a7a46d47 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 9 May 2024 12:59:37 +0200 Subject: [PATCH 0106/1214] [prep-release-3.3.12] Add migration for 3.3.12-RC1 --- .../phpbb/db/migration/data/v33x/v3312rc1.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/v3312rc1.php diff --git a/phpBB/phpbb/db/migration/data/v33x/v3312rc1.php b/phpBB/phpbb/db/migration/data/v33x/v3312rc1.php new file mode 100644 index 00000000000..77247b96f25 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3312rc1.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3312rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.12-RC1', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\jquery_update_v3', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.12-RC1']], + ]; + } +} From 6753961756da4998d98d651e6a09db763c088b38 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 9 May 2024 12:59:38 +0200 Subject: [PATCH 0107/1214] [prep-release-3.3.12] Update stylesheet hashes for 3.3.12-RC1 --- phpBB/styles/prosilver/theme/stylesheet.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/styles/prosilver/theme/stylesheet.css b/phpBB/styles/prosilver/theme/stylesheet.css index 5665c839baf..e866175bf71 100644 --- a/phpBB/styles/prosilver/theme/stylesheet.css +++ b/phpBB/styles/prosilver/theme/stylesheet.css @@ -12,10 +12,10 @@ @import url("utilities.css?hash=d8f72c42"); @import url("common.css?hash=a9741ba1"); @import url("links.css?hash=18286e16"); -@import url("content.css?hash=be57a41d"); +@import url("content.css?hash=d0e24377"); @import url("buttons.css?hash=56f0d25f"); @import url("cp.css?hash=50d868ab"); -@import url("forms.css?hash=b64464fb"); +@import url("forms.css?hash=9016b55c"); @import url("icons.css?hash=64da33ce"); @import url("colours.css?hash=fcb2f289"); -@import url("responsive.css?hash=87b53e08"); +@import url("responsive.css?hash=91525545"); From 7b734ef52996043b0ff940635ece56fe215de4fc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 9 May 2024 13:01:53 +0200 Subject: [PATCH 0108/1214] [prep-release-3.3.12] Update changelog for 3.3.12-RC1 --- phpBB/docs/CHANGELOG.html | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index b0e774b29e3..37fe4a2cb58 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -50,6 +50,7 @@

Changelog

  1. Changelog
      +
    • Changes since 3.3.11
    • Changes since 3.3.10
    • Changes since 3.3.10-RC1
    • Changes since 3.3.9
    • @@ -167,6 +168,51 @@

      Changelog

      +

      Changes since 3.3.11

      +

      Bug

      +
        +
      • [PHPBB3-14047] - Jabber discards messages when stream gets closed without waiting for acknowledgement
      • +
      • [PHPBB3-15325] - Global moderator permissions shown in forum moderator permissions
      • +
      • [PHPBB3-16470] - Memberlist bug - sorting by Last active date is incorrect
      • +
      • [PHPBB3-17077] - Multiple posts at once, even if the user shouldn't ignore the flood interval
      • +
      • [PHPBB3-17117] - Deactivated notification method leads to crash
      • +
      • [PHPBB3-17130] - Text reparser changes magic URL state in posts
      • +
      • [PHPBB3-17187] - Unread Topic URL Link Not Working On MCP View Forum Topic List
      • +
      • [PHPBB3-17200] - Color Parse Error In viewonline.php Legend
      • +
      • [PHPBB3-17201] - Redirect to installer might be invalid when accessing subfolder
      • +
      • [PHPBB3-17202] - The bidi.css File Is Loaded When Viewing LTR Topic Print View Page
      • +
      • [PHPBB3-17203] - Group Description With BBCode Ordered List Breaks Layout
      • +
      • [PHPBB3-17207] - Extensions are unable to use PHPBB_USE_BOARD_URL_PATH
      • +
      • [PHPBB3-17208] - Update Error YouTube Profilfeld
      • +
      • [PHPBB3-17212] - Who is online incorrectly reports page when posting with only post URL parameter
      • +
      • [PHPBB3-17237] - QUICKMOD_ACTION_NOT_ALLOWED uses " instead of '
      • +
      • [PHPBB3-17286] - Non-existent urls to be written down to session_page
      • +
      • [PHPBB3-17292] - Link to spamhaus.org no longer valid
      • +
      • [PHPBB3-17296] - mod_security false positive denies access to ACP
      • +
      • [PHPBB3-17302] - Password reset function does not update all necessary data
      • +
      • [PHPBB3-17306] - Wrong declaration of function input values
      • +
      +

      Improvement

      +
        +
      • [PHPBB3-17230] - Update doctum for PHP 8.1 support
      • +
      • [PHPBB3-17232] - Improve MySQL error messages in PHP 8.1+
      • +
      • [PHPBB3-17233] - Add PHP 8.3 tests to the 3.3.x branch
      • +
      • [PHPBB3-17235] - Missing autocomplete for username & password
      • +
      • [PHPBB3-17236] - Update symfony dependencies to improve PHP 8.3 compatibility
      • +
      • [PHPBB3-17277] - Add template events to UCP
      • +
      • [PHPBB3-17284] - Add event to add content after the online users list in viewtopic
      • +
      • [PHPBB3-17293] - Update composer and dependencies to latest versions
      • +
      • [PHPBB3-17299] - Allow core event to modify variables while sending email
      • +
      • [PHPBB3-17303] - Update jQuery to 3.7+
      • +
      • [PHPBB3-17305] - Improve queries for unanswered topics and posts
      • +
      • [PHPBB3-17310] - Update GitHub actions workflows to Node.js 20
      • +
      +

      Task

      +
        +
      • [PHPBB3-17204] - Update composer and node dependencies
      • +
      • [PHPBB3-17280] - Fallback to branch name on branches without ticket ID
      • +
      +

      Changes since 3.3.10

      Bug

        From de31c4e797e30173706536b43ee380b91724364b Mon Sep 17 00:00:00 2001 From: battye Date: Sun, 12 May 2024 09:53:07 +0000 Subject: [PATCH 0109/1214] [ticket/17175] Fix topic title in the breadcrumbs when emailing a topic PHPBB3-17175 --- phpBB/memberlist.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index e9af6026d77..0c32adc6ba9 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -941,10 +941,19 @@ } else if ($topic_id) { - $sql = 'SELECT f.parent_id, f.forum_parents, f.left_id, f.right_id, f.forum_type, f.forum_name, f.forum_id, f.forum_desc, f.forum_desc_uid, f.forum_desc_bitfield, f.forum_desc_options, f.forum_options, t.topic_title - FROM ' . FORUMS_TABLE . ' as f, - ' . TOPICS_TABLE . ' as t - WHERE t.forum_id = f.forum_id'; + // Generate the navlinks based on the selected topic + $navlinks_sql_array = [ + 'SELECT' => 'f.parent_id, f.forum_parents, f.left_id, f.right_id, f.forum_type, f.forum_name, + f.forum_id, f.forum_desc, f.forum_desc_uid, f.forum_desc_bitfield, f.forum_desc_options, + f.forum_options, t.topic_title', + 'FROM' => [ + FORUMS_TABLE => 'f', + TOPICS_TABLE => 't', + ], + 'WHERE' => 't.forum_id = f.forum_id AND t.topic_id = ' . (int) $topic_id, + ]; + + $sql = $db->sql_build_query('SELECT', $navlinks_sql_array); $result = $db->sql_query($sql); $topic_data = $db->sql_fetchrow($result); $db->sql_freeresult($result); From e195e2ad82523418dcefbc7b1596efbd926eabc8 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 12 May 2024 21:10:16 +0200 Subject: [PATCH 0110/1214] [3.3.x] Update version to 3.3.13-dev --- build/build.xml | 6 +++--- phpBB/includes/constants.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/build.xml b/build/build.xml index c3a741b8917..ce9bf52c65d 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2,9 +2,9 @@ - - - + + + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index f5d8122628e..e7059af80d5 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '3.3.12-RC1'); +@define('PHPBB_VERSION', '3.3.13-dev'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 7ec43e3b547..cd8dc70e79d 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -316,7 +316,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.12-RC1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.13-dev'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); From 874fb8c59caacc34e17903835636d5cb5aa53cec Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Mon, 13 May 2024 19:21:52 +0200 Subject: [PATCH 0111/1214] [ticket/17311] Update PHP requirements and tested version PHPBB3-17176 --- phpBB/docs/INSTALL.html | 2 +- phpBB/docs/README.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/docs/INSTALL.html b/phpBB/docs/INSTALL.html index 2851151cddf..9262d2d612b 100644 --- a/phpBB/docs/INSTALL.html +++ b/phpBB/docs/INSTALL.html @@ -147,7 +147,7 @@

        Install

      • Oracle
      -
    • PHP 7.2.0+ up to and including PHP 8.1 with support for the database you intend to use.
    • +
    • PHP 7.2.0+ up to and including PHP 8.3 with support for the database you intend to use.
    • The following PHP modules are required:
      • json
      • diff --git a/phpBB/docs/README.html b/phpBB/docs/README.html index 2560401cac2..97c18c82c9a 100644 --- a/phpBB/docs/README.html +++ b/phpBB/docs/README.html @@ -327,7 +327,7 @@

        Readme

        Please remember that running any application on a development (unstable, e.g. a beta release) version of PHP can lead to strange/unexpected results which may appear to be bugs in the application. Therefore, we recommend you upgrade to the newest stable version of PHP before running phpBB. If you are running a development version of PHP please check any bugs you find on a system running a stable release before submitting.

        -

        This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQLi 4.1.3, 4.x, 5.x, MariaDB 5.x, PostgreSQL 8.x, Oracle 8 and SQLite 3. Versions of PHP used range from 7.2.0 to 7.4.x, 8.0.x and 8.1.x.

        +

        This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQLi 4.1.3, 4.x, 5.x, MariaDB 5.x, PostgreSQL 8.x, Oracle 8 and SQLite 3. Versions of PHP used range from 7.2.0 to 7.4.x and 8.0.x to 8.3.x.

        7.i. Notice on PHP security issues

        From 9cb10ec28d5920b06358084dc9cacedbebc96b22 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 13 May 2024 17:38:28 -0700 Subject: [PATCH 0112/1214] [ticket/17313] Add Push Module to Correct Parent PHPBB3-17313 Signed-off-by: Matt Friedman --- phpBB/phpbb/db/migration/data/v400/add_webpush.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush.php b/phpBB/phpbb/db/migration/data/v400/add_webpush.php index 351e02a3611..c0fabf722aa 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush.php @@ -77,7 +77,7 @@ public function update_data(): array ['config.add', ['webpush_vapid_private', '']], ['module.add', [ 'acp', - 'ACP_BOARD_CONFIGURATION', + 'ACP_CLIENT_COMMUNICATION', [ 'module_basename' => 'acp_board', 'module_langname' => 'ACP_WEBPUSH_SETTINGS', @@ -95,7 +95,7 @@ public function revert_data(): array ['config.remove', ['webpush_enable']], ['config.remove', ['webpush_vapid_public']], ['config.remove', ['webpush_vapid_private']], - ['module.remove', ['acp', 'ACP_BOARD_CONFIGURATION', 'ACP_WEBPUSH_SETTINGS']] + ['module.remove', ['acp', 'ACP_CLIENT_COMMUNICATION', 'ACP_WEBPUSH_SETTINGS']] ]; } } From a7a53de34d6372ccec79d3944741821b8592911e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 14 May 2024 21:42:02 +0200 Subject: [PATCH 0113/1214] [ticket/17312] Add migration for user_last_active column PHPBB3-17312 --- .../data/v33x/add_user_last_active.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php diff --git a/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php b/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php new file mode 100644 index 00000000000..58cc4f02401 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php @@ -0,0 +1,48 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +use phpbb\db\migration\migration; + +class add_user_last_active extends migration +{ + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3311', + ]; + } + + public function update_schema() + { + return [ + 'add_columns' => [ + $this->table_prefix . 'users' => [ + 'user_last_active' => ['TIMESTAMP', 0], + ], + ], + ]; + } + + public function revert_schema() + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'users' => [ + 'user_last_active' => ['TIMESTAMP', 0], + ], + ], + ]; + } +} From 766ecaed5a12bd01dfcd9b54f97ead8141739d1e Mon Sep 17 00:00:00 2001 From: Daniel James Date: Thu, 16 May 2024 11:55:24 +0100 Subject: [PATCH 0114/1214] [ticket/17315] Add template events to acp_groups PHPBB3-17315 --- phpBB/adm/style/acp_groups.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpBB/adm/style/acp_groups.html b/phpBB/adm/style/acp_groups.html index 8651b63b7f2..b91f91f12ef 100644 --- a/phpBB/adm/style/acp_groups.html +++ b/phpBB/adm/style/acp_groups.html @@ -225,6 +225,7 @@

        {L_ADD_USERS}

        {L_ADD_USERS} + {% EVENT acp_groups_add_user_form_before %}
        @@ -235,11 +236,13 @@

        {L_ADD_USERS}

        + {% EVENT acp_groups_add_user_form_during %}

        {L_USERNAMES_EXPLAIN}
        [ {L_FIND_USERNAME} ]
        + {% EVENT acp_groups_add_user_form_after %}

        From b2d2216b8741128cfa48a5af8540af0cd04c1535 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 16 May 2024 16:43:24 +0200 Subject: [PATCH 0115/1214] [ticket/17315] Rename events and add them to events.md PHPBB3-17315 --- phpBB/adm/style/acp_groups.html | 6 +++--- phpBB/docs/events.md | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/phpBB/adm/style/acp_groups.html b/phpBB/adm/style/acp_groups.html index b91f91f12ef..0df24bb1759 100644 --- a/phpBB/adm/style/acp_groups.html +++ b/phpBB/adm/style/acp_groups.html @@ -225,7 +225,7 @@

        {L_ADD_USERS}

        {L_ADD_USERS} - {% EVENT acp_groups_add_user_form_before %} + {% EVENT acp_groups_add_user_options_before %}
        @@ -236,13 +236,13 @@

        {L_ADD_USERS}

        - {% EVENT acp_groups_add_user_form_during %} + {% EVENT acp_groups_add_user_usernames_before %}

        {L_USERNAMES_EXPLAIN}
        [ {L_FIND_USERNAME} ]
        - {% EVENT acp_groups_add_user_form_after %} + {% EVENT acp_groups_add_user_options_after %}

        diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index d05cb876fdb..48a6c22630a 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -184,6 +184,24 @@ acp_group_types_prepend * Since: 3.2.9-RC1 * Purpose: Add additional group type options to group settings (prepend the list) +acp_groups_add_user_options_after +=== +* Location: adm/style/acp_groups.html +* Since: 3.3.13-RC1 +* Purpose: Add content after options for adding user to group in the ACP + +acp_groups_add_user_options_before +=== +* Location: adm/style/acp_groups.html +* Since: 3.3.13-RC1 +* Purpose: Add content before options for adding user to group in the ACP + +acp_groups_add_user_usernames_before +=== +* Location: adm/style/acp_groups.html +* Since: 3.3.13-RC1 +* Purpose: Add content before usernames option for adding user to group in the ACP + acp_groups_find_username_append === * Location: adm/style/acp_groups.html From ed0b5020a9c2e2b1c7ee12ebf7ff0c3791f3f664 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 16 May 2024 20:40:56 +0200 Subject: [PATCH 0116/1214] [ticket/17312] Use user_last_active instead of user_lastvisit where possible PHPBB3-17312 --- phpBB/memberlist.php | 10 ++--- .../data/v33x/add_user_last_active.php | 39 +++++++++++++++-- phpBB/phpbb/session.php | 43 ++++++++++++++----- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index e9af6026d77..c36ebd697ac 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -1036,7 +1036,7 @@ if ($auth->acl_get('u_viewonline')) { $sort_key_text['l'] = $user->lang['SORT_LAST_ACTIVE']; - $sort_key_sql['l'] = 'u.user_lastvisit'; + $sort_key_sql['l'] = 'u.user_last_active'; } $sort_key_text['m'] = $user->lang['SORT_RANK']; @@ -1138,15 +1138,15 @@ { if ($active_select === 'lt' && (int) $active[0] == 0 && (int) $active[1] == 0 && (int) $active[2] == 0) { - $sql_where .= ' AND u.user_lastvisit = 0'; + $sql_where .= ' AND u.user_last_active = 0'; } else if ($active_select === 'gt') { - $sql_where .= ' AND u.user_lastvisit ' . $find_key_match[$active_select] . ' ' . $active_time; + $sql_where .= ' AND u.user_last_active ' . $find_key_match[$active_select] . ' ' . $active_time; } else { - $sql_where .= ' AND (u.user_lastvisit > 0 AND u.user_lastvisit < ' . $active_time . ')'; + $sql_where .= ' AND (u.user_last_active > 0 AND u.user_last_active < ' . $active_time . ')'; } } } @@ -1713,7 +1713,7 @@ { $row['session_time'] = $session_ary[$row['user_id']]['session_time'] ?? 0; $row['session_viewonline'] = $session_ary[$row['user_id']]['session_viewonline'] ?? 0; - $row['last_visit'] = (!empty($row['session_time'])) ? $row['session_time'] : $row['user_lastvisit']; + $row['last_visit'] = (!empty($row['session_time'])) ? $row['session_time'] : $row['user_last_active']; $id_cache[$row['user_id']] = $row; } diff --git a/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php b/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php index 58cc4f02401..d4506eea392 100644 --- a/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php +++ b/phpBB/phpbb/db/migration/data/v33x/add_user_last_active.php @@ -29,7 +29,7 @@ public function update_schema() return [ 'add_columns' => [ $this->table_prefix . 'users' => [ - 'user_last_active' => ['TIMESTAMP', 0], + 'user_last_active' => ['TIMESTAMP', 0, 'after' => 'user_lastvisit'], ], ], ]; @@ -39,10 +39,41 @@ public function revert_schema() { return [ 'drop_columns' => [ - $this->table_prefix . 'users' => [ - 'user_last_active' => ['TIMESTAMP', 0], - ], + $this->table_prefix . 'users' => ['user_last_active'], ], ]; } + + public function update_data() + { + return [ + ['custom', [[$this, 'set_user_last_active']]], + ]; + } + + public function set_user_last_active($start = 0) + { + // Get maximum user id from database + $sql = "SELECT MAX(user_id) AS max_user_id + FROM {$this->table_prefix}users"; + $result = $this->db->sql_query($sql); + $max_id = (int) $this->db->sql_fetchfield('max_user_id'); + $this->db->sql_freeresult($result); + + if ($start > $max_id) + { + return; + } + + // Keep setting user_last_active time + $next_start = $start + 10000; + + $sql = 'UPDATE ' . $this->table_prefix . 'users + SET user_last_active = user_lastvisit + WHERE user_id > ' . (int) $start . ' + AND user_id <= ' . (int) ($next_start); + $this->db->sql_query($sql); + + return $next_start; + } } diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index 7cb7e15d78f..af39ecf2fa0 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -440,10 +440,10 @@ function session_begin($update_session_page = true) // Is user banned? Are they excluded? Won't return on ban, exists within method $this->check_ban_for_current_session($config); - // Update user last visit time accordingly, but in a minute or so - if ((int) $this->data['session_time'] - (int) $this->data['user_lastvisit'] > 60) + // Update user last active time accordingly, but in a minute or so + if ((int) $this->data['session_time'] - (int) $this->data['user_last_active'] > 60) { - $this->update_user_lastvisit(); + $this->update_last_active_time(); } return true; @@ -690,8 +690,8 @@ function session_create($user_id = false, $set_admin = false, $persist_login = f { $this->session_id = $this->data['session_id']; - // Only sync user last visit time in a minute or so after last session data update or if the page changes - if ((int) $this->data['session_time'] - (int) $this->data['user_lastvisit'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) + // Only update session DB a minute or so after last update or if page changes + if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) { // Update the last visit time $this->update_user_lastvisit(); @@ -818,22 +818,26 @@ function session_create($user_id = false, $set_admin = false, $persist_login = f $this->data['user_form_salt'] = unique_id(); // Update the form key $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\' + SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\', + user_last_active = ' . (int) $this->data['session_time'] . ' WHERE user_id = ' . (int) $this->data['user_id']; $db->sql_query($sql); } + else + { + $this->update_last_active_time(); + } } else { $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; + $this->update_user_lastvisit(); + $SID = '?sid='; $_SID = ''; } - // Update the last visit time - $this->update_user_lastvisit(); - $session_data = $sql_ary; /** * Event to send new session data to extension @@ -1807,7 +1811,26 @@ public function update_user_lastvisit() if (isset($this->data['session_time'], $this->data['user_id'])) { $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ', + user_last_active = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + } + } + + /** + * Update user's last active time + * + * @return void + */ + public function update_last_active_time() + { + global $db; + + if (isset($this->data['session_time'], $this->data['user_id'])) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_last_active = ' . (int) $this->data['session_time'] . ' WHERE user_id = ' . (int) $this->data['user_id']; $db->sql_query($sql); } From 611faafaf3802a661814d9a7c1765017eed94630 Mon Sep 17 00:00:00 2001 From: battye Date: Fri, 17 May 2024 14:02:12 +0000 Subject: [PATCH 0117/1214] [ticket/17109] Check attach signature permissions Don't show the attach signature option if the user doesn't have permission or if the signature feature is disabled for the board. PHPBB3-17109 --- phpBB/includes/ucp/ucp_prefs.php | 2 ++ phpBB/posting.php | 2 +- phpBB/styles/prosilver/template/ucp_prefs_post.html | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 0d6a178c672..3bb575106a7 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -488,6 +488,8 @@ function main($id, $mode) } $template->assign_vars(array( + 'S_SIG_ALLOWED' => $config['allow_sig'] && $auth->acl_get('u_sig'), + 'S_BBCODE' => $data['bbcode'], 'S_SMILIES' => $data['smilies'], 'S_SIG' => $data['sig'], diff --git a/phpBB/posting.php b/phpBB/posting.php index f99b0db0061..b3562b48f03 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1966,7 +1966,7 @@ 'S_BBCODE_CHECKED' => ($bbcode_checked) ? ' checked="checked"' : '', 'S_SMILIES_ALLOWED' => $smilies_status, 'S_SMILIES_CHECKED' => ($smilies_checked) ? ' checked="checked"' : '', - 'S_SIG_ALLOWED' => ($auth->acl_get('f_sigs', $forum_id) && $config['allow_sig'] && $user->data['is_registered']) ? true : false, + 'S_SIG_ALLOWED' => ($user->data['is_registered'] && $config['allow_sig'] && $auth->acl_get('f_sigs', $forum_id) && $auth->acl_get('u_sig')) ? true : false, 'S_SIGNATURE_CHECKED' => ($sig_checked) ? ' checked="checked"' : '', 'S_NOTIFY_ALLOWED' => (!$user->data['is_registered'] || ($mode == 'edit' && $user->data['user_id'] != $post_data['poster_id']) || !$config['allow_topic_notify'] || !$config['email_enable']) ? false : true, 'S_NOTIFY_CHECKED' => ($notify_checked) ? ' checked="checked"' : '', diff --git a/phpBB/styles/prosilver/template/ucp_prefs_post.html b/phpBB/styles/prosilver/template/ucp_prefs_post.html index 39197c50623..4a7e7896672 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_post.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_post.html @@ -23,6 +23,7 @@

        {L_TITLE}

        + {% if S_SIG_ALLOWED %}
        @@ -30,6 +31,7 @@

        {L_TITLE}

        + {% endif %}
        From f9861384675725466a5210e00ed33bd3712636f3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 17 May 2024 16:41:40 +0200 Subject: [PATCH 0118/1214] [ticket/17312] Update tests for new column PHPBB3-17312 --- tests/auth/provider_apache_test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index 567ec538f20..2dbb1f32a98 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -115,6 +115,7 @@ public function test_autologin() 'user_email' => 'example@example.com', 'user_birthday' => '', 'user_lastvisit' => 0, + 'user_last_active' => 0, 'user_lastmark' => 0, 'user_lastpost_time' => 0, 'user_lastpage' => '', From 8954a689538da6431b67fd215cc661b363fd5de2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 May 2024 09:37:09 +0200 Subject: [PATCH 0119/1214] [ticket/17308] Change project key to start with PHPBB- PHPBB-17308 --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- git-tools/hooks/commit-msg | 4 ++-- git-tools/hooks/prepare-commit-msg | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 179d83d8372..c7bec59af96 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,4 +7,4 @@ Checklist: Tracker ticket: -https://tracker.phpbb.com/browse/PHPBB3-12345 +https://tracker.phpbb.com/browse/PHPBB-12345 diff --git a/git-tools/hooks/commit-msg b/git-tools/hooks/commit-msg index 5f5e9c29ceb..6405d5b7c80 100755 --- a/git-tools/hooks/commit-msg +++ b/git-tools/hooks/commit-msg @@ -224,7 +224,7 @@ do "footer") err=$ERR_FOOTER; # Each ticket is on its own line - echo "$line" | grep -Eq "^PHPBB3-[0-9]+$"; + echo "$line" | grep -Eq "^PHPBB3?-[0-9]+$"; ;; "eof") err=$ERR_EOF; @@ -356,7 +356,7 @@ echo "$expecting" | grep -q "eof" || ( # Check the branch ticket is mentioned, doesn't make sense otherwise if [ $ticket -gt 0 ] then - echo "$tickets" | grep -Eq "\bPHPBB3-$ticket\b" || ( + echo "$tickets" | grep -Eq "\bPHPBB3?-$ticket\b" || ( complain "Ticket ID [$ticket] of branch missing from list of tickets:" >&2; complain "$tickets" | sed 's/ /\n/g;s/^/* /g' >&2; quit $ERR_FOOTER; diff --git a/git-tools/hooks/prepare-commit-msg b/git-tools/hooks/prepare-commit-msg index 83db1f9ba16..6e5b701fdea 100755 --- a/git-tools/hooks/prepare-commit-msg +++ b/git-tools/hooks/prepare-commit-msg @@ -47,7 +47,7 @@ then # Branch is prefixed with 'ticket/', append ticket ID to message if [ "$branch" != "${branch##ticket/}" ]; then - tail="$(printf '\n\nPHPBB3-%s' "$ticket_id")"; + tail="$(printf '\n\nPHPBB-%s' "$ticket_id")"; fi fi From 9ecf09dbe8b88eef81e3a987cd30ece4e323ed98 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 18 May 2024 20:58:07 +0200 Subject: [PATCH 0120/1214] [ticket/17312] Add and use user_last_active where needed and useful PHPBB3-17312 --- phpBB/includes/acp/acp_users.php | 2 +- phpBB/includes/functions_display.php | 2 +- phpBB/includes/functions_user.php | 1 + phpBB/install/convertors/convert_phpbb20.php | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 14864cfdd48..7c3468f90df 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1084,7 +1084,7 @@ function main($id, $mode) $s_action_options .= ''; } - $last_active = (!empty($user_row['session_time'])) ? $user_row['session_time'] : $user_row['user_lastvisit']; + $last_active = (!empty($user_row['session_time'])) ? $user_row['session_time'] : $user_row['user_last_active']; $inactive_reason = ''; if ($user_row['user_type'] == USER_INACTIVE) diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 8fd70855868..d995676e8fb 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -1603,7 +1603,7 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl if ($data['user_allow_viewonline'] || $auth->acl_get('u_viewonline')) { - $last_active = (!empty($data['session_time'])) ? $data['session_time'] : $data['user_lastvisit']; + $last_active = (!empty($data['session_time'])) ? $data['session_time'] : $data['user_last_active']; } else { diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 0110034016e..25adf4215aa 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -227,6 +227,7 @@ function user_add($user_row, $cp_data = false, $notifications_data = null) 'user_inactive_time' => 0, 'user_lastmark' => time(), 'user_lastvisit' => 0, + 'user_last_active' => 0, 'user_lastpost_time' => 0, 'user_lastpage' => '', 'user_posts' => 0, diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 2d6a868938d..7f9449609ef 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -901,6 +901,7 @@ array('user_email', 'users.user_email', 'strtolower'), array('user_birthday', ((defined('MOD_BIRTHDAY')) ? 'users.user_birthday' : ''), 'phpbb_get_birthday'), array('user_lastvisit', 'users.user_lastvisit', 'intval'), + array('user_last_active', 'users.user_lastvisit', 'intval'), array('user_lastmark', 'users.user_lastvisit', 'intval'), array('user_lang', $config['default_lang'], ''), array('', 'users.user_lang', ''), From 7d8b4d58174653a3655a49aa59950c6d8e7647e2 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 18 May 2024 20:54:59 -0700 Subject: [PATCH 0121/1214] [ticket/17318] Fix notification avatars in dropdown PHPBB3-17318 Signed-off-by: Matt Friedman --- phpBB/styles/prosilver/template/notification_dropdown.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/template/notification_dropdown.html b/phpBB/styles/prosilver/template/notification_dropdown.html index e4fc984d8a5..31e7d4309e7 100644 --- a/phpBB/styles/prosilver/template/notification_dropdown.html +++ b/phpBB/styles/prosilver/template/notification_dropdown.html @@ -22,7 +22,7 @@ - {notifications.AVATAR_HTML} + {notifications.AVATAR_HTML}

        {notifications.FORMATTED_TITLE}

        {notifications.REFERENCE}

        From e850915190ab2907845b8a5c8dac5ff5ff0045ba Mon Sep 17 00:00:00 2001 From: Daniel James Date: Thu, 23 May 2024 16:35:28 +0100 Subject: [PATCH 0122/1214] [ticket/17320] Remove unused js code from prosilver The forum_fn.js file contained a small block of code that was commented out so there is no need to keep it in the codebase, especially as it was a fix for IE8 and older browsers. PHPBB3-17320 --- phpBB/styles/prosilver/template/forum_fn.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index 60a2fd8faa9..009a9de6219 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -329,17 +329,6 @@ function parseDocument($container) { }, 100); }); - /** - * Adjust HTML code for IE8 and older versions - */ - // if (oldBrowser) { - // // Fix .linklist.bulletin lists - // $container - // .find('ul.linklist.bulletin > li') - // .filter(':first-child, .rightside:last-child') - // .addClass('no-bulletin'); - // } - /** * Resize navigation (breadcrumbs) block to keep all links on same line */ From cd34cdedee2e6470238b5ee12154fd056f6192e8 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Fri, 17 May 2024 12:48:37 +0100 Subject: [PATCH 0123/1214] [ticket/17317] Change button text for pm recipient to increase readability PHPBB3-17317 --- phpBB/styles/prosilver/template/posting_pm_header.html | 4 ++-- phpBB/styles/prosilver/theme/common.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/styles/prosilver/template/posting_pm_header.html b/phpBB/styles/prosilver/template/posting_pm_header.html index 2fee21fd971..738d5b25f2d 100644 --- a/phpBB/styles/prosilver/template/posting_pm_header.html +++ b/phpBB/styles/prosilver/template/posting_pm_header.html @@ -31,7 +31,7 @@
        • - + {to_recipient.NAME}{to_recipient.NAME_FULL}
        • @@ -48,7 +48,7 @@
          • - + {bcc_recipient.NAME}{bcc_recipient.NAME_FULL}
          • diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index e1d9e757f9c..ebfb4782744 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -780,10 +780,10 @@ fieldset.fields1 dd.recipients { margin-left: 1em; } -fieldset.fields1 ul.recipients input.button2 { +fieldset.fields1 ul.recipients input.button2 { font-size: 0.8em; margin-right: 0; - padding: 0; + padding: 2px 0; } fieldset.fields1 dl.pmlist > dt { From 40ed6c6458b5db0ab3ce817cf0b2310af101fea3 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Fri, 17 May 2024 12:58:27 +0100 Subject: [PATCH 0124/1214] [ticket/17317] Fix padding on button, left and right not top and bottom PHPBB3-17317 --- phpBB/styles/prosilver/theme/common.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index ebfb4782744..eaa9cf332ab 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -783,7 +783,7 @@ fieldset.fields1 dd.recipients { fieldset.fields1 ul.recipients input.button2 { font-size: 0.8em; margin-right: 0; - padding: 2px 0; + padding: 0 2px; } fieldset.fields1 dl.pmlist > dt { From 9fcf956888c2f5fce21a0d1c072387b360deb225 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Wed, 22 May 2024 11:17:25 +0100 Subject: [PATCH 0125/1214] [ticket/17317] Replace button with small red fa-icon Updated the button to a small red X using Font Awesome rather than using the default phpBB button with text which adds a better visual distinction in the interface between the username and action. Credit to @iMattPro for the suggestion in the ticket comments. PHPBB3-17317 --- .../styles/prosilver/template/posting_pm_header.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/prosilver/template/posting_pm_header.html b/phpBB/styles/prosilver/template/posting_pm_header.html index 738d5b25f2d..aecd98eacce 100644 --- a/phpBB/styles/prosilver/template/posting_pm_header.html +++ b/phpBB/styles/prosilver/template/posting_pm_header.html @@ -31,7 +31,11 @@
            • - + + + {to_recipient.NAME}{to_recipient.NAME_FULL}
            • @@ -48,7 +52,11 @@
              • - + + + {bcc_recipient.NAME}{bcc_recipient.NAME_FULL}
              • From c330500c4d46de0cd39c030217d1672a084d00e7 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Wed, 22 May 2024 17:39:24 +0100 Subject: [PATCH 0126/1214] [ticket/17317] Add new icon for groups and remove CSS change PHPBB3-17317 --- phpBB/styles/prosilver/template/posting_pm_header.html | 8 ++++++-- phpBB/styles/prosilver/theme/common.css | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/phpBB/styles/prosilver/template/posting_pm_header.html b/phpBB/styles/prosilver/template/posting_pm_header.html index aecd98eacce..6b03febeec0 100644 --- a/phpBB/styles/prosilver/template/posting_pm_header.html +++ b/phpBB/styles/prosilver/template/posting_pm_header.html @@ -77,8 +77,12 @@ diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index eaa9cf332ab..dc4d7e07bd2 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -783,7 +783,7 @@ fieldset.fields1 dd.recipients { fieldset.fields1 ul.recipients input.button2 { font-size: 0.8em; margin-right: 0; - padding: 0 2px; + padding: 0; } fieldset.fields1 dl.pmlist > dt { From 92e545fc26185ac236aa6a880a483ed33824bd45 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Thu, 23 May 2024 16:25:57 +0100 Subject: [PATCH 0127/1214] [ticket/17317] Remove unused CSS styling for inputs PHPBB3-17317 --- phpBB/styles/prosilver/theme/common.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index dc4d7e07bd2..e184557a894 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -780,12 +780,6 @@ fieldset.fields1 dd.recipients { margin-left: 1em; } -fieldset.fields1 ul.recipients input.button2 { - font-size: 0.8em; - margin-right: 0; - padding: 0; -} - fieldset.fields1 dl.pmlist > dt { width: auto !important; } From 05fafdc44555fd216adffe50eddc5a6a9452d087 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 25 May 2024 04:40:44 +0200 Subject: [PATCH 0128/1214] [ticket/17323] Fix division by 0 when there is 0 posts PHPBB3-17323 --- phpBB/includes/acp/acp_search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_search.php b/phpBB/includes/acp/acp_search.php index 48a3b103f5c..2cd7529222c 100644 --- a/phpBB/includes/acp/acp_search.php +++ b/phpBB/includes/acp/acp_search.php @@ -516,7 +516,7 @@ protected function get_post_index_progress(int $post_counter): array $this->db->sql_freeresult($result); $total_count = $done_count + $remain_count; - $percent = ($done_count / $total_count) * 100; + $percent = $total_count > 0 ? ($done_count / $total_count) * 100 : 100; return [ 'VALUE' => $done_count, From 72770937f216f8e6bbf168ec3647b45b1e32b342 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 00:32:24 +0700 Subject: [PATCH 0129/1214] [ticket/17151] Make settings forms use macros PHPBB3-17151 --- phpBB/includes/acp/acp_board.php | 160 ++++++++++++++---- phpBB/includes/functions.php | 18 +- phpBB/includes/functions_acp.php | 55 +++--- phpBB/phpbb/template/twig/extension/forms.php | 13 +- .../all/template/macros/forms/select.twig | 11 +- .../all/template/macros/forms/textarea.twig | 3 +- .../template/ucp_prefs_personal.html | 4 +- 7 files changed, 187 insertions(+), 77 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d0ced6788f3..1f8f632788f 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -851,7 +851,7 @@ function select_auth_method($selected_method, $key = '') /* @var $auth_providers \phpbb\auth\provider_collection */ $auth_providers = $phpbb_container->get('auth.provider_collection'); - $auth_plugins = array(); + $auth_plugins = []; foreach ($auth_providers as $key => $value) { @@ -864,14 +864,22 @@ function select_auth_method($selected_method, $key = '') sort($auth_plugins); - $auth_select = ''; + $auth_select_options = []; foreach ($auth_plugins as $method) { - $selected = ($selected_method == $method) ? ' selected="selected"' : ''; - $auth_select .= "'; + $auth_select_options[] = [ + 'value' => $method, + 'selected' => $selected_method == $method, + 'label' => ucfirst($method), + 'data' => [ + 'toggle-setting' => "#auth_{$method}_settings", + ], + ]; } - return $auth_select; + return [ + 'options' => $auth_select_options, + ]; } /** @@ -881,15 +889,21 @@ function mail_auth_select($selected_method, $key = '') { global $user; - $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP'); - $s_smtp_auth_options = ''; + $auth_methods = ['PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP']; + $s_smtp_auth_options = []; foreach ($auth_methods as $method) { - $s_smtp_auth_options .= ''; + $s_smtp_auth_options[] = [ + 'value' => $method, + 'selected' => $selected_method == $method, + 'label' => $user->lang('SMTP_' . str_replace('-', '_', $method)), + ]; } - return $s_smtp_auth_options; + return [ + 'options' => $s_smtp_auth_options, + ]; } /** @@ -899,7 +913,22 @@ function full_folder_select($value, $key = '') { global $user; - return ''; + $full_folder_select_options = [ + 0 => [ + 'value' => 1, + 'selected' => $value == 1, + 'label' => $user->lang('DELETE_OLDEST_MESSAGES'), + ], + 1 => [ + 'value' => 2, + 'selected' => $value == 2, + 'label' => $user->lang('HOLD_NEW_MESSAGES_SHORT'), + ], + ]; + + return [ + 'options' => $full_folder_select_options, + ]; } /** @@ -907,7 +936,7 @@ function full_folder_select($value, $key = '') */ function select_ip_check($value, $key = '') { - $radio_ary = array(4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION'); + $radio_ary = [4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION']; return h_radio('config[ip_check]', $radio_ary, $value, $key); } @@ -917,7 +946,7 @@ function select_ip_check($value, $key = '') */ function select_ref_check($value, $key = '') { - $radio_ary = array(REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION'); + $radio_ary = [REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION']; return h_radio('config[referer_validation]', $radio_ary, $value, $key); } @@ -929,23 +958,28 @@ function select_acc_activation($selected_value, $value) { global $user, $config; - $act_ary = array( - 'ACC_DISABLE' => array(true, USER_ACTIVATION_DISABLE), - 'ACC_NONE' => array(true, USER_ACTIVATION_NONE), - 'ACC_USER' => array($config['email_enable'], USER_ACTIVATION_SELF), - 'ACC_ADMIN' => array($config['email_enable'], USER_ACTIVATION_ADMIN), - ); + $act_ary = [ + 'ACC_DISABLE' => [true, USER_ACTIVATION_DISABLE], + 'ACC_NONE' => [true, USER_ACTIVATION_NONE], + 'ACC_USER' => [$config['email_enable'], USER_ACTIVATION_SELF], + 'ACC_ADMIN' => [$config['email_enable'], USER_ACTIVATION_ADMIN], + ]; - $act_options = ''; + $act_options = []; foreach ($act_ary as $key => $data) { list($available, $value) = $data; - $selected = ($selected_value == $value) ? ' selected="selected"' : ''; - $class = (!$available) ? ' class="disabled-option"' : ''; - $act_options .= ''; + $act_options[] = [ + 'value' => $value, + 'selected' => $selected_value == $value, + 'label' => $user->lang($key), + 'disabled' => !$available, + ]; } - return $act_options; + return [ + 'options' => $act_options, + ]; } /** @@ -955,7 +989,27 @@ function username_length($value, $key = '') { global $user; - return ' ' . $user->lang['MIN_CHARS'] . '   ' . $user->lang['MAX_CHARS']; + return [ + [ + 'tag' => 'input', + 'id' => $key, + 'type' => 'number', + 'name' => 'config[min_name_chars]', + 'min' => 1, + 'max' => 999, + 'value' => $value, + 'append' => $user->lang('MIN_CHARS') . '  ', + ], + [ + 'tag' => 'input', + 'type' => 'number', + 'name' => 'config[max_name_chars]', + 'min' => 8, + 'max' => 180, + 'value' => $this->new_config['max_name_chars'], + 'append' => $user->lang('MAX_CHARS'), + ], + ]; } /** @@ -965,15 +1019,20 @@ function select_username_chars($selected_value, $key) { global $user; - $user_char_ary = array('USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII'); - $user_char_options = ''; + $user_char_ary = ['USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII']; + $user_char_options = []; foreach ($user_char_ary as $user_type) { - $selected = ($selected_value == $user_type) ? ' selected="selected"' : ''; - $user_char_options .= ''; + $user_char_options[] = [ + 'value' => $user_type, + 'selected' => $selected_value == $user_type, + 'label' => $user->lang($user_type), + ]; } - return $user_char_options; + return [ + 'options' => $user_char_options, + ]; } /** @@ -983,7 +1042,16 @@ function password_length($value, $key) { global $user; - return ' ' . $user->lang['MIN_CHARS']; + return [ + [ + 'tag' => 'input', + 'id' => $key, + 'type' => 'number', + 'name' => 'config[min_pass_chars]', + 'value' => $value, + 'append' => $user->lang('MIN_CHARS'), + ], + ]; } /** @@ -994,14 +1062,20 @@ function select_password_chars($selected_value, $key) global $user; $pass_type_ary = array('PASS_TYPE_ANY', 'PASS_TYPE_CASE', 'PASS_TYPE_ALPHA', 'PASS_TYPE_SYMBOL'); - $pass_char_options = ''; + $pass_char_options = []; foreach ($pass_type_ary as $pass_type) { - $selected = ($selected_value == $pass_type) ? ' selected="selected"' : ''; - $pass_char_options .= ''; + $pass_char_options[] = [ + 'tag' => 'select', + 'value' => $pass_type, + 'selected' => $selected_value == $pass_type, + 'label' => $user->lang[$pass_type], + ]; } - return $pass_char_options; + return [ + 'options' => $pass_char_options, + ]; } /** @@ -1359,8 +1433,22 @@ function send_test_email($value, $key) { global $user; - return ' - '; + return [ + [ + 'tag' => 'input', + 'type' => 'submit', + 'name' => $key, + 'id' => $key, + 'class' => 'button2', + 'value' => $user->lang('SEND_TEST_EMAIL'), + ], + [ + 'tag' => 'textarea', + 'name' => $key . '_text', + 'id' => $key . '_text', + 'placeholder' => $user->lang('MESSAGE'), + ], + ]; } /** diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index bca9a97c48c..0bdb36eb795 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -291,7 +291,9 @@ function phpbb_language_select(\phpbb\db\driver\driver_interface $db, string $de ]; } - return $lang_options; + return [ + 'options' => $lang_options + ]; } /** @@ -319,14 +321,20 @@ function style_select($default = '', $all = false, array $styledata = []) $db->sql_freeresult($result); } - $style_options = ''; + $style_options = []; foreach ($styledata as $row) { - $selected = ($row['style_id'] == $default) ? ' selected="selected"' : ''; - $style_options .= ''; + $style_options[] = [ + 'tag' => 'select', + 'value' => $row['style_id'], + 'selected' => $row['style_id'] == $default, + 'label' => $row['style_name'], + ]; } - return $style_options; + return [ + 'options' => $style_options, + ]; } /** diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index b8f007cdfd2..6058b948fb8 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -398,6 +398,32 @@ function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $conf $tpl['buttons'] = [$no_button, $yes_button]; } break; + + case 'select': + $tpl = [ + 'tag' => 'select', + 'class' => $tpl_type['class'] ?? false, + 'id' => $key, + 'data' => $tpl_type['data'] ?? [], + 'name' => $name, + 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), + 'options' => $tpl_type['options'], + 'group_only' => $tpl_type['group_only'] ?? false, + 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, + 'multiple' => $tpl_type['multiple'] ?? false, + ]; + break; + + case 'button': + $tpl = [ + 'tag' => 'input', + 'class' => $tpl_type['options']['class'], + 'id' => $key, + 'type' => $tpl_type['options']['type'], + 'name' => $tpl_type['options']['name'] ?? $name, + 'value' => $tpl_type['options']['value'], + ]; + break; } return $tpl; @@ -484,38 +510,15 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) $return = call_user_func_array($call, $args); - if ($tpl_type[0] == 'select') + if (in_array($tpl_type[0], ['select', 'button'])) { - $size = (isset($tpl_type[1])) ? (int) $tpl_type[1] : 1; - - if (is_string($return)) - { - $data_toggle = (!empty($tpl_type[2])) ? ' data-togglable-settings="true"' : ''; - - $tpl = ''; - } - else - { - $tpl = [ - 'tag' => 'select', - 'id' => $key, - 'name' => $name, - 'toggleable' => !empty($tpl_type[2]), - 'options' => $return, - ]; - - // Add size if it differs from default value of 1 - if ($size != 1) - { - $tpl['size'] = $size; - } - } + $tpl_type = array_merge($tpl_type, $return); + $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); } else { $tpl = $return; } - break; default: diff --git a/phpBB/phpbb/template/twig/extension/forms.php b/phpBB/phpbb/template/twig/extension/forms.php index 4d8de94b9d3..ee47e19449c 100644 --- a/phpBB/phpbb/template/twig/extension/forms.php +++ b/phpBB/phpbb/template/twig/extension/forms.php @@ -179,7 +179,7 @@ public function select(environment $environment, array $form_data): string 'CLASS' => (string) ($form_data['class'] ?? ''), 'ID' => (string) ($form_data['id'] ?? ''), 'DATA' => $form_data['data'] ?? [], - 'NAME' => (string) $form_data['name'], + 'NAME' => (string) ($form_data['name'] ?? ''), 'TOGGLEABLE' => (bool) ($form_data['toggleable'] ?? false), 'OPTIONS' => $form_data['options'] ?? [], 'GROUP_ONLY' => (bool) ($form_data['group_only'] ?? false), @@ -206,13 +206,14 @@ public function textarea(environment $environment, array $form_data): string try { return $environment->render('macros/forms/textarea.twig', [ - 'CLASS' => (string) ($form_data['class'] ?? ''), + 'CLASS' => (string) ($form_data['class'] ?? ''), 'ID' => (string) $form_data['id'], - 'DATA' => $form_data['data'] ?? [], + 'DATA' => $form_data['data'] ?? [], 'NAME' => (string) $form_data['name'], - 'ROWS' => (int) $form_data['rows'], - 'COLS' => (int) $form_data['cols'], - 'CONTENT' => (string) $form_data['content'], + 'ROWS' => (int) ($form_data['rows'] ?? ''), + 'COLS' => (int) ($form_data['cols'] ?? ''), + 'CONTENT' => (string) ($form_data['content'] ?? ''), + 'PLACEHOLDER' => (string) ($form_data['placeholder'] ?? ''), ]); } catch (\Twig\Error\Error $e) diff --git a/phpBB/styles/all/template/macros/forms/select.twig b/phpBB/styles/all/template/macros/forms/select.twig index 3cab1e71998..1ff690268f5 100644 --- a/phpBB/styles/all/template/macros/forms/select.twig +++ b/phpBB/styles/all/template/macros/forms/select.twig @@ -21,11 +21,18 @@ label="{{ element.label }}"> {% endapply %} {% for option in element.options %} - + {% endfor %} {% else %} - + {% endif %} {% endfor %} diff --git a/phpBB/styles/all/template/macros/forms/textarea.twig b/phpBB/styles/all/template/macros/forms/textarea.twig index e4fada13f2b..a81c5a09cfe 100644 --- a/phpBB/styles/all/template/macros/forms/textarea.twig +++ b/phpBB/styles/all/template/macros/forms/textarea.twig @@ -7,4 +7,5 @@ {% endfor %} name="{{ NAME }}" rows="{{ ROWS }}" - cols="{{ COLS }}">{% endapply %}{{ CONTENT }} + cols="{{ COLS }}" + {% if PLACEHOLDER %}placeholder="{{ PLACEHOLDER }}"{% endif %}>{% endapply %}{{ CONTENT }} diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 4e81b78fb36..fe1ea17c746 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -61,7 +61,9 @@

                {L_TITLE}

                -
                +
                + {{ FormsSelect(S_STYLE_OPTIONS) }} +
                From 9350e82d71f19ce29a63f3eec7c5e9c5365086a7 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 10:23:08 +0700 Subject: [PATCH 0130/1214] [ticket/17151] Fix test error PHPBB3-17151 --- tests/functions_acp/build_cfg_template_test.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/functions_acp/build_cfg_template_test.php b/tests/functions_acp/build_cfg_template_test.php index 76558152ff8..8153fdfabfd 100644 --- a/tests/functions_acp/build_cfg_template_test.php +++ b/tests/functions_acp/build_cfg_template_test.php @@ -506,13 +506,14 @@ public function test_build_cfg_template_select($tpl_type, $key, $new, $config_ke public function select_helper() { - return build_select( - array( - '1' => 'First_Option', - '2' => 'Second_Option', - '3' => 'Third_Option', - ), - '2' - ); + return [ + 'options' => build_select( + [ + '1' => 'First_Option', + '2' => 'Second_Option', + '3' => 'Third_Option', + ], + '2'), + ]; } } From 52517a5efde8805328fc20f7a2c0964b3dae6888 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 13:16:57 +0700 Subject: [PATCH 0131/1214] [ticket/17151] Fix registration test error PHPBB3-17151 --- phpBB/adm/style/acp_bots.html | 8 ++++++-- phpBB/adm/style/acp_users_prefs.html | 4 +++- phpBB/includes/acp/acp_board.php | 16 ++++++++++++++-- phpBB/includes/acp/acp_bots.php | 23 +++++++++++++++++------ phpBB/includes/acp/acp_users.php | 6 +++++- phpBB/includes/functions.php | 9 ++------- phpBB/includes/ucp/ucp_prefs.php | 10 +++++++--- 7 files changed, 54 insertions(+), 22 deletions(-) diff --git a/phpBB/adm/style/acp_bots.html b/phpBB/adm/style/acp_bots.html index b4f8ea50721..8332df7e2b5 100644 --- a/phpBB/adm/style/acp_bots.html +++ b/phpBB/adm/style/acp_bots.html @@ -27,7 +27,9 @@

                {L_WARNING}


        {L_BOT_STYLE_EXPLAIN}
        -
        +
        + {{ FormsSelect(S_STYLE_OPTIONS) }} +

        {L_BOT_LANG_EXPLAIN}
        @@ -37,7 +39,9 @@

        {L_WARNING}

        -
        +
        + {{ FormsSelect(S_ACTIVE_OPTIONS) }} +

        {L_BOT_AGENT_EXPLAIN}
        diff --git a/phpBB/adm/style/acp_users_prefs.html b/phpBB/adm/style/acp_users_prefs.html index 68420389b42..d485d1e1132 100644 --- a/phpBB/adm/style/acp_users_prefs.html +++ b/phpBB/adm/style/acp_users_prefs.html @@ -48,7 +48,9 @@
        -
        +
        + {{ FormsSelect(S_STYLE_OPTIONS) }} +
        diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 1f8f632788f..1e82c0e6d53 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -86,8 +86,8 @@ function main($id, $mode) 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'custom', 'method' => 'timezone_select', 'explain' => true), 'legend2' => 'BOARD_STYLE', - 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), - 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), + 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'method' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), + 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'method' => 'style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), 'override_user_style' => array('lang' => 'OVERRIDE_STYLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'legend3' => 'WARNINGS', @@ -1127,6 +1127,18 @@ public function language_select(string $default = '', array $langdata = []): arr return phpbb_language_select($db, $default, $langdata); } + /** + * Wrapper function for style_select() + * + * @return array + */ + public function style_select(): array + { + global $db; + + return ['options' => style_select()]; + } + /** * Board disable option and message */ diff --git a/phpBB/includes/acp/acp_bots.php b/phpBB/includes/acp/acp_bots.php index 323dd1baee6..982a8967b78 100644 --- a/phpBB/includes/acp/acp_bots.php +++ b/phpBB/includes/acp/acp_bots.php @@ -321,12 +321,15 @@ function main($id, $mode) unset($bot_row['user_lang'], $bot_row['user_style']); } - $s_active_options = ''; + $s_active_options = []; $_options = array('0' => 'NO', '1' => 'YES'); foreach ($_options as $value => $lang) { - $selected = ($bot_row['bot_active'] == $value) ? ' selected="selected"' : ''; - $s_active_options .= ''; + $s_active_options[] = [ + 'value' => $value, + 'selected' => $bot_row['bot_active'] == $value, + 'label' => $user->lang($lang), + ]; } $style_select = style_select($bot_row['bot_style'], true); @@ -345,14 +348,22 @@ function main($id, $mode) 'BOT_AGENT' => $bot_row['bot_agent'], 'S_EDIT_BOT' => true, - 'S_ACTIVE_OPTIONS' => $s_active_options, - 'S_STYLE_OPTIONS' => $style_select, + 'S_ACTIVE_OPTIONS' => [ + 'id' => 'bot_active', + 'name' => 'bot_active', + 'options' => $s_active_options, + ], + 'S_STYLE_OPTIONS' => [ + 'id' => 'bot_style', + 'name' => 'bot_style', + 'options' => $style_select, + ], 'LANG_OPTIONS' => [ 'id' => 'bot_lang', 'name' => 'bot_lang', 'options' => $lang_options, ], - 'S_ERROR' => (count($error)) ? true : false, + 'S_ERROR' => (bool) count($error), )); return; diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 7a5036f9316..32be512d810 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1838,7 +1838,11 @@ function main($id, $mode) 'name' => 'lang', 'options' => $lang_options, ], - 'S_STYLE_OPTIONS' => style_select($data['style']), + 'S_STYLE_OPTIONS' => [ + 'id' => 'style', + 'name' => 'style', + 'options' => style_select($data['style']) + ], 'TIMEZONE_OPTIONS' => [ 'tag' => 'select', 'name' => 'tz', diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 0bdb36eb795..68e4e75dda5 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -291,9 +291,7 @@ function phpbb_language_select(\phpbb\db\driver\driver_interface $db, string $de ]; } - return [ - 'options' => $lang_options - ]; + return $lang_options; } /** @@ -325,16 +323,13 @@ function style_select($default = '', $all = false, array $styledata = []) foreach ($styledata as $row) { $style_options[] = [ - 'tag' => 'select', 'value' => $row['style_id'], 'selected' => $row['style_id'] == $default, 'label' => $row['style_name'], ]; } - return [ - 'options' => $style_options, - ]; + return $style_options; } /** diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index d2fc72621d5..103f647ab03 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -205,14 +205,18 @@ function main($id, $mode) 'name' => 'lang', 'options' => $lang_options, ], - 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['user_style'], false, $styles_row), + 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : [ + 'id' => 'user_style', + 'name' => 'user_style', + 'options' => style_select($data['user_style'], false, $styles_row) + ], 'TIMEZONE_OPTIONS' => [ 'tag' => 'select', 'name' => 'tz', 'options' => $timezone_select, ], - 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, - 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) + 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, + 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) ); break; From 7b04e411b62423ea4c640331b2805786c069f03b Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 15:30:52 +0700 Subject: [PATCH 0132/1214] [ticket/17151] Fix tests PHPBB3-17151 --- phpBB/includes/acp/acp_board.php | 8 +- .../all/template/macros/forms/select.twig | 4 +- tests/acp_board/select_auth_method_test.php | 32 ++++- .../fixtures/ext/foo/bar/acp/main_module.php | 24 +++- tests/functions/style_select_test.php | 121 ++++++++++++++++-- .../functions_acp/build_cfg_template_test.php | 15 ++- 6 files changed, 180 insertions(+), 24 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 1e82c0e6d53..1babdceb016 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -970,10 +970,10 @@ function select_acc_activation($selected_value, $value) { list($available, $value) = $data; $act_options[] = [ - 'value' => $value, - 'selected' => $selected_value == $value, - 'label' => $user->lang($key), - 'disabled' => !$available, + 'value' => $value, + 'selected' => $selected_value == $value, + 'label' => $user->lang($key), + 'class' => !$available ? 'disabled-option' : '', ]; } diff --git a/phpBB/styles/all/template/macros/forms/select.twig b/phpBB/styles/all/template/macros/forms/select.twig index 1ff690268f5..052cf89f9b8 100644 --- a/phpBB/styles/all/template/macros/forms/select.twig +++ b/phpBB/styles/all/template/macros/forms/select.twig @@ -24,14 +24,14 @@ + {% if option.disabled %} disabled="disabled"{% endif %}{% if option.class %} class="{{ option.class }}"{% endif %}>{{ option.label }} {% endfor %} {% else %} {% endif %} {% endfor %} diff --git a/tests/acp_board/select_auth_method_test.php b/tests/acp_board/select_auth_method_test.php index e06b5f845d8..a9efdbc0adf 100644 --- a/tests/acp_board/select_auth_method_test.php +++ b/tests/acp_board/select_auth_method_test.php @@ -22,8 +22,36 @@ class phpbb_acp_board_select_auth_method_test extends phpbb_test_case public static function select_auth_method_data() { return [ - ['acp_board_valid', ''], - ['acp_board_invalid', ''], + [ + 'acp_board_valid', + [ + 'options' => [ + 0 => [ + 'value' => 'acp_board_valid', + 'label' => 'Acp_board_valid', + 'selected' => true, + 'data' => [ + 'toggle-setting' => '#auth_acp_board_valid_settings', + ], + ] + ], + ] + ], + [ + 'acp_board_invalid', + [ + 'options' => [ + 0 => [ + 'value' => 'acp_board_valid', + 'label' => 'Acp_board_valid', + 'selected' => false, + 'data' => [ + 'toggle-setting' => '#auth_acp_board_valid_settings', + ], + ] + ], + ] + ], ]; } diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php index 08cb73da077..660d7eb1c90 100644 --- a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php +++ b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php @@ -125,11 +125,25 @@ function main($id, $mode) function create_select() { - return ' - - - - '; + return [ + 'options' => [ + [ + 'value' => 1, + 'selected' => true, + 'label' => 'Option 1', + ], + [ + 'value' => 2, + 'selected' => false, + 'label' => 'Option 2', + ], + [ + 'value' => 3, + 'selected' => false, + 'label' => 'Option 3', + ], + ] + ]; } function submit_button() diff --git a/tests/functions/style_select_test.php b/tests/functions/style_select_test.php index e36b799bde2..aadacadb02c 100644 --- a/tests/functions/style_select_test.php +++ b/tests/functions/style_select_test.php @@ -20,14 +20,119 @@ public function getDataSet() static public function style_select_data() { - return array( - array('', false, ''), - array('', true, ''), - array('1', false, ''), - array('1', true, ''), - array('3', false, ''), - array('3', true, ''), - ); + return [ + [ + '', + false, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '', + true, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => false, + 'label' => 'zoo', + ], + ] + ], + [ + '1', + false, + [ + [ + 'value' => '1', + 'selected' => true, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '1', + true, + [ + [ + 'value' => '1', + 'selected' => true, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => false, + 'label' => 'zoo', + ], + ] + ], + [ + '3', + false, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '3', + true, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => true, + 'label' => 'zoo', + ], + ] + ], + ]; } /** diff --git a/tests/functions_acp/build_cfg_template_test.php b/tests/functions_acp/build_cfg_template_test.php index 8153fdfabfd..53a1ef3045f 100644 --- a/tests/functions_acp/build_cfg_template_test.php +++ b/tests/functions_acp/build_cfg_template_test.php @@ -431,8 +431,11 @@ public function build_cfg_template_select_data() ['method' => 'select_helper'], [ 'tag' => 'select', + 'class' => false, 'id' => 'key_name', + 'data' => [], 'name' => 'config[config_key_name]', + 'toggleable' => false, 'options' => [ [ 'value' => 1, @@ -450,7 +453,9 @@ public function build_cfg_template_select_data() 'selected' => false, ] ], - 'toggleable' => false, + 'group_only' => false, + 'size' => 1, + 'multiple' => false, ], ], [ @@ -461,9 +466,11 @@ public function build_cfg_template_select_data() ['method' => 'select_helper'], [ 'tag' => 'select', + 'class' => false, 'id' => 'key_name', + 'data' => [], 'name' => 'config[config_key_name]', - 'size' => 8, + 'toggleable' => false, 'options' => [ [ 'value' => 1, @@ -481,7 +488,9 @@ public function build_cfg_template_select_data() 'selected' => false, ] ], - 'toggleable' => false, + 'group_only' => false, + 'size' => 8, + 'multiple' => false, ], ], ]; From 7f365855ce3a848f365647f0a239bc40a8f28f5d Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 22:34:18 +0700 Subject: [PATCH 0133/1214] [ticket/17151] Deduplicate code for building configuration options data PHPBB3-17151 --- phpBB/includes/functions_acp.php | 134 +++++++++++-------------------- 1 file changed, 46 insertions(+), 88 deletions(-) diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index 6058b948fb8..277d992f3ab 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -247,18 +247,19 @@ function h_radio($name, $input_ary, $input_default = false, $id = false, $key = } /** - * HTML-less version of build_cfg_template + * Build configuration data arrays or templates for configuration settings * - * @param array $tpl_type Template type - * @param string $key Config key - * @param $new_ary - * @param $config_key - * @param $vars - * @return array + * @param array $tpl_type Configuration setting type data + * @param string $key Configuration option name + * @param array $new_ary Updated configuration data + * @param string $config_key Configuration option name + * @param Array $vars Configuration setting data + * + * @return array|string */ -function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $config_key, $vars): array +function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) { - global $language; + global $language, $module, $phpbb_dispatcher; $tpl = []; $name = 'config[' . $config_key . ']'; @@ -399,78 +400,9 @@ function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $conf } break; - case 'select': - $tpl = [ - 'tag' => 'select', - 'class' => $tpl_type['class'] ?? false, - 'id' => $key, - 'data' => $tpl_type['data'] ?? [], - 'name' => $name, - 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), - 'options' => $tpl_type['options'], - 'group_only' => $tpl_type['group_only'] ?? false, - 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, - 'multiple' => $tpl_type['multiple'] ?? false, - ]; - break; - case 'button': - $tpl = [ - 'tag' => 'input', - 'class' => $tpl_type['options']['class'], - 'id' => $key, - 'type' => $tpl_type['options']['type'], - 'name' => $tpl_type['options']['name'] ?? $name, - 'value' => $tpl_type['options']['value'], - ]; - break; - } - - return $tpl; -} - -/** -* Build configuration template for acp configuration pages -*/ -function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) -{ - global $module, $phpbb_dispatcher; - - $tpl = ''; - $name = 'config[' . $config_key . ']'; - - // Make sure there is no notice printed out for non-existent config options (we simply set them) - if (!isset($new_ary[$config_key])) - { - $new_ary[$config_key] = ''; - } - - switch ($tpl_type[0]) - { - case 'password': - case 'text': - case 'url': - case 'email': - case 'tel': - case 'search': - case 'color': - case 'datetime': - case 'datetime-local': - case 'month': - case 'week': - case 'date': - case 'time': - case 'number': - case 'range': - case 'dimension': - case 'textarea': - case 'radio': - $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); - break; - case 'select': case 'custom': - if (isset($vars['method'])) { $call = array($module->module, $vars['method']); @@ -513,7 +445,33 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) if (in_array($tpl_type[0], ['select', 'button'])) { $tpl_type = array_merge($tpl_type, $return); - $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); + + if ($tpl_type[0] == 'select') + { + $tpl = [ + 'tag' => 'select', + 'class' => $tpl_type['class'] ?? false, + 'id' => $key, + 'data' => $tpl_type['data'] ?? [], + 'name' => $name, + 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), + 'options' => $tpl_type['options'], + 'group_only' => $tpl_type['group_only'] ?? false, + 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, + 'multiple' => $tpl_type['multiple'] ?? false, + ]; + } + else + { + $tpl = [ + 'tag' => 'input', + 'class' => $tpl_type['options']['class'], + 'id' => $key, + 'type' => $tpl_type['options']['type'], + 'name' => $tpl_type['options']['name'] ?? $name, + 'value' => $tpl_type['options']['value'], + ]; + } } else { @@ -542,15 +500,15 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) * Overwrite the html code we display for the config value * * @event core.build_config_template - * @var array tpl_type Config type array: - * 0 => data type - * 1 [optional] => string: size, int: minimum - * 2 [optional] => string: max. length, int: maximum - * @var string key Should be used for the id attribute in html - * @var array new Array with the config values we display - * @var string name Should be used for the name attribute - * @var array vars Array with the options for the config - * @var string tpl The resulting html code we display + * @var array tpl_type Config type array: + * 0 => data type + * 1 [optional] => string: size, int: minimum + * 2 [optional] => string: max. length, int: maximum + * @var string key Should be used for the id attribute in html + * @var array new Array with the config values we display + * @var string name Should be used for the name attribute + * @var array vars Array with the options for the config + * @var array|string tpl The resulting html code we display * @since 3.1.0-a1 */ $vars = array('tpl_type', 'key', 'new', 'name', 'vars', 'tpl'); From 1cbe1d86da92ab652fc5a716a5d41486888b8d17 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 20 Sep 2023 17:36:46 +0700 Subject: [PATCH 0134/1214] [ticket/17151] Improve radio input type handling - allow more than 2 buttons count - allow custom buttons order - allow custom button labels Implemented by allowing JSON data format, backward compatibility preserved. PHPBB3-17151 --- phpBB/includes/functions_acp.php | 60 +++++++++++-------- phpBB/phpbb/template/twig/extension/forms.php | 5 +- .../template/macros/forms/radio_buttons.twig | 5 +- tests/functional/extension_module_test.php | 10 ++++ .../fixtures/ext/foo/bar/acp/main_module.php | 1 + 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index 277d992f3ab..cd7e8a382cd 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -270,6 +270,12 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) $new_ary[$config_key] = ''; } + // For BC check if parameter is json format and if yes split $tpl_type in 2 parts only + if (isset($tpl_type[1]) && strpos($tpl_type[1], '{') !== false) + { + $tpl_type = explode(':', $vars['type'], 2); + } + switch ($tpl_type[0]) { case 'password': @@ -369,35 +375,37 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) break; case 'radio': - $tpl_type_cond = explode('_', $tpl_type[1]); - $type_no = $tpl_type_cond[0] != 'disabled' && $tpl_type_cond[0] != 'enabled'; - - $no_button = [ - 'type' => 'radio', - 'name' => $name, - 'value' => 0, - 'checked' => !$new_ary[$config_key], - 'label' => $type_no ? $language->lang('NO') : $language->lang('DISABLED'), - ]; - - $yes_button = [ - 'id' => $key, - 'type' => 'radio', - 'name' => $name, - 'value' => 1, - 'checked' => (bool) $new_ary[$config_key], - 'label' => $type_no ? $language->lang('YES') : $language->lang('ENABLED'), - ]; - - $tpl = ['tag' => 'radio']; - if ($tpl_type_cond[0] == 'yes' || $tpl_type_cond[0] == 'enabled') + // Convert 'old' radio button parameters to json encoded string for BC + if (in_array($tpl_type[1], ['yes_no', 'enabled_disabled'])) { - $tpl['buttons'] = [$yes_button, $no_button]; + $params = explode('_', $tpl_type[1]); + $tpl_type[1] = '{"' . $params[0] . '":1, "' . $params[1] . '":0}'; } - else + + $params = json_decode($tpl_type[1], true); + $id_assigned = false; + $buttons = []; + foreach ($params as $param => $value) { - $tpl['buttons'] = [$no_button, $yes_button]; - } + $buttons[] = [ + 'type' => 'radio', + 'name' => $name, + 'value' => $value, + 'checked' => $new_ary[$config_key] == $value, + 'label' => $language->lang(strtoupper($param)), + ]; + + // Only assign id to the one (1st) button in the list + if (!$id_assigned) + { + $buttons[key($buttons)]['id'] = $key; + } + }; + + $tpl = [ + 'tag' => 'radio', + 'buttons' => $buttons, + ]; break; case 'button': diff --git a/phpBB/phpbb/template/twig/extension/forms.php b/phpBB/phpbb/template/twig/extension/forms.php index ee47e19449c..72be295e220 100644 --- a/phpBB/phpbb/template/twig/extension/forms.php +++ b/phpBB/phpbb/template/twig/extension/forms.php @@ -151,10 +151,7 @@ public function radio_buttons(environment $environment, array $form_data): strin try { return $environment->render('macros/forms/radio_buttons.twig', [ - 'FIRST_BUTTON' => $form_data['buttons'][0], - 'FIRST_BUTTON_LABEL' => $form_data['buttons'][0]['label'], - 'SECOND_BUTTON' => $form_data['buttons'][1], - 'SECOND_BUTTON_LABEL' => $form_data['buttons'][1]['label'], + 'BUTTONS' => $form_data['buttons'], ]); } catch (\Twig\Error\Error $e) diff --git a/phpBB/styles/all/template/macros/forms/radio_buttons.twig b/phpBB/styles/all/template/macros/forms/radio_buttons.twig index 1ef7804c296..922eb1b432b 100644 --- a/phpBB/styles/all/template/macros/forms/radio_buttons.twig +++ b/phpBB/styles/all/template/macros/forms/radio_buttons.twig @@ -1,2 +1,3 @@ - - +{% for button in BUTTONS %} + +{% endfor %} diff --git a/tests/functional/extension_module_test.php b/tests/functional/extension_module_test.php index 67dc35ae737..6894f5d6449 100644 --- a/tests/functional/extension_module_test.php +++ b/tests/functional/extension_module_test.php @@ -101,6 +101,16 @@ public function test_acp() $this->assertStringContainsString('SETTING_9', $crawler->filter('dl')->eq(9)->filter('dt > label[for="setting_9"]')->text()); $this->assertStringContainsString('SETTING_9_EXPLAIN', $crawler->filter('dl')->eq(9)->filter('dt > span')->text()); $this->assertEquals(2, $crawler->filter('dl')->eq(9)->filter('dd > label > input[type="radio"]')->count()); + + $this->assertStringContainsString('SETTING_10', $crawler->filter('dl')->eq(10)->filter('dt > label[for="setting_10"]')->text()); + $this->assertStringContainsString('SETTING_10_EXPLAIN', $crawler->filter('dl')->eq(10)->filter('dt > span')->text()); + $this->assertEquals(3, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->count()); + $this->assertEquals(1, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(0)->attr('value')); + $this->assertStringContainsString('LABEL_1', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(0)->text()); + $this->assertEquals(3, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(1)->attr('value')); + $this->assertStringContainsString('LABEL_3', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(1)->text()); + $this->assertEquals(2, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(2)->attr('value')); + $this->assertStringContainsString('LABEL_2', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(2)->text()); } public function test_ucp() diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php index 660d7eb1c90..77174c95f64 100644 --- a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php +++ b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php @@ -49,6 +49,7 @@ function main($id, $mode) 'setting_7' => ['lang' => 'SETTING_7', 'validate' => 'email', 'type' => 'email:0:100', 'explain' => true], 'setting_8' => ['lang' => 'SETTING_8', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true], 'setting_9' => ['lang' => 'SETTING_9', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true], + 'setting_10'=> ['lang' => 'SETTING_10', 'validate' => 'bool', 'type' => 'radio:{"LABEL_1":1, "LABEL_3":3, "LABEL_2":2}', 'explain' => true], ] ]; From 130366edfa7a1565007ff41e45a9e19ea37747e0 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 23 Sep 2023 01:27:00 +0700 Subject: [PATCH 0135/1214] [ticket/17151] Fix PHP warning on board settings page, add ACP pages test PHPBB3-17151 --- phpBB/includes/acp/acp_board.php | 2 +- tests/functional/acp_main_test.php | 38 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 1babdceb016..ff3dbba6d40 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -1124,7 +1124,7 @@ public function language_select(string $default = '', array $langdata = []): arr { global $db; - return phpbb_language_select($db, $default, $langdata); + return ['options' => phpbb_language_select($db, $default, $langdata)]; } /** diff --git a/tests/functional/acp_main_test.php b/tests/functional/acp_main_test.php index d392102e1a3..2caa3bbae26 100644 --- a/tests/functional/acp_main_test.php +++ b/tests/functional/acp_main_test.php @@ -28,4 +28,42 @@ public function test_acp_database_size() $this->assertContainsLang('DATABASE_SIZE', $crawler->filter('tbody > tr')->eq(2)->filter('td[class="tabled"]')->eq(0)->text()); $this->assertNotContainsLang('NOT_AVAILABLE', $crawler->filter('tbody > tr')->eq(2)->filter('td[class="tabled"]')->eq(1)->text()); } + + public function test_all_acp_module_links() + { + $this->add_lang('common'); + $this->login(); + $this->admin_login(); + + // Browse ACP main page + $crawler = self::request('GET', 'index.php'); + $crawler = self::$client->click($crawler->selectLink($this->lang('ACP_SHORT'))->link()); + + // Get all ACP module URLs array + $acp_modules = $crawler->filter('.tabs a')->each( + function ($node, $i) + { + return $node->link(); + } + ); + + // Browse all ACP modules and get their mode URLs array + $acp_submodules = []; + foreach ($acp_modules as $module) + { + $crawler = self::$client->click($module); + $acp_submodules = array_merge($acp_submodules, $crawler->filter('.menu-block > ul a')->each( + function ($node, $i) + { + return $node->link(); + } + )); + } + + // Browse all ACP submodules' modes + foreach ($acp_submodules as $acp_submodule) + { + self::$client->click($acp_submodule); + } + } } From 830c1f3dc3e7375ad9b95833d59e110c203d0e51 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 23 Sep 2023 10:21:07 +0700 Subject: [PATCH 0136/1214] [ticket/17151] Adjust template output formatting PHPBB3-17151 --- phpBB/styles/all/template/macros/forms/input.twig | 2 +- phpBB/styles/all/template/macros/forms/select.twig | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/phpBB/styles/all/template/macros/forms/input.twig b/phpBB/styles/all/template/macros/forms/input.twig index 1a2ff13fa39..dcfeddf369c 100644 --- a/phpBB/styles/all/template/macros/forms/input.twig +++ b/phpBB/styles/all/template/macros/forms/input.twig @@ -1,4 +1,4 @@ -{% apply replace({"\n": ' ', "\t": ''}) %} +{% apply replace({"\n\t": ' ', "\t": '', "\n": ''}) %} -{% endapply %} + {% if TOGGLEABLE %}data-togglable-settings="true"{% endif %} + {% if MULTIPLE %}multiple="multiple"{% endif %} + {% if SIZE %}size="{{ SIZE }}"{% endif %}> {% for element in OPTIONS %} {% if not GROUP_ONLY and element.options %} - {% apply replace({"\n": ' ', '\t': ''}) %} - {% endapply %} {% for option in element.options %}
      + {% EVENT notification_dropdown_footer_before %} + {% EVENT notification_dropdown_footer_after %}
    • From a88040df85c6c0f91568c25df6e7cd03ebb4cda5 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 28 May 2024 07:21:11 +0700 Subject: [PATCH 0149/1214] [ticket/17324] Adjust target version PHPBB3-17324 --- phpBB/docs/events.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index fb8f9b1b19a..3e72be3e417 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -1601,14 +1601,14 @@ notification_dropdown_footer_after === * Locations: + styles/prosilver/template/notification_dropdown.html -* Since: 3.3.12 +* Since: prep-release-3.3.12 * Purpose: Add content after notifications list footer. notification_dropdown_footer_before === * Locations: + styles/prosilver/template/notification_dropdown.html -* Since: 3.3.12 +* Since: prep-release-3.3.12 * Purpose: Add content before notifications list footer. overall_footer_after From 9ea4aabe9a3a43f785decfab0e5ba926faa1f4f7 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 28 May 2024 21:15:31 +0700 Subject: [PATCH 0150/1214] [ticket/17324] Allow targeting events to prep-releases PHPBB3-17324 --- phpBB/docs/events.md | 4 ++-- phpBB/phpbb/event/md_exporter.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 3e72be3e417..d8606c7e4c8 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -1601,14 +1601,14 @@ notification_dropdown_footer_after === * Locations: + styles/prosilver/template/notification_dropdown.html -* Since: prep-release-3.3.12 +* Since: 3.3.12-prep-release * Purpose: Add content after notifications list footer. notification_dropdown_footer_before === * Locations: + styles/prosilver/template/notification_dropdown.html -* Since: prep-release-3.3.12 +* Since: 3.3.12-prep-release * Purpose: Add content before notifications list footer. overall_footer_after diff --git a/phpBB/phpbb/event/md_exporter.php b/phpBB/phpbb/event/md_exporter.php index 1a16a174130..7182ce697f4 100644 --- a/phpBB/phpbb/event/md_exporter.php +++ b/phpBB/phpbb/event/md_exporter.php @@ -489,7 +489,7 @@ public function validate_changed($changed) */ public function validate_version($version) { - return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version); + return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+|(?:-prep-release))?$#', $version); } /** From ae18669fcd2ad61020d180f4fd00c85aef2853b5 Mon Sep 17 00:00:00 2001 From: LukeWCS Date: Tue, 28 May 2024 20:55:49 +0200 Subject: [PATCH 0151/1214] [ticket/17325] Show message for "Re-Check version" if phpBB is still up to date Show explicit message for "Re-Check version" if installed version is still up to date PHPBB3-17325 --- phpBB/adm/style/acp_main.html | 30 +++++++++++++++++------------- phpBB/includes/acp/acp_main.php | 1 + 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/phpBB/adm/style/acp_main.html b/phpBB/adm/style/acp_main.html index 05ccd106054..8ae4f0721c1 100644 --- a/phpBB/adm/style/acp_main.html +++ b/phpBB/adm/style/acp_main.html @@ -14,27 +14,31 @@

      {L_WELCOME_PHPBB}

      {L_ADMIN_INTRO}

      - + {% if S_UPDATE_INCOMPLETE %}
      -

      {L_UPDATE_INCOMPLETE} {L_MORE_INFORMATION}

      +

      {{ lang('UPDATE_INCOMPLETE') }} {{ lang('MORE_INFORMATION') }}

      - + {% elseif S_VERSIONCHECK_FAIL %}
      -

      {L_VERSIONCHECK_FAIL}

      -

      {VERSIONCHECK_FAIL_REASON}

      -

      {L_VERSIONCHECK_FORCE_UPDATE} · {L_MORE_INFORMATION}

      +

      {{ lang('VERSIONCHECK_FAIL') }}

      +

      {{ VERSIONCHECK_FAIL_REASON }}

      +

      {{ lang('VERSIONCHECK_FORCE_UPDATE') }} · {{ lang('MORE_INFORMATION') }}

      - + {% elseif !S_VERSION_UP_TO_DATE %}
      -

      {L_VERSION_NOT_UP_TO_DATE_TITLE}

      -

      {L_VERSIONCHECK_FORCE_UPDATE} · {L_MORE_INFORMATION}

      +

      {{ lang('VERSION_NOT_UP_TO_DATE_TITLE') }}

      +

      {{ lang('VERSIONCHECK_FORCE_UPDATE') }} · {{ lang('MORE_INFORMATION') }}

      - - + {% elseif S_VERSION_UP_TO_DATE && S_VERSIONCHECK_FORCE %} +
      +

      {{ lang('VERSION_UP_TO_DATE_ACP') }}

      +
      + {% endif %} + {% if S_VERSION_UPGRADEABLE %}
      -

      {UPGRADE_INSTRUCTIONS}

      +

      {{ UPGRADE_INSTRUCTIONS }}

      - + {% endif %}
      diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index 0451e2ee1fa..c39e984f736 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -454,6 +454,7 @@ function main($id, $mode) $template->assign_vars(array( 'S_VERSION_UP_TO_DATE' => empty($updates_available), 'S_VERSION_UPGRADEABLE' => !empty($upgrades_available), + 'S_VERSIONCHECK_FORCE' => (bool) $recheck, 'UPGRADE_INSTRUCTIONS' => !empty($upgrades_available) ? $user->lang('UPGRADE_INSTRUCTIONS', $upgrades_available['current'], $upgrades_available['announcement']) : false, )); } From 51da43f77fab61e07d0e04520e6190571d268320 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 29 May 2024 21:53:11 +0200 Subject: [PATCH 0152/1214] [prep-release-3.3.12] Fix since version and undo md exporter change --- phpBB/docs/events.md | 4 ++-- phpBB/phpbb/event/md_exporter.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index d8606c7e4c8..fb8f9b1b19a 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -1601,14 +1601,14 @@ notification_dropdown_footer_after === * Locations: + styles/prosilver/template/notification_dropdown.html -* Since: 3.3.12-prep-release +* Since: 3.3.12 * Purpose: Add content after notifications list footer. notification_dropdown_footer_before === * Locations: + styles/prosilver/template/notification_dropdown.html -* Since: 3.3.12-prep-release +* Since: 3.3.12 * Purpose: Add content before notifications list footer. overall_footer_after diff --git a/phpBB/phpbb/event/md_exporter.php b/phpBB/phpbb/event/md_exporter.php index 7182ce697f4..1a16a174130 100644 --- a/phpBB/phpbb/event/md_exporter.php +++ b/phpBB/phpbb/event/md_exporter.php @@ -489,7 +489,7 @@ public function validate_changed($changed) */ public function validate_version($version) { - return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+|(?:-prep-release))?$#', $version); + return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version); } /** From a63a1913fa3960a2060d8febd70feec99136a1f5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 28 Apr 2024 10:34:37 +0200 Subject: [PATCH 0153/1214] [ticket/security-276] Add migration for user_actkey expiration column SECURITY-276 --- .../v33x/add_resend_activation_expiration.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/add_resend_activation_expiration.php diff --git a/phpBB/phpbb/db/migration/data/v33x/add_resend_activation_expiration.php b/phpBB/phpbb/db/migration/data/v33x/add_resend_activation_expiration.php new file mode 100644 index 00000000000..9ebbe9811a2 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/add_resend_activation_expiration.php @@ -0,0 +1,48 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +use phpbb\db\migration\migration; + +class add_resend_activation_expiration extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v33x\v3311', + ]; + } + + public function update_schema(): array + { + return [ + 'add_columns' => [ + $this->table_prefix . 'users' => [ + 'user_actkey_expiration' => ['TIMESTAMP', 0, 'after' => 'user_actkey'], + ], + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'users' => [ + 'user_actkey_expiration', + ], + ], + ]; + } +} From f853f6523fbf5896816843c5a519bd033e54a257 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 28 Apr 2024 13:46:25 +0200 Subject: [PATCH 0154/1214] [ticket/security/276] Prevent sending activation emails multiple times per day SECURITY-276 --- phpBB/includes/acp/acp_inactive.php | 5 ++-- phpBB/includes/acp/acp_users.php | 20 ++++++++------ phpBB/includes/functions_user.php | 23 ++++++++-------- phpBB/includes/ucp/ucp_profile.php | 7 ++--- phpBB/includes/ucp/ucp_register.php | 25 ++++++++--------- phpBB/includes/ucp/ucp_resend.php | 27 +++++++++++++++++++ phpBB/install/schemas/schema_data.sql | 4 +-- phpBB/phpbb/console/command/user/add.php | 34 +++++++++++++++++++++--- 8 files changed, 104 insertions(+), 41 deletions(-) diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 7b4536f755a..b85ed86ee59 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -238,10 +238,11 @@ function main($id, $mode) $messenger->save_queue(); - // Add the remind state to the database + // Add the remind state to the database and increase activation expiration by one day $sql = 'UPDATE ' . USERS_TABLE . ' SET user_reminded = user_reminded + 1, - user_reminded_time = ' . time() . ' + user_reminded_time = ' . time() . ', + user_actkey_expiration = ' . (int) strtotime('+1 day') . ' WHERE ' . $db->sql_in_set('user_id', $user_ids); $db->sql_query($sql); diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 7c3468f90df..f54ff08db43 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -385,14 +385,18 @@ function main($id, $mode) $user_actkey = empty($user_activation_key) ? $user_actkey : $user_activation_key; } - if ($user_row['user_type'] == USER_NORMAL || empty($user_activation_key)) - { - $sql = 'UPDATE ' . USERS_TABLE . " - SET user_actkey = '" . $db->sql_escape($user_actkey) . "' - WHERE user_id = $user_id"; - $db->sql_query($sql); - } - + // Always update actkey even if same and also update actkey expiration to 24 hours from now + $sql_ary = [ + 'user_actkey' => $user_actkey, + 'user_actkey_expiration' => strtotime('+1 day'), + ]; + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user_id; + $db->sql_query($sql); + + // Start sending email $messenger = new messenger(false); $messenger->template($email_template, $user_row['user_lang']); diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 25adf4215aa..c33caba8e8d 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -210,18 +210,19 @@ function user_add($user_row, $cp_data = false, $notifications_data = null) // These are the additional vars able to be specified $additional_vars = array( - 'user_permissions' => '', - 'user_timezone' => $config['board_timezone'], - 'user_dateformat' => $config['default_dateformat'], - 'user_lang' => $config['default_lang'], - 'user_style' => (int) $config['default_style'], - 'user_actkey' => '', - 'user_ip' => '', - 'user_regdate' => time(), - 'user_passchg' => time(), - 'user_options' => 230271, + 'user_permissions' => '', + 'user_timezone' => $config['board_timezone'], + 'user_dateformat' => $config['default_dateformat'], + 'user_lang' => $config['default_lang'], + 'user_style' => (int) $config['default_style'], + 'user_actkey' => '', + 'user_actkey_expiration' => 0, + 'user_ip' => '', + 'user_regdate' => time(), + 'user_passchg' => time(), + 'user_options' => 230271, // We do not set the new flag here - registration scripts need to specify it - 'user_new' => 0, + 'user_new' => 0, 'user_inactive_reason' => 0, 'user_inactive_time' => 0, diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 4a8abcd46ad..616792d9ac9 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -196,9 +196,10 @@ function main($id, $mode) { $notifications_manager = $phpbb_container->get('notification_manager'); $notifications_manager->add_notifications('notification.type.admin_activate_user', array( - 'user_id' => $user->data['user_id'], - 'user_actkey' => $user_actkey, - 'user_regdate' => time(), // Notification time + 'user_id' => $user->data['user_id'], + 'user_actkey' => $user_actkey, + 'user_actkey_expiration' => strtotime('+1 day'), // 24 hours until activation can be resent + 'user_regdate' => time(), // Notification time )); } diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 5f7a87f74b6..4d6677567ad 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -381,18 +381,19 @@ function main($id, $mode) $passwords_manager = $phpbb_container->get('passwords.manager'); $user_row = array( - 'username' => $data['username'], - 'user_password' => $passwords_manager->hash($data['new_password']), - 'user_email' => $data['email'], - 'group_id' => (int) $group_id, - 'user_timezone' => $data['tz'], - 'user_lang' => $data['lang'], - 'user_type' => $user_type, - 'user_actkey' => $user_actkey, - 'user_ip' => $user->ip, - 'user_regdate' => time(), - 'user_inactive_reason' => $user_inactive_reason, - 'user_inactive_time' => $user_inactive_time, + 'username' => $data['username'], + 'user_password' => $passwords_manager->hash($data['new_password']), + 'user_email' => $data['email'], + 'group_id' => (int) $group_id, + 'user_timezone' => $data['tz'], + 'user_lang' => $data['lang'], + 'user_type' => $user_type, + 'user_actkey' => $user_actkey, + 'user_actkey_expiration' => strtotime('+1 day'), // 24 hours until activation can be resent + 'user_ip' => $user->ip, + 'user_regdate' => time(), + 'user_inactive_reason' => $user_inactive_reason, + 'user_inactive_time' => $user_inactive_time, ); if ($config['new_member_post_limit']) diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 7f952af6c31..6ff1e53027b 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -73,6 +73,12 @@ function main($id, $mode) trigger_error('ACCOUNT_DEACTIVATED'); } + // Do not resend activation email if valid one still exists + if (!empty($user_row['user_actkey']) && (int) $user_row['user_actkey_expiration'] >= time()) + { + trigger_error('ACTIVATION_EMAIL_ALREADY_SENT'); + } + // Determine coppa status on group (REGISTERED(_COPPA)) $sql = 'SELECT group_name, group_type FROM ' . GROUPS_TABLE . ' @@ -144,6 +150,8 @@ function main($id, $mode) $db->sql_freeresult($result); } + $this->update_activation_expiration(); + meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); $message = ($config['require_activation'] == USER_ACTIVATION_ADMIN) ? $user->lang['ACTIVATION_EMAIL_SENT_ADMIN'] : $user->lang['ACTIVATION_EMAIL_SENT']; @@ -160,4 +168,23 @@ function main($id, $mode) $this->tpl_name = 'ucp_resend'; $this->page_title = 'UCP_RESEND'; } + + /** + * Update activation expiration to 1 day from now + * + * @return void + */ + protected function update_activation_expiration(): void + { + global $db, $user; + + $sql_ary = [ + 'user_actkey_expiration' => strtotime('+1 day'), + ]; + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $user->id(); + $db->sql_query($sql); + } } diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 7ec43e3b547..a9c8d4a0495 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -527,10 +527,10 @@ INSERT INTO phpbb_forums (forum_name, forum_desc, left_id, right_id, parent_id, INSERT INTO phpbb_forums (forum_name, forum_desc, left_id, right_id, parent_id, forum_type, forum_posts_approved, forum_posts_unapproved, forum_posts_softdeleted, forum_topics_approved, forum_topics_unapproved, forum_topics_softdeleted, forum_last_post_id, forum_last_poster_id, forum_last_poster_name, forum_last_poster_colour, forum_last_post_subject, forum_last_post_time, forum_link, forum_password, forum_image, forum_rules, forum_rules_link, forum_rules_uid, forum_desc_uid, prune_freq, prune_days, prune_viewed, forum_parents, forum_flags) VALUES ('{L_FORUMS_TEST_FORUM_TITLE}', '{L_FORUMS_TEST_FORUM_DESC}', 2, 3, 1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 'Admin', 'AA0000', '{L_TOPICS_TOPIC_TITLE}', 972086460, '', '', '', '', '', '', '', 1, 7, 7, '', 48); # -- Users / Anonymous user -INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_newpasswd, user_allow_massemail) VALUES (2, 1, 'Anonymous', 'anonymous', 0, '', '', 'en', 1, 0, '', 0, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', '', 0); +INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_actkey_expiration, user_newpasswd, user_allow_massemail) VALUES (2, 1, 'Anonymous', 'anonymous', 0, '', '', 'en', 1, 0, '', 0, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', 0, '', 0); # -- username: Admin password: admin (change this or remove it once everything is working!) -INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_newpasswd) VALUES (3, 5, 'Admin', 'admin', 0, '21232f297a57a5a743894a0e4a801fc3', 'admin@yourdomain.com', 'en', 1, 1, 'AA0000', 1, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', ''); +INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_actkey_expiration, user_newpasswd) VALUES (3, 5, 'Admin', 'admin', 0, '21232f297a57a5a743894a0e4a801fc3', 'admin@yourdomain.com', 'en', 1, 1, 'AA0000', 1, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', 0, ''); # -- Groups INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('GUESTS', 3, 0, '', 0, '', '', '', 5); diff --git a/phpBB/phpbb/console/command/user/add.php b/phpBB/phpbb/console/command/user/add.php index 40f866c1761..334ff714153 100644 --- a/phpBB/phpbb/console/command/user/add.php +++ b/phpBB/phpbb/console/command/user/add.php @@ -290,18 +290,17 @@ protected function send_activation_email($user_id) { case USER_ACTIVATION_SELF: $email_template = 'user_welcome_inactive'; - $user_actkey = gen_rand_string(mt_rand(6, 10)); break; case USER_ACTIVATION_ADMIN: $email_template = 'admin_welcome_inactive'; - $user_actkey = gen_rand_string(mt_rand(6, 10)); break; default: $email_template = 'user_welcome'; - $user_actkey = ''; break; } + $user_actkey = $this->get_activation_key($user_id); + if (!class_exists('messenger')) { require($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); @@ -321,6 +320,35 @@ protected function send_activation_email($user_id) $messenger->send(NOTIFY_EMAIL); } + /** + * Get user activation key + * + * @param int $user_id User ID + * + * @return string User activation key for user + */ + protected function get_activation_key(int $user_id): string + { + $user_actkey = ''; + + if ($this->config['require_activation'] == USER_ACTIVATION_SELF || $this->config['require_activation'] == USER_ACTIVATION_ADMIN) + { + $user_actkey = gen_rand_string(mt_rand(6, 10)); + + $sql_ary = [ + 'user_actkey' => $user_actkey, + 'user_actkey_expiration' => strtotime('+1 day'), + ]; + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user_id; + $this->db->sql_query($sql); + } + + return $user_actkey; + } + /** * Helper to translate questions to the user * From 24dd47adcf222fa12359ba2f39f00b6f8feda1fd Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 28 Apr 2024 13:51:29 +0200 Subject: [PATCH 0155/1214] [ticket/security/276] Add missing information message SECURITY-276 --- phpBB/includes/ucp/ucp_resend.php | 2 +- phpBB/language/en/common.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 6ff1e53027b..0c01838f4e7 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -76,7 +76,7 @@ function main($id, $mode) // Do not resend activation email if valid one still exists if (!empty($user_row['user_actkey']) && (int) $user_row['user_actkey_expiration'] >= time()) { - trigger_error('ACTIVATION_EMAIL_ALREADY_SENT'); + trigger_error('ACTIVATION_ALREADY_SENT'); } // Determine coppa status on group (REGISTERED(_COPPA)) diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 1f4fc9f6a81..5694bbd5dcf 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -64,6 +64,7 @@ 'ACCOUNT_DEACTIVATED' => 'Your account has been manually deactivated and is only able to be reactivated by an administrator.', 'ACP' => 'Administration Control Panel', 'ACP_SHORT' => 'ACP', + 'ACTIVATION_ALREADY_SENT' => 'The activation email has already been sent to your email address. You can try again after 24 hours. If you continue to have problems activating your account, please contact a board administrator.', 'ACTIVE' => 'active', 'ACTIVE_ERROR' => 'The specified username is currently inactive. If you have problems activating your account, please contact a board administrator.', 'ADMINISTRATOR' => 'Administrator', From 7c661746cf31510f56f5cc5b1ed6b26d431aa581 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 28 Apr 2024 20:10:24 +0200 Subject: [PATCH 0156/1214] [ticket/security/276] Add test for expiration timer SECURITY-276 --- phpBB/includes/ucp/ucp_resend.php | 2 +- tests/auth/provider_apache_test.php | 1 + tests/functional/user_password_reset_test.php | 84 ++++++++++++++----- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 0c01838f4e7..609ef97b20f 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -45,7 +45,7 @@ function main($id, $mode) trigger_error('FORM_INVALID'); } - $sql = 'SELECT user_id, group_id, username, user_email, user_type, user_lang, user_actkey, user_inactive_reason + $sql = 'SELECT user_id, group_id, username, user_email, user_type, user_lang, user_actkey, user_actkey_expiration, user_inactive_reason FROM ' . USERS_TABLE . " WHERE user_email = '" . $db->sql_escape($email) . "' AND username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index 2dbb1f32a98..df5b53841ca 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -162,6 +162,7 @@ public function test_autologin() 'user_sig_bbcode_bitfield' => '', 'user_jabber' => '', 'user_actkey' => '', + 'user_actkey_expiration' => 0, 'user_newpasswd' => '', 'user_form_salt' => '', 'user_new' => 1, diff --git a/tests/functional/user_password_reset_test.php b/tests/functional/user_password_reset_test.php index e912b8ce08f..754e4de7d1c 100644 --- a/tests/functional/user_password_reset_test.php +++ b/tests/functional/user_password_reset_test.php @@ -18,10 +18,14 @@ class phpbb_functional_user_password_reset_test extends phpbb_functional_test_ca { protected $user_data; + protected const TEST_USER = 'reset-password-test-user'; + + protected const TEST_EMAIL = 'reset-password-test-user@test.com'; + public function test_password_reset() { $this->add_lang('ucp'); - $user_id = $this->create_user('reset-password-test-user', 'reset-password-test-user@test.com'); + $user_id = $this->create_user(self::TEST_USER, self::TEST_EMAIL); // test without email $crawler = self::request('GET', "ucp.php?mode=sendpassword&sid={$this->sid}"); @@ -41,13 +45,13 @@ public function test_password_reset() // test with correct email $crawler = self::request('GET', "app.php/user/forgot_password?sid={$this->sid}"); $form = $crawler->selectButton('submit')->form(array( - 'email' => 'reset-password-test-user@test.com', + 'email' => self::TEST_EMAIL, )); $crawler = self::submit($form); $this->assertContainsLang('PASSWORD_RESET_LINK_SENT', $crawler->text()); // Check if columns in database were updated for password reset - $this->get_user_data('reset-password-test-user'); + $this->get_user_data(self::TEST_USER); $this->assertNotEmpty($this->user_data['reset_token']); $this->assertNotEmpty($this->user_data['reset_token_expiration']); $reset_token = $this->user_data['reset_token']; @@ -56,31 +60,31 @@ public function test_password_reset() // Check that reset token is only created once per day $crawler = self::request('GET', "app.php/user/forgot_password?sid={$this->sid}"); $form = $crawler->selectButton('submit')->form(array( - 'email' => 'reset-password-test-user@test.com', + 'email' => self::TEST_EMAIL, )); $crawler = self::submit($form); $this->assertContainsLang('PASSWORD_RESET_LINK_SENT', $crawler->text()); - $this->get_user_data('reset-password-test-user'); + $this->get_user_data(self::TEST_USER); $this->assertNotEmpty($this->user_data['reset_token']); $this->assertNotEmpty($this->user_data['reset_token_expiration']); $this->assertEquals($reset_token, $this->user_data['reset_token']); $this->assertEquals($reset_token_expiration, $this->user_data['reset_token_expiration']); // Create another user with the same email - $this->create_user('reset-password-test-user1', 'reset-password-test-user@test.com'); + $this->create_user('reset-password-test-user1', self::TEST_EMAIL); // Test that username is now also required $crawler = self::request('GET', "app.php/user/forgot_password?sid={$this->sid}"); $form = $crawler->selectButton('submit')->form(array( - 'email' => 'reset-password-test-user@test.com', + 'email' => self::TEST_EMAIL, )); $crawler = self::submit($form); $this->assertContainsLang('EMAIL_NOT_UNIQUE', $crawler->text()); // Provide both username and email $form = $crawler->selectButton('submit')->form(array( - 'email' => 'reset-password-test-user@test.com', + 'email' => self::TEST_EMAIL, 'username' => 'reset-password-test-user1', )); $crawler = self::submit($form); @@ -95,7 +99,7 @@ public function test_password_reset() public function test_login_after_reset() { - $this->login('reset-password-test-user'); + $this->login(self::TEST_USER); } public function data_reset_user_password() @@ -117,7 +121,7 @@ public function data_reset_user_password() public function test_reset_user_password($expected, $user_id, $token) { $this->add_lang('ucp'); - $this->get_user_data('reset-password-test-user'); + $this->get_user_data(self::TEST_USER); $user_id = !$user_id ? $this->user_data['user_id'] : $user_id; $token = !$token ? $this->user_data['reset_token'] : $token; @@ -131,8 +135,8 @@ public function test_reset_user_password($expected, $user_id, $token) { $form = $crawler->filter('input[type=submit]')->form(); $values = array_merge($form->getValues(), [ - 'new_password' => 'reset-password-test-user', - 'new_password_confirm' => 'reset-password-test-user', + 'new_password' => self::TEST_USER, + 'new_password_confirm' => self::TEST_USER, ]); $crawler = self::submit($form, $values); $this->assertContainsLang('PASSWORD_RESET', $crawler->text()); @@ -146,7 +150,7 @@ public function test_login() $this->assertStringContainsString($this->lang('LOGIN_EXPLAIN_UCP'), $crawler->filter('html')->text()); $form = $crawler->selectButton($this->lang('LOGIN'))->form(); - $crawler = self::submit($form, array('username' => 'reset-password-test-user', 'password' => 'reset-password-test-user')); + $crawler = self::submit($form, array('username' => self::TEST_USER, 'password' => self::TEST_USER)); $this->assertStringNotContainsString($this->lang('LOGIN'), $crawler->filter('.navbar')->text()); $cookies = self::$cookieJar->all(); @@ -167,17 +171,17 @@ public function test_login() $form = $crawler->selectButton($this->lang('LOGIN'))->form(); // Try logging in with the old password - $crawler = self::submit($form, array('username' => 'reset-password-test-user', 'password' => 'reset-password-test-userreset-password-test-user')); + $crawler = self::submit($form, array('username' => self::TEST_USER, 'password' => 'reset-password-test-userreset-password-test-user')); $this->assertStringContainsString($this->lang('LOGIN_ERROR_PASSWORD', '', ''), $crawler->filter('html')->text()); } /** * @depends test_login */ - public function test_acivateAfterDeactivate() + public function test_activateAfterDeactivate() { // User is active, actkey should not exist - $this->get_user_data('reset-password-test-user'); + $this->get_user_data(self::TEST_USER); $this->assertEmpty($this->user_data['user_actkey']); $this->login(); @@ -189,7 +193,7 @@ public function test_acivateAfterDeactivate() $this->assertContainsLang('FIND_USERNAME', $crawler->filter('html')->text()); $form = $crawler->selectButton('Submit')->form(); - $crawler = self::submit($form, array('username' => 'reset-password-test-user')); + $crawler = self::submit($form, array('username' => self::TEST_USER)); // Deactivate account and go back to overview of current user $this->assertContainsLang('USER_TOOLS', $crawler->filter('html')->text()); @@ -201,7 +205,7 @@ public function test_acivateAfterDeactivate() $crawler = self::request('GET', preg_replace('#(.+)(adm/index.php.+)#', '$2', $link->getUri())); // Ensure again that actkey is empty after deactivation - $this->get_user_data('reset-password-test-user'); + $this->get_user_data(self::TEST_USER); $this->assertEmpty($this->user_data['user_actkey']); // Force reactivation of account and check that act key is not empty anymore @@ -210,8 +214,50 @@ public function test_acivateAfterDeactivate() $crawler = self::submit($form, array('action' => 'reactivate')); $this->assertContainsLang('FORCE_REACTIVATION_SUCCESS', $crawler->filter('html')->text()); - $this->get_user_data('reset-password-test-user'); + $this->get_user_data(self::TEST_USER); + $this->assertNotEmpty($this->user_data['user_actkey']); + + // Logout and try resending activation email, account is deactivated though + $this->logout(); + $this->add_lang('ucp'); + + $crawler = self::request('GET', 'ucp.php?mode=resend_act'); + $this->assertContainsLang('UCP_RESEND', $crawler->filter('html')->text()); + $form = $crawler->filter('input[name=submit]')->selectButton('Submit')->form(); + $crawler = self::submit($form, [ + 'username' => self::TEST_USER, + 'email' => self::TEST_EMAIL, + ]); + $this->assertContainsLang('ACCOUNT_DEACTIVATED', $crawler->filter('html')->text()); + } + + /** + * @depends test_activateAfterDeactivate + */ + public function test_resendActivation() + { + // User is deactivated and should have actkey, actkey should not exist + $this->get_user_data(self::TEST_USER); $this->assertNotEmpty($this->user_data['user_actkey']); + + // Change reason for inactivity + $db = $this->get_db(); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_inactive_reason = ' . INACTIVE_REMIND . ' + WHERE user_id = ' . (int) $this->user_data['user_id']; + $db->sql_query($sql); + + $this->add_lang('ucp'); + + $crawler = self::request('GET', 'ucp.php?mode=resend_act'); + $this->assertContainsLang('UCP_RESEND', $crawler->filter('html')->text()); + $form = $crawler->filter('input[name=submit]')->selectButton('Submit')->form(); + $crawler = self::submit($form, [ + 'username' => self::TEST_USER, + 'email' => self::TEST_EMAIL, + ]); + $this->assertContainsLang('ACTIVATION_ALREADY_SENT', $crawler->filter('html')->text()); } protected function get_user_data($username) From 1c1c981b1778c1bbb57a79349a5cf813746e3b59 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 9 May 2024 11:51:59 +0200 Subject: [PATCH 0157/1214] [ticket/security/276] Centralise call for token expiration SECURITY-276 --- phpBB/includes/acp/acp_inactive.php | 2 +- phpBB/includes/acp/acp_users.php | 4 ++-- phpBB/includes/ucp/ucp_profile.php | 2 +- phpBB/includes/ucp/ucp_register.php | 2 +- phpBB/includes/ucp/ucp_resend.php | 2 +- phpBB/phpbb/console/command/user/add.php | 4 ++-- phpBB/phpbb/ucp/controller/reset_password.php | 2 +- phpBB/phpbb/user.php | 12 +++++++++++- 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index b85ed86ee59..d74c09af3a0 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -242,7 +242,7 @@ function main($id, $mode) $sql = 'UPDATE ' . USERS_TABLE . ' SET user_reminded = user_reminded + 1, user_reminded_time = ' . time() . ', - user_actkey_expiration = ' . (int) strtotime('+1 day') . ' + user_actkey_expiration = ' . (int) $user::get_token_expiration() . ' WHERE ' . $db->sql_in_set('user_id', $user_ids); $db->sql_query($sql); diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index f54ff08db43..611e72a9b80 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -388,12 +388,12 @@ function main($id, $mode) // Always update actkey even if same and also update actkey expiration to 24 hours from now $sql_ary = [ 'user_actkey' => $user_actkey, - 'user_actkey_expiration' => strtotime('+1 day'), + 'user_actkey_expiration' => $user::get_token_expiration(), ]; $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_id = ' . $user_id; + WHERE user_id = ' . (int) $user_id; $db->sql_query($sql); // Start sending email diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 616792d9ac9..2a04ed42a08 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -198,7 +198,7 @@ function main($id, $mode) $notifications_manager->add_notifications('notification.type.admin_activate_user', array( 'user_id' => $user->data['user_id'], 'user_actkey' => $user_actkey, - 'user_actkey_expiration' => strtotime('+1 day'), // 24 hours until activation can be resent + 'user_actkey_expiration' => $user::get_token_expiration(), 'user_regdate' => time(), // Notification time )); } diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 4d6677567ad..a881a0e81d8 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -389,7 +389,7 @@ function main($id, $mode) 'user_lang' => $data['lang'], 'user_type' => $user_type, 'user_actkey' => $user_actkey, - 'user_actkey_expiration' => strtotime('+1 day'), // 24 hours until activation can be resent + 'user_actkey_expiration' => $user::get_token_expiration(), 'user_ip' => $user->ip, 'user_regdate' => time(), 'user_inactive_reason' => $user_inactive_reason, diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 609ef97b20f..31e08786638 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -179,7 +179,7 @@ protected function update_activation_expiration(): void global $db, $user; $sql_ary = [ - 'user_actkey_expiration' => strtotime('+1 day'), + 'user_actkey_expiration' => $user::get_token_expiration(), ]; $sql = 'UPDATE ' . USERS_TABLE . ' diff --git a/phpBB/phpbb/console/command/user/add.php b/phpBB/phpbb/console/command/user/add.php index 334ff714153..11380373cdc 100644 --- a/phpBB/phpbb/console/command/user/add.php +++ b/phpBB/phpbb/console/command/user/add.php @@ -337,12 +337,12 @@ protected function get_activation_key(int $user_id): string $sql_ary = [ 'user_actkey' => $user_actkey, - 'user_actkey_expiration' => strtotime('+1 day'), + 'user_actkey_expiration' => \phpbb\user::get_token_expiration(), ]; $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_id = ' . $user_id; + WHERE user_id = ' . (int) $user_id; $this->db->sql_query($sql); } diff --git a/phpBB/phpbb/ucp/controller/reset_password.php b/phpBB/phpbb/ucp/controller/reset_password.php index 442de50ec77..a451856065b 100644 --- a/phpBB/phpbb/ucp/controller/reset_password.php +++ b/phpBB/phpbb/ucp/controller/reset_password.php @@ -242,7 +242,7 @@ public function request() $sql_ary = [ 'reset_token' => $reset_token, - 'reset_token_expiration' => strtotime('+1 day'), + 'reset_token_expiration' => $this->user::get_token_expiration(), ]; $sql = 'UPDATE ' . $this->users_table . ' diff --git a/phpBB/phpbb/user.php b/phpBB/phpbb/user.php index 9f7b15bf69c..de556f99512 100644 --- a/phpBB/phpbb/user.php +++ b/phpBB/phpbb/user.php @@ -57,7 +57,7 @@ class user extends \phpbb\session * @param \phpbb\language\language $lang phpBB's Language loader * @param string $datetime_class Class name of datetime class */ - function __construct(\phpbb\language\language $lang, $datetime_class) + public function __construct(\phpbb\language\language $lang, $datetime_class) { global $phpbb_root_path; @@ -78,6 +78,16 @@ public function is_setup() return $this->is_setup_flag; } + /** + * Get expiration time for user tokens, e.g. activation or reset password tokens + * + * @return int Expiration for user tokens + */ + public static function get_token_expiration(): int + { + return strtotime('+1 day') ?: 0; + } + /** * Magic getter for BC compatibility * From 1fb7e6d2c85d48be5839cd3fd3a8004d54f13d78 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 18 May 2024 21:43:21 +0200 Subject: [PATCH 0158/1214] [ticket/security/278] Always release cron lock, even invalid task is passed SECURITY-278 --- phpBB/phpbb/cron/event/cron_runner_listener.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpBB/phpbb/cron/event/cron_runner_listener.php b/phpBB/phpbb/cron/event/cron_runner_listener.php index c9c13ddc069..ba030ca3268 100644 --- a/phpBB/phpbb/cron/event/cron_runner_listener.php +++ b/phpBB/phpbb/cron/event/cron_runner_listener.php @@ -85,9 +85,8 @@ public function on_kernel_terminate(PostResponseEvent $event) { $task->run(); } - - $this->cron_lock->release(); } + $this->cron_lock->release(); } } From bf55502c626affbd201f4f50b50253366741cbf4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 30 May 2024 10:40:25 +0200 Subject: [PATCH 0159/1214] [ticket/17325] Use not instead of ! in template PHPBB3-17325 --- phpBB/adm/style/acp_main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/adm/style/acp_main.html b/phpBB/adm/style/acp_main.html index 8ae4f0721c1..95420bdb611 100644 --- a/phpBB/adm/style/acp_main.html +++ b/phpBB/adm/style/acp_main.html @@ -24,7 +24,7 @@

      {L_WELCOME_PHPBB}

      {{ VERSIONCHECK_FAIL_REASON }}

      {{ lang('VERSIONCHECK_FORCE_UPDATE') }} · {{ lang('MORE_INFORMATION') }}

      - {% elseif !S_VERSION_UP_TO_DATE %} + {% elseif not S_VERSION_UP_TO_DATE %}

      {{ lang('VERSION_NOT_UP_TO_DATE_TITLE') }}

      {{ lang('VERSIONCHECK_FORCE_UPDATE') }} · {{ lang('MORE_INFORMATION') }}

      From 166c4eeb7e391b6a6518c0791a79d8a3fba232b1 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Fri, 17 May 2024 03:56:10 +0100 Subject: [PATCH 0160/1214] [ticket/17316] Add template events to ucp_groups_manage PHPBB3-17316 --- phpBB/docs/events.md | 14 ++++++++++++++ .../prosilver/template/ucp_groups_manage.html | 2 ++ 2 files changed, 16 insertions(+) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index d05cb876fdb..2a8fd5002d1 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -2521,6 +2521,20 @@ ucp_friend_list_before * Since: 3.1.0-a4 * Purpose: Add optional elements before list of friends in UCP +ucp_group_settings_after +=== +* Locations: + + styles/prosilver/template/ucp_groups_manage.html +* Since: 3.3.13-RC1 +* Purpose: Add content after options for managing a group in the UCP + +ucp_group_settings_before +=== +* Locations: + + styles/prosilver/template/ucp_groups_manage.html +* Since: 3.3.13-RC1 +* Purpose: Add content before options for managing a group in the UCP + ucp_header_content_before === * Locations: diff --git a/phpBB/styles/prosilver/template/ucp_groups_manage.html b/phpBB/styles/prosilver/template/ucp_groups_manage.html index 63b9560a7a4..e592d12928b 100644 --- a/phpBB/styles/prosilver/template/ucp_groups_manage.html +++ b/phpBB/styles/prosilver/template/ucp_groups_manage.html @@ -52,6 +52,7 @@

      {L_GROUP_DETAILS}

      {L_GROUP_SETTINGS_SAVE}

      +

      {L_GROUP_COLOR_EXPLAIN}
      @@ -65,6 +66,7 @@

      {L_GROUP_SETTINGS_SAVE}

      +
      From 607fe555afaf2af861fc07d5ea51d25472bcfd86 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 30 May 2024 11:00:22 +0200 Subject: [PATCH 0161/1214] [prep-release-3.3.12] Update version numbers to 3.3.12 --- build/build.xml | 4 ++-- phpBB/includes/constants.php | 2 +- phpBB/install/phpbbcli.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/build.xml b/build/build.xml index c3a741b8917..6eb00cf4fab 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2,9 +2,9 @@ - + - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index f5d8122628e..d80ad6b5877 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '3.3.12-RC1'); +@define('PHPBB_VERSION', '3.3.12'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/phpbbcli.php b/phpBB/install/phpbbcli.php index 31072dbd6b8..07b13ddda03 100755 --- a/phpBB/install/phpbbcli.php +++ b/phpBB/install/phpbbcli.php @@ -23,7 +23,7 @@ define('IN_PHPBB', true); define('IN_INSTALL', true); define('PHPBB_ENVIRONMENT', 'production'); -define('PHPBB_VERSION', '3.3.12-RC1'); +define('PHPBB_VERSION', '3.3.12'); $phpbb_root_path = __DIR__ . '/../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index a9c8d4a0495..1400d986c19 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -316,7 +316,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.12-RC1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.12'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); From 6cef48af2fe3ef33f74e01689f38974ff742f4a8 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 30 May 2024 11:00:23 +0200 Subject: [PATCH 0162/1214] [prep-release-3.3.12] Add migration for 3.3.12 --- phpBB/phpbb/db/migration/data/v33x/v3312.php | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/v3312.php diff --git a/phpBB/phpbb/db/migration/data/v33x/v3312.php b/phpBB/phpbb/db/migration/data/v33x/v3312.php new file mode 100644 index 00000000000..36e78b95ef5 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3312.php @@ -0,0 +1,38 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3312 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.12', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\add_resend_activation_expiration', + '\phpbb\db\migration\data\v33x\add_user_last_active', + '\phpbb\db\migration\data\v33x\v3312rc1', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.12']], + ]; + } +} From 80a12f7108270de2dec30ddd847a0edfb59aa9fe Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 30 May 2024 11:03:06 +0200 Subject: [PATCH 0163/1214] [prep-release-3.3.12] Update changelog for 3.3.12 --- phpBB/docs/CHANGELOG.html | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 37fe4a2cb58..ccd3c88558c 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -50,6 +50,7 @@

      Changelog

      1. Changelog
          +
        • Changes since 3.3.12-RC1
        • Changes since 3.3.11
        • Changes since 3.3.10
        • Changes since 3.3.10-RC1
        • @@ -168,6 +169,26 @@

          Changelog

          +

          Changes since 3.3.12-RC1

          +

          Bug

          +
            +
          • [PHPBB3-17312] - User last visit gets updated too often
          • +
          +

          Improvement

          +
            +
          • [PHPBB3-17324] - Add template event to notification_dropdown.html
          • +
          +

          Hardening

          +
            +
          • [SECURITY-132] - Limit CAPTCHA attempts at registration for single session
          • +
          • [SECURITY-279] - Escape smilies URL and prevent paths in .pak filename
          • +
          +

          Hardening

          +
            +
          • [SECURITY-276] - Prevent resending activation email too often
          • +
          • [SECURITY-278] - Always release cron lock, even invalid task is passed
          • +
          +

          Changes since 3.3.11

          Bug

            From 2aba9b0e3feea6504abc5892a4f8665a9404ec65 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 30 May 2024 20:39:52 +0200 Subject: [PATCH 0164/1214] [prep-release-3.3.12] Remove columns from user_add These have default values and do not require setting in user_add(). --- phpBB/includes/functions_user.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index c33caba8e8d..4846436a72f 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -216,7 +216,6 @@ function user_add($user_row, $cp_data = false, $notifications_data = null) 'user_lang' => $config['default_lang'], 'user_style' => (int) $config['default_style'], 'user_actkey' => '', - 'user_actkey_expiration' => 0, 'user_ip' => '', 'user_regdate' => time(), 'user_passchg' => time(), @@ -228,7 +227,6 @@ function user_add($user_row, $cp_data = false, $notifications_data = null) 'user_inactive_time' => 0, 'user_lastmark' => time(), 'user_lastvisit' => 0, - 'user_last_active' => 0, 'user_lastpost_time' => 0, 'user_lastpage' => '', 'user_posts' => 0, From 902283c368e379f563ce3e716f4ac321d927161b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 09:47:37 +0200 Subject: [PATCH 0165/1214] [ticket/17153] Use twig and avatar helper to render avatars PHPBB3-17153 --- .../default/container/services_twig.yml | 2 + .../phpbb/template/twig/extension/avatar.php | 73 +++++++++++-------- phpBB/styles/all/template/macros/avatar.twig | 1 + tests/template/extension_test.php | 64 +++++++++++++++- tests/template/templates/avatar_group.html | 1 + 5 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 phpBB/styles/all/template/macros/avatar.twig create mode 100644 tests/template/templates/avatar_group.html diff --git a/phpBB/config/default/container/services_twig.yml b/phpBB/config/default/container/services_twig.yml index 17ca9c75038..e7a7155deba 100644 --- a/phpBB/config/default/container/services_twig.yml +++ b/phpBB/config/default/container/services_twig.yml @@ -45,6 +45,8 @@ services: template.twig.extensions.avatar: class: phpbb\template\twig\extension\avatar + arguments: + - '@avatar.helper' tags: - { name: twig.extension } diff --git a/phpBB/phpbb/template/twig/extension/avatar.php b/phpBB/phpbb/template/twig/extension/avatar.php index fb7ec92655f..238caddaf50 100644 --- a/phpBB/phpbb/template/twig/extension/avatar.php +++ b/phpBB/phpbb/template/twig/extension/avatar.php @@ -13,16 +13,36 @@ namespace phpbb\template\twig\extension; +use phpbb\avatar\helper; +use phpbb\avatar\manager; +use phpbb\template\twig\environment; +use Twig\Error\Error; use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; class avatar extends AbstractExtension { + /** + * @var helper + */ + private $avatar_helper; + + /** + * Constructor for avatar extension + * + * @param helper $avatar_helper + */ + public function __construct(helper $avatar_helper) + { + $this->avatar_helper = $avatar_helper; + } + /** * Get the name of this extension * * @return string */ - public function getName() + public function getName(): string { return 'avatar'; } @@ -30,13 +50,13 @@ public function getName() /** * Returns a list of global functions to add to the existing list. * - * @return \Twig\TwigFunction[] An array of global functions + * @return TwigFunction[] An array of global functions */ public function getFunctions(): array { - return array( - new \Twig\TwigFunction('avatar', array($this, 'get_avatar')), - ); + return [ + new TwigFunction('avatar', [$this, 'get_avatar'], ['needs_environment' => true]), + ]; } /** @@ -48,35 +68,30 @@ public function getFunctions(): array * The mode and row (group_row or user_row) are required. * The other fields (alt|ignore_config|lazy) are optional. * - * @uses \phpbb_get_group_avatar() - * @uses \phpbb_get_user_avatar() - * * @return string The avatar HTML for the specified mode */ - public function get_avatar() + public function get_avatar(environment $environment, string $mode, array $row, ?string $alt, ?bool $ignore_config, ?bool $lazy): string { - $args = func_get_args(); + $alt = $alt ?? false; + $ignore_config = $ignore_config ?? false; + $lazy = $lazy ?? false; + $row = manager::clean_row($row, $mode); + $avatar = $this->avatar_helper->get_avatar($row, $alt, $ignore_config, $lazy); - $mode = (string) $args[0]; - $row = (array) $args[1]; - $alt = isset($args[2]) ? (string) $args[2] : false; - $ignore_config = isset($args[3]) ? (bool) $args[3] : false; - $lazy = isset($args[4]) ? (bool) $args[4] : false; - - // To prevent having to redefine alt attribute ('USER_AVATAR'|'GROUP_AVATAR'), we check if an alternative has been provided - switch ($mode) + try { - case 'group': - return $alt ? phpbb_get_group_avatar($row, $alt, $ignore_config, $lazy) : phpbb_get_group_avatar($row); - break; - - case 'user': - return $alt ? phpbb_get_user_avatar($row, $alt, $ignore_config, $lazy) : phpbb_get_user_avatar($row); - break; - - default: - return ''; - break; + return $environment->render('macros/avatar.twig', [ + 'SRC' => $avatar['lazy'] ? $this->avatar_helper->get_no_avatar_source() : $avatar['src'], + 'DATA_SRC' => $avatar['lazy'] ? $avatar['src'] : '', + 'WIDTH' => $avatar['width'], + 'HEIGHT' => $avatar['height'], + 'TITLE' => $avatar['title'], + 'LAZY' => $avatar['lazy'], + ]); + } + catch (Error $e) + { + return ''; } } } diff --git a/phpBB/styles/all/template/macros/avatar.twig b/phpBB/styles/all/template/macros/avatar.twig new file mode 100644 index 00000000000..6c3a3809cde --- /dev/null +++ b/phpBB/styles/all/template/macros/avatar.twig @@ -0,0 +1 @@ +{% if SRC %}{{ lang(TITLE) }}{% endif %} diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index 207aa5e99f6..64da6b79b37 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -89,6 +89,14 @@ protected function setup_engine(array $new_config = []) $enabled_drivers = $class->getProperty('enabled_drivers'); $enabled_drivers->setAccessible(true); $enabled_drivers->setValue($class, false); + $avatar_helper = new phpbb\avatar\helper( + $config, + $phpbb_dispatcher, + $lang, + $phpbb_container->get('avatar.manager'), + $phpbb_path_helper, + new \phpbb\user($lang, '\phpbb\datetime') + ); $this->template_path = $this->test_path . '/templates'; @@ -122,7 +130,7 @@ protected function setup_engine(array $new_config = []) $this->user, [ new \phpbb\template\twig\extension($context, $twig, $this->lang), - new \phpbb\template\twig\extension\avatar(), + new \phpbb\template\twig\extension\avatar($avatar_helper), new \phpbb\template\twig\extension\config($config), new \phpbb\template\twig\extension\icon($this->user), new \phpbb\template\twig\extension\username(), @@ -153,7 +161,7 @@ public function data_template_extensions() ], [], [], - 'foo', + 'foo', [] ], [ @@ -171,7 +179,7 @@ public function data_template_extensions() ], [], [], - 'foo', + 'foo', [] ], [ @@ -190,6 +198,56 @@ public function data_template_extensions() '', [] ], + [ + 'avatar_group.html', + [ + 'row' => [ + 'group_avatar' => 'great_avatar.png', + 'group_avatar_type' => 'avatar.driver.upload', + 'group_avatar_width' => 90, + 'group_avatar_height' => 90, + ], + 'alt' => 'foo' + ], + [], + [], + 'foo', + [] + ], + [ + 'avatar_group.html', + [ + 'row' => [ + 'group_avatar' => 'great_avatar.png', + 'group_avatar_type' => 'avatar.driver.upload', + 'group_avatar_width' => 90, + 'group_avatar_height' => 90, + ], + 'alt' => 'foo', + 'ignore_config' => true, + 'lazy' => true, + ], + [], + [], + 'foo', + [] + ], + [ + 'avatar_group.html', + [ + 'row' => [ + 'group_avatar' => 'foo@bar.com', + 'group_avatar_type' => 'avatar.driver.gravatar', + 'group_avatar_width' => 90, + 'group_avatar_height' => 90, + ], + 'alt' => 'foo' + ], + [], + [], + '', + [] + ], [ 'extension_username_test.html', [ diff --git a/tests/template/templates/avatar_group.html b/tests/template/templates/avatar_group.html new file mode 100644 index 00000000000..95ae9c08c0f --- /dev/null +++ b/tests/template/templates/avatar_group.html @@ -0,0 +1 @@ +{{ avatar('group', row, alt, ignore_config, lazy) }} From cf44868393832ddad6c765b90682c3fc9482b16b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 09:48:15 +0200 Subject: [PATCH 0166/1214] [ticket/17153] Remove phpbb_get_group_avatar() PHPBB3-17153 --- phpBB/includes/functions.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index bca9a97c48c..0c38e70eb6a 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3559,24 +3559,6 @@ function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); } -/** -* Get group avatar -* -* @deprecated 4.0.0 Use \phpbb\avatar\helper::get_group_avatar() instead -* -* @param array $group_row Row from the groups table -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar html -*/ -function phpbb_get_group_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false) -{ - $row = \phpbb\avatar\manager::clean_row($group_row, 'group'); - return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); -} - /** * Get avatar * From 808b2f3a417eb16a4e325a42c3f8c224bc96f6a5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 09:48:56 +0200 Subject: [PATCH 0167/1214] [ticket/17153] Remove phpbb_get_user_avatar PHPBB3-17153 --- phpBB/includes/functions.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 0c38e70eb6a..b2440a7cf31 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3541,24 +3541,6 @@ function phpbb_quoteattr($data, $entities = null) return $data; } -/** -* Get user avatar -* -* @deprecated 4.0.0 Use \phpbb\avatar\helper::get_user_avatar() instead -* -* @param array $user_row Row from the users table -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar html -*/ -function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) -{ - $row = \phpbb\avatar\manager::clean_row($user_row, 'user'); - return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); -} - /** * Get avatar * From f54bafec293229808417b3ba4f01011ad3728f28 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 09:50:32 +0200 Subject: [PATCH 0168/1214] [ticket/17153] Remove get_user_avatar PHPBB3-17153 --- phpBB/includes/functions_compatibility.php | 28 ---------------------- 1 file changed, 28 deletions(-) diff --git a/phpBB/includes/functions_compatibility.php b/phpBB/includes/functions_compatibility.php index c5ca053f560..85ec1bd449a 100644 --- a/phpBB/includes/functions_compatibility.php +++ b/phpBB/includes/functions_compatibility.php @@ -19,34 +19,6 @@ exit; } -/** -* Get user avatar -* -* @deprecated 3.1.0-a1 (To be removed: 4.0.0) -* -* @param string $avatar Users assigned avatar name -* @param int $avatar_type Type of avatar -* @param string $avatar_width Width of users avatar -* @param string $avatar_height Height of users avatar -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar image -*/ -function get_user_avatar($avatar, $avatar_type, $avatar_width, $avatar_height, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) -{ - // map arguments to new function phpbb_get_avatar() - $row = array( - 'avatar' => $avatar, - 'avatar_type' => $avatar_type, - 'avatar_width' => $avatar_width, - 'avatar_height' => $avatar_height, - ); - - return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); -} - /** * Hash the password * From 4db4a5571b8f9af937956ecf86257b6080b44826 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 09:51:21 +0200 Subject: [PATCH 0169/1214] [ticket/17153] Remove phpbb_get_avatar() PHPBB3-17153 --- phpBB/includes/functions.php | 85 ------------------------------------ 1 file changed, 85 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index b2440a7cf31..2568eb20b25 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3541,91 +3541,6 @@ function phpbb_quoteattr($data, $entities = null) return $data; } -/** -* Get avatar -* -* @deprecated 4.0.0 Use \phpbb\avatar\helper::get_avatar() instead -* -* @param array $row Row cleaned by \phpbb\avatar\manager::clean_row -* @param string $alt Optional language string for alt tag within image, can be a language key or text -* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP -* @param bool $lazy If true, will be lazy loaded (requires JS) -* -* @return string Avatar html -*/ -function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false) -{ - global $user, $config; - global $phpbb_container, $phpbb_dispatcher; - - if (!$config['allow_avatar'] && !$ignore_config) - { - return ''; - } - - $avatar_data = array( - 'src' => $row['avatar'], - 'width' => $row['avatar_width'], - 'height' => $row['avatar_height'], - ); - - /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ - $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); - $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config); - $html = ''; - - if ($driver) - { - $html = $driver->get_custom_html($user, $row, $alt); - $avatar_data = $driver->get_data($row); - } - else - { - $avatar_data['src'] = ''; - } - - if (empty($html) && !empty($avatar_data['src'])) - { - if ($lazy) - { - // This path is sent with the base template paths in the assign_vars() - // call below. We need to correct it in case we are accessing from a - // controller because the web paths will be incorrect otherwise. - $phpbb_path_helper = $phpbb_container->get('path_helper'); - $web_path = $phpbb_path_helper->get_web_root_path(); - - $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme'; - - $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"'; - } - else - { - $src = 'src="' . $avatar_data['src'] . '"'; - } - - $html = ''; - } - - /** - * Event to modify HTML tag of avatar - * - * @event core.get_avatar_after - * @var array row Row cleaned by \phpbb\avatar\manager::clean_row - * @var string alt Optional language string for alt tag within image, can be a language key or text - * @var bool ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP - * @var array avatar_data The HTML attributes for avatar tag - * @var string html The HTML tag of generated avatar - * @since 3.1.6-RC1 - */ - $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html'); - extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars))); - - return $html; -} - /** * Generate page header */ From aac7d5a99cdcfe3f32ff8edec02ac1d9099504af Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 19:31:00 +0200 Subject: [PATCH 0170/1214] [ticket/17153] Use routing helper instead of controller helper in upload avatar The controller helper class is not needed, routing helper brings all the needed functionality already. PHPBB3-17153 --- phpBB/config/default/container/services_avatar.yml | 2 +- phpBB/phpbb/avatar/driver/upload.php | 12 ++++++------ tests/avatar/manager_test.php | 3 ++- tests/template/extension_test.php | 9 +++------ 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/phpBB/config/default/container/services_avatar.yml b/phpBB/config/default/container/services_avatar.yml index e8048278c7b..a7c93869cc9 100644 --- a/phpBB/config/default/container/services_avatar.yml +++ b/phpBB/config/default/container/services_avatar.yml @@ -56,11 +56,11 @@ services: class: phpbb\avatar\driver\upload arguments: - '@config' - - '@controller.helper' - '%core.root_path%' - '%core.php_ext%' - '@storage.avatar' - '@path_helper' + - '@routing.helper' - '@event_dispatcher' - '@files.factory' - '@php_ini' diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php index 416034b6741..14b9e4026cb 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -15,10 +15,10 @@ use bantu\IniGetWrapper\IniGetWrapper; use phpbb\config\config; -use phpbb\controller\helper; use phpbb\event\dispatcher_interface; use phpbb\files\factory; use phpbb\path_helper; +use phpbb\routing\helper; use phpbb\storage\exception\exception as storage_exception; use phpbb\storage\storage; @@ -30,7 +30,7 @@ class upload extends \phpbb\avatar\driver\driver /** * @var helper */ - private $controller_helper; + private $routing_helper; /** * @var storage @@ -56,23 +56,23 @@ class upload extends \phpbb\avatar\driver\driver * Construct a driver object * * @param config $config phpBB configuration - * @param helper $controller_helper * @param string $phpbb_root_path Path to the phpBB root * @param string $php_ext PHP file extension * @param storage $storage phpBB avatar storage * @param path_helper $path_helper phpBB path helper + * @param helper $routing_helper phpBB routing helper * @param dispatcher_interface $dispatcher phpBB Event dispatcher object * @param factory $files_factory File classes factory * @param IniGetWrapper $php_ini ini_get() wrapper */ - public function __construct(config $config, helper $controller_helper, string $phpbb_root_path, string $php_ext, storage $storage, path_helper $path_helper, dispatcher_interface $dispatcher, factory $files_factory, IniGetWrapper $php_ini) + public function __construct(config $config, string $phpbb_root_path, string $php_ext, storage $storage, path_helper $path_helper, helper $routing_helper, dispatcher_interface $dispatcher, factory $files_factory, IniGetWrapper $php_ini) { $this->config = $config; - $this->controller_helper = $controller_helper; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->storage = $storage; $this->path_helper = $path_helper; + $this->routing_helper = $routing_helper; $this->dispatcher = $dispatcher; $this->files_factory = $files_factory; $this->php_ini = $php_ini; @@ -84,7 +84,7 @@ public function __construct(config $config, helper $controller_helper, string $p public function get_data($row) { return array( - 'src' => $this->controller_helper->route('phpbb_storage_avatar', ['file' => $row['avatar']]), + 'src' => $this->routing_helper->route('phpbb_storage_avatar', ['file' => $row['avatar']]), 'width' => $row['avatar_width'], 'height' => $row['avatar_height'], ); diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php index 19b7797eb4f..c3210807b94 100644 --- a/tests/avatar/manager_test.php +++ b/tests/avatar/manager_test.php @@ -58,6 +58,7 @@ protected function setUp(): void $phpbb_dispatcher = $dispatcher; $controller_helper = $this->createMock('\phpbb\controller\helper'); + $routing_helper = $this->createMock('\phpbb\routing\helper'); // $this->avatar_foobar will be needed later on $this->avatar_foobar = $this->getMockBuilder('\phpbb\avatar\driver\foobar') @@ -96,7 +97,7 @@ protected function setUp(): void { $cur_avatar = $this->getMockBuilder('\phpbb\avatar\driver\\' . $driver) ->setMethods(array('get_name')) - ->setConstructorArgs(array($this->config, $controller_helper, $phpbb_root_path, $phpEx, $storage, $path_helper, $dispatcher, $files_factory, $php_ini)) + ->setConstructorArgs(array($this->config, $phpbb_root_path, $phpEx, $storage, $path_helper, $routing_helper, $dispatcher, $files_factory, $php_ini)) ->getMock(); } $cur_avatar->expects($this->any()) diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index 64da6b79b37..8295a613a02 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -11,8 +11,6 @@ * */ -use phpbb\controller\helper; - require_once __DIR__ . '/template_test_case.php'; class phpbb_template_extension_test extends phpbb_template_template_test_case @@ -68,9 +66,8 @@ protected function setup_engine(array $new_config = []) ->disableOriginalConstructor() ->getMock(); - $controller_helper = $this->createMock(helper::class); - $controller_helper - ->method('route') + $routing_helper = $this->createMock(\phpbb\routing\helper::class); + $routing_helper->method('route') ->willReturnCallback(function($route, $params) { return 'download/avatar/' . $params['file']; }); @@ -78,7 +75,7 @@ protected function setup_engine(array $new_config = []) $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); $phpbb_container = new phpbb_mock_container_builder(); $files = new phpbb\files\factory($phpbb_container); - $upload_avatar_driver = new phpbb\avatar\driver\upload($config, $controller_helper, $phpbb_root_path, $phpEx, $storage, $phpbb_path_helper, $phpbb_dispatcher, $files, new \bantu\IniGetWrapper\IniGetWrapper()); + $upload_avatar_driver = new phpbb\avatar\driver\upload($config, $phpbb_root_path, $phpEx, $storage, $phpbb_path_helper, $routing_helper, $phpbb_dispatcher, $files, new \bantu\IniGetWrapper\IniGetWrapper()); $upload_avatar_driver->set_name('avatar.driver.upload'); $phpbb_container->set('avatar.manager', new \phpbb\avatar\manager($config, $phpbb_dispatcher, [ $upload_avatar_driver, From ad7721368ce4f3505319fca4085314ce0c056072 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 19:32:38 +0200 Subject: [PATCH 0171/1214] [ticket/17153] Add basic avatar config file for installer It doesn't actually add proper avatar functionality to the installer but provides the necessary base classes for templating. PHPBB3-17153 --- phpBB/config/installer/container/services.yml | 1 + .../installer/container/services_avatar.yml | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 phpBB/config/installer/container/services_avatar.yml diff --git a/phpBB/config/installer/container/services.yml b/phpBB/config/installer/container/services.yml index b4e731306d5..70fb0ee308d 100644 --- a/phpBB/config/installer/container/services.yml +++ b/phpBB/config/installer/container/services.yml @@ -1,5 +1,6 @@ imports: - { resource: services_installer.yml } + - { resource: services_avatar.yml } - { resource: ../../default/container/services_event.yml } - { resource: ../../default/container/services_filesystem.yml } - { resource: ../../default/container/services_http.yml } diff --git a/phpBB/config/installer/container/services_avatar.yml b/phpBB/config/installer/container/services_avatar.yml new file mode 100644 index 00000000000..31d28e77fb5 --- /dev/null +++ b/phpBB/config/installer/container/services_avatar.yml @@ -0,0 +1,25 @@ +services: + avatar.manager: + class: phpbb\avatar\manager + arguments: + - '@config' + - '@dispatcher' + - '@avatar.driver_collection' + + avatar.helper: + class: phpbb\avatar\helper + arguments: + - '@config' + - '@dispatcher' + - '@language' + - '@avatar.manager' + - '@path_helper' + - '@user' + + # ----- Avatar drivers ----- + avatar.driver_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: avatar.driver } From c495a26f98ecbf776a08b3a5282d1ee9a543ce3f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jul 2023 20:29:16 +0200 Subject: [PATCH 0172/1214] [ticket/17153] Use mocked user class in extension test PHPBB3-17153 --- tests/template/extension_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index 8295a613a02..5caae111ca8 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -32,7 +32,7 @@ protected function setup_engine(array $new_config = []) $this->user->style['style_parent_id'] = 0; global $auth, $request, $symfony_request, $user; - $user = new phpbb_mock_user(); + $user = $this->createMock(\phpbb\user::class); $user->optionset('user_id', 2); $user->style['style_path'] = ''; $user->data['user_id'] = 2; @@ -92,7 +92,7 @@ protected function setup_engine(array $new_config = []) $lang, $phpbb_container->get('avatar.manager'), $phpbb_path_helper, - new \phpbb\user($lang, '\phpbb\datetime') + $user ); $this->template_path = $this->test_path . '/templates'; From be6bb099845e7e32445b6dcd90b8f29b14cbd67d Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 31 May 2024 20:05:39 +0200 Subject: [PATCH 0173/1214] [prep-release-3.3.12] Update changelog for 3.3.12 --- phpBB/docs/CHANGELOG.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index ccd3c88558c..8b20e374b16 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -179,11 +179,6 @@

            Improvement

          • [PHPBB3-17324] - Add template event to notification_dropdown.html

          Hardening

          -
            -
          • [SECURITY-132] - Limit CAPTCHA attempts at registration for single session
          • -
          • [SECURITY-279] - Escape smilies URL and prevent paths in .pak filename
          • -
          -

          Hardening

          • [SECURITY-276] - Prevent resending activation email too often
          • [SECURITY-278] - Always release cron lock, even invalid task is passed
          • From 56d8a7e43f6bbf9837227ea4da712a10287a7c64 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 31 May 2024 22:30:05 +0200 Subject: [PATCH 0174/1214] [ticket/17327] Use class name from use statement PHPBB3-17327 --- phpBB/phpbb/console/command/user/add.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/console/command/user/add.php b/phpBB/phpbb/console/command/user/add.php index 11380373cdc..1dadabb5a41 100644 --- a/phpBB/phpbb/console/command/user/add.php +++ b/phpBB/phpbb/console/command/user/add.php @@ -337,7 +337,7 @@ protected function get_activation_key(int $user_id): string $sql_ary = [ 'user_actkey' => $user_actkey, - 'user_actkey_expiration' => \phpbb\user::get_token_expiration(), + 'user_actkey_expiration' => user::get_token_expiration(), ]; $sql = 'UPDATE ' . USERS_TABLE . ' From efc8400694118d8c9d76070fa9e1c80bbbc6c855 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Jun 2024 15:55:00 +0200 Subject: [PATCH 0175/1214] [ticket/17328] Update requires for exporting events PHPBB-17328 --- phpBB/develop/export_events_for_bbcode.php | 3 ++- phpBB/develop/export_events_for_rst.php | 3 ++- phpBB/develop/export_events_for_wiki.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/phpBB/develop/export_events_for_bbcode.php b/phpBB/develop/export_events_for_bbcode.php index e30027bf94d..fc8c604f800 100644 --- a/phpBB/develop/export_events_for_bbcode.php +++ b/phpBB/develop/export_events_for_bbcode.php @@ -65,7 +65,8 @@ function validate_argument_count($arguments, $count) require __DIR__ . '/../phpbb/event/rst_exporter.' . $phpEx; require __DIR__ . '/../includes/functions.' . $phpEx; require __DIR__ . '/../phpbb/event/recursive_event_filter_iterator.' . $phpEx; -require __DIR__ . '/../phpbb/iterator/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_path_iterator.' . $phpEx; switch ($action) { diff --git a/phpBB/develop/export_events_for_rst.php b/phpBB/develop/export_events_for_rst.php index 908d4298d65..9ca58e6e69f 100644 --- a/phpBB/develop/export_events_for_rst.php +++ b/phpBB/develop/export_events_for_rst.php @@ -68,7 +68,8 @@ function validate_argument_count($arguments, $count) require __DIR__ . '/../phpbb/event/rst_exporter.' . $phpEx; require __DIR__ . '/../includes/functions.' . $phpEx; require __DIR__ . '/../phpbb/event/recursive_event_filter_iterator.' . $phpEx; -require __DIR__ . '/../phpbb/iterator/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_path_iterator.' . $phpEx; switch ($action) { diff --git a/phpBB/develop/export_events_for_wiki.php b/phpBB/develop/export_events_for_wiki.php index 128d6f3c570..cf8bc3c61ce 100644 --- a/phpBB/develop/export_events_for_wiki.php +++ b/phpBB/develop/export_events_for_wiki.php @@ -67,7 +67,8 @@ function validate_argument_count($arguments, $count) require __DIR__ . '/../phpbb/event/md_exporter.' . $phpEx; require __DIR__ . '/../includes/functions.' . $phpEx; require __DIR__ . '/../phpbb/event/recursive_event_filter_iterator.' . $phpEx; -require __DIR__ . '/../phpbb/iterator/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_dot_prefix_filter_iterator.' . $phpEx; +require __DIR__ . '/../phpbb/finder/recursive_path_iterator.' . $phpEx; switch ($action) { From d499850532caa5f168e4109754271608c5d9013a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 2 Jun 2024 21:35:16 +0200 Subject: [PATCH 0176/1214] [ticket/17321] Add versioning to push worker to force updates PHPBB-17321 --- phpBB/phpbb/notification/method/webpush.php | 1 + phpBB/phpbb/ucp/controller/webpush.php | 1 + phpBB/styles/all/js/push_worker.js.twig | 22 ++++++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index e5b8910a074..271e23dffe8 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -210,6 +210,7 @@ protected function notify_using_webpush(): void $data = [ 'item_id' => $notification->item_id, 'type_id' => $notification->notification_type_id, + 'version' => $this->config['assets_version'], ]; $json_data = json_encode($data); diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index a7ebbffae8e..b60fa437b3b 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -138,6 +138,7 @@ public function worker(): Response // @todo: only work for logged in users, no anonymous & bot $content = $this->template->render('push_worker.js.twig', [ 'U_WEBPUSH_GET_NOTIFICATION' => $this->controller_helper->route('phpbb_ucp_push_get_notification_controller'), + 'ASSETS_VERSION' => $this->config['assets_version'], ]); $response = new Response($content); diff --git a/phpBB/styles/all/js/push_worker.js.twig b/phpBB/styles/all/js/push_worker.js.twig index 8d6ec3c6af2..f9905c3d0bf 100644 --- a/phpBB/styles/all/js/push_worker.js.twig +++ b/phpBB/styles/all/js/push_worker.js.twig @@ -1,3 +1,15 @@ +/** + * Event listener for install event + */ +self.addEventListener('install', () => { + // Call to ensure service worker is correctly updated + self.skipWaiting(); +}); + +self.addEventListener('activate', event => { + event.waitUntil(clients.claim()); +}); + /** * Event listener for push event */ @@ -7,17 +19,25 @@ self.addEventListener('push', event => { } let itemId = 0; - let typeId = 0; + let typeId = 0; + let notificationVersion = 5; try { const notificationData = event.data.json(); itemId = notificationData.item_id; typeId = notificationData.type_id; + notificationVersion = parseInt(notificationData.version, 10); } catch { self.registration.showNotification(event.data.text()); return; } const getNotificationUrl = '{{ U_WEBPUSH_GET_NOTIFICATION }}'; + const assetsVersion = parseInt('{{ ASSETS_VERSION }}', 10); + + // Force update if versions differ + if (assetsVersion !== notificationVersion) { + self.registration.update(); + } const formData = new FormData(); formData.append('item_id', itemId.toString(10)); From 86064cb054a831fc70927a805e747ee3ab31907b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 3 Jun 2024 17:11:50 +0200 Subject: [PATCH 0177/1214] [ticket/17321] Use self.clients in activate event listener PHPBB-17321 --- phpBB/styles/all/js/push_worker.js.twig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpBB/styles/all/js/push_worker.js.twig b/phpBB/styles/all/js/push_worker.js.twig index f9905c3d0bf..f2807cb43ab 100644 --- a/phpBB/styles/all/js/push_worker.js.twig +++ b/phpBB/styles/all/js/push_worker.js.twig @@ -6,8 +6,11 @@ self.addEventListener('install', () => { self.skipWaiting(); }); +/** + * Event listener for activate event + */ self.addEventListener('activate', event => { - event.waitUntil(clients.claim()); + event.waitUntil(self.clients.claim()); }); /** From 03e2222dd70c28bcf38bec42f8e383d8d862b94a Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 4 Jun 2024 00:28:22 +0700 Subject: [PATCH 0178/1214] [ticket/17151] Add core.build_config_template event @changed information The event location's function has been renamed from build_config_template() to phpbb_build_cfg_template(). PHPBB3-17151 --- phpBB/includes/functions_acp.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index d2cc39aec8b..646d2f34d5a 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -515,6 +515,7 @@ function phpbb_build_cfg_template(array $tpl_type, string $key, array|object &$n * @var array vars Array with the options for the config * @var array|string tpl The resulting html code we display * @since 3.1.0-a1 + * @changed 4.0.0-a1 The event location's function renamed from build_config_template() to phpbb_build_cfg_template() */ $vars = array('tpl_type', 'key', 'new', 'name', 'vars', 'tpl'); extract($phpbb_dispatcher->trigger_event('core.build_config_template', compact($vars))); From e35a6f7aa5616e9d3ffe46c22c10bda0f4d3bfb5 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 08:26:21 -0700 Subject: [PATCH 0179/1214] [ticket/17333] ACP option to enable-disable all push notifications PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/includes/acp/acp_board.php | 1 + phpBB/install/schemas/schema_data.sql | 1 + phpBB/language/en/acp/board.php | 2 + .../data/v400/add_webpush_options.php | 45 +++++++++++++++++++ phpBB/phpbb/notification/method/webpush.php | 8 ++++ 5 files changed, 57 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v400/add_webpush_options.php diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d0ced6788f3..8eaaa9fc883 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -494,6 +494,7 @@ function main($id, $mode) 'webpush_enable' => ['lang' => 'WEBPUSH_ENABLE', 'validate' => 'bool', 'type' => 'custom', 'method' => 'webpush_enable', 'explain' => true], 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], + 'webpush_method_enables' => ['lang' => 'WEBPUSH_METHOD_ENABLES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], 'legend3' => 'ACP_SUBMIT_CHANGES', ], diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 68a97decb79..f57a9f466bf 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -329,6 +329,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\backup\con INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_enable', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_public', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_private', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_enables', '0'); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index c57cefe6857..484d651c75e 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -590,6 +590,8 @@ 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key is shared to authenticate push messages from your site.
            Caution: Modifying the VAPID public key will automatically render all Web Push subscriptions invalid.', 'WEBPUSH_VAPID_PRIVATE' => 'Server identification private key', 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key is used to generate authenticated push messages dispatched from your site. The VAPID private key must form a valid public-private key pair alongside the VAPID public key.
            Caution: Modifying the VAPID private key will automatically render all Web Push subscriptions invalid.', + 'WEBPUSH_METHOD_ENABLES' => 'Enable all user-based web push notification options by default', + 'WEBPUSH_METHOD_ENABLES_EXPLAIN'=> 'When this setting is enabled, users who subscribe and allow browser notifications will start receiving them automatically. Users only need to visit the UCP Notification settings to disable any unwanted notifications.

            If this setting is disabled, users will not receive any notifications, even if they have subscribed, until they visit the UCP Notification settings to enable the specific notification options they wish to receive.', ]); // Jabber settings diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php new file mode 100644 index 00000000000..99ef6bd28f7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php @@ -0,0 +1,45 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class add_webpush_options extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\add_webpush', + ]; + } + + public function effectively_installed(): bool + { + return $this->config->offsetExists('webpush_method_enables'); + } + + public function update_data(): array + { + return [ + ['config.add', ['webpush_method_enables', false]], + ]; + } + + public function revert_data(): array + { + return [ + ['config.remove', ['webpush_method_enables']], + ]; + } +} diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index e5b8910a074..e1fc1f04eea 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -91,6 +91,14 @@ public function is_available(type_interface $notification_type = null): bool && !empty($this->config['webpush_vapid_public']) && !empty($this->config['webpush_vapid_private']); } + /** + * {@inheritDoc} + */ + public function is_enabled_by_default() + { + return $this->config['webpush_method_enables']; + } + /** * {@inheritdoc} */ From d55ec608eceabe0b028ac33082545b2d205ff5a8 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 09:34:02 -0700 Subject: [PATCH 0180/1214] [ticket/17333] Add push subscribe toggle to notification dropdown PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/includes/acp/acp_board.php | 1 + phpBB/includes/functions.php | 16 ++++++++++++++-- phpBB/install/schemas/schema_data.sql | 1 + phpBB/language/en/acp/board.php | 2 ++ phpBB/language/en/common.php | 3 +++ .../migration/data/v400/add_webpush_options.php | 4 +++- phpBB/phpbb/notification/method/webpush.php | 2 +- .../template/notification_dropdown.html | 7 +++++++ .../prosilver/template/overall_header.html | 4 ++++ .../template/ucp_notifications_options.html | 4 ---- phpBB/styles/prosilver/theme/buttons.css | 6 ++++++ phpBB/styles/prosilver/theme/common.css | 9 +++++++++ 12 files changed, 51 insertions(+), 8 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 8eaaa9fc883..53faa4bc7de 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -495,6 +495,7 @@ function main($id, $mode) 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], 'webpush_method_enables' => ['lang' => 'WEBPUSH_METHOD_ENABLES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], + 'webpush_dropdown_subscribe'=> ['lang' => 'WEBPUSH_DROPDOWN_SUBSCRIBE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], 'legend3' => 'ACP_SUBMIT_CHANGES', ], diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index bca9a97c48c..fdc2d42ab9a 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3853,6 +3853,9 @@ function page_header($page_title = '', $display_online_list = false, $item_id = $timezone_name = $user->lang['timezones'][$timezone_name]; } + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + // Output the notifications $notifications = false; if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE) @@ -3869,10 +3872,19 @@ function page_header($page_title = '', $display_online_list = false, $item_id = { $template->assign_block_vars('notifications', $notification->prepare_for_display()); } + + // Get web push notification data + $methods = $phpbb_notifications->get_subscription_methods(); + if ($config['webpush_dropdown_subscribe'] && array_key_exists('notification.method.webpush', $methods)) + { + /** @var \phpbb\form\form_helper $form_helper */ + $form_helper = $phpbb_container->get('form_helper'); + + $template_ary = $methods['notification.method.webpush']['method']->get_ucp_template_data($controller_helper, $form_helper); + $template->assign_vars($template_ary); + } } - /** @var \phpbb\controller\helper $controller_helper */ - $controller_helper = $phpbb_container->get('controller.helper'); $notification_mark_hash = generate_link_hash('mark_all_notifications_read'); $phpbb_version_parts = explode('.', PHPBB_VERSION, 3); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index f57a9f466bf..9cabeb3e277 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -330,6 +330,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_enable', ' INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_public', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_private', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_enables', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_dropdown_subscribe', '0'); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 484d651c75e..3353a360e8a 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -592,6 +592,8 @@ 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key is used to generate authenticated push messages dispatched from your site. The VAPID private key must form a valid public-private key pair alongside the VAPID public key.
            Caution: Modifying the VAPID private key will automatically render all Web Push subscriptions invalid.', 'WEBPUSH_METHOD_ENABLES' => 'Enable all user-based web push notification options by default', 'WEBPUSH_METHOD_ENABLES_EXPLAIN'=> 'When this setting is enabled, users who subscribe and allow browser notifications will start receiving them automatically. Users only need to visit the UCP Notification settings to disable any unwanted notifications.

            If this setting is disabled, users will not receive any notifications, even if they have subscribed, until they visit the UCP Notification settings to enable the specific notification options they wish to receive.', + 'WEBPUSH_DROPDOWN_SUBSCRIBE' => 'Show “Subscribe” button in notification dropdown', + 'WEBPUSH_DROPDOWN_SUBSCRIBE_EXPLAIN'=> 'Display a “Subscribe” button in the Notification dropdown, allowing users to easily subscribe to push notifications from anywhere in the forum.', ]); // Jabber settings diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 71790e499b1..5933cc8f5cf 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -512,6 +512,9 @@ ), 'NOTIFY_ADMIN' => 'Please notify the board administrator or webmaster.', 'NOTIFY_ADMIN_EMAIL' => 'Please notify the board administrator or webmaster: %1$s', + 'NOTIFY_WEB_PUSH_ENABLE' => 'Enable Web Push notifications', + 'NOTIFY_WEB_PUSH_SUBSCRIBE' => 'Subscribe', + 'NOTIFY_WEB_PUSH_SUBSCRIBED'=> 'Subscribed', 'NO_ACCESS_ATTACHMENT' => 'You are not allowed to access this file.', 'NO_ACTION' => 'No action specified.', 'NO_ADMINISTRATORS' => 'There are no administrators.', diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php index 99ef6bd28f7..d7478edfeaf 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php @@ -26,13 +26,14 @@ public static function depends_on(): array public function effectively_installed(): bool { - return $this->config->offsetExists('webpush_method_enables'); + return $this->config->offsetExists('webpush_method_enables') || $this->config->offsetExists('webpush_dropdown_subscribe'); } public function update_data(): array { return [ ['config.add', ['webpush_method_enables', false]], + ['config.add', ['webpush_dropdown_subscribe', false]], ]; } @@ -40,6 +41,7 @@ public function revert_data(): array { return [ ['config.remove', ['webpush_method_enables']], + ['config.remove', ['webpush_dropdown_subscribe']], ]; } } diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index e1fc1f04eea..dcf1d1ebbf8 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -355,7 +355,7 @@ public function get_ucp_template_data(helper $controller_helper, form_helper $fo } return [ - 'NOTIFICATIONS_WEBPUSH_ENABLE' => true, + 'NOTIFICATIONS_WEBPUSH_ENABLE' => $this->config['webpush_dropdown_subscribe'] || stripos($this->user->page['page'], 'notification_options'), 'U_WEBPUSH_SUBSCRIBE' => $controller_helper->route('phpbb_ucp_push_subscribe_controller'), 'U_WEBPUSH_UNSUBSCRIBE' => $controller_helper->route('phpbb_ucp_push_unsubscribe_controller'), 'VAPID_PUBLIC_KEY' => $this->config['webpush_vapid_public'], diff --git a/phpBB/styles/prosilver/template/notification_dropdown.html b/phpBB/styles/prosilver/template/notification_dropdown.html index 52bc5469766..841f5417444 100644 --- a/phpBB/styles/prosilver/template/notification_dropdown.html +++ b/phpBB/styles/prosilver/template/notification_dropdown.html @@ -44,6 +44,13 @@ + {% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %} + + {% endif %} {% EVENT notification_dropdown_footer_after %}
          diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index b88de2aaa87..790f793d8fb 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -63,6 +63,10 @@ +{% if NOTIFICATIONS_WEBPUSH_ENABLE %} + {% include('ucp_notifications_webpush.html') %} +{% endif %} + diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html index a0d9caad12e..b3be983f169 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications_options.html +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -1,9 +1,5 @@ {% include('ucp_header.html') %} -{% if NOTIFICATIONS_WEBPUSH_ENABLE %} - {% include('ucp_notifications_webpush.html') %} -{% endif %} -

          {{ TITLE }}

          diff --git a/phpBB/styles/prosilver/theme/buttons.css b/phpBB/styles/prosilver/theme/buttons.css index e8f8fbe961a..fda72c86138 100644 --- a/phpBB/styles/prosilver/theme/buttons.css +++ b/phpBB/styles/prosilver/theme/buttons.css @@ -220,3 +220,9 @@ button::-moz-focus-inner { .avatar-cropper-buttons > .button-group { margin: 4px; } + +/* Notification buttons +--------------------------------------------- */ +.notification-subscribe_toggle:disabled { + opacity: 0.7; +} diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 240ba25d039..2300f9c611a 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -1340,6 +1340,15 @@ ul.linklist:after, display: block; } +.dropdown-extended .notification-dropdown-footer { + white-space: nowrap; + border-top: solid 1px #b9b9b9; + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + padding: 5px 10px; +} + .notification-avatar, .notification-menu .notification-list .notification-item .avatar, .notification-menu .notification-list .notification-item .gravatar { From 4afba5c9feec00a375a0ab86f05bc8aa0ba620ce Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 10:15:54 -0700 Subject: [PATCH 0181/1214] [ticket/17333] Enable new configs by default PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/install/schemas/schema_data.sql | 4 ++-- phpBB/phpbb/db/migration/data/v400/add_webpush_options.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 9cabeb3e277..7756586c706 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -329,8 +329,8 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\backup\con INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_enable', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_public', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_private', ''); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_enables', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_dropdown_subscribe', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_enables', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_dropdown_subscribe', '1'); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1); diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php index d7478edfeaf..1a1727b2568 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php @@ -32,8 +32,8 @@ public function effectively_installed(): bool public function update_data(): array { return [ - ['config.add', ['webpush_method_enables', false]], - ['config.add', ['webpush_dropdown_subscribe', false]], + ['config.add', ['webpush_method_enables', true]], + ['config.add', ['webpush_dropdown_subscribe', true]], ]; } From ef4db99709a650df0a0c24ebd7419f6963b229a7 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 5 Jun 2024 00:20:49 +0700 Subject: [PATCH 0182/1214] [ticket/17332] Fix permission migrator tool to work with copied permissions PHPBB-17332 --- phpBB/phpbb/db/migration/tool/permission.php | 61 +++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/phpBB/phpbb/db/migration/tool/permission.php b/phpBB/phpbb/db/migration/tool/permission.php index 66ca2ed0ee6..a0ae3ceddf2 100644 --- a/phpBB/phpbb/db/migration/tool/permission.php +++ b/phpBB/phpbb/db/migration/tool/permission.php @@ -435,15 +435,14 @@ public function permission_set($name, $auth_option, $type = 'role', $has_permiss } $this->db->sql_freeresult($result); - if (empty($new_auth)) + $type = (string) $type; // Prevent PHP bug. + if (empty($new_auth) || !in_array($type, ['role','group'])) { return; } $current_auth = array(); - $type = (string) $type; // Prevent PHP bug. - switch ($type) { case 'role': @@ -525,40 +524,32 @@ function ($option) use ($role_type) break; } - $sql_ary = array(); - switch ($type) + $sql_ary = $auth_update_list = []; + $table = $type == 'role' ? ACL_ROLES_DATA_TABLE : ACL_GROUPS_TABLE; + foreach ($new_auth as $auth_option_id) { - case 'role': - foreach ($new_auth as $auth_option_id) - { - if (!isset($current_auth[$auth_option_id])) - { - $sql_ary[] = array( - 'role_id' => $role_id, - 'auth_option_id' => $auth_option_id, - 'auth_setting' => $has_permission, - ); - } - } - - $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); - break; - - case 'group': - foreach ($new_auth as $auth_option_id) - { - if (!isset($current_auth[$auth_option_id])) - { - $sql_ary[] = array( - 'group_id' => $group_id, - 'auth_option_id' => $auth_option_id, - 'auth_setting' => $has_permission, - ); - } - } + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = [ + $type . '_id' => ${$type . '_id'}, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => (int) $has_permission, + ]; + } + else + { + $auth_update_list[] = $auth_option_id; + } + } + $this->db->sql_multi_insert($table, $sql_ary); - $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); - break; + if (count($auth_update_list)) + { + $sql = 'UPDATE ' . $table . ' + SET auth_setting = ' . (int) $has_permission . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $auth_update_list) . ' + AND ' . $type . '_id = ' . (int) ${$type . '_id'}; + $this->db->sql_query($sql); } $this->auth->acl_clear_prefetch(); From e162b67c48b0a84d8f388f5e705ffb95191554f3 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 10:25:35 -0700 Subject: [PATCH 0183/1214] [ticket/17333] Fix toggle icon colors PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/styles/prosilver/template/notification_dropdown.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/prosilver/template/notification_dropdown.html b/phpBB/styles/prosilver/template/notification_dropdown.html index 841f5417444..ff52543d0a7 100644 --- a/phpBB/styles/prosilver/template/notification_dropdown.html +++ b/phpBB/styles/prosilver/template/notification_dropdown.html @@ -47,8 +47,8 @@ {% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %} {% endif %} {% EVENT notification_dropdown_footer_after %} From 5c58fd2055c2cb137af9f1759712fbff2eff81e9 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 11:10:44 -0700 Subject: [PATCH 0184/1214] [ticket/17333] Add tests and fixes PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/phpbb/notification/method/webpush.php | 2 +- .../functional/notification_webpush_test.php | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tests/functional/notification_webpush_test.php diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index dcf1d1ebbf8..a823aed6a07 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -96,7 +96,7 @@ public function is_available(type_interface $notification_type = null): bool */ public function is_enabled_by_default() { - return $this->config['webpush_method_enables']; + return (bool) $this->config['webpush_method_enables']; } /** diff --git a/tests/functional/notification_webpush_test.php b/tests/functional/notification_webpush_test.php new file mode 100644 index 00000000000..a07e0a89486 --- /dev/null +++ b/tests/functional/notification_webpush_test.php @@ -0,0 +1,127 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @group functional +*/ +class phpbb_functional_notification_webpush_test extends phpbb_functional_test_case +{ + public function test_acp_module() + { + $this->login(); + $this->admin_login(); + + $this->add_lang(['acp/board', 'acp/common']); + + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=webpush&sid=' . $this->sid); + + $this->assertContainsLang('ACP_WEBPUSH_SETTINGS', $crawler->filter('div.main > h1')->text()); + $this->assertContainsLang('ACP_WEBPUSH_SETTINGS_EXPLAIN', $crawler->filter('div.main > p')->text()); + $this->assertContainsLang('WEBPUSH_GENERATE_VAPID_KEYS', $crawler->filter('input[type="button"]')->attr('value')); + + $form_data = [ + 'config[webpush_enable]' => 1, + 'config[webpush_vapid_public]' => 'BDnYSJHVZBxq834LqDGr893IfazEez7q-jYH2QBNlT0ji2C9UwGosiqz8Dp_ZN23lqAngBZyRjXVWF4ZLA8X2zI', + 'config[webpush_vapid_private]' => 'IE5OYlmfWsMbBU1lzvr0bxrxVAXIteSkAnwGlZIhmRk', + 'config[webpush_method_enables]' => 1, + 'config[webpush_dropdown_subscribe]' => 1, + ]; + $form = $crawler->selectButton('submit')->form($form_data); + $crawler = self::submit($form); + $this->assertStringContainsString($this->lang('CONFIG_UPDATED'), $crawler->filter('.successbox')->text()); + + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=webpush&sid=' . $this->sid); + + foreach ($form_data as $config_name => $config_value) + { + $config_value = ($config_name === 'config[webpush_vapid_private]') ? '********' : $config_value; + $this->assertEquals($config_value, $crawler->filter('input[name="' . $config_name . '"]')->attr('value')); + } + } + + public function test_ucp_module() + { + $this->login(); + $this->admin_login(); + + $this->add_lang('ucp'); + + $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); + + $this->assertContainsLang('NOTIFY_WEBPUSH_ENABLE', $crawler->filter('label[for="subscribe_webpush"]')->text()); + $this->assertContainsLang('NOTIFICATION_METHOD_WEBPUSH', $crawler->filter('th.mark')->eq(2)->text()); + + // Assert checkbox is checked + $wp_list = $crawler->filter('.table1'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.bookmark_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.mention_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.post_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.quote_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.topic_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.forum_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.group_request_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.pm_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.report_pm_closed_notification.method.webpush'); + $this->assert_checkbox_is_checked($wp_list, 'notification.type.report_post_closed_notification.method.webpush'); + + $this->set_acp_option('webpush_method_enables', 0); + + $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); + + // Assert checkbox is unchecked + $wp_list = $crawler->filter('.table1'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.bookmark_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.mention_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.post_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.quote_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.topic_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.forum_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.group_request_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.pm_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.report_pm_closed_notification.method.webpush'); + $this->assert_checkbox_is_unchecked($wp_list, 'notification.type.report_post_closed_notification.method.webpush'); + } + + public function test_dropdown_subscribe_button() + { + $this->login(); + $this->admin_login(); + + // Assert subscribe dropdown is present + $crawler = self::request('GET', 'index.php'); + $this->assertCount(1, $crawler->filter('.notification-dropdown-footer')); + $this->assertContainsLang('NOTIFY_WEB_PUSH_SUBSCRIBE', $crawler->filter('.notification-dropdown-footer #subscribe_webpush')->text()); + $this->assertContainsLang('NOTIFY_WEB_PUSH_SUBSCRIBED', $crawler->filter('.notification-dropdown-footer #unsubscribe_webpush')->text()); + + // Assert subscribe button is not displayed in UCP when dropdown subscribe is present + $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); + $this->assertCount(0, $crawler->filter('.notification-dropdown-footer')); + + $this->set_acp_option('webpush_dropdown_subscribe', 0); + + // Assert subscribe dropdown is not present by default + $crawler = self::request('GET', 'index.php'); + $this->assertCount(0, $crawler->filter('.notification-dropdown-footer')); + } + + protected function set_acp_option($option, $value) + { + $crawler = self::request('GET', 'adm/index.php?i=acp_board&mode=webpush&sid=' . $this->sid); + $form = $crawler->selectButton('Submit')->form(); + $values = $form->getValues(); + $values["config[{$option}]"] = $value; + $form->setValues($values); + $crawler = self::submit($form); + $this->assertEquals(1, $crawler->filter('.successbox')->count()); + } +} From 0ca85246331043bdf031ec7d8b9340b19c91abbb Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 12:29:42 -0700 Subject: [PATCH 0185/1214] [ticket/17333] Fix Subscribe text button color PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/styles/prosilver/theme/colours.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 7bdd5f4b87f..e803a3f6cf3 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -1179,3 +1179,7 @@ input.disabled { background-color: #d41142; color: #ffffff; } + +.notification-subscribe_toggle { + color: #47536b; +} From 73e0412c88b38cd4a47b30202d93f3de41db9059 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 4 Jun 2024 14:19:37 -0700 Subject: [PATCH 0186/1214] [ticket/17333] Prevent double loading web push template data PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/includes/functions.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index fdc2d42ab9a..8228b29e374 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3873,15 +3873,17 @@ function page_header($page_title = '', $display_online_list = false, $item_id = $template->assign_block_vars('notifications', $notification->prepare_for_display()); } - // Get web push notification data - $methods = $phpbb_notifications->get_subscription_methods(); - if ($config['webpush_dropdown_subscribe'] && array_key_exists('notification.method.webpush', $methods)) + // Assign notification web push template data (if not done already by ucp_notifications) + if ($config['webpush_dropdown_subscribe'] && $template->retrieve_var('NOTIFICATIONS_WEBPUSH_ENABLE') === null) { - /** @var \phpbb\form\form_helper $form_helper */ - $form_helper = $phpbb_container->get('form_helper'); + $methods = $phpbb_notifications->get_subscription_methods(); + if (isset($methods['notification.method.webpush'])) + { + $form_helper = $phpbb_container->get('form_helper'); - $template_ary = $methods['notification.method.webpush']['method']->get_ucp_template_data($controller_helper, $form_helper); - $template->assign_vars($template_ary); + $template_ary = $methods['notification.method.webpush']['method']->get_ucp_template_data($controller_helper, $form_helper); + $template->assign_vars($template_ary); + } } } From 51b773e588da76dc1070cdb9c34f3825dbc49a75 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 5 Jun 2024 12:38:58 +0700 Subject: [PATCH 0187/1214] [ticket/17332] Add test PHPBB-17332 --- .../migrator_tool_permission_role_test.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/dbal/migrator_tool_permission_role_test.php b/tests/dbal/migrator_tool_permission_role_test.php index 55bda8c0ce0..dc180dc1542 100644 --- a/tests/dbal/migrator_tool_permission_role_test.php +++ b/tests/dbal/migrator_tool_permission_role_test.php @@ -28,6 +28,12 @@ class phpbb_dbal_migrator_tool_permission_role_test extends phpbb_database_test_ 'ADMINISTRATORS' => 5, ]; + public $role_ids = [ + 'ROLE_ADMIN_STANDARD' => 1, + 'ROLE_USER_FULL' => 5, + 'ROLE_MOD_FULL' => 10, + ]; + public $new_roles = [ [ 'ROLE_ADMIN_NEW', @@ -196,4 +202,32 @@ public function test_permission_new_role_remove($ug_type, $forum_id, $group_name $this->assertFalse($this->db->sql_fetchfield('auth_role_id')); $this->db->sql_freeresult($result); } + + public function test_copied_permission_set() + { + $sql = 'SELECT rdt.auth_setting + FROM ' . ACL_OPTIONS_TABLE. ' ot, ' . ACL_ROLES_DATA_TABLE . ' rdt + WHERE rdt.role_id = ' . $this->role_ids['ROLE_ADMIN_STANDARD'] . " + AND auth_option = 'u_copied_permission' + AND ot.auth_option_id = rdt.auth_option_id"; + + // Add new local 'u_copied_permission' copied from 'u_test' + // It should be added to the ROLE_ADMIN_STANDARD role automatically similar to 'u_test' permission + $this->tool->add('u_copied_permission', false, 'u_test'); + $this->assertEquals(true, $this->tool->exists('u_copied_permission', false)); + + // Copied permission setting should be equal to what it was copied from + $result = $this->db->sql_query($sql); + $this->assertEquals(0, $this->db->sql_fetchfield('auth_setting')); + $this->db->sql_freeresult($result); + + // Set new permission for copied auth option for the role + $this->tool->permission_set('ROLE_ADMIN_STANDARD', 'u_copied_permission', 'role', true); + + // Copied permission setting should be updated + $result = $this->db->sql_query($sql); + $this->assertEquals(1, $this->db->sql_fetchfield('auth_setting')); + $this->db->sql_freeresult($result); + } + } From a90c59a8a17fc545082e0cf1ddba563344c95551 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 5 Jun 2024 22:27:06 +0200 Subject: [PATCH 0188/1214] [ticket/17330] Handle mozilla not properly handling webpush padding PHPBB-17330 --- phpBB/phpbb/notification/method/webpush.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 271e23dffe8..2ef52df5201 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -48,6 +48,9 @@ class webpush extends messenger_base implements extended_method_interface /** @var string Notification push subscriptions table */ protected $push_subscriptions_table; + /** @var int Fallback size for padding if endpoint is mozilla, see https://github.com/web-push-libs/web-push-php/issues/108#issuecomment-2133477054 */ + const MOZILLA_FALLBACK_PADDING = 2820; + /** * Notification Method Web Push constructor * @@ -218,6 +221,7 @@ protected function notify_using_webpush(): void { try { + $this->set_endpoint_padding($web_push, $subscription['endpoint']); $push_subscription = Subscription::create([ 'endpoint' => $subscription['endpoint'], 'keys' => [ @@ -430,4 +434,21 @@ protected function clean_expired_subscriptions(array $user_subscription_map, arr $this->remove_subscriptions($remove_subscriptions); } + + /** + * Set web push padding for endpoint + * + * @param \Minishlink\WebPush\WebPush $web_push + * @param string $endpoint + * + * @return void + * @throws \Exception + */ + protected function set_endpoint_padding(\Minishlink\WebPush\WebPush $web_push, string $endpoint): void + { + if (str_contains($endpoint, 'mozilla.com') || str_contains($endpoint, 'mozaws.net')) + { + $web_push->setAutomaticPadding(self::MOZILLA_FALLBACK_PADDING); + } + } } From 36527a97b8ddc60b0dffea4d1ad53bc47a0afb1f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 5 Jun 2024 18:47:57 -0700 Subject: [PATCH 0189/1214] [ticket/17333] Optimize loading web push data only when needed PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/includes/functions.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 8228b29e374..da22c4739d7 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3873,15 +3873,18 @@ function page_header($page_title = '', $display_online_list = false, $item_id = $template->assign_block_vars('notifications', $notification->prepare_for_display()); } - // Assign notification web push template data (if not done already by ucp_notifications) - if ($config['webpush_dropdown_subscribe'] && $template->retrieve_var('NOTIFICATIONS_WEBPUSH_ENABLE') === null) + // Assign web push template vars globally (if not done already by ucp_notifications) for the dropdown subscribe button + if ($config['webpush_enable'] + && $config['webpush_dropdown_subscribe'] + && $template->retrieve_var('NOTIFICATIONS_WEBPUSH_ENABLE') === null) { $methods = $phpbb_notifications->get_subscription_methods(); - if (isset($methods['notification.method.webpush'])) - { - $form_helper = $phpbb_container->get('form_helper'); + $webpush = $methods['notification.method.webpush'] ?? null; - $template_ary = $methods['notification.method.webpush']['method']->get_ucp_template_data($controller_helper, $form_helper); + if ($webpush) + { + $formHelper = $phpbb_container->get('form_helper'); + $template_ary = $webpush['method']->get_ucp_template_data($controller_helper, $formHelper); $template->assign_vars($template_ary); } } From e813bd02c608e6aacc5a4a0279930129896271d6 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 6 Jun 2024 20:07:40 +0200 Subject: [PATCH 0190/1214] [ticket/17330] Add test for set_endpoint_padding PHPBB-17330 --- .../notification_method_webpush_test.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index ff95c74b093..1116a62c6f3 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -653,6 +653,47 @@ public function test_prune_notifications($notification_type, $post_data, $expect $this->assertCount(0, $cur_notifications, 'Assert that no notifications have been pruned'); } + public function data_set_endpoint_padding(): array + { + return [ + [ + 'foo.mozilla.com', + webpush::MOZILLA_FALLBACK_PADDING + ], + [ + 'foo.mozaws.net', + webpush::MOZILLA_FALLBACK_PADDING + ], + [ + 'foo.android.googleapis.com', + \Minishlink\WebPush\Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH, + ], + ]; + } + + /** + * @dataProvider data_set_endpoint_padding + */ + public function test_set_endpoint_padding($endpoint, $expected_padding): void + { + $web_push_reflection = new \ReflectionMethod($this->notification_method_webpush, 'set_endpoint_padding'); + $web_push_reflection->setAccessible(true); + + $auth = [ + 'VAPID' => [ + 'subject' => generate_board_url(), + 'publicKey' => $this->config['webpush_vapid_public'], + 'privateKey' => $this->config['webpush_vapid_private'], + ], + ]; + + $web_push = new \Minishlink\WebPush\WebPush($auth); + + $this->assertEquals(\Minishlink\WebPush\Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH, $web_push->getAutomaticPadding()); + $web_push_reflection->invoke($this->notification_method_webpush, $web_push, $endpoint); + $this->assertEquals($expected_padding, $web_push->getAutomaticPadding()); + } + protected function create_subscription_for_user($user_id, bool $invalidate_endpoint = false): array { $client = new \GuzzleHttp\Client(); From 35436f078c05fc6714cdf075172df6b4f1d0eea3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 6 Jun 2024 20:21:39 +0200 Subject: [PATCH 0191/1214] [ticket/17330] Add try/catch block though it's not really needed PHPBB-17330 --- phpBB/phpbb/notification/method/webpush.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 2ef52df5201..a5a3b5e6992 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -442,13 +442,19 @@ protected function clean_expired_subscriptions(array $user_subscription_map, arr * @param string $endpoint * * @return void - * @throws \Exception */ protected function set_endpoint_padding(\Minishlink\WebPush\WebPush $web_push, string $endpoint): void { if (str_contains($endpoint, 'mozilla.com') || str_contains($endpoint, 'mozaws.net')) { - $web_push->setAutomaticPadding(self::MOZILLA_FALLBACK_PADDING); + try + { + $web_push->setAutomaticPadding(self::MOZILLA_FALLBACK_PADDING); + } + catch (\Exception) + { + // This shouldn't happen since we won't pass padding length outside limits + } } } } From fd2e4592c3fc01c969c752d6da00f4ed7792c03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 25 Jun 2018 20:04:13 +0200 Subject: [PATCH 0192/1214] [ticket/15699] Move files between filesystems PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 9 ++++ phpBB/includes/acp/acp_storage.php | 72 ++++++++++++++++++++++++++++++ phpBB/language/en/acp/storage.php | 20 +++++---- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 3886f006d12..49535c1a74a 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -97,6 +97,15 @@

          {{ lang('WARNING') }}

          {% endfor %} {% endfor %} +
          +
          +

          {{ lang('STORAGE_REMOVE_OLD_FILES_EXPLAIN') }}
          +
          + +
          +
          +
          +
          {{ lang('SUBMIT') }}   diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 3ea1036167a..f9e0e75ae06 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -24,6 +24,9 @@ class acp_storage /** @var \phpbb\config\config $config */ protected $config; + /** @var \db\driver\driver_interface $db */ + protected $db; + /** @var \phpbb\language\language $lang */ protected $lang; @@ -33,6 +36,9 @@ class acp_storage /** @var \phpbb\template\template */ protected $template; + /** @var \phpbb\di\service_collection */ + protected $adapter_collection; + /** @var \phpbb\di\service_collection */ protected $provider_collection; @@ -63,10 +69,13 @@ public function main($id, $mode) global $phpbb_container, $phpbb_dispatcher, $phpbb_root_path; $this->config = $phpbb_container->get('config'); + $this->db = $phpbb_container->get('dbal.conn'); $this->filesystem = $phpbb_container->get('filesystem'); $this->lang = $phpbb_container->get('language'); $this->request = $phpbb_container->get('request'); $this->template = $phpbb_container->get('template'); + $this->user = $phpbb_container->get('user'); + $this->adapter_collection = $phpbb_container->get('storage.adapter_collection'); $this->provider_collection = $phpbb_container->get('storage.provider_collection'); $this->storage_collection = $phpbb_container->get('storage.storage_collection'); $this->phpbb_root_path = $phpbb_root_path; @@ -153,6 +162,31 @@ public function overview($id, $mode) { foreach ($modified_storages as $storage_name) { + $current_adapter = $this->get_current_adapter($storage_name); + $new_adapter = $this->get_new_adapter($storage_name); + + $sql = 'SELECT file_path + FROM ' . STORAGE_TABLE . " + WHERE storage = '" . $storage_name . "'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $stream = $current_adapter->read_stream($row['file_path']); + $new_adapter->write_stream($row['file_path'], $stream); + fclose($stream); + } + + $this->db->sql_rowseek(0, $result); + + if ($this->request->variable('remove_old', false)) + { + while ($row = $this->db->sql_fetchrow($result)) + { + $current_adapter->delete($row['file_path']); + } + } + $this->update_storage_config($storage_name); } @@ -375,4 +409,42 @@ protected function validate_path($storage_name, $options, &$messages) } } } + + protected function get_current_adapter($storage_name) + { + $provider = $this->get_current_provider($storage_name); + $provider_class = $this->provider_collection->get_by_class($provider); + + $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); + $definitions = $this->get_provider_options($provider); + + $options = []; + foreach (array_keys($definitions) as $definition) + { + $options[$definition] = $this->get_current_definition($storage_name, $definition); + } + + $adapter->configure($options); + + return $adapter; + } + + protected function get_new_adapter($storage_name) + { + $provider = $this->get_new_provider($storage_name); + $provider_class = $this->provider_collection->get_by_class($provider); + + $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); + $definitions = $this->get_provider_options($provider); + + $options = []; + foreach (array_keys($definitions) as $definition) + { + $options[$definition] = $this->get_new_definition($storage_name, $definition); + } + + $adapter->configure($options); + + return $adapter; + } } diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 98ad84df536..ae3c007c4a2 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -39,15 +39,17 @@ $lang = array_merge($lang, array( // Template - 'STORAGE_TITLE' => 'Storage Settings', - 'STORAGE_TITLE_EXPLAIN' => 'Change storage providers for the file storage types of phpBB. Choose local or remote providers to store files added to or created by phpBB.', - 'STORAGE_SELECT' => 'Select storage', - 'STORAGE_SELECT_DESC' => 'Select a storage from the list.', - 'STORAGE_NAME' => 'Storage name', - 'STORAGE_NUM_FILES' => 'Number of files', - 'STORAGE_SIZE' => 'Size', - 'STORAGE_FREE' => 'Available space', - 'STORAGE_UNKNOWN' => 'Unknown', + 'STORAGE_TITLE' => 'Storage Settings', + 'STORAGE_TITLE_EXPLAIN' => 'Change storage providers for the file storage types of phpBB. Choose local or remote providers to store files added to or created by phpBB.', + 'STORAGE_SELECT' => 'Select storage', + 'STORAGE_SELECT_DESC' => 'Select a storage from the list.', + 'STORAGE_NAME' => 'Storage name', + 'STORAGE_NUM_FILES' => 'Number of files', + 'STORAGE_SIZE' => 'Size', + 'STORAGE_FREE' => 'Available space', + 'STORAGE_UNKNOWN' => 'Unknown', + 'STORAGE_REMOVE_OLD_FILES' => 'Remove old files', + 'STORAGE_REMOVE_OLD_FILES_EXPLAIN' => 'Remove old files after they are copied to the new storage system.', // Storage names 'STORAGE_ATTACHMENT_TITLE' => 'Attachments storage', From 5e8bc99697e430def79d608a8d16b5c4b83c9437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 2 Jul 2018 03:34:14 +0200 Subject: [PATCH 0193/1214] [ticket/15699] Move line PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index f9e0e75ae06..8a027e4b071 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -177,10 +177,10 @@ public function overview($id, $mode) fclose($stream); } - $this->db->sql_rowseek(0, $result); - if ($this->request->variable('remove_old', false)) { + $this->db->sql_rowseek(0, $result); + while ($row = $this->db->sql_fetchrow($result)) { $current_adapter->delete($row['file_path']); From 8f7e5fec0b29428f09a047b98bf8770f5d14fcf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Wed, 18 Jul 2018 14:00:52 +0200 Subject: [PATCH 0194/1214] [ticket/15699] Update acp_storage PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 8a027e4b071..50d18c251e6 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -91,14 +91,17 @@ public function main($id, $mode) */ $phpbb_dispatcher->trigger_event('core.acp_storage_load'); - $this->overview($id, $mode); + @ini_set('memory_limit', '128M'); + + switch ($mode) + { + case 'settings': + $this->settings($id, $mode); + break; + } } - /** - * @param string $id - * @param string $mode - */ - public function overview($id, $mode) + public function settings($id, $mode) { $form_key = 'acp_storage'; add_form_key($form_key); From 683c99086cde66c8994803178012f17cee6bd803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Tue, 24 Jul 2018 11:34:47 +0200 Subject: [PATCH 0195/1214] [ticket/15699] Move file between storages with progress bar PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 140 +++++++----- phpBB/includes/acp/acp_storage.php | 347 ++++++++++++++++++++++------- phpBB/language/en/acp/common.php | 2 + phpBB/language/en/acp/storage.php | 10 + 4 files changed, 365 insertions(+), 134 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 49535c1a74a..999d5622ea2 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -4,59 +4,83 @@

          {{ lang('STORAGE_TITLE') }}

          -

          {{ lang('STORAGE_TITLE_EXPLAIN') }}

          + + - - - - - - - - - - - {% for storage in STORAGE_STATS %} - - - - - - - {% endfor %} - -
          {{ lang('STORAGE_NAME') }}{{ lang('STORAGE_NUM_FILES') }}{{ lang('STORAGE_SIZE') }}{{ lang('STORAGE_FREE') }}
          {{ storage.name }}{{ storage.files }}{{ storage.size }}{{ storage.free_space }}
          +

          {L_CONTINUE_EXPLAIN}

          -{% if S_ERROR %} + +
          + {L_SUBMIT} +   + + {S_FORM_TOKEN} +
          + + + +

          {{ lang('STORAGE_TITLE_EXPLAIN') }}

          + + + + + + + + + + + + {% for storage in STORAGE_STATS %} + + + + + + + {% endfor %} + +
          {{ lang('STORAGE_NAME') }}{{ lang('STORAGE_NUM_FILES') }}{{ lang('STORAGE_SIZE') }}{{ lang('STORAGE_FREE') }}
          {{ storage.name }}{{ storage.files }}{{ storage.size }}{{ storage.free_space }}
          + + {% if S_ERROR %}

          {{ lang('WARNING') }}

          {{ ERROR_MSG }}

          -{% endif %} + {% endif %} -
          + - {% for storage in STORAGES %} -
          - {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} -
          -

          {{ lang('STORAGE_SELECT_DESC') }}
          -
          - + {% for provider in PROVIDERS if provider.is_available %} - {% endif %} - {% endfor %} - -
          -
          -
          + {% endfor %} + + + +
          - {% for provider in PROVIDERS %} - {% if provider.is_available %} + {% for provider in PROVIDERS if provider.is_available %}
          {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} - {{ lang('STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_NAME') }} {% for name, options in provider.get_options %} @@ -93,25 +117,25 @@

          {{ lang('WARNING') }}

          {% endfor %}
          - {% endif %} + {% endfor %} {% endfor %} - {% endfor %} -
          -
          -

          {{ lang('STORAGE_REMOVE_OLD_FILES_EXPLAIN') }}
          -
          - -
          -
          -
          +
          +
          +

          {{ lang('STORAGE_REMOVE_OLD_FILES_EXPLAIN') }}
          +
          + +
          +
          +
          -
          - {{ lang('SUBMIT') }} -   - - {{ S_FORM_TOKEN }} -
          - +
          + {{ lang('SUBMIT') }} +   + + {{ S_FORM_TOKEN }} +
          + + {% include 'overall_footer.html' %} diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 50d18c251e6..af7141dc927 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -24,11 +24,14 @@ class acp_storage /** @var \phpbb\config\config $config */ protected $config; + /** @var \phpbb\db_text $config_text */ + protected $config_text; + /** @var \db\driver\driver_interface $db */ protected $db; - /** @var \phpbb\language\language $lang */ - protected $lang; + /** @var \phpbb\path_helper $path_helper */ + protected $path_helper; /** @var \phpbb\request\request */ protected $request; @@ -60,6 +63,9 @@ class acp_storage /** @var string */ public $u_action; + /** @var mixed */ + protected $state; + /** * @param string $id * @param string $mode @@ -69,9 +75,10 @@ public function main($id, $mode) global $phpbb_container, $phpbb_dispatcher, $phpbb_root_path; $this->config = $phpbb_container->get('config'); + $this->config_text = $phpbb_container->get('config_text'); $this->db = $phpbb_container->get('dbal.conn'); $this->filesystem = $phpbb_container->get('filesystem'); - $this->lang = $phpbb_container->get('language'); + $this->path_helper = $phpbb_container->get('path_helper'); $this->request = $phpbb_container->get('request'); $this->template = $phpbb_container->get('template'); $this->user = $phpbb_container->get('user'); @@ -81,7 +88,7 @@ public function main($id, $mode) $this->phpbb_root_path = $phpbb_root_path; // Add necesary language files - $this->lang->add_lang(['acp/storage']); + $this->user->add_lang(['acp/storage']); /** * Add language strings @@ -112,14 +119,172 @@ public function settings($id, $mode) // Set page title $this->page_title = 'STORAGE_TITLE'; + $action = $this->request->variable('action', ''); + $this->load_state(); + + // If user cancelled to continue, remove state + if ($this->request->is_set_post('cancel', false)) + { + if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + { + trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($this->request->variable('cancel', false)) + { + $action = ''; + $this->state = false; + $this->save_state(); + } + } + + if ($action) + { + switch ($action) + { + case 'progress_bar': + $this->display_progress_bar(); + break; + case 'update': + // Just continue + break; + default: + trigger_error('NO_ACTION', E_USER_ERROR); + break; + } + + if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + { + trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } + + // TODO: If both providers are the same, and remove + // old files is checked, the files could be only moved + + // Copy files from the old to the new storage + $i = 0; + foreach ($this->state['storages'] as $storage_name => $storage_options) + { + // Skip storages that have already moved files + if ($this->state['storage_index'] > $i++) + { + continue; + } + + $current_adapter = $this->get_current_adapter($storage_name); + $new_adapter = $this->get_new_adapter($storage_name); + + $sql = 'SELECT file_id, file_path + FROM ' . STORAGE_TABLE . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state['file_index']; + $result = $this->db->sql_query($sql); + + $starttime = microtime(true); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!still_on_time()) + { + $this->save_state(); + meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); + trigger_error($this->user->lang('STORAGE_UPDATE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + } + + $stream = $current_adapter->read_stream($row['file_path']); + $new_adapter->write_stream($row['file_path'], $stream); + fclose($stream); + + $this->state['file_index'] = $row['file_id']; // Set last uploaded file + } + + // Copied all files of a storage, increase storage index and reset file index + $this->state['storage_index']++; + $this->state['file_index'] = 0; + } + + if ($this->state['remove_old']) + { + $i = 0; + foreach ($this->state['storages'] as $storage_name => $storage_options) + { + // Skip storages that have already moved files + if ($this->state['remove_storage_index'] > $i++) + { + continue; + } + + $current_adapter = $this->get_current_adapter($storage_name); + + $sql = 'SELECT file_id, file_path + FROM ' . STORAGE_TABLE . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state['file_index']; + $result = $this->db->sql_query($sql); + + $starttime = microtime(true); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!still_on_time()) + { + $this->save_state(); + meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); + trigger_error($this->user->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + } + + $current_adapter->delete($row['file_path']); + + $this->state['file_index'] = $row['file_id']; // Set last uploaded file + } + + // Remove all files of a storage, increase storage index and reset file index + $this->state['remove_storage_index']++; + $this->state['file_index'] = 0; + } + } + + // Here all files have been copied/moved, so save new configuration + foreach (array_keys($this->state['storages']) as $storage_name) + { + $this->update_storage_config($storage_name); + } + + $this->state = false; + $this->save_state(); + + //$phpbb_log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false); // todo + trigger_error($this->user->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); + } + + // There is an updating in progress, show the form to continue or cancel + if ($this->state != false) + { + $this->template->assign_vars(array( + 'UA_PROGRESS_BAR' => addslashes(append_sid($this->path_helper->get_phpbb_root_path() . $this->path_helper->get_adm_relative_path() . "index." . $this->path_helper->get_php_ext(), "i=$id&mode=$mode&action=progress_bar")), + 'S_CONTINUE_UPDATING' => true, + 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), + 'L_CONTINUE' => $this->user->lang('CONTINUE_UPDATING'), + 'L_CONTINUE_EXPLAIN' => $this->user->lang('CONTINUE_UPDATING_EXPLAIN'), + )); + + return; + } + + // Process form and create a "state" for the update, + // then show a confirm form $messages = []; + if ($this->request->is_set_post('submit')) { + if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + { + trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } + $modified_storages = []; if (!check_form_key($form_key)) { - $messages[] = $this->lang->lang('FORM_INVALID'); + $messages[] = $this->user->lang('FORM_INVALID'); } foreach ($this->storage_collection as $storage) @@ -133,7 +298,7 @@ public function settings($id, $mode) $modified = false; // Check if provider have been modified - if ($this->get_new_provider($storage_name) != $this->get_current_provider($storage_name)) + if ($this->request->variable([$storage_name, 'provider'], '') != $this->get_current_provider($storage_name)) { $modified = true; } @@ -143,7 +308,7 @@ public function settings($id, $mode) { foreach (array_keys($options) as $definition) { - if ($this->get_new_definition($storage_name, $definition) != $this->get_current_definition($storage_name, $definition)) + if ($this->request->variable([$storage_name, $definition], '') != $this->get_current_definition($storage_name, $definition)) { $modified = true; break; @@ -163,37 +328,41 @@ public function settings($id, $mode) { if (empty($messages)) { + // Create state + $this->state = [ + // Save the value of the checkbox, to remove all files from the + // old storage once they have been successfully moved + 'remove_old' => $this->request->variable('remove_old', false), + 'storage_index' => 0, + 'file_index' => 0, + 'remove_storage_index' => 0, + ]; + + // Save in the state the selected storages and their configuration foreach ($modified_storages as $storage_name) { - $current_adapter = $this->get_current_adapter($storage_name); - $new_adapter = $this->get_new_adapter($storage_name); + $this->state['storages'][$storage_name]['provider'] = $this->request->variable([$storage_name, 'provider'], ''); - $sql = 'SELECT file_path - FROM ' . STORAGE_TABLE . " - WHERE storage = '" . $storage_name . "'"; - $result = $this->db->sql_query($sql); + $options = $this->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); - while ($row = $this->db->sql_fetchrow($result)) + foreach (array_keys($options) as $definition) { - $stream = $current_adapter->read_stream($row['file_path']); - $new_adapter->write_stream($row['file_path'], $stream); - fclose($stream); + $this->state['storages'][$storage_name]['config'][$definition] = $this->request->variable([$storage_name, $definition], ''); } + } - if ($this->request->variable('remove_old', false)) - { - $this->db->sql_rowseek(0, $result); - - while ($row = $this->db->sql_fetchrow($result)) - { - $current_adapter->delete($row['file_path']); - } - } + $this->save_state(); // A storage update is going to be done here - $this->update_storage_config($storage_name); - } + // Show the confirmation form to start the process + $this->template->assign_vars(array( + 'UA_PROGRESS_BAR' => addslashes(append_sid($this->path_helper->get_phpbb_root_path() . $this->path_helper->get_adm_relative_path() . "index." . $this->path_helper->get_php_ext(), "i=$id&mode=$mode&action=progress_bar")), // same + 'S_CONTINUE_UPDATING' => true, + 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), + 'L_CONTINUE' => $this->user->lang('START_UPDATING'), + 'L_CONTINUE_EXPLAIN' => $this->user->lang('START_UPDATING_EXPLAIN'), + )); - trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action), E_USER_NOTICE); + return; } else { @@ -202,9 +371,10 @@ public function settings($id, $mode) } // If there is no changes - trigger_error($this->lang->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->user->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); } + // Top table with stats of each storage $storage_stats = []; foreach ($this->storage_collection as $storage) { @@ -219,11 +389,11 @@ public function settings($id, $mode) } catch (\phpbb\storage\exception\exception $e) { - $free_space = $this->lang->lang('STORAGE_UNKNOWN'); + $free_space = $this->user->lang('STORAGE_UNKNOWN'); } $storage_stats[] = [ - 'name' => $this->lang->lang('STORAGE_' . strtoupper($storage->get_name()) . '_TITLE'), + 'name' => $this->user->lang('STORAGE_' . strtoupper($storage->get_name()) . '_TITLE'), 'files' => $storage->get_num_files(), 'size' => get_formatted_filesize($storage->get_size()), 'free_space' => $free_space, @@ -231,15 +401,63 @@ public function settings($id, $mode) } $this->template->assign_vars([ - 'STORAGES' => $this->storage_collection, - 'STORAGE_STATS' => $storage_stats, - 'PROVIDERS' => $this->provider_collection, + 'STORAGES' => $this->storage_collection, + 'STORAGE_STATS' => $storage_stats, + 'PROVIDERS' => $this->provider_collection, + + 'ERROR_MSG' => implode('
          ', $messages), + 'S_ERROR' => !empty($messages), - 'ERROR_MSG' => implode('
          ', $messages), - 'S_ERROR' => !empty($messages), + 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_storage'), ]); } + protected function display_progress_bar() + { + adm_page_header($this->user->lang('STORAGE_UPDATE_IN_PROGRESS')); + $this->template->set_filenames(array( + 'body' => 'progress_bar.html') + ); + $this->template->assign_vars(array( + 'L_PROGRESS' => $this->user->lang('STORAGE_UPDATE_IN_PROGRESS'), + 'L_PROGRESS_EXPLAIN' => $this->user->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN')) + ); + adm_page_footer(); + } + + function close_popup_js() + { + return "\n"; + } + + protected function save_state() + { + $state = $this->state; + + if ($state == false) + { + $state = []; + } + + $this->config_text->set('storage_update_state', json_encode($state)); + } + + protected function load_state() + { + $state = json_decode($this->config_text->get('storage_update_state'), true); + + if ($state == null || empty($state)) + { + $state = false; + } + + $this->state = $state; + } + /** * Get the current provider from config * @@ -251,17 +469,6 @@ protected function get_current_provider($storage_name) return $this->config['storage\\' . $storage_name . '\\provider']; } - /** - * Get the new provider from the request - * - * @param string $storage_name Storage name - * @return string The new provider - */ - protected function get_new_provider($storage_name) - { - return $this->request->variable([$storage_name, 'provider'], ''); - } - /** * Get adapter definitions from a provider * @@ -285,18 +492,6 @@ protected function get_current_definition($storage_name, $definition) return $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; } - /** - * Get the new value of the definition of a storage from the request - * - * @param string $storage_name Storage name - * @param string $definition Definition - * @return string Definition value - */ - protected function get_new_definition($storage_name, $definition) - { - return $this->request->variable([$storage_name, $definition], ''); - } - /** * Validates data * @@ -305,56 +500,56 @@ protected function get_new_definition($storage_name, $definition) */ protected function validate_data($storage_name, &$messages) { - $storage_title = $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); + $storage_title = $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); // Check if provider exists try { - $new_provider = $this->provider_collection->get_by_class($this->get_new_provider($storage_name)); + $new_provider = $this->provider_collection->get_by_class($this->request->variable([$storage_name, 'provider'], '')); } catch (\Exception $e) { - $messages[] = $this->lang->lang('STORAGE_PROVIDER_NOT_EXISTS', $storage_title); + $messages[] = $this->user->lang('STORAGE_PROVIDER_NOT_EXISTS', $storage_title); return; } // Check if provider is available if (!$new_provider->is_available()) { - $messages[] = $this->lang->lang('STORAGE_PROVIDER_NOT_AVAILABLE', $storage_title); + $messages[] = $this->user->lang('STORAGE_PROVIDER_NOT_AVAILABLE', $storage_title); return; } // Check options - $new_options = $this->get_provider_options($this->get_new_provider($storage_name)); + $new_options = $this->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); foreach ($new_options as $definition_key => $definition_value) { - $provider = $this->provider_collection->get_by_class($this->get_new_provider($storage_name)); - $definition_title = $this->lang->lang('STORAGE_ADAPTER_' . strtoupper($provider->get_name()) . '_OPTION_' . strtoupper($definition_key)); + $provider = $this->provider_collection->get_by_class($this->request->variable([$storage_name, 'provider'], '')); + $definition_title = $this->user->lang('STORAGE_ADAPTER_' . strtoupper($provider->get_name()) . '_OPTION_' . strtoupper($definition_key)); - $value = $this->get_new_definition($storage_name, $definition_key); + $value = $this->request->variable([$storage_name, $definition_key], ''); switch ($definition_value['type']) { case 'email': if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { - $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); + $messages[] = $this->user->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); } case 'text': case 'password': $maxlength = isset($definition_value['maxlength']) ? $definition_value['maxlength'] : 255; if (strlen($value) > $maxlength) { - $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); + $messages[] = $this->user->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); } break; case 'radio': case 'select': if (!in_array($value, array_values($definition_value['options']))) { - $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); + $messages[] = $this->user->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); } break; } @@ -377,14 +572,14 @@ protected function update_storage_config($storage_name) } // Update provider - $this->config->set('storage\\' . $storage_name . '\\provider', $this->get_new_provider($storage_name)); + $this->config->set('storage\\' . $storage_name . '\\provider', $this->state['storages'][$storage_name]['provider']); // Set new storage config - $new_options = $this->get_provider_options($this->get_new_provider($storage_name)); + $new_options = $this->get_provider_options($this->state['storages'][$storage_name]['provider']); foreach (array_keys($new_options) as $definition) { - $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $this->get_new_definition($storage_name, $definition)); + $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $this->state['storages'][$storage_name]['config'][$definition]); } } @@ -434,7 +629,7 @@ protected function get_current_adapter($storage_name) protected function get_new_adapter($storage_name) { - $provider = $this->get_new_provider($storage_name); + $provider = $this->state['storages'][$storage_name]['provider']; $provider_class = $this->provider_collection->get_by_class($provider); $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); @@ -443,7 +638,7 @@ protected function get_new_adapter($storage_name) $options = []; foreach (array_keys($definitions) as $definition) { - $options[$definition] = $this->get_new_definition($storage_name, $definition); + $options[$definition] = $this->state['storages'][$storage_name]['config'][$definition]; } $adapter->configure($options); diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index bcbbe5d931a..729888d509b 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -768,6 +768,8 @@ 'LOG_STYLE_EDIT_DETAILS' => 'Edited style
          » %s', 'LOG_STYLE_EXPORT' => 'Exported style
          » %s', + 'LOG_STORAGE_UPDATE' => 'Storage updated', + 'LOG_UPDATE_DATABASE' => 'Updated Database from version %1$s to version %2$s', 'LOG_UPDATE_PHPBB' => 'Updated phpBB from version %1$s to version %2$s', diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index ae3c007c4a2..7c921b46e21 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -50,6 +50,16 @@ 'STORAGE_UNKNOWN' => 'Unknown', 'STORAGE_REMOVE_OLD_FILES' => 'Remove old files', 'STORAGE_REMOVE_OLD_FILES_EXPLAIN' => 'Remove old files after they are copied to the new storage system.', + 'START_UPDATING' => 'Start update process', + 'START_UPDATING_EXPLAIN' => 'Start the storage update process', + 'CONTINUE_UPDATING' => 'Continue previous update process', + 'CONTINUE_UPDATING_EXPLAIN' => 'An update process has been started. In order to access the storage settings page you will have to complete it or cancel it.', + 'SORAGE_UPDATE_REDIRECT' => 'Files of %s (%d/%d) are being moved.
          ', + 'SORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %s (%d/%d) are being removed.
          ', + + // Template progress bar + 'STORAGE_UPDATE_IN_PROGRESS' => 'Storage update in progress', + 'STORAGE_UPDATE_IN_PROGRESS_EXPLAIN' => 'Files are being moved between storages. This can take some minutes.', // Storage names 'STORAGE_ATTACHMENT_TITLE' => 'Attachments storage', From a2c7255b5b0d37455b4273e8f47330a1dcc78e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Sat, 28 Jul 2018 12:25:54 +0200 Subject: [PATCH 0196/1214] [ticket/15699] Use twig syntax PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 999d5622ea2..031f453cddf 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -4,7 +4,7 @@

          {{ lang('STORAGE_TITLE') }}

          - +{% if S_CONTINUE_UPDATING %} -

          {L_CONTINUE_EXPLAIN}

          +

          {{ lang('CONTINUE_EXPLAIN') }}

          -
          +
          - {L_SUBMIT} -   - - {S_FORM_TOKEN} + {{ lang('SUBMIT }} +   + + {{ S_FORM_TOKEN }}
          - - +{% else %}

          {{ lang('STORAGE_TITLE_EXPLAIN') }}

          @@ -136,6 +135,6 @@

          {{ lang('WARNING') }}

          {{ S_FORM_TOKEN }} - +{% endif %} {% include 'overall_footer.html' %} From 6a54b01f6d01686045aaf38ac33ca01ee279552c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 30 Jul 2018 14:42:46 +0200 Subject: [PATCH 0197/1214] [ticket/15699] Remove comment PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index af7141dc927..bddf1cf460a 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -158,9 +158,6 @@ public function settings($id, $mode) trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } - // TODO: If both providers are the same, and remove - // old files is checked, the files could be only moved - // Copy files from the old to the new storage $i = 0; foreach ($this->state['storages'] as $storage_name => $storage_options) From 9dc67e81ff92bb20242ebaaa6b2579c0f76540f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Wed, 1 Aug 2018 20:59:23 +0200 Subject: [PATCH 0198/1214] [ticket/15699] Fix twig syntax PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 031f453cddf..97069b0b57b 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -23,7 +23,7 @@

          {{ lang('STORAGE_TITLE') }}

          - {{ lang('SUBMIT }} + {{ lang('SUBMIT') }}   {{ S_FORM_TOKEN }} @@ -91,7 +91,7 @@

          {{ lang('WARNING') }}

          {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %}
          - + {% if lang_defined(description) %}
          {{ lang(description) }} {% endif %} From 8cb5ea8e209e881b71b1eff2379adc703bb441f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Wed, 1 Aug 2018 21:04:55 +0200 Subject: [PATCH 0199/1214] [ticket/15699] Check if is resource before close PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index bddf1cf460a..3eb34498549 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -189,7 +189,11 @@ public function settings($id, $mode) $stream = $current_adapter->read_stream($row['file_path']); $new_adapter->write_stream($row['file_path'], $stream); - fclose($stream); + + if (is_resource($stream)) + { + fclose($stream); + } $this->state['file_index'] = $row['file_id']; // Set last uploaded file } From 9c51b3099a429652ae8fbf2ad78d8af01606e6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Wed, 1 Aug 2018 21:24:45 +0200 Subject: [PATCH 0200/1214] [ticket/15699] Fix messages PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 8 +++++--- phpBB/language/en/acp/storage.php | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 3eb34498549..967780745ea 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -153,7 +153,7 @@ public function settings($id, $mode) break; } - if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) { trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } @@ -163,8 +163,9 @@ public function settings($id, $mode) foreach ($this->state['storages'] as $storage_name => $storage_options) { // Skip storages that have already moved files - if ($this->state['storage_index'] > $i++) + if ($this->state['storage_index'] > $i) { + $i++; continue; } @@ -209,8 +210,9 @@ public function settings($id, $mode) foreach ($this->state['storages'] as $storage_name => $storage_options) { // Skip storages that have already moved files - if ($this->state['remove_storage_index'] > $i++) + if ($this->state['remove_storage_index'] > $i) { + $i++; continue; } diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 7c921b46e21..892aa42e724 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -54,8 +54,8 @@ 'START_UPDATING_EXPLAIN' => 'Start the storage update process', 'CONTINUE_UPDATING' => 'Continue previous update process', 'CONTINUE_UPDATING_EXPLAIN' => 'An update process has been started. In order to access the storage settings page you will have to complete it or cancel it.', - 'SORAGE_UPDATE_REDIRECT' => 'Files of %s (%d/%d) are being moved.
          ', - 'SORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %s (%d/%d) are being removed.
          ', + 'STORAGE_UPDATE_REDIRECT' => 'Files of %s (%d/%d) are being moved.
          ', + 'STORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %s (%d/%d) are being removed.
          ', // Template progress bar 'STORAGE_UPDATE_IN_PROGRESS' => 'Storage update in progress', From 0c71ff5eab070a67e2037ed712b965e4fd400991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Fri, 3 Aug 2018 12:08:23 +0200 Subject: [PATCH 0201/1214] [ticket/15699] Log update PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 967780745ea..5e2d4a2d4f2 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -30,6 +30,9 @@ class acp_storage /** @var \db\driver\driver_interface $db */ protected $db; + /** @var \log\log_interface $log */ + protected $log; + /** @var \phpbb\path_helper $path_helper */ protected $path_helper; @@ -78,6 +81,7 @@ public function main($id, $mode) $this->config_text = $phpbb_container->get('config_text'); $this->db = $phpbb_container->get('dbal.conn'); $this->filesystem = $phpbb_container->get('filesystem'); + $this->log = $phpbb_container->get('log'); $this->path_helper = $phpbb_container->get('path_helper'); $this->request = $phpbb_container->get('request'); $this->template = $phpbb_container->get('template'); @@ -254,7 +258,7 @@ public function settings($id, $mode) $this->state = false; $this->save_state(); - //$phpbb_log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false); // todo + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false); // todo trigger_error($this->user->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); } From 9f641963d6cb158c3b3012a5048188eda92a1843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Fri, 3 Aug 2018 12:17:41 +0200 Subject: [PATCH 0202/1214] [ticket/15699] Fix docblocks PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 5e2d4a2d4f2..0941551c3e5 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -27,10 +27,10 @@ class acp_storage /** @var \phpbb\db_text $config_text */ protected $config_text; - /** @var \db\driver\driver_interface $db */ + /** @var \phpbb\db\driver\driver_interface $db */ protected $db; - /** @var \log\log_interface $log */ + /** @var \phpbb\log\log_interface $log */ protected $log; /** @var \phpbb\path_helper $path_helper */ From 6de2d44c8705cf6b741f3447708aa7833dcacb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 20 Aug 2018 16:49:08 +0200 Subject: [PATCH 0203/1214] [ticket/15699] Make adapter to know the storage name PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 0941551c3e5..78ea0f941d6 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -630,6 +630,7 @@ protected function get_current_adapter($storage_name) } $adapter->configure($options); + $adapter->set_storage($storage_name); return $adapter; } @@ -649,6 +650,7 @@ protected function get_new_adapter($storage_name) } $adapter->configure($options); + $adapter->set_storage($storage_name); return $adapter; } From 3a25c0222b2d5ba097adc8b65b2907c697da8ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Sat, 25 Aug 2018 17:54:18 +0200 Subject: [PATCH 0204/1214] [ticket/15699] Add option to udpate configuration only PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 6 +- phpBB/includes/acp/acp_storage.php | 110 +++++++++++++++-------------- phpBB/language/en/acp/storage.php | 6 +- 3 files changed, 65 insertions(+), 57 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 97069b0b57b..43bb855bf3d 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -121,9 +121,11 @@

          {{ lang('WARNING') }}

          -

          {{ lang('STORAGE_REMOVE_OLD_FILES_EXPLAIN') }}
          +
          - + + +
          diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 78ea0f941d6..dfa35bb35e4 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -162,65 +162,21 @@ public function settings($id, $mode) trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } - // Copy files from the old to the new storage - $i = 0; - foreach ($this->state['storages'] as $storage_name => $storage_options) - { - // Skip storages that have already moved files - if ($this->state['storage_index'] > $i) - { - $i++; - continue; - } - - $current_adapter = $this->get_current_adapter($storage_name); - $new_adapter = $this->get_new_adapter($storage_name); - - $sql = 'SELECT file_id, file_path - FROM ' . STORAGE_TABLE . " - WHERE storage = '" . $this->db->sql_escape($storage_name) . "' - AND file_id > " . $this->state['file_index']; - $result = $this->db->sql_query($sql); - - $starttime = microtime(true); - while ($row = $this->db->sql_fetchrow($result)) - { - if (!still_on_time()) - { - $this->save_state(); - meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->user->lang('STORAGE_UPDATE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); - } - - $stream = $current_adapter->read_stream($row['file_path']); - $new_adapter->write_stream($row['file_path'], $stream); - - if (is_resource($stream)) - { - fclose($stream); - } - - $this->state['file_index'] = $row['file_id']; // Set last uploaded file - } - - // Copied all files of a storage, increase storage index and reset file index - $this->state['storage_index']++; - $this->state['file_index'] = 0; - } - - if ($this->state['remove_old']) + // If update_type is copy or move, copy files from the old to the new storage + if ($this->state['update_type'] >= 1) { $i = 0; foreach ($this->state['storages'] as $storage_name => $storage_options) { // Skip storages that have already moved files - if ($this->state['remove_storage_index'] > $i) + if ($this->state['storage_index'] > $i) { $i++; continue; } $current_adapter = $this->get_current_adapter($storage_name); + $new_adapter = $this->get_new_adapter($storage_name); $sql = 'SELECT file_id, file_path FROM ' . STORAGE_TABLE . " @@ -235,18 +191,66 @@ public function settings($id, $mode) { $this->save_state(); meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->user->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + trigger_error($this->user->lang('STORAGE_UPDATE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); } - $current_adapter->delete($row['file_path']); + $stream = $current_adapter->read_stream($row['file_path']); + $new_adapter->write_stream($row['file_path'], $stream); + + if (is_resource($stream)) + { + fclose($stream); + } $this->state['file_index'] = $row['file_id']; // Set last uploaded file } - // Remove all files of a storage, increase storage index and reset file index - $this->state['remove_storage_index']++; + // Copied all files of a storage, increase storage index and reset file index + $this->state['storage_index']++; $this->state['file_index'] = 0; } + + // If update_type is move files, remove the old files + if ($this->state['update_type'] == 2) + { + $i = 0; + foreach ($this->state['storages'] as $storage_name => $storage_options) + { + // Skip storages that have already moved files + if ($this->state['remove_storage_index'] > $i) + { + $i++; + continue; + } + + $current_adapter = $this->get_current_adapter($storage_name); + + $sql = 'SELECT file_id, file_path + FROM ' . STORAGE_TABLE . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state['file_index']; + $result = $this->db->sql_query($sql); + + $starttime = microtime(true); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!still_on_time()) + { + $this->save_state(); + meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); + trigger_error($this->user->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + } + + $current_adapter->delete($row['file_path']); + + $this->state['file_index'] = $row['file_id']; // Set last uploaded file + } + + // Remove all files of a storage, increase storage index and reset file index + $this->state['remove_storage_index']++; + $this->state['file_index'] = 0; + } + } } // Here all files have been copied/moved, so save new configuration @@ -339,7 +343,7 @@ public function settings($id, $mode) $this->state = [ // Save the value of the checkbox, to remove all files from the // old storage once they have been successfully moved - 'remove_old' => $this->request->variable('remove_old', false), + 'update_type' => $this->request->variable('update_type', 0), 'storage_index' => 0, 'file_index' => 0, 'remove_storage_index' => 0, diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 892aa42e724..1530af05229 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -48,8 +48,10 @@ 'STORAGE_SIZE' => 'Size', 'STORAGE_FREE' => 'Available space', 'STORAGE_UNKNOWN' => 'Unknown', - 'STORAGE_REMOVE_OLD_FILES' => 'Remove old files', - 'STORAGE_REMOVE_OLD_FILES_EXPLAIN' => 'Remove old files after they are copied to the new storage system.', + 'STORAGE_UPDATE_TYPE' => 'Update type', + 'STORAGE_UPDATE_TYPE_CONFIG' => 'Update configuration only', + 'STORAGE_UPDATE_TYPE_COPY' => 'Update configuration and copy files', + 'STORAGE_UPDATE_TYPE_MOVE' => 'Update configuration and move files', 'START_UPDATING' => 'Start update process', 'START_UPDATING_EXPLAIN' => 'Start the storage update process', 'CONTINUE_UPDATING' => 'Continue previous update process', From 2c9f9fa75efd3807d54245601a556806e2cce290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Sat, 25 Aug 2018 18:01:58 +0200 Subject: [PATCH 0205/1214] [ticket/15699] Add information to logs PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 3 ++- phpBB/language/en/acp/common.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index dfa35bb35e4..160d49c1868 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -259,10 +259,11 @@ public function settings($id, $mode) $this->update_storage_config($storage_name); } + $storages = array_keys($this->state['storages']); $this->state = false; $this->save_state(); - $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false); // todo + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false, $storages); trigger_error($this->user->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); } diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index 729888d509b..62c08f02262 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -768,7 +768,7 @@ 'LOG_STYLE_EDIT_DETAILS' => 'Edited style
          » %s', 'LOG_STYLE_EXPORT' => 'Exported style
          » %s', - 'LOG_STORAGE_UPDATE' => 'Storage updated', + 'LOG_STORAGE_UPDATE' => 'Storage updated
          » %s', 'LOG_UPDATE_DATABASE' => 'Updated Database from version %1$s to version %2$s', 'LOG_UPDATE_PHPBB' => 'Updated phpBB from version %1$s to version %2$s', From 64e8bea62a5edef284534660bdd201dbf9fbb938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Sat, 25 Aug 2018 18:12:57 +0200 Subject: [PATCH 0206/1214] [ticket/15699] Lazy-load adapters PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 62 ++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 160d49c1868..23af6131b9b 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -622,41 +622,55 @@ protected function validate_path($storage_name, $options, &$messages) protected function get_current_adapter($storage_name) { - $provider = $this->get_current_provider($storage_name); - $provider_class = $this->provider_collection->get_by_class($provider); + static $adapters = []; - $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); - $definitions = $this->get_provider_options($provider); - - $options = []; - foreach (array_keys($definitions) as $definition) + if(!isset($adapters[$storage_name])) { - $options[$definition] = $this->get_current_definition($storage_name, $definition); - } + $provider = $this->get_current_provider($storage_name); + $provider_class = $this->provider_collection->get_by_class($provider); + + $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); + $definitions = $this->get_provider_options($provider); + + $options = []; + foreach (array_keys($definitions) as $definition) + { + $options[$definition] = $this->get_current_definition($storage_name, $definition); + } - $adapter->configure($options); - $adapter->set_storage($storage_name); + $adapter->configure($options); + //$adapter->set_storage($storage_name); + + $adapters[$storage_name] = $adapter; + } - return $adapter; + return $adapters[$storage_name]; } protected function get_new_adapter($storage_name) { - $provider = $this->state['storages'][$storage_name]['provider']; - $provider_class = $this->provider_collection->get_by_class($provider); + static $adapters = []; - $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); - $definitions = $this->get_provider_options($provider); - - $options = []; - foreach (array_keys($definitions) as $definition) + if(!isset($adapters[$storage_name])) { - $options[$definition] = $this->state['storages'][$storage_name]['config'][$definition]; - } + $provider = $this->state['storages'][$storage_name]['provider']; + $provider_class = $this->provider_collection->get_by_class($provider); + + $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); + $definitions = $this->get_provider_options($provider); + + $options = []; + foreach (array_keys($definitions) as $definition) + { + $options[$definition] = $this->state['storages'][$storage_name]['config'][$definition]; + } - $adapter->configure($options); - $adapter->set_storage($storage_name); + $adapter->configure($options); + //$adapter->set_storage($storage_name); + + $adapters[$storage_name] = $adapter; + } - return $adapter; + return $adapters[$storage_name]; } } From d426f9e3204d2bccb95e724d68c26d68d47d160a Mon Sep 17 00:00:00 2001 From: rubencm Date: Fri, 14 Sep 2018 09:58:16 +0000 Subject: [PATCH 0207/1214] [ticket/15699] Use language PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 54 ++++++++++++++++-------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 23af6131b9b..b25ada9ce03 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -30,6 +30,9 @@ class acp_storage /** @var \phpbb\db\driver\driver_interface $db */ protected $db; + /** @var \phpbb\language\language $log */ + protected $lang; + /** @var \phpbb\log\log_interface $log */ protected $log; @@ -81,6 +84,7 @@ public function main($id, $mode) $this->config_text = $phpbb_container->get('config_text'); $this->db = $phpbb_container->get('dbal.conn'); $this->filesystem = $phpbb_container->get('filesystem'); + $this->lang = $phpbb_container->get('language'); $this->log = $phpbb_container->get('log'); $this->path_helper = $phpbb_container->get('path_helper'); $this->request = $phpbb_container->get('request'); @@ -92,7 +96,7 @@ public function main($id, $mode) $this->phpbb_root_path = $phpbb_root_path; // Add necesary language files - $this->user->add_lang(['acp/storage']); + $this->lang->add_lang(['acp/storage']); /** * Add language strings @@ -131,7 +135,7 @@ public function settings($id, $mode) { if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) { - trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } if ($this->request->variable('cancel', false)) @@ -159,7 +163,7 @@ public function settings($id, $mode) if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) { - trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } // If update_type is copy or move, copy files from the old to the new storage @@ -191,7 +195,7 @@ public function settings($id, $mode) { $this->save_state(); meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->user->lang('STORAGE_UPDATE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + trigger_error($this->lang->lang('STORAGE_UPDATE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); } $stream = $current_adapter->read_stream($row['file_path']); @@ -238,7 +242,7 @@ public function settings($id, $mode) { $this->save_state(); meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->user->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + trigger_error($this->lang->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); } $current_adapter->delete($row['file_path']); @@ -264,7 +268,7 @@ public function settings($id, $mode) $this->save_state(); $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false, $storages); - trigger_error($this->user->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); + trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); } // There is an updating in progress, show the form to continue or cancel @@ -274,8 +278,8 @@ public function settings($id, $mode) 'UA_PROGRESS_BAR' => addslashes(append_sid($this->path_helper->get_phpbb_root_path() . $this->path_helper->get_adm_relative_path() . "index." . $this->path_helper->get_php_ext(), "i=$id&mode=$mode&action=progress_bar")), 'S_CONTINUE_UPDATING' => true, 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), - 'L_CONTINUE' => $this->user->lang('CONTINUE_UPDATING'), - 'L_CONTINUE_EXPLAIN' => $this->user->lang('CONTINUE_UPDATING_EXPLAIN'), + 'L_CONTINUE' => $this->lang->lang('CONTINUE_UPDATING'), + 'L_CONTINUE_EXPLAIN' => $this->lang->lang('CONTINUE_UPDATING_EXPLAIN'), )); return; @@ -289,14 +293,14 @@ public function settings($id, $mode) { if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) { - trigger_error($this->user->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } $modified_storages = []; if (!check_form_key($form_key)) { - $messages[] = $this->user->lang('FORM_INVALID'); + $messages[] = $this->lang->lang('FORM_INVALID'); } foreach ($this->storage_collection as $storage) @@ -370,8 +374,8 @@ public function settings($id, $mode) 'UA_PROGRESS_BAR' => addslashes(append_sid($this->path_helper->get_phpbb_root_path() . $this->path_helper->get_adm_relative_path() . "index." . $this->path_helper->get_php_ext(), "i=$id&mode=$mode&action=progress_bar")), // same 'S_CONTINUE_UPDATING' => true, 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), - 'L_CONTINUE' => $this->user->lang('START_UPDATING'), - 'L_CONTINUE_EXPLAIN' => $this->user->lang('START_UPDATING_EXPLAIN'), + 'L_CONTINUE' => $this->lang->lang('START_UPDATING'), + 'L_CONTINUE_EXPLAIN' => $this->lang->lang('START_UPDATING_EXPLAIN'), )); return; @@ -383,7 +387,7 @@ public function settings($id, $mode) } // If there is no changes - trigger_error($this->user->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($this->lang->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); } // Top table with stats of each storage @@ -401,11 +405,11 @@ public function settings($id, $mode) } catch (\phpbb\storage\exception\exception $e) { - $free_space = $this->user->lang('STORAGE_UNKNOWN'); + $free_space = $this->lang->lang('STORAGE_UNKNOWN'); } $storage_stats[] = [ - 'name' => $this->user->lang('STORAGE_' . strtoupper($storage->get_name()) . '_TITLE'), + 'name' => $this->lang->lang('STORAGE_' . strtoupper($storage->get_name()) . '_TITLE'), 'files' => $storage->get_num_files(), 'size' => get_formatted_filesize($storage->get_size()), 'free_space' => $free_space, @@ -426,13 +430,13 @@ public function settings($id, $mode) protected function display_progress_bar() { - adm_page_header($this->user->lang('STORAGE_UPDATE_IN_PROGRESS')); + adm_page_header($this->lang->lang('STORAGE_UPDATE_IN_PROGRESS')); $this->template->set_filenames(array( 'body' => 'progress_bar.html') ); $this->template->assign_vars(array( - 'L_PROGRESS' => $this->user->lang('STORAGE_UPDATE_IN_PROGRESS'), - 'L_PROGRESS_EXPLAIN' => $this->user->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN')) + 'L_PROGRESS' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS'), + 'L_PROGRESS_EXPLAIN' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN')) ); adm_page_footer(); } @@ -512,7 +516,7 @@ protected function get_current_definition($storage_name, $definition) */ protected function validate_data($storage_name, &$messages) { - $storage_title = $this->user->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); + $storage_title = $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); // Check if provider exists try @@ -521,14 +525,14 @@ protected function validate_data($storage_name, &$messages) } catch (\Exception $e) { - $messages[] = $this->user->lang('STORAGE_PROVIDER_NOT_EXISTS', $storage_title); + $messages[] = $this->lang->lang('STORAGE_PROVIDER_NOT_EXISTS', $storage_title); return; } // Check if provider is available if (!$new_provider->is_available()) { - $messages[] = $this->user->lang('STORAGE_PROVIDER_NOT_AVAILABLE', $storage_title); + $messages[] = $this->lang->lang('STORAGE_PROVIDER_NOT_AVAILABLE', $storage_title); return; } @@ -538,7 +542,7 @@ protected function validate_data($storage_name, &$messages) foreach ($new_options as $definition_key => $definition_value) { $provider = $this->provider_collection->get_by_class($this->request->variable([$storage_name, 'provider'], '')); - $definition_title = $this->user->lang('STORAGE_ADAPTER_' . strtoupper($provider->get_name()) . '_OPTION_' . strtoupper($definition_key)); + $definition_title = $this->lang->lang('STORAGE_ADAPTER_' . strtoupper($provider->get_name()) . '_OPTION_' . strtoupper($definition_key)); $value = $this->request->variable([$storage_name, $definition_key], ''); @@ -547,21 +551,21 @@ protected function validate_data($storage_name, &$messages) case 'email': if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { - $messages[] = $this->user->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); + $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); } case 'text': case 'password': $maxlength = isset($definition_value['maxlength']) ? $definition_value['maxlength'] : 255; if (strlen($value) > $maxlength) { - $messages[] = $this->user->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); + $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); } break; case 'radio': case 'select': if (!in_array($value, array_values($definition_value['options']))) { - $messages[] = $this->user->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); + $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); } break; } From b260e9a6f200336ada12d940e614fee0b4abc9a9 Mon Sep 17 00:00:00 2001 From: rubencm Date: Fri, 14 Sep 2018 12:19:35 +0000 Subject: [PATCH 0208/1214] [ticket/15699] Fix code style PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index b25ada9ce03..c38dac57739 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -628,7 +628,7 @@ protected function get_current_adapter($storage_name) { static $adapters = []; - if(!isset($adapters[$storage_name])) + if (!isset($adapters[$storage_name])) { $provider = $this->get_current_provider($storage_name); $provider_class = $this->provider_collection->get_by_class($provider); @@ -655,7 +655,7 @@ protected function get_new_adapter($storage_name) { static $adapters = []; - if(!isset($adapters[$storage_name])) + if (!isset($adapters[$storage_name])) { $provider = $this->state['storages'][$storage_name]['provider']; $provider_class = $this->provider_collection->get_by_class($provider); From b10409cb8a6379d14f1efae227b5856040b4b540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 1 Oct 2018 21:52:26 +0200 Subject: [PATCH 0209/1214] [ticket/15699] Update comments PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index c38dac57739..a2350b59163 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -24,7 +24,7 @@ class acp_storage /** @var \phpbb\config\config $config */ protected $config; - /** @var \phpbb\db_text $config_text */ + /** @var \phpbb\config\db_text $config_text */ protected $config_text; /** @var \phpbb\db\driver\driver_interface $db */ From 958a43cb872f7c80c55af7a0a1b8b0998c2db517 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Tue, 9 Oct 2018 20:47:26 +0000 Subject: [PATCH 0210/1214] [ticket/15699] Remove unused variable and switch PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index a2350b59163..e575079ff35 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -148,17 +148,13 @@ public function settings($id, $mode) if ($action) { - switch ($action) + if ($action == 'progress_bar') { - case 'progress_bar': - $this->display_progress_bar(); - break; - case 'update': - // Just continue - break; - default: - trigger_error('NO_ACTION', E_USER_ERROR); - break; + $this->display_progress_bar(); + } + else if ($action != 'update') + { + trigger_error('NO_ACTION', E_USER_ERROR); } if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) @@ -188,7 +184,6 @@ public function settings($id, $mode) AND file_id > " . $this->state['file_index']; $result = $this->db->sql_query($sql); - $starttime = microtime(true); while ($row = $this->db->sql_fetchrow($result)) { if (!still_on_time()) @@ -235,7 +230,6 @@ public function settings($id, $mode) AND file_id > " . $this->state['file_index']; $result = $this->db->sql_query($sql); - $starttime = microtime(true); while ($row = $this->db->sql_fetchrow($result)) { if (!still_on_time()) From 43b402a7dab7b016cac3bfb98a14885e470e2f72 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 7 Mar 2021 10:04:09 +0100 Subject: [PATCH 0211/1214] [ticket/15699] Update acp_storage to be compatible with twig 3 PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 82 +++++++++++++++++--------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 43bb855bf3d..aa25b8feb3e 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -69,53 +69,57 @@

          {{ lang('WARNING') }}


          {{ lang('STORAGE_SELECT_DESC') }}
          - {% for provider in PROVIDERS if provider.is_available %} -
          - {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} - {{ lang('STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_NAME') }} - {% for name, options in provider.get_options %} - {% set title = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper %} - {% set description = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper ~ '_EXPLAIN' %} - {% set input_id = storage.get_name ~ '_' ~ provider.get_name ~ '_' ~ name %} - {% set input_type = options['type'] %} - {% set input_name = storage.get_name ~ '[' ~ name ~ ']' %} - {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %} -
          -
          - - {% if lang_defined(description) %} -
          {{ lang(description) }} - {% endif %} -
          -
          - {% if input_type in ['text', 'password', 'email'] %} - - {% elseif input_type == 'textarea' %} - - {% elseif input_type == 'radio' %} - {% for option_name, option_value in options['options'] %} - {{ lang(option_name) }} - {% endfor %} - {% elseif input_type == 'select' %} - + {% elseif input_type == 'textarea' %} + + {% elseif input_type == 'radio' %} {% for option_name, option_value in options['options'] %} - + {{ lang(option_name) }} {% endfor %} - - {% endif %} -
          -
          - {% endfor %} -
          + {% elseif input_type == 'select' %} + + {% endif %} + + + {% endfor %} + + {% endif %} {% endfor %} {% endfor %} From a6cf020639949b8ce915a904aa850fd5db7b2fa1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 7 Mar 2021 10:30:04 +0100 Subject: [PATCH 0212/1214] [ticket/15699] Adjust validate path for removed method PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index e575079ff35..2ec086a232d 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -605,7 +605,7 @@ protected function validate_path($storage_name, $options, &$messages) { if ($this->provider_collection->get_by_class($this->get_current_provider($storage_name))->get_name() == 'local' && isset($options['path'])) { - $path = $this->request->is_set_post('submit') ? $this->get_new_definition($storage_name, 'path') : $this->get_current_definition($storage_name, 'path'); + $path = $this->request->is_set_post('submit') ? $this->request->variable([$storage_name, 'path'], '') : $this->get_current_definition($storage_name, 'path'); if (empty($path)) { From fba8990fa08f153158ee895e54dd033671f8e211 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 7 Mar 2021 10:39:12 +0100 Subject: [PATCH 0213/1214] [ticket/15699] Add type hints, add use statements, improve code readability PHPBB3-15699 --- phpBB/includes/acp/acp_storage.php | 97 ++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 2ec086a232d..72de7997253 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -11,6 +11,17 @@ * */ +use phpbb\config\db as config; +use phpbb\config\db_text as config_text; +use phpbb\db\driver\driver_interface; +use phpbb\di\service_collection; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\path_helper; +use phpbb\request\request; +use phpbb\template\template; +use phpbb\user; + /** * @ignore */ @@ -21,37 +32,40 @@ class acp_storage { - /** @var \phpbb\config\config $config */ + /** @var config $config */ protected $config; - /** @var \phpbb\config\db_text $config_text */ + /** @var config_text $config_text */ protected $config_text; - /** @var \phpbb\db\driver\driver_interface $db */ + /** @var driver_interface $db */ protected $db; - /** @var \phpbb\language\language $log */ + /** @var language $log */ protected $lang; - /** @var \phpbb\log\log_interface $log */ + /** @var log_interface $log */ protected $log; - /** @var \phpbb\path_helper $path_helper */ + /** @var path_helper $path_helper */ protected $path_helper; - /** @var \phpbb\request\request */ + /** @var request */ protected $request; - /** @var \phpbb\template\template */ + /** @var template */ protected $template; - /** @var \phpbb\di\service_collection */ + /** @var user */ + protected $user; + + /** @var service_collection */ protected $adapter_collection; - /** @var \phpbb\di\service_collection */ + /** @var service_collection */ protected $provider_collection; - /** @var \phpbb\di\service_collection */ + /** @var service_collection */ protected $storage_collection; /** @var \phpbb\filesystem\filesystem */ @@ -422,7 +436,10 @@ public function settings($id, $mode) ]); } - protected function display_progress_bar() + /** + * Display progress bar + */ + protected function display_progress_bar() : void { adm_page_header($this->lang->lang('STORAGE_UPDATE_IN_PROGRESS')); $this->template->set_filenames(array( @@ -435,7 +452,12 @@ protected function display_progress_bar() adm_page_footer(); } - function close_popup_js() + /** + * Get JS code for closing popup + * + * @return string Popup JS code + */ + function close_popup_js() : string { return "\n"; } - protected function save_state() + /** + * Save state of storage update + */ + protected function save_state() : void { $state = $this->state; @@ -456,7 +481,10 @@ protected function save_state() $this->config_text->set('storage_update_state', json_encode($state)); } - protected function load_state() + /** + * Load state of storage update + */ + protected function load_state() : void { $state = json_decode($this->config_text->get('storage_update_state'), true); @@ -474,7 +502,7 @@ protected function load_state() * @param string $storage_name Storage name * @return string The current provider */ - protected function get_current_provider($storage_name) + protected function get_current_provider(string $storage_name) : string { return $this->config['storage\\' . $storage_name . '\\provider']; } @@ -485,7 +513,7 @@ protected function get_current_provider($storage_name) * @param string $provider Provider class * @return array Adapter definitions */ - protected function get_provider_options($provider) + protected function get_provider_options(string $provider) : array { return $this->provider_collection->get_by_class($provider)->get_options(); } @@ -497,7 +525,7 @@ protected function get_provider_options($provider) * @param string $definition Definition * @return string Definition value */ - protected function get_current_definition($storage_name, $definition) + protected function get_current_definition(string $storage_name, string $definition) : string { return $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; } @@ -508,7 +536,7 @@ protected function get_current_definition($storage_name, $definition) * @param string $storage_name Storage name * @param array $messages Reference to messages array */ - protected function validate_data($storage_name, &$messages) + protected function validate_data(string $storage_name, array &$messages) { $storage_title = $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); @@ -547,6 +575,8 @@ protected function validate_data($storage_name, &$messages) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); } + // no break + case 'text': case 'password': $maxlength = isset($definition_value['maxlength']) ? $definition_value['maxlength'] : 255; @@ -554,14 +584,15 @@ protected function validate_data($storage_name, &$messages) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); } - break; + break; + case 'radio': case 'select': if (!in_array($value, array_values($definition_value['options']))) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); } - break; + break; } } } @@ -571,7 +602,7 @@ protected function validate_data($storage_name, &$messages) * * @param string $storage_name Storage name */ - protected function update_storage_config($storage_name) + protected function update_storage_config(string $storage_name) : void { $current_options = $this->get_provider_options($this->get_current_provider($storage_name)); @@ -598,10 +629,10 @@ protected function update_storage_config($storage_name) * * @param string $storage_name Storage name * @param array $options Storage provider configuration keys - * @param array $messages Reference to error messages array + * @param array $messages Error messages array * @return void */ - protected function validate_path($storage_name, $options, &$messages) + protected function validate_path(string $storage_name, array $options, array &$messages) : void { if ($this->provider_collection->get_by_class($this->get_current_provider($storage_name))->get_name() == 'local' && isset($options['path'])) { @@ -618,7 +649,14 @@ protected function validate_path($storage_name, $options, &$messages) } } - protected function get_current_adapter($storage_name) + /** + * Get current storage adapter + * + * @param string $storage_name Storage adapter name + * + * @return object Storage adapter instance + */ + protected function get_current_adapter(string $storage_name): object { static $adapters = []; @@ -645,7 +683,14 @@ protected function get_current_adapter($storage_name) return $adapters[$storage_name]; } - protected function get_new_adapter($storage_name) + /** + * Get new storage adapter + * + * @param string $storage_name + * + * @return object Storage adapter instance + */ + protected function get_new_adapter(string $storage_name) : object { static $adapters = []; From 7b0fc16e640b081229efca45c028a32bd591a5f8 Mon Sep 17 00:00:00 2001 From: rubencm Date: Sun, 7 Mar 2021 21:19:11 +0100 Subject: [PATCH 0214/1214] [ticket/15699] Update language string PHPBB3-15699 --- phpBB/language/en/acp/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 1530af05229..47ef2acdfc1 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -56,7 +56,7 @@ 'START_UPDATING_EXPLAIN' => 'Start the storage update process', 'CONTINUE_UPDATING' => 'Continue previous update process', 'CONTINUE_UPDATING_EXPLAIN' => 'An update process has been started. In order to access the storage settings page you will have to complete it or cancel it.', - 'STORAGE_UPDATE_REDIRECT' => 'Files of %s (%d/%d) are being moved.
          ', + 'STORAGE_UPDATE_REDIRECT' => 'Files of %1$s (%2$d/%3$d) are being moved.
          ', 'STORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %s (%d/%d) are being removed.
          ', // Template progress bar From 43fb6ebe7a9539442f41fb2c646e311d3373d2d4 Mon Sep 17 00:00:00 2001 From: rubencm Date: Sun, 7 Mar 2021 21:19:32 +0100 Subject: [PATCH 0215/1214] [ticket/15699] Update language string PHPBB3-15699 --- phpBB/language/en/acp/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 47ef2acdfc1..2ed3a1b2a51 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -57,7 +57,7 @@ 'CONTINUE_UPDATING' => 'Continue previous update process', 'CONTINUE_UPDATING_EXPLAIN' => 'An update process has been started. In order to access the storage settings page you will have to complete it or cancel it.', 'STORAGE_UPDATE_REDIRECT' => 'Files of %1$s (%2$d/%3$d) are being moved.
          ', - 'STORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %s (%d/%d) are being removed.
          ', + 'STORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %1$s (%2$d/%3$d) are being removed.
          ', // Template progress bar 'STORAGE_UPDATE_IN_PROGRESS' => 'Storage update in progress', From 4b46939258a4a474c63609b99984d6a2cb678d88 Mon Sep 17 00:00:00 2001 From: rubencm Date: Sun, 21 Mar 2021 17:59:45 +0100 Subject: [PATCH 0216/1214] [ticket/15699] Apply suggested fixes PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 3 +-- phpBB/includes/acp/acp_storage.php | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index aa25b8feb3e..59389f1e445 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -5,7 +5,7 @@

          {{ lang('STORAGE_TITLE') }}

          {% if S_CONTINUE_UPDATING %} - +

          {{ lang('STORAGE_TITLE_EXPLAIN') }}

          -

          {{ lang('CONTINUE_EXPLAIN') }}

          +
          + + + + + + + + + + {% for storage in STORAGE_STATS %} + + + + + + + {% endfor %} + +
          {{ lang('STORAGE_NAME') }}{{ lang('STORAGE_NUM_FILES') }}{{ lang('STORAGE_SIZE') }}{{ lang('STORAGE_FREE') }}
          {{ storage.name }}{{ storage.files }}{{ storage.size }}{{ storage.free_space }}
          - -
          - {{ lang('SUBMIT') }} -   - - {{ S_FORM_TOKEN }} -
          - -{% else %} -

          {{ lang('STORAGE_TITLE_EXPLAIN') }}

          - - - - - - - - - - - - {% for storage in STORAGE_STATS %} - - - - - - - {% endfor %} - -
          {{ lang('STORAGE_NAME') }}{{ lang('STORAGE_NUM_FILES') }}{{ lang('STORAGE_SIZE') }}{{ lang('STORAGE_FREE') }}
          {{ storage.name }}{{ storage.files }}{{ storage.size }}{{ storage.free_space }}
          - - {% if S_ERROR %} -
          -

          {{ lang('WARNING') }}

          -

          {{ ERROR_MSG }}

          -
          - {% endif %} - -
          - {% for storage in STORAGES %} -
          - {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} -
          -

          {{ lang('STORAGE_SELECT_DESC') }}
          -
          - -
          -
          -
          - - {% for provider in PROVIDERS %} - {% if provider.is_available %} -
          - {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} - {{ lang('STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_NAME') }} - {% for name, options in provider.get_options %} - {% set title = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper %} - {% set description = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper ~ '_EXPLAIN' %} - {% set input_id = storage.get_name ~ '_' ~ provider.get_name ~ '_' ~ name %} - {% set input_type = options['type'] %} - {% set input_name = storage.get_name ~ '[' ~ name ~ ']' %} - {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %} -
          -
          - - {% if lang_defined(description) %} -
          {{ lang(description) }} - {% endif %} -
          -
          - {% if input_type in ['text', 'password', 'email'] %} - - {% elseif input_type == 'textarea' %} - - {% elseif input_type == 'radio' %} - {% for option_name, option_value in options['options'] %} - {{ lang(option_name) }} - {% endfor %} - {% elseif input_type == 'select' %} - - {% endif %} -
          -
          - {% endfor %} -
          - {% endif %} - {% endfor %} - {% endfor %} +{% if ERROR_MESSAGES is not empty %} +
          +

          {{ lang('WARNING') }}

          + {% for ERROR_MESSAGE in ERROR_MESSAGES %} +

          {{ ERROR_MESSAGE }}

          + {% endfor %} +
          +{% endif %} + + {% for storage in STORAGES %}
          + {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }}
          -
          +

          {{ lang('STORAGE_SELECT_DESC') }}
          - - - +
          -
          - {{ lang('SUBMIT') }} -   - - {{ S_FORM_TOKEN }} -
          -
          -{% endif %} + {% for provider in PROVIDERS %} + {% if provider.is_available %} +
          + {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} - {{ lang('STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_NAME') }} + {% for name, options in provider.get_options %} + {% set title = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper %} + {% set description = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper ~ '_EXPLAIN' %} + {% set input_id = storage.get_name ~ '_' ~ provider.get_name ~ '_' ~ name %} + {% set input_type = options['type'] %} + {% set input_name = storage.get_name ~ '[' ~ name ~ ']' %} + {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %} +
          +
          + + {% if lang_defined(description) %} +
          {{ lang(description) }} + {% endif %} +
          +
          + {% if input_type in ['text', 'password', 'email'] %} + + {% elseif input_type == 'textarea' %} + + {% elseif input_type == 'radio' %} + {% for option_name, option_value in options['options'] %} + {{ lang(option_name) }} + {% endfor %} + {% elseif input_type == 'select' %} + + {% endif %} +
          +
          + {% endfor %} +
          + {% endif %} + {% endfor %} + {% endfor %} + +
          +
          +
          +
          + + + +
          +
          +
          + +
          + {{ lang('SUBMIT') }} +   + + {{ S_FORM_TOKEN }} +
          + {% include 'overall_footer.html' %} diff --git a/phpBB/adm/style/acp_storage_update_inprogress.html b/phpBB/adm/style/acp_storage_update_inprogress.html new file mode 100644 index 00000000000..9c4b9fd8040 --- /dev/null +++ b/phpBB/adm/style/acp_storage_update_inprogress.html @@ -0,0 +1,32 @@ +{% include 'overall_header.html' %} + + + +

          {{ lang('STORAGE_TITLE') }}

          + + + +

          {{ lang('CONTINUE_EXPLAIN') }}

          + +
          +
          + {{ lang('SUBMIT') }} +   + + {{ S_FORM_TOKEN }} +
          +
          + +{% include 'overall_footer.html' %} diff --git a/phpBB/config/default/container/services_storage.yml b/phpBB/config/default/container/services_storage.yml index 4ef28ccf3b6..401357861d3 100644 --- a/phpBB/config/default/container/services_storage.yml +++ b/phpBB/config/default/container/services_storage.yml @@ -107,3 +107,20 @@ services: - '@storage.attachment' - '@symfony_request' - '@user' + +# Helpers + storage.state_helper: + class: phpbb\storage\state_helper + arguments: + - '@config' + - '@config_text' + - '@storage.provider_collection' + + storage.helper: + class: phpbb\storage\helper + arguments: + - '@config' + - '@storage.provider_collection' + - '@storage.adapter_collection' + - '@storage.adapter.factory' + - '@storage.state_helper' diff --git a/phpBB/includes/acp/acp_database.php b/phpBB/includes/acp/acp_database.php index 7b8dcc3ee52..85943dc5e49 100644 --- a/phpBB/includes/acp/acp_database.php +++ b/phpBB/includes/acp/acp_database.php @@ -287,7 +287,7 @@ function main($id, $mode) fclose($fp); fclose($stream); } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { trigger_error($user->lang['RESTORE_DOWNLOAD_FAIL'] . adm_back_link($this->u_action)); } diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index d268476b463..996a0f641b4 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -11,14 +11,14 @@ * */ -use phpbb\config\db as config; -use phpbb\config\db_text as config_text; use phpbb\db\driver\driver_interface; use phpbb\di\service_collection; use phpbb\language\language; use phpbb\log\log_interface; -use phpbb\path_helper; use phpbb\request\request; +use phpbb\storage\helper; +use phpbb\storage\state_helper; +use phpbb\storage\update_type; use phpbb\template\template; use phpbb\user; @@ -32,12 +32,6 @@ class acp_storage { - /** @var config $config */ - protected $config; - - /** @var config_text $config_text */ - protected $config_text; - /** @var driver_interface $db */ protected $db; @@ -47,9 +41,6 @@ class acp_storage /** @var log_interface $log */ protected $log; - /** @var path_helper $path_helper */ - protected $path_helper; - /** @var request */ protected $request; @@ -59,9 +50,6 @@ class acp_storage /** @var user */ protected $user; - /** @var service_collection */ - protected $adapter_collection; - /** @var service_collection */ protected $provider_collection; @@ -83,52 +71,44 @@ class acp_storage /** @var string */ public $u_action; - /** @var mixed */ - protected $state; + /** @var state_helper */ + private $state_helper; - /** - * Update type constants - */ - public const STORAGE_UPDATE_TYPE_CONFIG = 0; - public const STORAGE_UPDATE_TYPE_COPY = 1; - public const STORAGE_UPDATE_TYPE_MOVE = 2; + /** @var helper */ + private $storage_helper; /** * @param string $id * @param string $mode */ - public function main(string $id, string $mode) + public function main(string $id, string $mode): void { global $phpbb_container, $phpbb_dispatcher, $phpbb_root_path; - $this->config = $phpbb_container->get('config'); - $this->config_text = $phpbb_container->get('config_text'); $this->db = $phpbb_container->get('dbal.conn'); - $this->filesystem = $phpbb_container->get('filesystem'); $this->lang = $phpbb_container->get('language'); $this->log = $phpbb_container->get('log'); - $this->path_helper = $phpbb_container->get('path_helper'); $this->request = $phpbb_container->get('request'); $this->template = $phpbb_container->get('template'); $this->user = $phpbb_container->get('user'); - $this->adapter_collection = $phpbb_container->get('storage.adapter_collection'); $this->provider_collection = $phpbb_container->get('storage.provider_collection'); $this->storage_collection = $phpbb_container->get('storage.storage_collection'); + $this->filesystem = $phpbb_container->get('filesystem'); $this->phpbb_root_path = $phpbb_root_path; + $this->state_helper = $phpbb_container->get('storage.state_helper'); + $this->storage_helper = $phpbb_container->get('storage.helper'); - // Add necesary language files + // Add necessary language files $this->lang->add_lang(['acp/storage']); /** * Add language strings * * @event core.acp_storage_load - * @since 3.3.0-a1 + * @since 4.0.0-a1 */ $phpbb_dispatcher->trigger_event('core.acp_storage_load'); - @ini_set('memory_limit', '128M'); - switch ($mode) { case 'settings': @@ -141,173 +121,173 @@ public function main(string $id, string $mode) * @param string $id * @param string $mode */ - public function settings(string $id, string $mode) + private function settings(string $id, string $mode): void { - $form_key = 'acp_storage'; - add_form_key($form_key); - - // Template from adm/style - $this->tpl_name = 'acp_storage'; - - // Set page title - $this->page_title = 'STORAGE_TITLE'; - $action = $this->request->variable('action', ''); - $this->load_state(); - - // If user cancelled to continue, remove state - if ($this->request->is_set_post('cancel')) + if ($action && !$this->request->is_set_post('cancel')) { - if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + switch ($action) { - trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); - } + case 'progress_bar': + $this->display_progress_bar(); + break; - if ($this->request->variable('cancel', false)) - { - $action = ''; - $this->state = false; - $this->save_state(); + case 'update': + $this->update_action(); + break; + + default: + trigger_error('NO_ACTION', E_USER_ERROR); } } - - if ($action) + else { - if ($action == 'progress_bar') + // If clicked to cancel (acp_storage_update_progress form) + if ($this->request->is_set_post('cancel')) + { + $this->state_helper->clear_state(); + } + + // There is an updating in progress, show the form to continue or cancel + if ($this->state_helper->is_action_in_progress()) { - $this->display_progress_bar(); + $this->update_inprogress($id, $mode); } - else if ($action != 'update') + else { - trigger_error('NO_ACTION', E_USER_ERROR); + $this->settings_form($id, $mode); } + } + } + + private function update_action(): void + { + // Probably it has sense to disable the forum while this is in progress - if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) + { + trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } + + // If update_type is copy or move, copy files from the old to the new storage + if (in_array($this->state_helper->update_type(), [update_type::COPY, update_type::MOVE], true)) + { + $i = 0; + foreach ($this->state_helper->storages() as $storage_name) { - trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + // Skip storages that have already copied files + if ($this->state_helper->storage_index() > $i++) + { + continue; + } + + $sql = 'SELECT file_id, file_path + FROM ' . STORAGE_TABLE . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state_helper->file_index(); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!still_on_time()) + { + $this->db->sql_freeresult($result); + meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); + // Here could be included the current file compared with the number of total files too + trigger_error($this->lang->lang('STORAGE_UPDATE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state_helper->storages()))); + } + + // Copy file from old adapter to the new one + $this->storage_helper->copy_file_to_new_adapter($storage_name, $row['file_path']); + + $this->state_helper->set_file_index($row['file_id']); // update last file index copied + } + + $this->db->sql_freeresult($result); + + // Copied all files of a storage, increase storage index and reset file index + $this->state_helper->set_storage_index($this->state_helper->storage_index()+1); + $this->state_helper->set_file_index(0); } - // If update_type is copy or move, copy files from the old to the new storage - if (in_array($this->state['update_type'], [self::STORAGE_UPDATE_TYPE_COPY, self::STORAGE_UPDATE_TYPE_MOVE], true)) + // If update_type is move files, remove the old files + if ($this->state_helper->update_type() === update_type::MOVE) { $i = 0; - foreach ($this->state['storages'] as $storage_name => $storage_options) + foreach ($this->state_helper->storages() as $storage_name) { // Skip storages that have already moved files - if ($this->state['storage_index'] > $i) + if ($this->state_helper->remove_storage_index() > $i++) { - $i++; continue; } - $current_adapter = $this->get_current_adapter($storage_name); - $new_adapter = $this->get_new_adapter($storage_name); - $sql = 'SELECT file_id, file_path - FROM ' . STORAGE_TABLE . " - WHERE storage = '" . $this->db->sql_escape($storage_name) . "' - AND file_id > " . (int) $this->state['file_index']; + FROM ' . STORAGE_TABLE . " + WHERE storage = '" . $this->db->sql_escape($storage_name) . "' + AND file_id > " . $this->state_helper->file_index(); $result = $this->db->sql_query($sql); while ($row = $this->db->sql_fetchrow($result)) { if (!still_on_time()) { - $this->save_state(); + $this->db->sql_freeresult($result); meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->lang->lang('self::STORAGE_UPDATE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); + trigger_error($this->lang->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state_helper->storages()))); } - $stream = $current_adapter->read_stream($row['file_path']); - $new_adapter->write_stream($row['file_path'], $stream); + // remove file from old (current) adapter + $current_adapter = $this->storage_helper->get_current_adapter($storage_name); + $current_adapter->delete($row['file_path']); - if (is_resource($stream)) - { - fclose($stream); - } - - $this->state['file_index'] = $row['file_id']; // Set last uploaded file + $this->state_helper->set_file_index($row['file_id']); } - // Copied all files of a storage, increase storage index and reset file index - $this->state['storage_index']++; - $this->state['file_index'] = 0; - } - - // If update_type is move files, remove the old files - if ($this->state['update_type'] === self::STORAGE_UPDATE_TYPE_MOVE) - { - $i = 0; - foreach ($this->state['storages'] as $storage_name => $storage_options) - { - // Skip storages that have already moved files - if ($this->state['remove_storage_index'] > $i) - { - $i++; - continue; - } + $this->db->sql_freeresult($result); - $current_adapter = $this->get_current_adapter($storage_name); - - $sql = 'SELECT file_id, file_path - FROM ' . STORAGE_TABLE . " - WHERE storage = '" . $this->db->sql_escape($storage_name) . "' - AND file_id > " . (int) $this->state['file_index']; - $result = $this->db->sql_query($sql); - - while ($row = $this->db->sql_fetchrow($result)) - { - if (!still_on_time()) - { - $this->save_state(); - meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->lang->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state['storages']))); - } - - $current_adapter->delete($row['file_path']); - - $this->state['file_index'] = $row['file_id']; // Set last uploaded file - } - - // Remove all files of a storage, increase storage index and reset file index - $this->state['remove_storage_index']++; - $this->state['file_index'] = 0; - } + // Remove all files of a storage, increase storage index and reset file index + $this->state_helper->set_remove_storage_index($this->state_helper->remove_storage_index()+1); + $this->state_helper->set_file_index(0); } } + } - // Here all files have been copied/moved, so save new configuration - foreach (array_keys($this->state['storages']) as $storage_name) - { - $this->update_storage_config($storage_name); - } + // Here all files have been copied/moved, so save new configuration + foreach ($this->state_helper->storages() as $storage_name) + { + $this->storage_helper->update_storage_config($storage_name); + } - $storages = array_keys($this->state['storages']); - $this->state = false; - $this->save_state(); + $storages = $this->state_helper->storages(); + $this->state_helper->clear_state(); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false, [implode(', ', $storages)]); + trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); + } - $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false, $storages); - trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); - } + private function update_inprogress(string $id, string $mode): void + { + // Template from adm/style + $this->tpl_name = 'acp_storage_update_inprogress'; - // There is an updating in progress, show the form to continue or cancel - if ($this->state != false) - { - $this->template->assign_vars(array( - 'UA_PROGRESS_BAR' => addslashes(append_sid($this->path_helper->get_phpbb_root_path() . $this->path_helper->get_adm_relative_path() . "index." . $this->path_helper->get_php_ext(), "i=$id&mode=$mode&action=progress_bar")), - 'S_CONTINUE_UPDATING' => true, - 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), - 'L_CONTINUE' => $this->lang->lang('CONTINUE_UPDATING'), - 'L_CONTINUE_EXPLAIN' => $this->lang->lang('CONTINUE_UPDATING_EXPLAIN'), - )); + // Set page title + $this->page_title = 'STORAGE_TITLE'; - return; - } + $this->template->assign_vars(array( + 'UA_PROGRESS_BAR' => addslashes(append_sid($this->u_action, "action=progress_bar")), + 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), + 'L_CONTINUE' => $this->lang->lang('CONTINUE_UPDATING'), + 'L_CONTINUE_EXPLAIN' => $this->lang->lang('CONTINUE_UPDATING_EXPLAIN'), + )); + } + + private function settings_form(string $id, string $mode): void + { + $form_key = 'acp_storage'; + add_form_key($form_key); // Process form and create a "state" for the update, // then show a confirm form - $messages = []; - if ($this->request->is_set_post('submit')) { if (!check_form_key($form_key) || !check_link_hash($this->request->variable('hash', ''), 'acp_storage')) @@ -315,109 +295,125 @@ public function settings(string $id, string $mode) trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } - $modified_storages = []; + $modified_storages = $this->get_modified_storages(); - foreach ($this->storage_collection as $storage) + // validate submited paths if they are local + $messages = []; + foreach ($modified_storages as $storage_name) { - $storage_name = $storage->get_name(); - - $options = $this->get_provider_options($this->get_current_provider($storage_name)); + $this->validate_data($storage_name, $messages); + } + if (!empty($messages)) + { + trigger_error(implode('
          ', $messages) . adm_back_link($this->u_action), E_USER_WARNING); + } - $this->validate_path($storage_name, $options, $messages); + // Start process and show form + if (!empty($modified_storages)) + { + // Create state + $this->state_helper->init(update_type::from((int) $this->request->variable('update_type', update_type::CONFIG->value)), $modified_storages, $this->request); - $modified = false; + // Show the confirmation form to start the process + $this->template->assign_vars(array( + 'UA_PROGRESS_BAR' => addslashes(append_sid($this->u_action, "action=progress_bar")), + 'S_CONTINUE_UPDATING' => true, + 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), + 'L_CONTINUE' => $this->lang->lang('START_UPDATING'), + 'L_CONTINUE_EXPLAIN' => $this->lang->lang('START_UPDATING_EXPLAIN'), + )); - // Check if provider have been modified - if ($this->request->variable([$storage_name, 'provider'], '') != $this->get_current_provider($storage_name)) - { - $modified = true; - } + // Template from adm/style + $this->tpl_name = 'acp_storage_update_inprogress'; - // Check if options have been modified - if (!$modified) - { - foreach (array_keys($options) as $definition) - { - if ($this->request->variable([$storage_name, $definition], '') != $this->get_current_definition($storage_name, $definition)) - { - $modified = true; - break; - } - } - } + // Set page title + $this->page_title = 'STORAGE_TITLE'; - // If the storage have been modified, validate options - if ($modified) - { - $modified_storages[] = $storage_name; - $this->validate_data($storage_name, $messages); - } + return; } - if (!empty($modified_storages)) - { - if (empty($messages)) - { - // Create state - $this->state = [ - // Save the value of the checkbox, to remove all files from the - // old storage once they have been successfully moved - 'update_type' => (int) $this->request->variable('update_type', self::STORAGE_UPDATE_TYPE_CONFIG), - 'storage_index' => 0, - 'file_index' => 0, - 'remove_storage_index' => 0, - ]; - - // Save in the state the selected storages and their configuration - foreach ($modified_storages as $storage_name) - { - $this->state['storages'][$storage_name]['provider'] = $this->request->variable([$storage_name, 'provider'], ''); + // If there is no changes + trigger_error($this->lang->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); + } - $options = $this->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); + // Template from adm/style + $this->tpl_name = 'acp_storage'; - foreach (array_keys($options) as $definition) - { - $this->state['storages'][$storage_name]['config'][$definition] = $this->request->variable([$storage_name, $definition], ''); - } - } + // Set page title + $this->page_title = 'STORAGE_TITLE'; - $this->save_state(); // A storage update is going to be done here + $this->storage_stats(); // Show table with storage stats - // Show the confirmation form to start the process - $this->template->assign_vars(array( - 'UA_PROGRESS_BAR' => addslashes(append_sid($this->path_helper->get_phpbb_root_path() . $this->path_helper->get_adm_relative_path() . "index." . $this->path_helper->get_php_ext(), "i=$id&mode=$mode&action=progress_bar")), // same - 'S_CONTINUE_UPDATING' => true, - 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), - 'L_CONTINUE' => $this->lang->lang('START_UPDATING'), - 'L_CONTINUE_EXPLAIN' => $this->lang->lang('START_UPDATING_EXPLAIN'), - )); + // Validate local paths to check if everything is fine + $messages = []; + foreach ($this->storage_collection as $storage) + { + $this->validate_path($storage->get_name(), $messages); + } - return; - } - else + $this->template->assign_vars([ + 'STORAGES' => $this->storage_collection, + 'PROVIDERS' => $this->provider_collection, + + 'ERROR_MESSAGES' => $messages, + + 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_storage'), + + 'STORAGE_UPDATE_TYPE_CONFIG' => update_type::CONFIG->value, + 'STORAGE_UPDATE_TYPE_COPY' => update_type::COPY->value, + 'STORAGE_UPDATE_TYPE_MOVE' => update_type::MOVE->value, + ]); + } + + private function get_modified_storages(): array + { + $modified_storages = []; + + foreach ($this->storage_collection as $storage) + { + $storage_name = $storage->get_name(); + $options = $this->storage_helper->get_provider_options($this->storage_helper->get_current_provider($storage_name)); + + $modified = false; + + // Check if provider have been modified + if ($this->request->variable([$storage_name, 'provider'], '') != $this->storage_helper->get_current_provider($storage_name)) + { + $modified = true; + } + else + { + // Check if options have been modified + foreach (array_keys($options) as $definition) { - trigger_error(implode('
          ', $messages) . adm_back_link($this->u_action), E_USER_WARNING); + if ($this->request->variable([$storage_name, $definition], '') != $this->storage_helper->get_current_definition($storage_name, $definition)) + { + $modified = true; + break; + } } } - // If there is no changes - trigger_error($this->lang->lang('STORAGE_NO_CHANGES') . adm_back_link($this->u_action), E_USER_WARNING); + if ($modified) + { + $modified_storages[] = $storage_name; + } } + return $modified_storages; + } + + protected function storage_stats() + { // Top table with stats of each storage $storage_stats = []; foreach ($this->storage_collection as $storage) { - $storage_name = $storage->get_name(); - $options = $this->get_provider_options($this->get_current_provider($storage_name)); - - $this->validate_path($storage_name, $options, $messages); - try { $free_space = get_formatted_filesize($storage->free_space()); } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { $free_space = $this->lang->lang('STORAGE_UNKNOWN'); } @@ -431,18 +427,7 @@ public function settings(string $id, string $mode) } $this->template->assign_vars([ - 'STORAGES' => $this->storage_collection, - 'STORAGE_STATS' => $storage_stats, - 'PROVIDERS' => $this->provider_collection, - - 'ERROR_MSG' => implode('
          ', $messages), - 'S_ERROR' => !empty($messages), - - 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_storage'), - - 'STORAGE_UPDATE_TYPE_CONFIG' => self::STORAGE_UPDATE_TYPE_CONFIG, - 'STORAGE_UPDATE_TYPE_COPY' => self::STORAGE_UPDATE_TYPE_COPY, - 'STORAGE_UPDATE_TYPE_MOVE' => self::STORAGE_UPDATE_TYPE_MOVE, + 'STORAGE_STATS' => $storage_stats, ]); } @@ -453,11 +438,11 @@ protected function display_progress_bar() : void { adm_page_header($this->lang->lang('STORAGE_UPDATE_IN_PROGRESS')); $this->template->set_filenames(array( - 'body' => 'progress_bar.html') + 'body' => 'progress_bar.html') ); $this->template->assign_vars(array( - 'L_PROGRESS' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS'), - 'L_PROGRESS_EXPLAIN' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN')) + 'L_PROGRESS' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS'), + 'L_PROGRESS_EXPLAIN' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN')) ); adm_page_footer(); } @@ -476,70 +461,6 @@ function close_popup_js() : string "\n"; } - /** - * Save state of storage update - */ - protected function save_state() : void - { - $state = $this->state; - - if ($state == false) - { - $state = []; - } - - $this->config_text->set('storage_update_state', json_encode($state)); - } - - /** - * Load state of storage update - */ - protected function load_state() : void - { - $state = json_decode($this->config_text->get('storage_update_state'), true); - - if ($state == null || empty($state)) - { - $state = false; - } - - $this->state = $state; - } - - /** - * Get the current provider from config - * - * @param string $storage_name Storage name - * @return string The current provider - */ - protected function get_current_provider(string $storage_name) : string - { - return $this->config['storage\\' . $storage_name . '\\provider']; - } - - /** - * Get adapter definitions from a provider - * - * @param string $provider Provider class - * @return array Adapter definitions - */ - protected function get_provider_options(string $provider) : array - { - return $this->provider_collection->get_by_class($provider)->get_options(); - } - - /** - * Get the current value of the definition of a storage from config - * - * @param string $storage_name Storage name - * @param string $definition Definition - * @return string Definition value - */ - protected function get_current_definition(string $storage_name, string $definition) : string - { - return $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; - } - /** * Validates data * @@ -569,7 +490,7 @@ protected function validate_data(string $storage_name, array &$messages) } // Check options - $new_options = $this->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); + $new_options = $this->storage_helper->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); foreach ($new_options as $definition_key => $definition_value) { @@ -594,6 +515,20 @@ protected function validate_data(string $storage_name, array &$messages) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); } + + if ($provider->get_name() == 'local' && $definition_key == 'path') + { + $path = $value; + + if (empty($path)) + { + $messages[] = $this->lang->lang('STORAGE_PATH_NOT_SET', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); + } + else if (!$this->filesystem->exists($this->phpbb_root_path . $path) || !$this->filesystem->is_writable($this->phpbb_root_path . $path)) + { + $messages[] = $this->lang->lang('STORAGE_PATH_NOT_EXISTS', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); + } + } break; case 'radio': @@ -608,122 +543,31 @@ protected function validate_data(string $storage_name, array &$messages) } /** - * Updates an storage with the info provided in the form - * - * @param string $storage_name Storage name - */ - protected function update_storage_config(string $storage_name) : void - { - $current_options = $this->get_provider_options($this->get_current_provider($storage_name)); - - // Remove old storage config - foreach (array_keys($current_options) as $definition) - { - $this->config->delete('storage\\' . $storage_name . '\\config\\' . $definition); - } - - // Update provider - $this->config->set('storage\\' . $storage_name . '\\provider', $this->state['storages'][$storage_name]['provider']); - - // Set new storage config - $new_options = $this->get_provider_options($this->state['storages'][$storage_name]['provider']); - - foreach (array_keys($new_options) as $definition) - { - $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $this->state['storages'][$storage_name]['config'][$definition]); - } - } - - /** - * Validates path + * Validates path when the filesystem is local * * @param string $storage_name Storage name - * @param array $options Storage provider configuration keys * @param array $messages Error messages array * @return void */ - protected function validate_path(string $storage_name, array $options, array &$messages) : void + protected function validate_path(string $storage_name, array &$messages) : void { - if ($this->provider_collection->get_by_class($this->get_current_provider($storage_name))->get_name() == 'local' && isset($options['path'])) + $current_provider = $this->storage_helper->get_current_provider($storage_name); + $options = $this->storage_helper->get_provider_options($current_provider); + + if ($this->provider_collection->get_by_class($current_provider)->get_name() == 'local' && isset($options['path'])) { - $path = $this->request->is_set_post('submit') ? $this->request->variable([$storage_name, 'path'], '') : $this->get_current_definition($storage_name, 'path'); + $path = $this->storage_helper->get_current_definition($storage_name, 'path'); if (empty($path)) { $messages[] = $this->lang->lang('STORAGE_PATH_NOT_SET', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); } - else if (!$this->filesystem->is_writable($this->phpbb_root_path . $path) || !$this->filesystem->exists($this->phpbb_root_path . $path)) + else if (!$this->filesystem->exists($this->phpbb_root_path . $path) || !$this->filesystem->is_writable($this->phpbb_root_path . $path)) { $messages[] = $this->lang->lang('STORAGE_PATH_NOT_EXISTS', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE')); } } } - /** - * Get current storage adapter - * - * @param string $storage_name Storage adapter name - * - * @return object Storage adapter instance - */ - protected function get_current_adapter(string $storage_name): object - { - static $adapters = []; - - if (!isset($adapters[$storage_name])) - { - $provider = $this->get_current_provider($storage_name); - $provider_class = $this->provider_collection->get_by_class($provider); - - $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); - $definitions = $this->get_provider_options($provider); - - $options = []; - foreach (array_keys($definitions) as $definition) - { - $options[$definition] = $this->get_current_definition($storage_name, $definition); - } - - $adapter->configure($options); - //$adapter->set_storage($storage_name); - - $adapters[$storage_name] = $adapter; - } - - return $adapters[$storage_name]; - } - /** - * Get new storage adapter - * - * @param string $storage_name - * - * @return object Storage adapter instance - */ - protected function get_new_adapter(string $storage_name) : object - { - static $adapters = []; - - if (!isset($adapters[$storage_name])) - { - $provider = $this->state['storages'][$storage_name]['provider']; - $provider_class = $this->provider_collection->get_by_class($provider); - - $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); - $definitions = $this->get_provider_options($provider); - - $options = []; - foreach (array_keys($definitions) as $definition) - { - $options[$definition] = $this->state['storages'][$storage_name]['config'][$definition]; - } - - $adapter->configure($options); - //$adapter->set_storage($storage_name); - - $adapters[$storage_name] = $adapter; - } - - return $adapters[$storage_name]; - } } diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index c41e54098e1..737332f4fac 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -1789,7 +1789,7 @@ function avatar_delete($mode, $row, $clean_db = false) return true; } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { // Fail is covered by return statement below } @@ -2131,7 +2131,7 @@ function group_correct_avatar($group_id, $old_entry) WHERE group_id = $group_id"; $db->sql_query($sql); } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { // If rename fail, dont execute the query } diff --git a/phpBB/phpbb/attachment/delete.php b/phpBB/phpbb/attachment/delete.php index 80fd6b62d4b..2620a7a94f4 100644 --- a/phpBB/phpbb/attachment/delete.php +++ b/phpBB/phpbb/attachment/delete.php @@ -464,7 +464,7 @@ public function unlink_attachment($filename, $mode = 'file', $entry_removed = fa return true; } } - catch (\phpbb\storage\exception\exception $exception) + catch (\phpbb\storage\exception\storage_exception $exception) { // Fail is covered by return statement below } diff --git a/phpBB/phpbb/attachment/upload.php b/phpBB/phpbb/attachment/upload.php index d5b961de5f6..c0b0c490c44 100644 --- a/phpBB/phpbb/attachment/upload.php +++ b/phpBB/phpbb/attachment/upload.php @@ -351,7 +351,7 @@ protected function check_disk_space() return false; } } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { // Do nothing } diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php index 14b9e4026cb..51425b506ff 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -19,7 +19,7 @@ use phpbb\files\factory; use phpbb\path_helper; use phpbb\routing\helper; -use phpbb\storage\exception\exception as storage_exception; +use phpbb\storage\exception\storage_exception; use phpbb\storage\storage; /** diff --git a/phpBB/phpbb/db/migration/data/v400/storage_track.php b/phpBB/phpbb/db/migration/data/v400/storage_track.php index 767eee5c0d3..7cf05503d65 100644 --- a/phpBB/phpbb/db/migration/data/v400/storage_track.php +++ b/phpBB/phpbb/db/migration/data/v400/storage_track.php @@ -14,7 +14,7 @@ namespace phpbb\db\migration\data\v400; use phpbb\db\migration\container_aware_migration; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; use phpbb\storage\storage; class storage_track extends container_aware_migration @@ -97,7 +97,7 @@ public function track_avatars() { $storage->track_file($this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } @@ -121,7 +121,7 @@ public function track_attachments() { $storage->track_file($row['physical_filename']); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } @@ -132,7 +132,7 @@ public function track_attachments() { $storage->track_file('thumb_' . $row['physical_filename']); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } @@ -157,7 +157,7 @@ public function track_backups() { $storage->track_file($row['filename']); } - catch (exception $e) + catch (storage_exception $e) { // If file doesn't exist, don't track it } diff --git a/phpBB/phpbb/files/filespec_storage.php b/phpBB/phpbb/files/filespec_storage.php index e7759357562..83678718b5a 100644 --- a/phpBB/phpbb/files/filespec_storage.php +++ b/phpBB/phpbb/files/filespec_storage.php @@ -453,7 +453,7 @@ public function move_file($storage, $overwrite = false, $skip_image_check = fals fclose($fp); } } - catch (\phpbb\storage\exception\exception $e) + catch (\phpbb\storage\exception\storage_exception $e) { $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); $this->file_moved = false; diff --git a/phpBB/phpbb/storage/adapter/adapter_interface.php b/phpBB/phpbb/storage/adapter/adapter_interface.php index 5bd6525a733..725b954c2d3 100644 --- a/phpBB/phpbb/storage/adapter/adapter_interface.php +++ b/phpBB/phpbb/storage/adapter/adapter_interface.php @@ -13,7 +13,7 @@ namespace phpbb\storage\adapter; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; interface adapter_interface { @@ -29,7 +29,7 @@ public function configure(array $options): void; * * @param string $path * @param string $content - * @throws exception When the file cannot be written + * @throws storage_exception When the file cannot be written */ public function put_contents(string $path, string $content): void; @@ -39,7 +39,7 @@ public function put_contents(string $path, string $content): void; * @param string $path The file to read * * @return string Returns file contents - * @throws exception When cannot read file contents + * @throws storage_exception When cannot read file contents */ public function get_contents(string $path): string; @@ -57,7 +57,7 @@ public function exists(string $path): bool; * * @param string $path file/directory to remove * - * @throws exception When removal fails. + * @throws storage_exception When removal fails. */ public function delete(string $path): void; @@ -67,7 +67,7 @@ public function delete(string $path): void; * @param string $path_orig The original file/direcotry * @param string $path_dest The target file/directory * - * @throws exception When file/directory cannot be renamed + * @throws storage_exception When file/directory cannot be renamed */ public function rename(string $path_orig, string $path_dest): void; @@ -77,7 +77,7 @@ public function rename(string $path_orig, string $path_dest): void; * @param string $path_orig The original filename * @param string $path_dest The target filename * - * @throws exception When the file cannot be copied + * @throws storage_exception When the file cannot be copied */ public function copy(string $path_orig, string $path_dest): void; @@ -94,7 +94,7 @@ public function get_link(string $path): string; * Get space available in bytes * * @return float Returns available space - * @throws exception When unable to retrieve available storage space + * @throws storage_exception When unable to retrieve available storage space */ public function free_space(): float; } diff --git a/phpBB/phpbb/storage/adapter/local.php b/phpBB/phpbb/storage/adapter/local.php index be4b3399d69..9923a4fa441 100644 --- a/phpBB/phpbb/storage/adapter/local.php +++ b/phpBB/phpbb/storage/adapter/local.php @@ -14,7 +14,7 @@ namespace phpbb\storage\adapter; use phpbb\storage\stream_interface; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; use phpbb\filesystem\exception\filesystem_exception; use phpbb\filesystem\filesystem; use phpbb\filesystem\helper as filesystem_helper; @@ -117,7 +117,7 @@ public function put_contents(string $path, string $content): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_WRITE_FILE', $path, array(), $e); + throw new storage_exception('STORAGE_CANNOT_WRITE_FILE', $path, array(), $e); } } @@ -130,7 +130,7 @@ public function get_contents(string $path): string if ($content === false) { - throw new exception('STORAGE_CANNOT_READ_FILE', $path); + throw new storage_exception('STORAGE_CANNOT_READ_FILE', $path); } return $content; @@ -155,7 +155,7 @@ public function delete(string $path): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_DELETE', $path, array(), $e); + throw new storage_exception('STORAGE_CANNOT_DELETE', $path, array(), $e); } } @@ -172,7 +172,7 @@ public function rename(string $path_orig, string $path_dest): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_RENAME', $path_orig, array(), $e); + throw new storage_exception('STORAGE_CANNOT_RENAME', $path_orig, array(), $e); } } @@ -189,7 +189,7 @@ public function copy(string $path_orig, string $path_dest): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_COPY', $path_orig, array(), $e); + throw new storage_exception('STORAGE_CANNOT_COPY', $path_orig, array(), $e); } } @@ -198,7 +198,7 @@ public function copy(string $path_orig, string $path_dest): void * * @param string $path The directory path * - * @throws exception On any directory creation failure + * @throws storage_exception On any directory creation failure */ protected function create_dir(string $path): void { @@ -208,7 +208,7 @@ protected function create_dir(string $path): void } catch (filesystem_exception $e) { - throw new exception('STORAGE_CANNOT_CREATE_DIR', $path, array(), $e); + throw new storage_exception('STORAGE_CANNOT_CREATE_DIR', $path, array(), $e); } } @@ -217,7 +217,7 @@ protected function create_dir(string $path): void * * @param string $path The file path * - * @throws exception On any directory creation failure + * @throws storage_exception On any directory creation failure */ protected function ensure_directory_exists(string $path): void { @@ -264,7 +264,7 @@ public function read_stream(string $path) if (!$stream) { - throw new exception('STORAGE_CANNOT_OPEN_FILE', $path); + throw new storage_exception('STORAGE_CANNOT_OPEN_FILE', $path); } return $stream; @@ -281,13 +281,13 @@ public function write_stream(string $path, $resource): void if (!$stream) { - throw new exception('STORAGE_CANNOT_CREATE_FILE', $path); + throw new storage_exception('STORAGE_CANNOT_CREATE_FILE', $path); } if (stream_copy_to_stream($resource, $stream) === false) { fclose($stream); - throw new exception('STORAGE_CANNOT_COPY_RESOURCE'); + throw new storage_exception('STORAGE_CANNOT_COPY_RESOURCE'); } fclose($stream); @@ -298,10 +298,10 @@ public function write_stream(string $path, $resource): void * * @param string $path The file * - * @throws exception When cannot get size - * * @return array Properties - * @throws exception When cannot get size + * @throws storage_exception When cannot get size + * + * @throws storage_exception When cannot get size * */ public function file_size(string $path): array @@ -310,7 +310,7 @@ public function file_size(string $path): array if ($size === null) { - throw new exception('STORAGE_CANNOT_GET_FILESIZE'); + throw new storage_exception('STORAGE_CANNOT_GET_FILESIZE'); } return ['size' => $size]; @@ -392,12 +392,12 @@ public function free_space(): float if ($free_space === false) { - throw new exception('STORAGE_CANNOT_GET_FREE_SPACE'); + throw new storage_exception('STORAGE_CANNOT_GET_FREE_SPACE'); } } else { - throw new exception('STORAGE_CANNOT_GET_FREE_SPACE'); + throw new storage_exception('STORAGE_CANNOT_GET_FREE_SPACE'); } return $free_space; diff --git a/phpBB/phpbb/storage/adapter_factory.php b/phpBB/phpbb/storage/adapter_factory.php index 5d08bfa7c20..b358328525f 100644 --- a/phpBB/phpbb/storage/adapter_factory.php +++ b/phpBB/phpbb/storage/adapter_factory.php @@ -15,7 +15,7 @@ use phpbb\config\config; use phpbb\di\service_collection; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; class adapter_factory { @@ -62,7 +62,7 @@ public function get($storage_name) if (!$provider->is_available()) { - throw new exception('STORAGE_ADAPTER_NOT_AVAILABLE'); + throw new storage_exception('STORAGE_ADAPTER_NOT_AVAILABLE'); } $adapter = $this->adapters->get_by_class($provider->get_adapter_class()); diff --git a/phpBB/phpbb/storage/controller/controller.php b/phpBB/phpbb/storage/controller/controller.php index 54f95aa2242..d1ea9f29f77 100644 --- a/phpBB/phpbb/storage/controller/controller.php +++ b/phpBB/phpbb/storage/controller/controller.php @@ -16,7 +16,7 @@ use phpbb\cache\service; use phpbb\db\driver\driver_interface; use phpbb\exception\http_exception; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; use phpbb\storage\storage; use Symfony\Component\HttpFoundation\Request as symfony_request; use Symfony\Component\HttpFoundation\Response; @@ -63,7 +63,7 @@ public function __construct(service $cache, driver_interface $db, storage $stora * @return Response a Symfony response object * * @throws http_exception when can't access $file - * @throws exception when there is an error reading the file + * @throws storage_exception when there is an error reading the file */ public function handle(string $file): Response { @@ -120,7 +120,7 @@ protected function file_exists(string $file): bool * @param string $file File path * * @return void - * @throws exception when there is an error reading the file + * @throws storage_exception when there is an error reading the file */ protected function prepare(StreamedResponse $response, string $file): void { @@ -133,7 +133,7 @@ protected function prepare(StreamedResponse $response, string $file): void { $content_type = $file_info->get('mimetype'); } - catch (exception $e) + catch (storage_exception $e) { $content_type = 'application/octet-stream'; } @@ -148,7 +148,7 @@ protected function prepare(StreamedResponse $response, string $file): void { $response->headers->set('Content-Length', $file_info->get('size')); } - catch (exception $e) + catch (storage_exception $e) { // Just don't send this header } diff --git a/phpBB/phpbb/storage/exception/action_in_progress_exception.php b/phpBB/phpbb/storage/exception/action_in_progress_exception.php new file mode 100644 index 00000000000..1e6f01495e6 --- /dev/null +++ b/phpBB/phpbb/storage/exception/action_in_progress_exception.php @@ -0,0 +1,18 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage\exception; + +class action_in_progress_exception extends storage_exception +{ +} diff --git a/phpBB/phpbb/storage/exception/no_action_in_progress_exception.php b/phpBB/phpbb/storage/exception/no_action_in_progress_exception.php new file mode 100644 index 00000000000..5b5e63fc07e --- /dev/null +++ b/phpBB/phpbb/storage/exception/no_action_in_progress_exception.php @@ -0,0 +1,18 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage\exception; + +class no_action_in_progress_exception extends storage_exception +{ +} diff --git a/phpBB/phpbb/storage/exception/exception.php b/phpBB/phpbb/storage/exception/storage_exception.php similarity index 96% rename from phpBB/phpbb/storage/exception/exception.php rename to phpBB/phpbb/storage/exception/storage_exception.php index 8268530c160..08c0cfa4ef1 100644 --- a/phpBB/phpbb/storage/exception/exception.php +++ b/phpBB/phpbb/storage/exception/storage_exception.php @@ -15,7 +15,7 @@ use phpbb\exception\runtime_exception; -class exception extends runtime_exception +class storage_exception extends runtime_exception { /** * Constructor diff --git a/phpBB/phpbb/storage/file_info.php b/phpBB/phpbb/storage/file_info.php index 2e938468380..3bb6b281319 100644 --- a/phpBB/phpbb/storage/file_info.php +++ b/phpBB/phpbb/storage/file_info.php @@ -13,7 +13,7 @@ namespace phpbb\storage; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; use phpbb\storage\adapter\adapter_interface; class file_info @@ -66,7 +66,7 @@ public function get($name) { if (!method_exists($this->adapter, 'file_' . $name)) { - throw new exception('STORAGE_METHOD_NOT_IMPLEMENTED'); + throw new storage_exception('STORAGE_METHOD_NOT_IMPLEMENTED'); } $this->properties = array_merge($this->properties, call_user_func([$this->adapter, 'file_' . $name], $this->path)); diff --git a/phpBB/phpbb/storage/helper.php b/phpBB/phpbb/storage/helper.php new file mode 100644 index 00000000000..6a5e2cc28c1 --- /dev/null +++ b/phpBB/phpbb/storage/helper.php @@ -0,0 +1,192 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +use phpbb\config\config; +use phpbb\di\service_collection; + +class helper +{ + /** @var config */ + protected $config; + + /** @var service_collection */ + protected $provider_collection; + + /** @var service_collection */ + protected $adapter_collection; + + /** @var adapter_factory */ + protected $adapter_factory; + + /** @var state_helper */ + protected $state_helper; + + public function __construct(config $config, service_collection $provider_collection, service_collection $adapter_collection, adapter_factory $adapter_factory, state_helper $state_helper) + { + $this->config = $config; + $this->provider_collection = $provider_collection; + $this->adapter_collection = $adapter_collection; + $this->adapter_factory = $adapter_factory; + $this->state_helper = $state_helper; + } + + /** + * Get adapter definitions from a provider + * + * @param string $provider Provider class + * @return array Adapter definitions + */ + public function get_provider_options(string $provider) : array + { + return $this->provider_collection->get_by_class($provider)->get_options(); + } + + /** + * Get the current provider from config + * + * @param string $storage_name Storage name + * @return string The current provider + */ + public function get_current_provider(string $storage_name) : string + { + return (string) $this->config['storage\\' . $storage_name . '\\provider']; + } + + /** + * Get the current value of the definition of a storage from config + * + * @param string $storage_name Storage name + * @param string $definition Definition + * @return string Definition value + */ + public function get_current_definition(string $storage_name, string $definition) : string + { + return (string) $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; + } + + /** + * Get current storage adapter + * + * @param string $storage_name Storage adapter name + * + * @return object Storage adapter instance + */ + public function get_current_adapter(string $storage_name): object + { + static $adapters = []; + + if (!isset($adapters[$storage_name])) + { + $adapters[$storage_name] = $this->adapter_factory->get($storage_name); + } + + return $adapters[$storage_name]; + } + + /** + * Get new storage adapter + * + * @param string $storage_name + * + * @return mixed Storage adapter instance + */ + public function get_new_adapter(string $storage_name) + { + static $adapters = []; + + if (!isset($adapters[$storage_name])) + { + $provider = $this->state_helper->new_provider($storage_name); + $provider_class = $this->provider_collection->get_by_class($provider); + + $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); + $definitions = $this->get_provider_options($provider); + + $options = []; + foreach (array_keys($definitions) as $definition) + { + $options[$definition] = $this->state_helper->new_definition_value($storage_name, $definition); + } + + $adapter->configure($options); + + $adapters[$storage_name] = $adapter; + } + + return $adapters[$storage_name]; + } + + public function delete_storage_options(string $storage_name): void + { + $provider = $this->get_current_provider($storage_name); + $options = $this->get_provider_options($provider); + + foreach (array_keys($options) as $definition) + { + $this->config->delete('storage\\' . $storage_name . '\\config\\' . $definition); + } + } + + public function set_storage_provider(string $storage_name, string $provider): void + { + $this->config->set('storage\\' . $storage_name . '\\provider', $provider); + } + + public function set_storage_definition(string $storage_name, string $definition, string $value): void + { + $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $value); + } + + public function copy_file_to_new_adapter($storage_name, $file): void + { + $current_adapter = $this->get_current_adapter($storage_name); + $new_adapter = $this->get_new_adapter($storage_name); + + $stream = $current_adapter->read_stream($file); + $new_adapter->write_stream($file, $stream); + + if (is_resource($stream)) + { + fclose($stream); + } + } + + + /** + * Updates a storage with the info provided in the form (that is stored in the state at this point) + * + * @param string $storage_name Storage name + */ + public function update_storage_config(string $storage_name) : void + { + + // Remove old storage config + $this->delete_storage_options($storage_name); + + // Update provider + $new_provider = $this->state_helper->new_provider($storage_name); + $this->set_storage_provider($storage_name, $new_provider); + + // Set new storage config + $new_options = $this->get_provider_options($new_provider); + + foreach (array_keys($new_options) as $definition) + { + $new_definition_value = $this->state_helper->new_definition_value($storage_name, $definition); + $this->set_storage_definition($storage_name, $definition, $new_definition_value); + } + } + +} diff --git a/phpBB/phpbb/storage/state_helper.php b/phpBB/phpbb/storage/state_helper.php new file mode 100644 index 00000000000..cc9de6dff35 --- /dev/null +++ b/phpBB/phpbb/storage/state_helper.php @@ -0,0 +1,215 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +use phpbb\config\config; +use phpbb\config\db_text; +use phpbb\di\service_collection; +use phpbb\request\request; +use phpbb\storage\exception\action_in_progress_exception; +use phpbb\storage\exception\no_action_in_progress_exception; + +class state_helper +{ + /** @var config */ + protected $config; + + /** @var db_text $config_text */ + protected $config_text; + + /** @var service_collection */ + protected $provider_collection; + + public function __construct(config $config, db_text $config_text, service_collection $provider_collection) + { + $this->config = $config; + $this->config_text = $config_text; + $this->provider_collection = $provider_collection; + } + + /** + * Returns if there is an action in progress + * + * @return bool + */ + public function is_action_in_progress(): bool + { + return !empty(json_decode($this->config_text->get('storage_update_state'), true)); + } + + public function new_provider(string $storage_name): string + { + $state = $this->load_state(); + + return $state['storages'][$storage_name]['provider']; + } + + public function new_definition_value(string $storage_name, string $definition): string + { + $state = $this->load_state(); + + return $state['storages'][$storage_name]['config'][$definition]; + } + + public function update_type(): update_type + { + $state = $this->load_state(); + + return update_type::from($state['update_type']); + } + + public function storage_index(): int + { + $state = $this->load_state(); + + return $state['storage_index']; + } + + public function set_storage_index(int $storage_index): void + { + $state = $this->load_state(); + + $state['storage_index'] = $storage_index; + + $this->save_state($state); + } + + public function remove_storage_index(): int + { + $state = $this->load_state(); + + return $state['remove_storage_index']; + } + + public function set_remove_storage_index(int $storage_index): void + { + $state = $this->load_state(); + + $state['remove_storage_index'] = $storage_index; + + $this->save_state($state); + } + + public function file_index(): int + { + $state = $this->load_state(); + + return $state['file_index']; + } + + public function set_file_index(int $file_index): void + { + $state = $this->load_state(); + + $state['file_index'] = $file_index; + + $this->save_state($state); + } + + public function storages(): array + { + $state = $this->load_state(); + + return array_keys($state['storages']); + } + + /** + * Start a indexing or delete process. + * + * @param update_type $update_type + * @param array $modified_storages + * @param request $request + * + * @throws action_in_progress_exception If there is an action in progress + * @throws \JsonException + */ + public function init(update_type $update_type, array $modified_storages, request $request): void + { + // Is not possible to start a new process when there is one already running + if ($this->is_action_in_progress()) + { + throw new action_in_progress_exception(); + } + + $state = [ + // Save the value of the checkbox, to remove all files from the + // old storage once they have been successfully moved + 'update_type' => $update_type->value, + 'storages' => [], + 'storage_index' => 0, + 'file_index' => 0, + 'remove_storage_index' => 0, + ]; + + // Save in the state the selected storages and their new configuration + foreach ($modified_storages as $storage_name) + { + $state['storages'][$storage_name] = []; + + $state['storages'][$storage_name]['provider'] = $request->variable([$storage_name, 'provider'], ''); + + $options = $this->provider_collection->get_by_class($request->variable([$storage_name, 'provider'], ''))->get_options(); + + foreach (array_keys($options) as $definition) + { + $state['storages'][$storage_name]['config'][$definition] = $request->variable([$storage_name, $definition], ''); + } + } + + $this->save_state($state); + } + + /** + * Clear the state + * + * @throws \JsonException + */ + public function clear_state(): void + { + $this->save_state([]); + } + + /** + * Load the state from the database + * + * @return array + * + * @throws no_action_in_progress_exception If there is no action in progress + */ + private function load_state(): array + { + // Is not possible to execute an action over state if is empty + if (!$this->is_action_in_progress()) + { + throw new no_action_in_progress_exception(); + } + + return json_decode($this->config_text->get('storage_update_state'), true) ?? []; + } + + /** + * Save the specified state in the database + * + * @param array $state + * + * @throws \JsonException + */ + private function save_state(array $state = []): void + { + $this->config_text->set('storage_update_state', json_encode($state, JSON_THROW_ON_ERROR)); + } + + + +} diff --git a/phpBB/phpbb/storage/storage.php b/phpBB/phpbb/storage/storage.php index 8e4620ab236..af0c9f0c671 100644 --- a/phpBB/phpbb/storage/storage.php +++ b/phpBB/phpbb/storage/storage.php @@ -15,7 +15,7 @@ use phpbb\cache\driver\driver_interface as cache; use phpbb\db\driver\driver_interface as db; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; /** * Experimental @@ -102,14 +102,14 @@ protected function get_adapter() * @param string $path The file to be written to. * @param string $content The data to write into the file. * - * @throws exception When the file already exists + * @throws storage_exception When the file already exists * When the file cannot be written */ public function put_contents($path, $content) { if ($this->exists($path)) { - throw new exception('STORAGE_FILE_EXISTS', $path); + throw new storage_exception('STORAGE_FILE_EXISTS', $path); } $this->get_adapter()->put_contents($path, $content); @@ -121,17 +121,17 @@ public function put_contents($path, $content) * * @param string $path The file to read * - * @throws exception When the file doesn't exist - * When cannot read file contents + * @return string Returns file contents * - * @return string Returns file contents + *@throws storage_exception When the file doesn't exist + * When cannot read file contents * */ public function get_contents($path) { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } return $this->get_adapter()->get_contents($path); @@ -155,14 +155,14 @@ public function exists($path, $full_check = false) * * @param string $path file/directory to remove * - * @throws exception When removal fails + * @throws storage_exception When removal fails * When the file doesn't exist */ public function delete($path) { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } $this->get_adapter()->delete($path); @@ -175,7 +175,7 @@ public function delete($path) * @param string $path_orig The original file/direcotry * @param string $path_dest The target file/directory * - * @throws exception When the file doesn't exist + * @throws storage_exception When the file doesn't exist * When target exists * When file/directory cannot be renamed */ @@ -183,12 +183,12 @@ public function rename($path_orig, $path_dest) { if (!$this->exists($path_orig)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path_orig); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); } if ($this->exists($path_dest)) { - throw new exception('STORAGE_FILE_EXISTS', $path_dest); + throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); } $this->get_adapter()->rename($path_orig, $path_dest); @@ -201,7 +201,7 @@ public function rename($path_orig, $path_dest) * @param string $path_orig The original filename * @param string $path_dest The target filename * - * @throws exception When the file doesn't exist + * @throws storage_exception When the file doesn't exist * When target exists * When the file cannot be copied */ @@ -209,12 +209,12 @@ public function copy($path_orig, $path_dest) { if (!$this->exists($path_orig)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path_orig); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); } if ($this->exists($path_dest)) { - throw new exception('STORAGE_FILE_EXISTS', $path_dest); + throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); } $this->get_adapter()->copy($path_orig, $path_dest); @@ -226,16 +226,16 @@ public function copy($path_orig, $path_dest) * * @param string $path File to read * - * @throws exception When the file doesn't exist + * @return resource Returns a file pointer + *@throws storage_exception When the file doesn't exist * When unable to open file * - * @return resource Returns a file pointer */ public function read_stream($path) { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } $stream = null; @@ -262,19 +262,19 @@ public function read_stream($path) * @param string $path The target file * @param resource $resource The resource * - * @throws exception When the file exist + * @throws storage_exception When the file exist * When target file cannot be created */ public function write_stream($path, $resource) { if ($this->exists($path)) { - throw new exception('STORAGE_FILE_EXISTS', $path); + throw new storage_exception('STORAGE_FILE_EXISTS', $path); } if (!is_resource($resource)) { - throw new exception('STORAGE_INVALID_RESOURCE'); + throw new storage_exception('STORAGE_INVALID_RESOURCE'); } $adapter = $this->get_adapter(); @@ -301,7 +301,7 @@ public function track_file($path, $update = false) { if (!$this->get_adapter()->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } $sql_ary = array( @@ -403,16 +403,16 @@ protected function track_rename($path_orig, $path_dest) * * @param string $path The file * - * @throws exception When the adapter doesn't implement the method + * @return \phpbb\storage\file_info Returns file_info object + *@throws storage_exception When the adapter doesn't implement the method * When the file doesn't exist * - * @return \phpbb\storage\file_info Returns file_info object */ public function file_info($path) { if (!$this->exists($path)) { - throw new exception('STORAGE_FILE_NO_EXIST', $path); + throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } return new file_info($this->get_adapter(), $path); @@ -484,9 +484,9 @@ public function get_num_files() /** * Get space available in bytes * - * @throws exception When unable to retrieve available storage space + * @return float Returns available space + *@throws storage_exception When unable to retrieve available storage space * - * @return float Returns available space */ public function free_space() { diff --git a/phpBB/phpbb/storage/stream_interface.php b/phpBB/phpbb/storage/stream_interface.php index 9687a2d9109..424ffcb95c2 100644 --- a/phpBB/phpbb/storage/stream_interface.php +++ b/phpBB/phpbb/storage/stream_interface.php @@ -13,7 +13,7 @@ namespace phpbb\storage; -use phpbb\storage\exception\exception; +use phpbb\storage\exception\storage_exception; interface stream_interface { @@ -23,7 +23,7 @@ interface stream_interface * @param string $path File to read * * @return resource Returns a file pointer - * @throws exception When unable to open file + * @throws storage_exception When unable to open file */ public function read_stream(string $path); @@ -34,7 +34,7 @@ public function read_stream(string $path); * @param resource $resource The resource * * @return void - * @throws exception When target file exists + * @throws storage_exception When target file exists * When target file cannot be created */ public function write_stream(string $path, $resource): void; diff --git a/phpBB/phpbb/storage/update_type.php b/phpBB/phpbb/storage/update_type.php new file mode 100644 index 00000000000..32f25ad13d9 --- /dev/null +++ b/phpBB/phpbb/storage/update_type.php @@ -0,0 +1,21 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +enum update_type: int +{ + case CONFIG = 0; + case COPY = 1; + case MOVE = 2; +} diff --git a/tests/attachment/delete_test.php b/tests/attachment/delete_test.php index faf2f960e63..7acac28f334 100644 --- a/tests/attachment/delete_test.php +++ b/tests/attachment/delete_test.php @@ -102,7 +102,7 @@ public function test_attachment_delete_success($remove_success, $exists_success, { $this->storage->expects($this->any()) ->method('delete') - ->willThrowException(new \phpbb\storage\exception\exception); + ->willThrowException(new \phpbb\storage\exception\storage_exception); } else { diff --git a/tests/functional/acp_storage_settings_test.php b/tests/functional/acp_storage_settings_test.php index b99e3e8fa96..f8c1728dcb2 100644 --- a/tests/functional/acp_storage_settings_test.php +++ b/tests/functional/acp_storage_settings_test.php @@ -68,9 +68,9 @@ public function test_storage_settings() // Visit ACP Storage settings again - warning should be displayed $crawler = self::request('GET', 'adm/index.php?i=acp_storage&mode=settings&sid=' . $this->sid); $this->assertContainsLang('WARNING', $crawler->filter('div[class="errorbox"] > h3')->text()); - $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_ATTACHMENT_TITLE')), $crawler->filter('div[class="errorbox"] > p')->text()); - $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_AVATAR_TITLE')), $crawler->filter('div[class="errorbox"] > p')->text()); - $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_BACKUP_TITLE')), $crawler->filter('div[class="errorbox"] > p')->text()); + $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_ATTACHMENT_TITLE')), $crawler->filter('div[class="errorbox"]')->text()); + $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_AVATAR_TITLE')), $crawler->filter('div[class="errorbox"]')->text()); + $this->assertStringContainsString($this->lang('STORAGE_PATH_NOT_EXISTS', $this->lang('STORAGE_BACKUP_TITLE')), $crawler->filter('div[class="errorbox"]')->text()); // Restore default state $filesystem->chmod($phpbb_root_path . $attachments_storage_path, 777); From 59a5163e2b02e0d82e281c5bd033637ed1594384 Mon Sep 17 00:00:00 2001 From: rubencm Date: Sat, 23 Sep 2023 11:58:52 +0000 Subject: [PATCH 0220/1214] [ticket/15699] Fix code style PHPBB3-15699 Co-authored-by: Marc Alexander --- phpBB/includes/acp/acp_storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 996a0f641b4..2e28ef9a503 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -247,7 +247,7 @@ private function update_action(): void $this->db->sql_freeresult($result); // Remove all files of a storage, increase storage index and reset file index - $this->state_helper->set_remove_storage_index($this->state_helper->remove_storage_index()+1); + $this->state_helper->set_remove_storage_index($this->state_helper->remove_storage_index() + 1); $this->state_helper->set_file_index(0); } } From bb84af9a4860b51bec20f803af95fd2a7a74bf8e Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 17 Feb 2024 19:19:09 +0100 Subject: [PATCH 0221/1214] [ticket/15699] Add progress bar and use template macros PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 31 ++--- .../style/acp_storage_update_inprogress.html | 38 +++--- .../style/acp_storage_update_progress.html | 20 +++ phpBB/includes/acp/acp_storage.php | 124 ++++++++++-------- phpBB/language/en/acp/storage.php | 10 +- phpBB/phpbb/storage/adapter_factory.php | 38 +++--- phpBB/phpbb/storage/helper.php | 19 +-- phpBB/phpbb/storage/provider/local.php | 5 +- phpBB/phpbb/storage/state_helper.php | 1 + phpBB/phpbb/storage/storage.php | 9 +- 10 files changed, 152 insertions(+), 143 deletions(-) create mode 100644 phpBB/adm/style/acp_storage_update_progress.html diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index bb9940554bd..63f52a276e2 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -61,34 +61,23 @@

          {{ lang('WARNING') }}

          {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} - {{ lang('STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_NAME') }} {% for name, options in provider.get_options %} - {% set title = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper %} - {% set description = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper ~ '_EXPLAIN' %} - {% set input_id = storage.get_name ~ '_' ~ provider.get_name ~ '_' ~ name %} - {% set input_type = options['type'] %} - {% set input_name = storage.get_name ~ '[' ~ name ~ ']' %} - {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %}
          - + {% set title = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper %} + {% set description = 'STORAGE_ADAPTER_' ~ provider.get_name | upper ~ '_OPTION_' ~ name | upper ~ '_EXPLAIN' %} + {% if lang_defined(description) %}
          {{ lang(description) }} {% endif %}
          - {% if input_type in ['text', 'password', 'email'] %} - - {% elseif input_type == 'textarea' %} - - {% elseif input_type == 'radio' %} - {% for option_name, option_value in options['options'] %} - {{ lang(option_name) }} - {% endfor %} - {% elseif input_type == 'select' %} - + {% set input_name = storage.get_name ~ '[' ~ name ~ ']' %} + {% set input_value = attribute(config, 'storage\\' ~ storage.get_name ~ '\\config\\' ~ name) %} + + {% if options['type'] in ['text', 'password', 'email'] %} + {{ FormsBuildTemplate(options | merge({"name": input_name, "value": input_value})) }} + {% elseif options['type'] == 'textarea' %} + {{ FormsBuildTemplate(options | merge({"name": input_name, "content": input_value})) }} {% endif %}
          diff --git a/phpBB/adm/style/acp_storage_update_inprogress.html b/phpBB/adm/style/acp_storage_update_inprogress.html index 9c4b9fd8040..5f7614cb2c7 100644 --- a/phpBB/adm/style/acp_storage_update_inprogress.html +++ b/phpBB/adm/style/acp_storage_update_inprogress.html @@ -4,27 +4,27 @@

          {{ lang('STORAGE_TITLE') }}

          - +

          {{ lang('STORAGE_TITLE_EXPLAIN') }}

          -

          {{ lang('CONTINUE_EXPLAIN') }}

          - -
          -
          + +
          {{ lang('SUBMIT') }} -   - + + {% if CONTINUE_PROGRESS %} +
          +
          +
          + {{ CONTINUE_PROGRESS.PERCENTAGE|number_format(2) ~ ' %' }} +
          + {% endif %} + +

          +   + +

          {{ S_FORM_TOKEN }}
          diff --git a/phpBB/adm/style/acp_storage_update_progress.html b/phpBB/adm/style/acp_storage_update_progress.html new file mode 100644 index 00000000000..17ba67a037f --- /dev/null +++ b/phpBB/adm/style/acp_storage_update_progress.html @@ -0,0 +1,20 @@ +{% include 'overall_header.html' %} + + + +
          +

          {{ INDEXING_TITLE }}

          +

          + {{ INDEXING_EXPLAIN }} + {% if INDEXING_PROGRESS_BAR %} +
          +
          + {{ INDEXING_PROGRESS_BAR.PERCENTAGE|number_format(2) ~ ' %' }} + {% endif %} +

          +
          + +{% include 'overall_footer.html' %} diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 2e28ef9a503..e11a7782786 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -16,6 +16,7 @@ use phpbb\language\language; use phpbb\log\log_interface; use phpbb\request\request; +use phpbb\storage\exception\storage_exception; use phpbb\storage\helper; use phpbb\storage\state_helper; use phpbb\storage\update_type; @@ -77,6 +78,9 @@ class acp_storage /** @var helper */ private $storage_helper; + /** @var string */ + private $storage_table; + /** * @param string $id * @param string $mode @@ -97,6 +101,7 @@ public function main(string $id, string $mode): void $this->phpbb_root_path = $phpbb_root_path; $this->state_helper = $phpbb_container->get('storage.state_helper'); $this->storage_helper = $phpbb_container->get('storage.helper'); + $this->storage_table = $phpbb_container->getParameter('tables.storage'); // Add necessary language files $this->lang->add_lang(['acp/storage']); @@ -128,10 +133,6 @@ private function settings(string $id, string $mode): void { switch ($action) { - case 'progress_bar': - $this->display_progress_bar(); - break; - case 'update': $this->update_action(); break; @@ -151,7 +152,7 @@ private function settings(string $id, string $mode): void // There is an updating in progress, show the form to continue or cancel if ($this->state_helper->is_action_in_progress()) { - $this->update_inprogress($id, $mode); + $this->update_inprogress(); } else { @@ -162,8 +163,6 @@ private function settings(string $id, string $mode): void private function update_action(): void { - // Probably it has sense to disable the forum while this is in progress - if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) { trigger_error($this->lang->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); @@ -182,7 +181,7 @@ private function update_action(): void } $sql = 'SELECT file_id, file_path - FROM ' . STORAGE_TABLE . " + FROM ' . $this->storage_table . " WHERE storage = '" . $this->db->sql_escape($storage_name) . "' AND file_id > " . $this->state_helper->file_index(); $result = $this->db->sql_query($sql); @@ -192,9 +191,8 @@ private function update_action(): void if (!still_on_time()) { $this->db->sql_freeresult($result); - meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - // Here could be included the current file compared with the number of total files too - trigger_error($this->lang->lang('STORAGE_UPDATE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state_helper->storages()))); + $this->display_progress_page(); + return; } // Copy file from old adapter to the new one @@ -223,7 +221,7 @@ private function update_action(): void } $sql = 'SELECT file_id, file_path - FROM ' . STORAGE_TABLE . " + FROM ' . $this->storage_table . " WHERE storage = '" . $this->db->sql_escape($storage_name) . "' AND file_id > " . $this->state_helper->file_index(); $result = $this->db->sql_query($sql); @@ -233,8 +231,8 @@ private function update_action(): void if (!still_on_time()) { $this->db->sql_freeresult($result); - meta_refresh(1, append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'))); - trigger_error($this->lang->lang('STORAGE_UPDATE_REMOVE_REDIRECT', $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'), $i + 1, count($this->state_helper->storages()))); + $this->display_progress_page(); + return; } // remove file from old (current) adapter @@ -262,10 +260,10 @@ private function update_action(): void $storages = $this->state_helper->storages(); $this->state_helper->clear_state(); $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_STORAGE_UPDATE', false, [implode(', ', $storages)]); - trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action) . $this->close_popup_js()); + trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action)); } - private function update_inprogress(string $id, string $mode): void + private function update_inprogress(): void { // Template from adm/style $this->tpl_name = 'acp_storage_update_inprogress'; @@ -274,10 +272,8 @@ private function update_inprogress(string $id, string $mode): void $this->page_title = 'STORAGE_TITLE'; $this->template->assign_vars(array( - 'UA_PROGRESS_BAR' => addslashes(append_sid($this->u_action, "action=progress_bar")), - 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), - 'L_CONTINUE' => $this->lang->lang('CONTINUE_UPDATING'), - 'L_CONTINUE_EXPLAIN' => $this->lang->lang('CONTINUE_UPDATING_EXPLAIN'), + 'U_ACTION' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), + 'CONTINUE_PROGRESS' => $this->get_storage_update_progress(), )); } @@ -308,27 +304,14 @@ private function settings_form(string $id, string $mode): void trigger_error(implode('
          ', $messages) . adm_back_link($this->u_action), E_USER_WARNING); } - // Start process and show form + // Start process and show progress if (!empty($modified_storages)) { // Create state $this->state_helper->init(update_type::from((int) $this->request->variable('update_type', update_type::CONFIG->value)), $modified_storages, $this->request); - // Show the confirmation form to start the process - $this->template->assign_vars(array( - 'UA_PROGRESS_BAR' => addslashes(append_sid($this->u_action, "action=progress_bar")), - 'S_CONTINUE_UPDATING' => true, - 'U_CONTINUE_UPDATING' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), - 'L_CONTINUE' => $this->lang->lang('START_UPDATING'), - 'L_CONTINUE_EXPLAIN' => $this->lang->lang('START_UPDATING_EXPLAIN'), - )); - - // Template from adm/style - $this->tpl_name = 'acp_storage_update_inprogress'; - - // Set page title - $this->page_title = 'STORAGE_TITLE'; - + // Start displaying progress on first submit + $this->display_progress_page(); return; } @@ -413,7 +396,7 @@ protected function storage_stats() { $free_space = get_formatted_filesize($storage->free_space()); } - catch (\phpbb\storage\exception\storage_exception $e) + catch (storage_exception $e) { $free_space = $this->lang->lang('STORAGE_UNKNOWN'); } @@ -432,33 +415,60 @@ protected function storage_stats() } /** - * Display progress bar + * Display progress page */ - protected function display_progress_bar() : void + protected function display_progress_page() : void { + $u_action = append_sid($this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage')); + meta_refresh(1, $u_action); + adm_page_header($this->lang->lang('STORAGE_UPDATE_IN_PROGRESS')); - $this->template->set_filenames(array( - 'body' => 'progress_bar.html') - ); - $this->template->assign_vars(array( - 'L_PROGRESS' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS'), - 'L_PROGRESS_EXPLAIN' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN')) - ); + $this->template->set_filenames([ + 'body' => 'acp_storage_update_progress.html' + ]); + + $this->template->assign_vars([ + 'INDEXING_TITLE' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS'), + 'INDEXING_EXPLAIN' => $this->lang->lang('STORAGE_UPDATE_IN_PROGRESS_EXPLAIN'), + 'INDEXING_PROGRESS_BAR' => $this->get_storage_update_progress(), + ]); adm_page_footer(); } - /** - * Get JS code for closing popup - * - * @return string Popup JS code - */ - function close_popup_js() : string + protected function get_storage_update_progress(): array { - return "\n"; + $file_index = $this->state_helper->file_index(); + $stage_is_copy = $this->state_helper->storage_index() < count($this->state_helper->storages()); + $storage_name = $this->state_helper->storages()[$stage_is_copy ? $this->state_helper->storage_index() : $this->state_helper->remove_storage_index()]; + + $sql = 'SELECT COUNT(file_id) as done_count + FROM ' . $this->storage_table . ' + WHERE file_id <= ' . $file_index . " + AND storage = '" . $this->db->sql_escape($storage_name) . "'"; + $result = $this->db->sql_query($sql); + $done_count = (int) $this->db->sql_fetchfield('done_count'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT COUNT(file_id) as remain_count + FROM ' . $this->storage_table . " + WHERE file_id > ' . $file_index . ' + AND storage = '" . $this->db->sql_escape($storage_name) . "'"; + $result = $this->db->sql_query($sql); + $remain_count = (int) $this->db->sql_fetchfield('remain_count'); + $this->db->sql_freeresult($result); + + $total_count = $done_count + $remain_count; + $percent = $done_count / $total_count; + + $steps = $this->state_helper->storage_index() + $this->state_helper->remove_storage_index() + $percent; + $multiplier = $this->state_helper->update_type() === update_type::MOVE ? 2 : 1; + $steps_total = count($this->state_helper->storages()) * $multiplier; + + return [ + 'VALUE' => $steps, + 'TOTAL' => $steps_total, + 'PERCENTAGE' => $steps / $steps_total * 100, + ]; } /** diff --git a/phpBB/language/en/acp/storage.php b/phpBB/language/en/acp/storage.php index 2ed3a1b2a51..9f928ee58ea 100644 --- a/phpBB/language/en/acp/storage.php +++ b/phpBB/language/en/acp/storage.php @@ -36,7 +36,7 @@ // 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 -$lang = array_merge($lang, array( +$lang = array_merge($lang, [ // Template 'STORAGE_TITLE' => 'Storage Settings', @@ -52,12 +52,6 @@ 'STORAGE_UPDATE_TYPE_CONFIG' => 'Update configuration only', 'STORAGE_UPDATE_TYPE_COPY' => 'Update configuration and copy files', 'STORAGE_UPDATE_TYPE_MOVE' => 'Update configuration and move files', - 'START_UPDATING' => 'Start update process', - 'START_UPDATING_EXPLAIN' => 'Start the storage update process', - 'CONTINUE_UPDATING' => 'Continue previous update process', - 'CONTINUE_UPDATING_EXPLAIN' => 'An update process has been started. In order to access the storage settings page you will have to complete it or cancel it.', - 'STORAGE_UPDATE_REDIRECT' => 'Files of %1$s (%2$d/%3$d) are being moved.
          ', - 'STORAGE_UPDATE_REMOVE_REDIRECT' => 'Files of old %1$s (%2$d/%3$d) are being removed.
          ', // Template progress bar 'STORAGE_UPDATE_IN_PROGRESS' => 'Storage update in progress', @@ -83,4 +77,4 @@ 'STORAGE_PATH_NOT_EXISTS' => '“%1$s” path does not exist or is not writable.', 'STORAGE_PATH_NOT_SET' => '“%1$s” path is not set.', -)); +]); diff --git a/phpBB/phpbb/storage/adapter_factory.php b/phpBB/phpbb/storage/adapter_factory.php index b358328525f..62e51f66aeb 100644 --- a/phpBB/phpbb/storage/adapter_factory.php +++ b/phpBB/phpbb/storage/adapter_factory.php @@ -15,6 +15,7 @@ use phpbb\config\config; use phpbb\di\service_collection; +use phpbb\storage\adapter\adapter_interface; use phpbb\storage\exception\storage_exception; class adapter_factory @@ -53,41 +54,36 @@ public function __construct(config $config, service_collection $adapters, servic * * @param string $storage_name * - * @return \phpbb\storage\adapter\adapter_interface + * @return adapter_interface */ - public function get($storage_name) + public function get(string $storage_name) { $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; $provider = $this->providers->get_by_class($provider_class); - if (!$provider->is_available()) + $options = []; + foreach (array_keys($provider->get_options()) as $definition) { - throw new storage_exception('STORAGE_ADAPTER_NOT_AVAILABLE'); + /** @psalm-suppress InvalidArrayOffset */ + $options[$definition] = $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; } - $adapter = $this->adapters->get_by_class($provider->get_adapter_class()); - $adapter->configure($this->build_options($storage_name, $provider->get_options())); - - return $adapter; + return $this->get_with_options($storage_name, $options); } - /** - * Obtains configuration for a given storage - * - * @param string $storage_name - * @param array $definitions - * - * @return array Returns storage configuration values - */ - public function build_options($storage_name, array $definitions) + public function get_with_options(string $storage_name, array $options) { - $options = []; + $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; + $provider = $this->providers->get_by_class($provider_class); - foreach (array_keys($definitions) as $definition) + if (!$provider->is_available()) { - $options[$definition] = $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; + throw new storage_exception('STORAGE_ADAPTER_NOT_AVAILABLE'); } - return $options; + $adapter = $this->adapters->get_by_class($provider->get_adapter_class()); + $adapter->configure($options); + + return $adapter; } } diff --git a/phpBB/phpbb/storage/helper.php b/phpBB/phpbb/storage/helper.php index 6a5e2cc28c1..de75ab337c5 100644 --- a/phpBB/phpbb/storage/helper.php +++ b/phpBB/phpbb/storage/helper.php @@ -45,12 +45,12 @@ public function __construct(config $config, service_collection $provider_collect /** * Get adapter definitions from a provider * - * @param string $provider Provider class + * @param string $provider_class Provider class * @return array Adapter definitions */ - public function get_provider_options(string $provider) : array + public function get_provider_options(string $provider_class) : array { - return $this->provider_collection->get_by_class($provider)->get_options(); + return $this->provider_collection->get_by_class($provider_class)->get_options(); } /** @@ -108,21 +108,16 @@ public function get_new_adapter(string $storage_name) if (!isset($adapters[$storage_name])) { - $provider = $this->state_helper->new_provider($storage_name); - $provider_class = $this->provider_collection->get_by_class($provider); - - $adapter = $this->adapter_collection->get_by_class($provider_class->get_adapter_class()); - $definitions = $this->get_provider_options($provider); + $provider_class = $this->state_helper->new_provider($storage_name); + $definitions = array_keys($this->get_provider_options($provider_class)); $options = []; - foreach (array_keys($definitions) as $definition) + foreach ($definitions as $definition) { $options[$definition] = $this->state_helper->new_definition_value($storage_name, $definition); } - $adapter->configure($options); - - $adapters[$storage_name] = $adapter; + $adapters[$storage_name] = $this->adapter_factory->get_with_options($storage_name, $options); } return $adapters[$storage_name]; diff --git a/phpBB/phpbb/storage/provider/local.php b/phpBB/phpbb/storage/provider/local.php index 3bec0c5ce5b..c6338e0c311 100644 --- a/phpBB/phpbb/storage/provider/local.php +++ b/phpBB/phpbb/storage/provider/local.php @@ -37,7 +37,10 @@ public function get_adapter_class(): string public function get_options() { return [ - 'path' => ['type' => 'text'], + 'path' => [ + 'tag' => 'input', + 'type' => 'text', + ], ]; } diff --git a/phpBB/phpbb/storage/state_helper.php b/phpBB/phpbb/storage/state_helper.php index cc9de6dff35..aa48c3377cc 100644 --- a/phpBB/phpbb/storage/state_helper.php +++ b/phpBB/phpbb/storage/state_helper.php @@ -163,6 +163,7 @@ public function init(update_type $update_type, array $modified_storages, request foreach (array_keys($options) as $definition) { + /** @psalm-suppress InvalidArrayOffset */ $state['storages'][$storage_name]['config'][$definition] = $request->variable([$storage_name, $definition], ''); } } diff --git a/phpBB/phpbb/storage/storage.php b/phpBB/phpbb/storage/storage.php index af0c9f0c671..e99d046a540 100644 --- a/phpBB/phpbb/storage/storage.php +++ b/phpBB/phpbb/storage/storage.php @@ -15,6 +15,7 @@ use phpbb\cache\driver\driver_interface as cache; use phpbb\db\driver\driver_interface as db; +use phpbb\storage\adapter\adapter_interface; use phpbb\storage\exception\storage_exception; /** @@ -23,7 +24,7 @@ class storage { /** - * @var \phpbb\storage\adapter\adapter_interface + * @var adapter_interface */ protected $adapter; @@ -39,7 +40,7 @@ class storage protected $cache; /** - * @var \phpbb\storage\adapter_factory + * @var adapter_factory */ protected $factory; @@ -58,7 +59,7 @@ class storage * * @param db $db * @param cache $cache - * @param \phpbb\storage\adapter_factory $factory + * @param adapter_factory $factory * @param string $storage_name * @param string $storage_table */ @@ -84,7 +85,7 @@ public function get_name() /** * Returns an adapter instance * - * @return \phpbb\storage\adapter\adapter_interface + * @return adapter_interface */ protected function get_adapter() { From bf467f8ece9d58312b5674397f1863dc971908d5 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 17 Feb 2024 20:15:39 +0100 Subject: [PATCH 0222/1214] [ticket/15699] Remove .phpunit.result.cache PHPBB3-15699 --- .gitignore | 1 + .phpunit.result.cache | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .phpunit.result.cache diff --git a/.gitignore b/.gitignore index 69f93652be4..6972ca1516e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ # Excludes test / dev files /phpunit.xml +/.phpunit.result.cache /phpBB/composer.phar /tests/phpbb_unit_tests.sqlite* /tests/test_config*.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 2de2a6daf1c..00000000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":[],"times":[]} \ No newline at end of file From 155b5168bee6e0f2ad23eddb247ce5671a1c61db Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sun, 18 Feb 2024 10:44:41 +0100 Subject: [PATCH 0223/1214] [ticket/15699] Improve code style PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 32 +++++++++++++-------------- phpBB/includes/acp/acp_storage.php | 6 ++--- phpBB/phpbb/storage/adapter/local.php | 2 -- phpBB/phpbb/storage/state_helper.php | 2 -- phpBB/phpbb/storage/storage.php | 8 +++---- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 63f52a276e2..39ca5bb012c 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -16,34 +16,34 @@

          {{ lang('STORAGE_TITLE') }}

          - {% for storage in STORAGE_STATS %} - - {{ storage.name }} - {{ storage.files }} - {{ storage.size }} - {{ storage.free_space }} - - {% endfor %} + {% for storage in STORAGE_STATS %} + + {{ storage.name }} + {{ storage.files }} + {{ storage.size }} + {{ storage.free_space }} + + {% endfor %} {% if ERROR_MESSAGES is not empty %} -
          -

          {{ lang('WARNING') }}

          - {% for ERROR_MESSAGE in ERROR_MESSAGES %} -

          {{ ERROR_MESSAGE }}

          - {% endfor %} -
          +
          +

          {{ lang('WARNING') }}

          + {% for ERROR_MESSAGE in ERROR_MESSAGES %} +

          {{ ERROR_MESSAGE }}

          + {% endfor %} +
          {% endif %}
          {% for storage in STORAGES %}
          - {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }} + {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }}

          {{ lang('STORAGE_SELECT_DESC') }}
          - {% for provider in PROVIDERS %} {% if provider.is_available %}
          diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index bd66516a3e0..150d7116725 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -458,7 +458,7 @@ protected function get_storage_update_progress(): array $this->db->sql_freeresult($result); $total_count = $done_count + $remain_count; - $percent = $done_count / $total_count; + $percent = $total_count > 0 ? $done_count / $total_count : 0; $steps = $this->state_helper->storage_index() + $this->state_helper->remove_storage_index() + $percent; $multiplier = $this->state_helper->update_type() === update_type::MOVE ? 2 : 1; @@ -509,18 +509,15 @@ protected function validate_data(string $storage_name, array &$messages) $value = $this->request->variable([$storage_name, $definition_key], ''); - switch ($definition_value['type']) + switch ($definition_value['tag']) { - case 'email': - if (!filter_var($value, FILTER_VALIDATE_EMAIL)) + case 'text': + if ($definition_value['type'] == 'email' && filter_var($value, FILTER_VALIDATE_EMAIL)) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); } - // no break - case 'text': - case 'password': - $maxlength = isset($definition_value['maxlength']) ? $definition_value['maxlength'] : 255; + $maxlength = isset($definition_value['max']) ? $definition_value['max'] : 255; if (strlen($value) > $maxlength) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); @@ -542,8 +539,34 @@ protected function validate_data(string $storage_name, array &$messages) break; case 'radio': + $found = false; + foreach ($definition_value['buttons'] as $button) + { + if ($button['value'] == $value) + { + $found = true; + break; + } + } + + if (!$found) + { + $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); + } + break; + case 'select': - if (!in_array($value, array_values($definition_value['options']))) + $found = false; + foreach ($definition_value['options'] as $option) + { + if ($option['value'] == $value) + { + $found = true; + break; + } + } + + if (!$found) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_SELECT_NOT_AVAILABLE', $definition_title, $storage_title); } From 195fb59b4e5d75b7204d22c18c11049b59c0ecbd Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 25 May 2024 16:35:44 +0200 Subject: [PATCH 0225/1214] [ticket/15699] Fixes code review PHPBB3-15699 --- phpBB/adm/style/acp_storage.html | 12 ++-- .../default/container/services_storage.yml | 4 +- phpBB/includes/acp/acp_storage.php | 52 +++++++++++--- phpBB/phpbb/storage/adapter_factory.php | 15 ++-- phpBB/phpbb/storage/helper.php | 65 +++++++++++++++--- phpBB/phpbb/storage/state_helper.php | 68 ++++++++++++++++++- 6 files changed, 181 insertions(+), 35 deletions(-) diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 1aedc50ce18..86c9298828b 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -41,7 +41,7 @@

          {{ lang('WARNING') }}

          {{ lang('STORAGE_' ~ storage.get_name | upper ~ '_TITLE') }}
          -

          {{ lang('STORAGE_SELECT_DESC') }}
          +

          {{ lang('STORAGE_SELECT_DESC') }}
          {{ lang('STORAGE_UPDATE_TYPE_CONFIG') }} diff --git a/phpBB/config/default/container/services_storage.yml b/phpBB/config/default/container/services_storage.yml index 401357861d3..496c30de5d8 100644 --- a/phpBB/config/default/container/services_storage.yml +++ b/phpBB/config/default/container/services_storage.yml @@ -120,7 +120,7 @@ services: class: phpbb\storage\helper arguments: - '@config' - - '@storage.provider_collection' - - '@storage.adapter_collection' - '@storage.adapter.factory' - '@storage.state_helper' + - '@storage.provider_collection' + - '@storage.adapter_collection' diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 150d7116725..2adf833562e 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -123,6 +123,8 @@ public function main(string $id, string $mode): void } /** + * Method to route the request to the correct page + * * @param string $id * @param string $mode */ @@ -156,11 +158,16 @@ private function settings(string $id, string $mode): void } else { - $this->settings_form($id, $mode); + $this->settings_form(); } } } + /** + * Page to update storage settings and move files + * + * @return void + */ private function update_action(): void { if (!check_link_hash($this->request->variable('hash', ''), 'acp_storage')) @@ -263,6 +270,11 @@ private function update_action(): void trigger_error($this->lang->lang('STORAGE_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action)); } + /** + * Page that show a form with the progress bar, and a button to continue or cancel + * + * @return void + */ private function update_inprogress(): void { // Template from adm/style @@ -271,13 +283,18 @@ private function update_inprogress(): void // Set page title $this->page_title = 'STORAGE_TITLE'; - $this->template->assign_vars(array( + $this->template->assign_vars([ 'U_ACTION' => $this->u_action . '&action=update&hash=' . generate_link_hash('acp_storage'), 'CONTINUE_PROGRESS' => $this->get_storage_update_progress(), - )); + ]); } - private function settings_form(string $id, string $mode): void + /** + * Main settings page, shows a form with all the storages and their configuration options + * + * @return void + */ + private function settings_form(): void { $form_key = 'acp_storage'; add_form_key($form_key); @@ -348,6 +365,12 @@ private function settings_form(string $id, string $mode): void ]); } + /** + * When submit the settings form, check which storages have been modified + * to update only those. + * + * @return array + */ private function get_modified_storages(): array { $modified_storages = []; @@ -386,7 +409,12 @@ private function get_modified_storages(): array return $modified_storages; } - protected function storage_stats() + /** + * Fill template variables to show storage stats in settings page + * + * @return void + */ + protected function storage_stats(): void { // Top table with stats of each storage $storage_stats = []; @@ -435,6 +463,11 @@ protected function display_progress_page() : void adm_page_footer(); } + /** + * Get storage update progress to show progress bar + * + * @return array + */ protected function get_storage_update_progress(): array { $file_index = $this->state_helper->file_index(); @@ -477,7 +510,7 @@ protected function get_storage_update_progress(): array * @param string $storage_name Storage name * @param array $messages Reference to messages array */ - protected function validate_data(string $storage_name, array &$messages) + protected function validate_data(string $storage_name, array &$messages): void { $storage_title = $this->lang->lang('STORAGE_' . strtoupper($storage_name) . '_TITLE'); @@ -499,6 +532,8 @@ protected function validate_data(string $storage_name, array &$messages) return; } + $this->validate_path($storage_name, $messages); + // Check options $new_options = $this->storage_helper->get_provider_options($this->request->variable([$storage_name, 'provider'], '')); @@ -517,7 +552,7 @@ protected function validate_data(string $storage_name, array &$messages) $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_EMAIL_INCORRECT_FORMAT', $definition_title, $storage_title); } - $maxlength = isset($definition_value['max']) ? $definition_value['max'] : 255; + $maxlength = $definition_value['max'] ?? 255; if (strlen($value) > $maxlength) { $messages[] = $this->lang->lang('STORAGE_FORM_TYPE_TEXT_TOO_LONG', $definition_title, $storage_title); @@ -589,7 +624,7 @@ protected function validate_path(string $storage_name, array &$messages) : void if ($this->provider_collection->get_by_class($current_provider)->get_name() == 'local' && isset($options['path'])) { - $path = $this->storage_helper->get_current_definition($storage_name, 'path'); + $path = $this->request->is_set_post('submit') ? $this->request->variable([$storage_name, 'path'], '') : $this->storage_helper->get_current_definition($storage_name, 'path'); if (empty($path)) { @@ -602,5 +637,4 @@ protected function validate_path(string $storage_name, array &$messages) : void } } - } diff --git a/phpBB/phpbb/storage/adapter_factory.php b/phpBB/phpbb/storage/adapter_factory.php index 62e51f66aeb..61ea8794829 100644 --- a/phpBB/phpbb/storage/adapter_factory.php +++ b/phpBB/phpbb/storage/adapter_factory.php @@ -15,7 +15,6 @@ use phpbb\config\config; use phpbb\di\service_collection; -use phpbb\storage\adapter\adapter_interface; use phpbb\storage\exception\storage_exception; class adapter_factory @@ -54,9 +53,9 @@ public function __construct(config $config, service_collection $adapters, servic * * @param string $storage_name * - * @return adapter_interface + * @return mixed */ - public function get(string $storage_name) + public function get(string $storage_name): mixed { $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; $provider = $this->providers->get_by_class($provider_class); @@ -71,7 +70,15 @@ public function get(string $storage_name) return $this->get_with_options($storage_name, $options); } - public function get_with_options(string $storage_name, array $options) + /** + * Obtains a configured adapters for a given storage with custom options + * + * @param string $storage_name + * @param array $options + * + * @return mixed + */ + public function get_with_options(string $storage_name, array $options): mixed { $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; $provider = $this->providers->get_by_class($provider_class); diff --git a/phpBB/phpbb/storage/helper.php b/phpBB/phpbb/storage/helper.php index de75ab337c5..66921b60a78 100644 --- a/phpBB/phpbb/storage/helper.php +++ b/phpBB/phpbb/storage/helper.php @@ -21,31 +21,41 @@ class helper /** @var config */ protected $config; - /** @var service_collection */ - protected $provider_collection; - - /** @var service_collection */ - protected $adapter_collection; - /** @var adapter_factory */ protected $adapter_factory; /** @var state_helper */ protected $state_helper; - public function __construct(config $config, service_collection $provider_collection, service_collection $adapter_collection, adapter_factory $adapter_factory, state_helper $state_helper) + /** @var service_collection */ + protected $provider_collection; + + /** @var service_collection */ + protected $adapter_collection; + + /** + * Constructor + * + * @param config $config + * @param adapter_factory $adapter_factory + * @param state_helper $state_helper + * @param service_collection $provider_collection + * @param service_collection $adapter_collection + */ + public function __construct(config $config, adapter_factory $adapter_factory, state_helper $state_helper, service_collection $provider_collection, service_collection $adapter_collection) { $this->config = $config; - $this->provider_collection = $provider_collection; - $this->adapter_collection = $adapter_collection; $this->adapter_factory = $adapter_factory; $this->state_helper = $state_helper; + $this->provider_collection = $provider_collection; + $this->adapter_collection = $adapter_collection; } /** * Get adapter definitions from a provider * * @param string $provider_class Provider class + * * @return array Adapter definitions */ public function get_provider_options(string $provider_class) : array @@ -57,6 +67,7 @@ public function get_provider_options(string $provider_class) : array * Get the current provider from config * * @param string $storage_name Storage name + * * @return string The current provider */ public function get_current_provider(string $storage_name) : string @@ -69,6 +80,7 @@ public function get_current_provider(string $storage_name) : string * * @param string $storage_name Storage name * @param string $definition Definition + * * @return string Definition value */ public function get_current_definition(string $storage_name, string $definition) : string @@ -102,7 +114,7 @@ public function get_current_adapter(string $storage_name): object * * @return mixed Storage adapter instance */ - public function get_new_adapter(string $storage_name) + public function get_new_adapter(string $storage_name): mixed { static $adapters = []; @@ -123,6 +135,13 @@ public function get_new_adapter(string $storage_name) return $adapters[$storage_name]; } + /** + * Delete configuration options for a given storage + * + * @param string $storage_name + * + * @return void + */ public function delete_storage_options(string $storage_name): void { $provider = $this->get_current_provider($storage_name); @@ -134,16 +153,41 @@ public function delete_storage_options(string $storage_name): void } } + /** + * Set a provider in configuration for a given storage + * + * @param string $storage_name + * @param string $provider + * + * @return void + */ public function set_storage_provider(string $storage_name, string $provider): void { $this->config->set('storage\\' . $storage_name . '\\provider', $provider); } + /** + * Set storage options in configuration for a given storage + * + * @param string $storage_name + * @param string $definition + * @param string $value + * + * @return void + */ public function set_storage_definition(string $storage_name, string $definition, string $value): void { $this->config->set('storage\\' . $storage_name . '\\config\\' . $definition, $value); } + /** + * Copy a file from the current adapter to the new adapter + * + * @param $storage_name + * @param $file + * + * @return void + */ public function copy_file_to_new_adapter($storage_name, $file): void { $current_adapter = $this->get_current_adapter($storage_name); @@ -166,7 +210,6 @@ public function copy_file_to_new_adapter($storage_name, $file): void */ public function update_storage_config(string $storage_name) : void { - // Remove old storage config $this->delete_storage_options($storage_name); diff --git a/phpBB/phpbb/storage/state_helper.php b/phpBB/phpbb/storage/state_helper.php index 6105dc25da3..82523f0aa01 100644 --- a/phpBB/phpbb/storage/state_helper.php +++ b/phpBB/phpbb/storage/state_helper.php @@ -31,6 +31,11 @@ class state_helper /** @var service_collection */ protected $provider_collection; + /** + * @param config $config + * @param db_text $config_text + * @param service_collection $provider_collection + */ public function __construct(config $config, db_text $config_text, service_collection $provider_collection) { $this->config = $config; @@ -48,6 +53,13 @@ public function is_action_in_progress(): bool return !empty(json_decode($this->config_text->get('storage_update_state'), true)); } + /** + * Get new provider for the specified storage + * + * @param string $storage_name + * + * @return string + */ public function new_provider(string $storage_name): string { $state = $this->load_state(); @@ -55,6 +67,14 @@ public function new_provider(string $storage_name): string return $state['storages'][$storage_name]['provider']; } + /** + * Get new definition value for the specified storage + * + * @param string $storage_name + * @param string $definition + * + * @return string + */ public function new_definition_value(string $storage_name, string $definition): string { $state = $this->load_state(); @@ -62,6 +82,11 @@ public function new_definition_value(string $storage_name, string $definition): return $state['storages'][$storage_name]['config'][$definition]; } + /** + * Get the update type + * + * @return update_type + */ public function update_type(): update_type { $state = $this->load_state(); @@ -69,6 +94,11 @@ public function update_type(): update_type return update_type::from($state['update_type']); } + /** + * Get the current storage index + * + * @return int + */ public function storage_index(): int { $state = $this->load_state(); @@ -76,6 +106,13 @@ public function storage_index(): int return $state['storage_index']; } + /** + * Update the storage index + * + * @param int $storage_index + * + * @return void + */ public function set_storage_index(int $storage_index): void { $state = $this->load_state(); @@ -85,6 +122,11 @@ public function set_storage_index(int $storage_index): void $this->save_state($state); } + /** + * Get the current remove storage index + * + * @return int + */ public function remove_storage_index(): int { $state = $this->load_state(); @@ -92,6 +134,13 @@ public function remove_storage_index(): int return $state['remove_storage_index']; } + /** + * Update the remove storage index + * + * @param int $storage_index + * + * @return void + */ public function set_remove_storage_index(int $storage_index): void { $state = $this->load_state(); @@ -101,6 +150,11 @@ public function set_remove_storage_index(int $storage_index): void $this->save_state($state); } + /** + * Get the file index + * + * @return int + */ public function file_index(): int { $state = $this->load_state(); @@ -108,6 +162,12 @@ public function file_index(): int return $state['file_index']; } + /** + * Set the file index + * + * @param int $file_index + * @return void + */ public function set_file_index(int $file_index): void { $state = $this->load_state(); @@ -117,6 +177,11 @@ public function set_file_index(int $file_index): void $this->save_state($state); } + /** + * Get the storage names to be updated + * + * @return array + */ public function storages(): array { $state = $this->load_state(); @@ -178,7 +243,7 @@ public function init(update_type $update_type, array $modified_storages, request */ public function clear_state(): void { - $this->save_state([]); + $this->save_state(); } /** @@ -210,5 +275,4 @@ private function save_state(array $state = []): void { $this->config_text->set('storage_update_state', json_encode($state, JSON_THROW_ON_ERROR)); } - } From ca918f893e135b36f226a79e07db0dd36bb277d4 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 6 Jun 2024 19:37:36 -0700 Subject: [PATCH 0226/1214] [ticket/17333] Suggested code improvements PHPBB-17333 Signed-off-by: Matt Friedman --- phpBB/includes/acp/acp_board.php | 4 ++-- phpBB/includes/functions.php | 8 +++----- phpBB/install/schemas/schema_data.sql | 2 +- phpBB/language/en/acp/board.php | 6 +++--- .../phpbb/db/migration/data/v400/add_webpush_options.php | 6 +++--- phpBB/phpbb/notification/method/webpush.php | 2 +- tests/functional/notification_webpush_test.php | 4 ++-- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 53faa4bc7de..a7b31cfe202 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -494,8 +494,8 @@ function main($id, $mode) 'webpush_enable' => ['lang' => 'WEBPUSH_ENABLE', 'validate' => 'bool', 'type' => 'custom', 'method' => 'webpush_enable', 'explain' => true], 'webpush_vapid_public' => ['lang' => 'WEBPUSH_VAPID_PUBLIC', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true], 'webpush_vapid_private' => ['lang' => 'WEBPUSH_VAPID_PRIVATE', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true], - 'webpush_method_enables' => ['lang' => 'WEBPUSH_METHOD_ENABLES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], - 'webpush_dropdown_subscribe'=> ['lang' => 'WEBPUSH_DROPDOWN_SUBSCRIBE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], + 'webpush_method_default_enable' => ['lang' => 'WEBPUSH_METHOD_DEFAULT_ENABLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], + 'webpush_dropdown_subscribe' => ['lang' => 'WEBPUSH_DROPDOWN_SUBSCRIBE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true], 'legend3' => 'ACP_SUBMIT_CHANGES', ], diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index da22c4739d7..355a3342bd8 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3874,8 +3874,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id = } // Assign web push template vars globally (if not done already by ucp_notifications) for the dropdown subscribe button - if ($config['webpush_enable'] - && $config['webpush_dropdown_subscribe'] + if ($config['webpush_enable'] && $config['webpush_dropdown_subscribe'] && $template->retrieve_var('NOTIFICATIONS_WEBPUSH_ENABLE') === null) { $methods = $phpbb_notifications->get_subscription_methods(); @@ -3883,9 +3882,8 @@ function page_header($page_title = '', $display_online_list = false, $item_id = if ($webpush) { - $formHelper = $phpbb_container->get('form_helper'); - $template_ary = $webpush['method']->get_ucp_template_data($controller_helper, $formHelper); - $template->assign_vars($template_ary); + $form_helper = $phpbb_container->get('form_helper'); + $template->assign_vars($webpush['method']->get_ucp_template_data($controller_helper, $form_helper)); } } } diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 7756586c706..ba8f654a908 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -329,7 +329,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\backup\con INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_enable', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_public', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_vapid_private', ''); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_enables', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_method_default_enable', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('webpush_dropdown_subscribe', '1'); INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 3353a360e8a..3228db126b5 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -590,10 +590,10 @@ 'WEBPUSH_VAPID_PUBLIC_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) public key is shared to authenticate push messages from your site.
          Caution: Modifying the VAPID public key will automatically render all Web Push subscriptions invalid.', 'WEBPUSH_VAPID_PRIVATE' => 'Server identification private key', 'WEBPUSH_VAPID_PRIVATE_EXPLAIN' => 'The Voluntary Application Server Identification (VAPID) private key is used to generate authenticated push messages dispatched from your site. The VAPID private key must form a valid public-private key pair alongside the VAPID public key.
          Caution: Modifying the VAPID private key will automatically render all Web Push subscriptions invalid.', - 'WEBPUSH_METHOD_ENABLES' => 'Enable all user-based web push notification options by default', - 'WEBPUSH_METHOD_ENABLES_EXPLAIN'=> 'When this setting is enabled, users who subscribe and allow browser notifications will start receiving them automatically. Users only need to visit the UCP Notification settings to disable any unwanted notifications.

          If this setting is disabled, users will not receive any notifications, even if they have subscribed, until they visit the UCP Notification settings to enable the specific notification options they wish to receive.', + 'WEBPUSH_METHOD_DEFAULT_ENABLE' => 'Enable all user-based web push notification options by default', + 'WEBPUSH_METHOD_DEFAULT_ENABLE_EXPLAIN' => 'When this setting is enabled, users who subscribe and allow browser notifications will start receiving them automatically. Users only need to visit the UCP Notification settings to disable any unwanted notifications.

          If this setting is disabled, users will not receive any notifications, even if they have subscribed, until they visit the UCP Notification settings to enable the specific notification options they wish to receive.', 'WEBPUSH_DROPDOWN_SUBSCRIBE' => 'Show “Subscribe” button in notification dropdown', - 'WEBPUSH_DROPDOWN_SUBSCRIBE_EXPLAIN'=> 'Display a “Subscribe” button in the Notification dropdown, allowing users to easily subscribe to push notifications from anywhere in the forum.', + 'WEBPUSH_DROPDOWN_SUBSCRIBE_EXPLAIN' => 'Display a “Subscribe” button in the Notification dropdown, allowing users to easily subscribe to push notifications from anywhere in the forum.', ]); // Jabber settings diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php index 1a1727b2568..751ab9e5f41 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_options.php @@ -26,13 +26,13 @@ public static function depends_on(): array public function effectively_installed(): bool { - return $this->config->offsetExists('webpush_method_enables') || $this->config->offsetExists('webpush_dropdown_subscribe'); + return $this->config->offsetExists('webpush_method_default_enable') || $this->config->offsetExists('webpush_dropdown_subscribe'); } public function update_data(): array { return [ - ['config.add', ['webpush_method_enables', true]], + ['config.add', ['webpush_method_default_enable', true]], ['config.add', ['webpush_dropdown_subscribe', true]], ]; } @@ -40,7 +40,7 @@ public function update_data(): array public function revert_data(): array { return [ - ['config.remove', ['webpush_method_enables']], + ['config.remove', ['webpush_method_default_enable']], ['config.remove', ['webpush_dropdown_subscribe']], ]; } diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index a823aed6a07..902880104d7 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -96,7 +96,7 @@ public function is_available(type_interface $notification_type = null): bool */ public function is_enabled_by_default() { - return (bool) $this->config['webpush_method_enables']; + return (bool) $this->config['webpush_method_default_enable']; } /** diff --git a/tests/functional/notification_webpush_test.php b/tests/functional/notification_webpush_test.php index a07e0a89486..61478cec986 100644 --- a/tests/functional/notification_webpush_test.php +++ b/tests/functional/notification_webpush_test.php @@ -33,7 +33,7 @@ public function test_acp_module() 'config[webpush_enable]' => 1, 'config[webpush_vapid_public]' => 'BDnYSJHVZBxq834LqDGr893IfazEez7q-jYH2QBNlT0ji2C9UwGosiqz8Dp_ZN23lqAngBZyRjXVWF4ZLA8X2zI', 'config[webpush_vapid_private]' => 'IE5OYlmfWsMbBU1lzvr0bxrxVAXIteSkAnwGlZIhmRk', - 'config[webpush_method_enables]' => 1, + 'config[webpush_method_default_enable]' => 1, 'config[webpush_dropdown_subscribe]' => 1, ]; $form = $crawler->selectButton('submit')->form($form_data); @@ -74,7 +74,7 @@ public function test_ucp_module() $this->assert_checkbox_is_checked($wp_list, 'notification.type.report_pm_closed_notification.method.webpush'); $this->assert_checkbox_is_checked($wp_list, 'notification.type.report_post_closed_notification.method.webpush'); - $this->set_acp_option('webpush_method_enables', 0); + $this->set_acp_option('webpush_method_default_enable', 0); $crawler = self::request('GET', 'ucp.php?i=ucp_notifications&mode=notification_options'); From ec34f72a3b6293ba7f720b343cabd4e7e3380dab Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 10 Jun 2024 22:16:25 -0700 Subject: [PATCH 0227/1214] [ticket/17336] Skip extension package aliases in dev stabilities PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 28aaa4171b9..1d3e6c84846 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -396,6 +396,11 @@ protected function do_get_available_packages($type) /** @var CompletePackage $version */ foreach ($versions as $version) { + if (strpos($version->getVersion(), '9999999') === 0) + { + continue; + } + if (!$highest_version || version_compare($version->getVersion(), $highest_version->getVersion(), '>')) { $highest_version = $version; From c2d91650bd5e8e7f9e2ba005e47934292faaafb0 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 11 Jun 2024 16:08:39 +0700 Subject: [PATCH 0228/1214] [ticket/17337] Fix mysqli driver is missing transaction begin statement PHPBB-17337 --- phpBB/phpbb/db/driver/mysqli.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php index e474b2584e1..6182801da60 100644 --- a/phpBB/phpbb/db/driver/mysqli.php +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -155,7 +155,9 @@ function _sql_transaction($status = 'begin') switch ($status) { case 'begin': - return @mysqli_autocommit($this->db_connect_id, false); + @mysqli_autocommit($this->db_connect_id, false); + $result = @mysqli_begin_transaction($this->db_connect_id); + return $result; break; case 'commit': From d8aa3f05e5c0754241fcdcc19da35fa1a6b2c852 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 11 Jun 2024 07:04:01 -0700 Subject: [PATCH 0229/1214] [ticket/17336] Correctly handle aliased packages PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 1d3e6c84846..68b7f2e5200 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -22,7 +22,9 @@ use Composer\Json\JsonFile; use Composer\Json\JsonValidationException; use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; +use Composer\Package\Version\VersionParser; use Composer\PartialComposer; use Composer\Repository\ComposerRepository; use Composer\Semver\Constraint\ConstraintInterface; @@ -390,23 +392,25 @@ protected function do_get_available_packages($type) foreach ($compatible_packages as $name => $versions) { // Determine the highest version of the package - /** @var CompletePackage $highest_version */ + /** @var CompletePackage|CompleteAliasPackage $highest_version */ $highest_version = null; - /** @var CompletePackage $version */ + /** @var CompletePackage|CompleteAliasPackage $version */ foreach ($versions as $version) { - if (strpos($version->getVersion(), '9999999') === 0) - { - continue; - } - if (!$highest_version || version_compare($version->getVersion(), $highest_version->getVersion(), '>')) { $highest_version = $version; } } + // If highest version is DEFAULT_BRANCH_ALIAS (9999999-dev), then it's a non-numeric dev branch handled by + // an Alias, so we need to get the actual package being aliased in order to show the true non-numeric version. + if ($highest_version->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) + { + $highest_version = $highest_version->getAliasOf(); + } + // Generates the entry $available[$name] = []; $available[$name]['name'] = $highest_version->getPrettyName(); From f98f2c5896fb4594e20d4f0ee77988fddfd6319c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 11 Jun 2024 20:12:01 +0200 Subject: [PATCH 0230/1214] [ticket/17301] Ensure reading invalid cache files is aborted PHPBB-17301 --- phpBB/phpbb/cache/driver/file.php | 40 +++++++-- tests/cache/file_driver_test.php | 140 +++++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 10 deletions(-) diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index a07b9a97010..768e314b182 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -330,6 +330,27 @@ function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, return $query_result; } + /** + * Cleanup when loading invalid data global file + * + * @param string $file Filename + * @param resource $handle + * + * @return void + */ + private function cleanup_invalid_data_global(string $file, $handle): void + { + if (is_resource($handle)) + { + fclose($handle); + } + + $this->vars = $this->var_expires = []; + $this->is_modified = false; + + $this->remove_file($file); + } + /** * Read cached data from a specified file * @@ -372,14 +393,7 @@ function _read($filename) if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0) { - // We cannot process the file without a valid number of bytes - // so we discard it - fclose($handle); - - $this->vars = $this->var_expires = array(); - $this->is_modified = false; - - $this->remove_file($file); + $this->cleanup_invalid_data_global($file, $handle); return false; } @@ -392,9 +406,17 @@ function _read($filename) } $var_name = substr(fgets($handle), 0, -1); + $data_length = $bytes - strlen($var_name); + + if ($data_length <= 0) + { + $this->cleanup_invalid_data_global($file, $handle); + + return false; + } // Read the length of bytes that consists of data. - $data = fread($handle, $bytes - strlen($var_name)); + $data = fread($handle, $data_length); $data = @unserialize($data); // Don't use the data if it was invalid diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index 10c9aec1824..cf36cda2bba 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -17,6 +17,9 @@ class phpbb_cache_file_driver_test extends phpbb_cache_common_test_case { private $cache_dir; + /** @var \phpbb\cache\driver\file */ + private $cache_file; + public function getDataSet() { return $this->createXMLDataSet(__DIR__ . '/fixtures/config.xml'); @@ -36,7 +39,8 @@ protected function setUp(): void } $this->create_cache_dir(); - $this->driver = new \phpbb\cache\driver\file($this->cache_dir); + $this->cache_file = new \phpbb\cache\driver\file($this->cache_dir); + $this->driver = $this->cache_file; } protected function tearDown(): void @@ -49,6 +53,140 @@ protected function tearDown(): void parent::tearDown(); } + public function test_read_not_readable() + { + global $phpEx; + + // Create file that is not readable + $this->assertTrue($this->cache_file->_write('unreadable', 'foo', time() + 86400)); + + $filename = "{$this->cache_dir}unreadable.$phpEx"; + @chmod($filename, 0000); + $this->assertFalse($this->cache_file->_read('unreadable')); + @chmod($filename, 0600); + $this->assertNotFalse($this->cache_file->_read('unreadable')); + } + + public function test_read_data_global_invalid() + { + global $phpEx; + + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + $filename = "{$this->cache_dir}data_global.$phpEx"; + $cache_data = file_get_contents($filename); + // Force negative read when retrieving data_global + $cache_data = str_replace("\n13\n", "\n1\n", $cache_data); + file_put_contents($filename, $cache_data); + + $this->assertFalse($this->cache_file->_read('data_global')); + } + + public function test_read_data_global_zero_bytes() + { + global $phpEx; + + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + $filename = "{$this->cache_dir}data_global.$phpEx"; + $cache_data = file_get_contents($filename); + // Force negative read when retrieving data_global + $cache_data = str_replace("\n13\n", "\n0\n", $cache_data); + file_put_contents($filename, $cache_data); + + $this->assertFalse($this->cache_file->_read('data_global')); + } + + public function test_read_data_global_hex_bytes() + { + global $phpEx; + + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + $filename = "{$this->cache_dir}data_global.$phpEx"; + $cache_data = file_get_contents($filename); + // Force negative read when retrieving data_global + $cache_data = str_replace("\n13\n", "\nA\n", $cache_data); + file_put_contents($filename, $cache_data); + + $this->assertFalse($this->cache_file->_read('data_global')); + } + + public function test_read_data_global_expired() + { + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() - 86400]); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + + // Clear data + $reflectionCacheVars->setValue($this->cache_file, []); + $reflectionCacheVarExpires->setValue($this->cache_file, []); + + $this->assertTrue($this->cache_file->_read('data_global')); + + // Check data, should be empty + $this->assertEquals([], $reflectionCacheVars->getValue($this->cache_file)); + } + + public function test_read_data_global() + { + $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); + $reflectionCacheVars->setAccessible(true); + $expectedVars = ['foo' => 'bar']; + $reflectionCacheVars->setValue($this->cache_file, $expectedVars); + + $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); + $reflectionCacheVarExpires->setAccessible(true); + $expectedVarExpires = ['foo' => time() + 86400]; + $reflectionCacheVarExpires->setValue($this->cache_file, $expectedVarExpires); + + // Create file in invalid format + $this->assertTrue($this->cache_file->_write('data_global')); + + // Clear data + $reflectionCacheVars->setValue($this->cache_file, []); + $reflectionCacheVarExpires->setValue($this->cache_file, []); + $this->assertEquals([], $reflectionCacheVars->getValue($this->cache_file)); + $this->assertEquals([], $reflectionCacheVarExpires->getValue($this->cache_file)); + + $this->assertTrue($this->cache_file->_read('data_global')); + + // Check data, should be empty + $this->assertEquals($expectedVars, $reflectionCacheVars->getValue($this->cache_file)); + $this->assertEquals($expectedVarExpires, $reflectionCacheVarExpires->getValue($this->cache_file)); + } + private function create_cache_dir() { $this->get_test_case_helpers()->makedirs($this->cache_dir); From 9e15802805d356b02834348d74e7f65b121663b7 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 11 Jun 2024 20:29:23 +0200 Subject: [PATCH 0231/1214] [ticket/17301] Do not test unreadable files on windows PHPBB-17301 --- tests/cache/file_driver_test.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index cf36cda2bba..56a1b28073e 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -55,6 +55,11 @@ protected function tearDown(): void public function test_read_not_readable() { + if (strtolower(substr(PHP_OS, 0, 3)) === 'win') + { + $this->markTestSkipped('Unable to test unreadable files on Windows'); + } + global $phpEx; // Create file that is not readable From 5229813c4761830f1c7ff067f55d860c68655bbb Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 11 Jun 2024 10:57:05 -0700 Subject: [PATCH 0232/1214] [ticket/17336] Optimize version sorting PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 68b7f2e5200..3c59fc552dc 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -24,7 +24,6 @@ use Composer\Package\BasePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; -use Composer\Package\Version\VersionParser; use Composer\PartialComposer; use Composer\Repository\ComposerRepository; use Composer\Semver\Constraint\ConstraintInterface; @@ -395,20 +394,23 @@ protected function do_get_available_packages($type) /** @var CompletePackage|CompleteAliasPackage $highest_version */ $highest_version = null; - /** @var CompletePackage|CompleteAliasPackage $version */ - foreach ($versions as $version) + // Sort the versions array in descending order + usort($versions, function ($a, $b) { - if (!$highest_version || version_compare($version->getVersion(), $highest_version->getVersion(), '>')) - { - $highest_version = $version; - } - } + return version_compare($b->getVersion(), $a->getVersion()); + }); - // If highest version is DEFAULT_BRANCH_ALIAS (9999999-dev), then it's a non-numeric dev branch handled by - // an Alias, so we need to get the actual package being aliased in order to show the true non-numeric version. - if ($highest_version->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) + // The first element in the sorted array is the highest version + if (!empty($versions)) { - $highest_version = $highest_version->getAliasOf(); + $highest_version = $versions[0]; + + // If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage, + // so we need to get the package being aliased in order to show the true non-numeric version. + if ($highest_version instanceof CompleteAliasPackage) + { + $highest_version = $highest_version->getAliasOf(); + } } // Generates the entry From 0a20edf6b450c1019a97ac8fd64ee7256f2b9e6f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 11 Jun 2024 10:59:29 -0700 Subject: [PATCH 0233/1214] [ticket/17336] Fix usage of a leaked loop-scoped variable PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 3c59fc552dc..bd6e6c3168e 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -420,7 +420,7 @@ protected function do_get_available_packages($type) $available[$name]['composer_name'] = $highest_version->getName(); $available[$name]['version'] = $highest_version->getPrettyVersion(); - if ($version instanceof CompletePackage) + if ($highest_version instanceof CompletePackage) { $available[$name]['description'] = $highest_version->getDescription(); $available[$name]['url'] = $highest_version->getHomepage(); From b08023f231ccd2ddb577f370b5cad53b74047d68 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 12 Jun 2024 06:10:23 -0700 Subject: [PATCH 0234/1214] [ticket/17336] Check extension compatibility correctly PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 36 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index bd6e6c3168e..a18e49d24d0 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -24,9 +24,11 @@ use Composer\Package\BasePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; use Composer\PartialComposer; use Composer\Repository\ComposerRepository; use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\VersionParser; use Composer\Util\HttpDownloader; use phpbb\composer\io\null_io; use phpbb\config\config; @@ -353,7 +355,7 @@ protected function do_get_available_packages($type) $downloader = new HttpDownloader($io, $composer_config); $json = $downloader->get($url)->getBody(); - /** @var \Composer\Package\PackageInterface $package */ + /** @var PackageInterface $package */ foreach (JsonFile::parseJson($json, $url)['packageNames'] as $package) { $versions = $repository->findPackages($package); @@ -365,7 +367,7 @@ protected function do_get_available_packages($type) { // Pre-filter repo packages by their type $packages = []; - /** @var \Composer\Package\PackageInterface $package */ + /** @var PackageInterface $package */ foreach ($repository->getPackages() as $package) { if ($package->getType() === $type) @@ -464,11 +466,11 @@ public function check_requirements() /** * Updates $compatible_packages with the versions of $versions compatibles with the $core_constraint * - * @param array $compatible_packages List of compatibles versions - * @param ConstraintInterface $core_constraint Constraint against the phpBB version + * @param array $compatible_packages List of compatibles versions + * @param ConstraintInterface $core_constraint Constraint against the phpBB version * @param string $core_stability Core stability - * @param string $package_name Considered package - * @param array $versions List of available versions + * @param string $package_name Considered package + * @param array $versions List of available versions * * @return array */ @@ -476,21 +478,35 @@ private function get_compatible_versions(array $compatible_packages, ConstraintI { $core_stability_value = BasePackage::$stabilities[$core_stability]; - /** @var \Composer\Package\PackageInterface $version */ + /** @var PackageInterface $version */ foreach ($versions as $version) { try { + // Check stability first to avoid unnecessary operations if (BasePackage::$stabilities[$version->getStability()] > $core_stability_value) { continue; } - if (array_key_exists('phpbb/phpbb', $version->getRequires())) + $requires = $version->getRequires(); + $extra = $version->getExtra(); + + // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'requires' + if (isset($requires['phpbb/phpbb'])) { - /** @var ConstraintInterface $package_constraint */ - $package_constraint = $version->getRequires()['phpbb/phpbb']->getConstraint(); + $package_constraint = $requires['phpbb/phpbb']->getConstraint(); + if (!$package_constraint->matches($core_constraint)) + { + continue; + } + } + // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'soft-require' + if (isset($extra['soft-require']['phpbb/phpbb'])) + { + $version_parser = new VersionParser(); + $package_constraint = $version_parser->parseConstraints($extra['soft-require']['phpbb/phpbb']); if (!$package_constraint->matches($core_constraint)) { continue; From 9a53df6f73a237e35132e5b7a6bef3afe4fd681b Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 12 Jun 2024 06:42:03 -0700 Subject: [PATCH 0235/1214] [ticket/17336] Move object instantiation outside of the loop PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index a18e49d24d0..a54e315ca0f 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -476,6 +476,8 @@ public function check_requirements() */ private function get_compatible_versions(array $compatible_packages, ConstraintInterface $core_constraint, $core_stability, $package_name, array $versions) { + $version_parser = new VersionParser(); + $core_stability_value = BasePackage::$stabilities[$core_stability]; /** @var PackageInterface $version */ @@ -505,7 +507,6 @@ private function get_compatible_versions(array $compatible_packages, ConstraintI // Check for compatibility with phpBB if 'phpbb/phpbb' exists in 'soft-require' if (isset($extra['soft-require']['phpbb/phpbb'])) { - $version_parser = new VersionParser(); $package_constraint = $version_parser->parseConstraints($extra['soft-require']['phpbb/phpbb']); if (!$package_constraint->matches($core_constraint)) { From 3690d307266b492a3bd4eb6c542ba5a1252ec21f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Wed, 12 Jun 2024 06:42:38 -0700 Subject: [PATCH 0236/1214] [ticket/17336] Also check for compatible extension by PHP version PHPBB-17336 Signed-off-by: Matt Friedman --- phpBB/phpbb/composer/installer.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index a54e315ca0f..cd7456baf64 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -514,6 +514,17 @@ private function get_compatible_versions(array $compatible_packages, ConstraintI } } + // Check for compatibility with php if 'php' exists in 'requires' + if (isset($requires['php'])) + { + $php_constraint = $version_parser->parseConstraints(PHP_VERSION); + $package_constraint = $requires['php']->getConstraint(); + if (!$package_constraint->matches($php_constraint)) + { + continue; + } + } + $compatible_packages[$package_name][] = $version; } catch (\Exception $e) From d66d4a0c6ab5d9ac4d4dd1a37a90d8b3ea2e9889 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 12 Jun 2024 22:16:41 +0200 Subject: [PATCH 0237/1214] [ticket/17339] Support receiving push notifications if not logged in PHPBB-17339 --- .../migration/data/v400/add_webpush_token.php | 53 ++++++++++++ phpBB/phpbb/notification/method/webpush.php | 8 ++ phpBB/phpbb/ucp/controller/webpush.php | 80 +++++++++++++++++-- phpBB/styles/all/js/push_worker.js.twig | 8 +- 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 phpBB/phpbb/db/migration/data/v400/add_webpush_token.php diff --git a/phpBB/phpbb/db/migration/data/v400/add_webpush_token.php b/phpBB/phpbb/db/migration/data/v400/add_webpush_token.php new file mode 100644 index 00000000000..f87dadf9e79 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/add_webpush_token.php @@ -0,0 +1,53 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class add_webpush_token extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\add_webpush', + ]; + } + + public function effectively_installed(): bool + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'notification_push', 'push_token'); + } + + public function update_schema(): array + { + return [ + 'add_columns' => [ + $this->table_prefix . 'notification_push' => [ + 'push_token' => ['VCHAR', ''], + ], + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'notification_push' => [ + 'push_token', + ], + ], + ]; + } +} diff --git a/phpBB/phpbb/notification/method/webpush.php b/phpBB/phpbb/notification/method/webpush.php index 4d2d18bdc65..700503f97d5 100644 --- a/phpBB/phpbb/notification/method/webpush.php +++ b/phpBB/phpbb/notification/method/webpush.php @@ -51,6 +51,9 @@ class webpush extends messenger_base implements extended_method_interface /** @var int Fallback size for padding if endpoint is mozilla, see https://github.com/web-push-libs/web-push-php/issues/108#issuecomment-2133477054 */ const MOZILLA_FALLBACK_PADDING = 2820; + /** @var array Map for storing push token between db insertion and sending of notifications */ + private array $push_token_map = []; + /** * Notification Method Web Push constructor * @@ -145,9 +148,11 @@ public function notify() 'avatar' => $notification->get_avatar(), ]), 'notification_time' => time(), + 'push_token' => hash('sha256', random_bytes(32)) ]; $data = self::clean_data($data); $insert_buffer->insert($data); + $this->push_token_map[$notification->notification_type_id][$notification->item_id] = $data['push_token']; } $insert_buffer->flush(); @@ -221,7 +226,9 @@ protected function notify_using_webpush(): void $data = [ 'item_id' => $notification->item_id, 'type_id' => $notification->notification_type_id, + 'user_id' => $notification->user_id, 'version' => $this->config['assets_version'], + 'token' => hash('sha256', $user['user_form_salt'] . $this->push_token_map[$notification->notification_type_id][$notification->item_id]), ]; $json_data = json_encode($data); @@ -337,6 +344,7 @@ public static function clean_data(array $data): array 'item_parent_id' => null, 'user_id' => null, 'push_data' => null, + 'push_token' => null, 'notification_time' => null, ]; diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index b60fa437b3b..15e54956fb2 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -100,10 +100,37 @@ public function __construct(config $config, controller_helper $controller_helper * @return JsonResponse */ public function notification(): JsonResponse + { + if (!$this->request->is_ajax() || $this->user->data['is_bot'] || $this->user->data['user_type'] == USER_INACTIVE) + { + throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden'); + } + + if ($this->user->id() !== ANONYMOUS) + { + $notification_data = $this->get_user_notifications(); + } + else + { + $notification_data = $this->get_anonymous_notifications(); + } + + // 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']) : ''; + + return new JsonResponse($data); + } + + /** + * Get notification data for logged in user + * + * @return string Notification data + */ + private function get_user_notifications(): string { // Subscribe should only be available for logged-in "normal" users - if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot'] - || $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE) + if ($this->user->data['user_type'] == USER_IGNORE) { throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden'); } @@ -119,10 +146,53 @@ public function notification(): JsonResponse $result = $this->db->sql_query($sql); $notification_data = $this->db->sql_fetchfield('push_data'); $this->db->sql_freeresult($result); - $data = json_decode($notification_data, true); - $data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : ''; - return new JsonResponse($data); + return $notification_data; + } + + /** + * Get notification data for not logged in user via token + * + * @return string + */ + private function get_anonymous_notifications(): string + { + $token = $this->request->variable('token', ''); + + if ($token) + { + $item_id = $this->request->variable('item_id', 0); + $type_id = $this->request->variable('type_id', 0); + $user_id = $this->request->variable('user_id', 0); + + $sql = 'SELECT push_data, push_token + FROM ' . $this->notification_webpush_table . ' + WHERE user_id = ' . (int) $user_id . ' + AND notification_type_id = ' . (int) $type_id . ' + AND item_id = ' . (int) $item_id; + $result = $this->db->sql_query($sql); + $notification_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $notification_data = $notification_row['push_data']; + $push_token = $notification_row['push_token']; + + // Check if passed push token is valid + $sql = 'SELECT user_form_salt + 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'); + $this->db->sql_freeresult($result); + + $expected_push_token = hash('sha256', $user_form_token . $push_token); + if ($expected_push_token === $token) + { + return $notification_data; + } + } + + throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden'); } /** diff --git a/phpBB/styles/all/js/push_worker.js.twig b/phpBB/styles/all/js/push_worker.js.twig index f2807cb43ab..cf0ca568ac2 100644 --- a/phpBB/styles/all/js/push_worker.js.twig +++ b/phpBB/styles/all/js/push_worker.js.twig @@ -23,12 +23,16 @@ self.addEventListener('push', event => { let itemId = 0; let typeId = 0; - let notificationVersion = 5; + let userId = 0; + let notificationVersion = 0; + let pushToken = ''; try { const notificationData = event.data.json(); itemId = notificationData.item_id; typeId = notificationData.type_id; + userId = notificationData.user_id; notificationVersion = parseInt(notificationData.version, 10); + pushToken = notificationData.token; } catch { self.registration.showNotification(event.data.text()); return; @@ -45,6 +49,8 @@ self.addEventListener('push', event => { const formData = new FormData(); formData.append('item_id', itemId.toString(10)); formData.append('type_id', typeId.toString(10)); + formData.append('user_id', userId.toString(10)); + formData.append('token', pushToken); fetch(getNotificationUrl, { method: 'POST', From e125f1f7094a8cd9b14e380d43e713721b6684e5 Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 13 Jun 2024 13:27:03 +0700 Subject: [PATCH 0238/1214] [ticket/17338] Prefer user_last_active to display user last activity info PHPBB-17338 --- phpBB/includes/acp/acp_users.php | 2 +- phpBB/includes/functions_display.php | 2 +- phpBB/memberlist.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 611e72a9b80..80089c2717d 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1088,7 +1088,7 @@ function main($id, $mode) $s_action_options .= ''; } - $last_active = (!empty($user_row['session_time'])) ? $user_row['session_time'] : $user_row['user_last_active']; + $last_active = $user_row['user_last_active'] ?: ($user_row['session_time'] ?? 0); $inactive_reason = ''; if ($user_row['user_type'] == USER_INACTIVE) diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index d995676e8fb..26f2d43cb5c 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -1603,7 +1603,7 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl if ($data['user_allow_viewonline'] || $auth->acl_get('u_viewonline')) { - $last_active = (!empty($data['session_time'])) ? $data['session_time'] : $data['user_last_active']; + $last_active = $data['user_last_active'] ?: ($data['session_time'] ?? 0); } else { diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index dbdfe80618e..c846bdde055 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -1722,7 +1722,7 @@ { $row['session_time'] = $session_ary[$row['user_id']]['session_time'] ?? 0; $row['session_viewonline'] = $session_ary[$row['user_id']]['session_viewonline'] ?? 0; - $row['last_visit'] = (!empty($row['session_time'])) ? $row['session_time'] : $row['user_last_active']; + $row['last_visit'] = $row['user_last_active'] ?: $row['session_time']; $id_cache[$row['user_id']] = $row; } From db9874546b95f17254ca944b050f4ea595d688b2 Mon Sep 17 00:00:00 2001 From: rxu Date: Fri, 14 Jun 2024 10:28:25 +0700 Subject: [PATCH 0239/1214] [ticket/17338] Add user_last_active to session_gc() PHPBB-17338 --- phpBB/phpbb/session.php | 10 ++++++---- tests/session/fixtures/sessions_garbage.xml | 3 +++ tests/session/garbage_collection_test.php | 4 ++++ tests/test_framework/phpbb_session_test_case.php | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index af39ecf2fa0..46e82d03baf 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -987,7 +987,7 @@ function session_gc() // For SQLite versions 3.8.3+ which support Common Table Expressions (CTE) $sql = "WITH s3 (session_page, session_user_id, session_time) AS ($sql_select) UPDATE " . USERS_TABLE . ' - SET (user_lastpage, user_lastvisit) = (SELECT session_page, session_time FROM s3 WHERE session_user_id = user_id) + SET (user_lastpage, user_lastvisit, user_last_active) = (SELECT session_page, session_time, session_time FROM s3 WHERE session_user_id = user_id) WHERE EXISTS (SELECT session_user_id FROM s3 WHERE session_user_id = user_id)'; $db->sql_query($sql); @@ -1000,7 +1000,9 @@ function session_gc() while ($row = $db->sql_fetchrow($result)) { $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' + SET user_lastvisit = ' . (int) $row['recent_time'] . ', + user_last_active = ' . (int) $row['recent_time'] . ", + user_lastpage = '" . $db->sql_escape($row['session_page']) . "' WHERE user_id = " . (int) $row['session_user_id']; $db->sql_query($sql); } @@ -1010,14 +1012,14 @@ function session_gc() case 'mysqli': $sql = 'UPDATE ' . USERS_TABLE . " u, ($sql_select) s3 - SET u.user_lastvisit = s3.recent_time, u.user_lastpage = s3.session_page + SET u.user_lastvisit = s3.recent_time, u.user_last_active = s3.recent_time, u.user_lastpage = s3.session_page WHERE u.user_id = s3.session_user_id"; $db->sql_query($sql); break; default: $sql = 'UPDATE ' . USERS_TABLE . " - SET user_lastvisit = s3.recent_time, user_lastpage = s3.session_page + SET user_lastvisit = s3.recent_time, user_last_active = s3.recent_time, user_lastpage = s3.session_page FROM ($sql_select) s3 WHERE user_id = s3.session_user_id"; $db->sql_query($sql); diff --git a/tests/session/fixtures/sessions_garbage.xml b/tests/session/fixtures/sessions_garbage.xml index 59a2dc2ebe7..16971428a70 100644 --- a/tests/session/fixtures/sessions_garbage.xml +++ b/tests/session/fixtures/sessions_garbage.xml @@ -7,6 +7,7 @@ user_sig user_lastpage user_lastvisit + user_last_active 4 bar @@ -14,6 +15,7 @@ oldpage_user_bar.php 1400000000 + 1300000999 5 @@ -22,6 +24,7 @@ oldpage_user_foo.php 1400000000 + 1300000998 diff --git a/tests/session/garbage_collection_test.php b/tests/session/garbage_collection_test.php index 9080478a285..32c4db3f85d 100644 --- a/tests/session/garbage_collection_test.php +++ b/tests/session/garbage_collection_test.php @@ -65,11 +65,13 @@ public function test_session_gc() [ 'username_clean' => 'bar', 'user_lastvisit' => 1400000000, + 'user_last_active' => 1300000999, 'user_lastpage' => 'oldpage_user_bar.php', ], [ 'username_clean' => 'foo', 'user_lastvisit' => 1400000000, + 'user_last_active' => 1300000998, 'user_lastpage' => 'oldpage_user_foo.php', ], ], @@ -89,11 +91,13 @@ public function test_session_gc() [ 'username_clean' => 'bar', 'user_lastvisit' => '1500000000', + 'user_last_active' => '1500000000', 'user_lastpage' => 'newpage_user_bar.php', ], [ 'username_clean' => 'foo', 'user_lastvisit' => '1500000000', + 'user_last_active' => '1500000000', 'user_lastpage' => 'newpage_user_foo.php', ], ], diff --git a/tests/test_framework/phpbb_session_test_case.php b/tests/test_framework/phpbb_session_test_case.php index deb76d4e5e5..15b5d44eb53 100644 --- a/tests/test_framework/phpbb_session_test_case.php +++ b/tests/test_framework/phpbb_session_test_case.php @@ -50,7 +50,7 @@ protected function setUp(): void protected function check_user_session_data($expected_session_data, $message) { - $sql= 'SELECT username_clean, user_lastvisit, user_lastpage + $sql= 'SELECT username_clean, user_lastvisit, user_last_active, user_lastpage FROM ' . USERS_TABLE . ' ORDER BY user_id'; From e21a8e02cdc21c5e6c70727114eab4006014dedd Mon Sep 17 00:00:00 2001 From: rxu Date: Fri, 14 Jun 2024 15:22:20 +0700 Subject: [PATCH 0240/1214] [ticket/17338] Update user_last_active on session removal and login keys reset PHPBB-17338 --- phpBB/phpbb/session.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index 46e82d03baf..a1d7df511a5 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -903,7 +903,8 @@ function session_kill($new_session = true) } $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ', + user_last_active = ' . (int) $this->data['session_time'] . ' WHERE user_id = ' . (int) $this->data['user_id']; $db->sql_query($sql); @@ -1652,7 +1653,9 @@ function reset_login_keys($user_id = false) if ($row) { $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' + SET user_lastvisit = ' . (int) $row['session_time'] . ', + user_last_active = ' . (int) $row['session_time'] . ", + user_lastpage = '" . $db->sql_escape($row['session_page']) . "' WHERE user_id = " . (int) $user_id; $db->sql_query($sql); } From df7dae9600f40ec7d4ed08ef86752e296e598006 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 14 Jun 2024 17:02:36 +0200 Subject: [PATCH 0241/1214] [ticket/17340] Update composer to 2.7.7 PHPBB-17340 --- composer.phar | Bin 2975214 -> 2994603 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/composer.phar b/composer.phar index bd5bab8c1f54ef0e61a61108a23af1b1cc87dd0f..15c4a2081c9ec2e651ab7a65ac68349d5f2625ce 100755 GIT binary patch delta 43023 zcmb@v2Vhf2(l~Cp$&xKuwj|3H%f=vgOYSzs$d)Va-59WCNw#Ink}SzJfJu&oG>-BD zn@UN$Gzg?A1p?%fTslcE<#K6|1o6GgN^86+hQT&#~zGH)xNppHlM1}RBHZ-kFe{Ev%WfO-tKg&)Z64U$XN z2~?plFE&#gd!tv==USe{}op+*j$e*OVYq@?{Ne#AVIRm#pXZ z2ny)XcLa+iDLu3mm#&ksBiw;7I(xM+hM@IoxR1}<3m&(77qV4I7a|Su@o6}@N9}ER zg*6-%`}pMbK2ymxi4qbjEw(!&JGKk9%F-EBt6-!z&=}=rQE{R%Brhh;bF5y zbG9L@fWpM3ZtS^@-r-V-R8km28*y29W2J(7yD^GukVpvJoA^FHe|tE>#SKg5OHLKh zp15pz?XwRt=WPq)bB{~n=&IkBES5YGO*`T8&n45oWO2GMg`~BMHsJD`GRdlVEZE+5Rl{1t9S zkbnL1L&MJXak!%o2B2i{93 z2>GEQ)Mm3_DcKEdMAX)VqD=BlCH;ZR+dsJB1F~8S<$DEFTcMF<1l$kxz?RQ^ts&fp z7jgt{7>lYUgO|W=4?h*j^HSu;D9TA^Kvr%HnUZo_lPRgH zqAB(l0rr;+exvg??1*L(x<8uD@=G+^va*BGLbrIOt_lwi30=7;z0 zVco@C(mbX$OXlgw+-8xF&z`GRL=!p2bKgHNOd=|$!*Q8#cg07;)tA;g$t<`k+`iL?13)Xd(0b0P|e&;Hv}N`>NRJQoYku-GDKFgh@Uw9jS&VI7>|w zSF57->LQTAsAZ9L+{=|w+y)ifup3p(7`~~>rgMmrGbR7pOcBH-Iw?MeIbd75kh3OB zB_~p78!mrMsVf8KFC|EQ3`p(EL04lVggg7DFzO_v(0xjU9jsXK(OVv9wJBn5SBij6 zdrt~gY3rbq&x_Z*&h1W{&nZ)xQCgf@NxN-KWpey{D)URkY0HA~cKr;(`1Rd0+_La! zJp0--#_HFmF*$!ft(L;bO3$Yc=JW!|KY2ibzKlBsDIs4#qAFSwtDBxbWJo=}JDOh9z7n;lGvU8}%x&eghle)9pi7vsz zapR>q;S|ITIaC#5bD*dwy_GtLq*tt@5yTtA#-+eFb~(2wS1UnGgmAelW#(s&S4~O2 zH&;wy9?s2`gf5^%aXIp8avF74aznYaJdwl)95@UYmu}OFaMl>ane(VG^Dpoqw8cAg zV6US&Nj9I`mq$hE&Hx{uxER5sUJ`$q7fUA<=U35(75Q{G8Ue=Rr3<%$3Xb9=1q$w+ z{9s9UGMyQh*N#P+L1GfP7xP0oRRKf3sDQz_w4jgzJX)}nKKKe!gK5;b7V`f$+_KXs9^*)~$q+ zxM4{VL#CmK@yzBTrpw>*)I=3CDO_1xBw5--r@-Y8J6wqbnf(Ci>Ec*2#bKb^@nuV| zAWVQ8LKiW1PG3ar(QfGR!0440Z;zWiJsw@eq*u7Oj;_CZF%^(paH}5O^y=%x`8~ZP zn0sk4b2(2gX0lPdq*T)8rr6=~;5Ed~bhG)tN&nmr{<-0v!3w(-{z<_a_} zV@)?KqslJ?l;6s%)L+T+@q`jBb0|`^OkC~SQi;8T?Io0!+~XaD(2Zqs?zA?PAl{(> zX$uQWCToNSboX$GfUNOWI5}cA;Oa}6*rQUWNj@uGBJrJ1n{oNxg8LsJj#OjWJjr!{ z86Jnrl5lt0M2-?ks0}d$61Vy;aR;Pg5b_ty%hcHXL}CasUaK3_5zw2^pzWS=Xk)_M!HmSy1s^SNE6N!*^w z`E*C0sm!AfyecLb+A1ccYdtlGJT*U76_7b9fJ+}Z{mC}s{5RGFa4V{*GZg~*+Wlg+ zlNb%$aD6p(JDp%KUa#77mRr{p#o>mxt0j_)u?WFF;PRu}nsnSIU`^Z@RU?-8YT52V zY4E?{b~v}pQ&gH=SpEYR(KcLupV@sI_y*~86*t!8(udb-(j_;r12-;9=lNYhlp=diQlNoFA6UB@Wm}+=$DI<}duKw{cx9f8OYr$>}1nFr3R699(wkO5(Ut zIQL^@*VTqe-eZ))hnqe#9SYrocBMyhO4iSkFn@o{Wb?7Qw2K&;c{a^%|r;c{>8 z@ZDa#5*lO@4PyjcKA4i{2FUYbTg(=d)j)AL3n*eD$SHPdigK1kqK31qlT>NQqUCNzu+n%lGg?#7l_ZeOdAyQnpoB5`HwVoAiseq>*8dF;KHL%4TgPTcEk zt3KIZB=W4W0&8CToJKy?w}1@+QXxi5;`$1vlcH(^{lLjtPBs| z($43;XctQEV0(<0iXf(Ww?cvBU=;W8`s}bj#QEoVFu_?8YXgk5BGI;J>)( z4n|0KM>C~}sFP`?@=oT%U){-!)%%^yhHAQ~r8otYUL5$*5wfEFonr1UUCdHE)wP^V zbQXGCl$ve>nl9k9E7)0c#fm)1uL*R0xO8tn-T*tDO)+?V1v9%b-OQZZyV>F4u5M<4 zPIWUYUc8ceu@j(iI&xQa04j0Z@zp|3ygHaWyfU0%@dJ3V+pfC&5Ze~UxS6Y3N$b79 zn)j6}eqych+>ur65cU2lhCuFW#&{E}nZN$nY8IJ2yP8FebUm!*(jG>pH+z_0qUvQ9 zd90T?lUYx?O+qrL{*@iyVIf9Hx!&sJxUt$w>#8r|a3yT+VY7re@j2Dm)Hd(lNm z4_&wy^+Otizt|a^;ZgSR)+mcB)H%B8@OvC9=|hOKmp<5?dV>F@fd3aC z$CxNXIz8;_$|bEA0dZgdyN`+6x?ajvyTsgPmx_*m)J0{%4dbu;kav*N?3Z%NF(%KQ zW0|zum19hZj*Ts+HS@-q3STwO#OjW5Cj6(ynK0%}FxH)zV0`}Y1cUR-1cOsM$vSVF zWRpHOSrpX*@`c$OmxsQ4^D+;67jdIg3%SxMrZRe`>dE?61NQzUqy7Z@7pEAYfaw|u zmT?RSm%p`te=oP^Q7L!PR5)7sb>)7!3*Re?xX4s^|GfX=?GQ*_x%nYOBqV=rliuLRq^X__j#+8Gw7_=7M#w|M- zv+ZKIBe<9a8%8f?U?01P2UDqcP>7WmfUD83iwt>}+ zdA{c?>k8)O5bI-0_F~3G!3G&u|AotuEro1~n0QxM&a=uH&e z5LnrB>W%;4ZhV5!a_1%*ikOju%l`3g;biZ>+>}a3#r~Pmb@b0GXw9)dx6_A|%}h04 zwV6@m-OY@gX8+6@vSGr**4}sWNgz)I`vpzR(UrM;*ROZt^f%4tmLa($4z7(bH{r7SuAN!jy_Z45 z_CW5>h~7HB1Z?rf>!ml)5zz1V@Yj*+`P?tpiqN{}69nAq>w+lWL)S4@ICve6EtLZ31@oJqrm`Qz;bHmL zi)rtw>uD(93s}?XKR+Hobs6rYknDmJI$jnoH>4#56NU%T#{j}^VE&o<2F7sfZlD)z zz5waVsi!uOwf=Gg3v@-_$f!AVBh^UH3PE}9o=zq!IdS6xijMFmrXRX)V%xXpCT0## z-b7ei06M6*CtAGQpbIQqr4A*Pva; zl`OoCc`XyS<LV@uYvdjvcw)jYYH@tqPF@@ zwi_^QV9r~+fK%oSzmsb4WZ=ucf3Po<3w?05G5;>M2P^K%Ba?W=H6BzFp5)M7)Sw;$ zcH&P3#DF48+IBY+a>d=uC+WPKsi0kVGh_G3-AsJ)?_o7F_b~dubPuC{{9d;Hp}ii_ z-plm18LxIz+xW|aHkGrlZFo= zmhjzF&vszo63%j87)9&i`mu)a2lRasIu;)p zfYe!$;-DX4+lW*xd4P>v_W&Dt^Z};h`42K)Tm2y2>rDXbpVtJuOIU2nL-FV(Er1$^ zS^_HMp;Z*Wk%y@G!K!?=zA=ID+}!a81JTRrK%t+QxN{+HHg%nwO})jzA)oEosddc)F<+{|sjtT5ls$AamY zy^pb7`TjAsuEK-Vx19&t)OWb0iRg#Z2gTg|2bn+g;z4@txgNrKk6+V1&MmzMFE5BI zImC4R%0u~*YX&HVartR!iw@)g_Y!k291_vF-#x^bP5n5d;MI>WK1Z?M{_vfhSVp%! z9?k_EX4A(Xrdseru!M=5^FGB?!5!{6%n0(rVdhb*jx83@(?k4{{?5TH^Al>#sfpN|&j==%Zzl$C@d4 zT=vHfo#94~vct=5N7?D)v!hIA7eCQT7kfqq&`W{ z>NA#lo~n(DgloC2ALQSE_B}`zYWOrhC&}b+*c@a);v7K5%EZh+0&xG>i7z#N-K!Su z$l%G*?p$7r`(-I_#uxQ&5C`S;7;ILT-fVRyCykq|Mw_Ei?;1>3CK=6+OjVXDB}I9W zFv&D&u#7p)HfytKz%;2`g67G1i4}T>LqF9sVsZ?al4%#C$(XL}o&yMjSe23j%TSsu zPSYR3N=IMj@)SrU<4MKm&gI@2!F$d(M<`4(SoBV3o!*|Y)L=2`9gR*$yV(XYa=^-2 zI)$;GyMmNOsP0d^NcT(>?-Fq$!BpwRF{M~p;&eI8)&b!~LUX?|xqFp+VKT2b#6u-F zQ0cXOsJl`rN5`)S4h)5SF}uxaa#UNLF1^)&_Uz?Fp$%o>VaU8CSb_vE^TcTJMZrS! z+siyPFA1IeB2*coT(V?|(&ZR4Dc7t)3Geb^(XU1PKqM^WhoGn4;)O>W%}#xv#nfVO znC&j-A9T+c;|IBady6OIp}18MeAIZxSH5E^I18b*!IAEbAM)%*WKZ>%pqE?yLs4~{ zuLxb~3>Trlr}#Ca0M1Y0&Z_c#t-n^eaG}!d>>;E8O6e_QCbGGB{d6ax)nazKl9LQ$ z4u{F=YA1d0xbxb_(Xab_3)J2%CBT5SDO6>e@*D`T`Sd@Ou-`Y!J$SG0s4mcIA9JNE z-EV&5JH28W3z=FKHEFqWBh?LFfg<@aB8MbI_ceajENNg{OXUS0&%2X-o8Wq6g zB-(~iVUK`HlF7X>!|x@5-LU|Z*MAbqP`>i($T z?_lVF0XzsLZ7dchxlEHT=#tn9_~QXe_-(RcBcn7ASV171P-Au}4SI{ksf3(q<%oVt zX|=hO6E?@Nk}xDRDLtxLX`rdqPV|b|F9MCa{1%~W-Vc(Yy>|vip_dN|g50l<`~4*k zUDDwji_krOru=ke|CrT)Er>Fi83a&WBSyR&hsibOu#%rdl&oVG3*O$86r@d&N!?+4 z{RWk2*<@mf`_dQvBKXB~fnh}mK$IRJ@Y4$f>r>oLD!?i$*P>m85s~iuU-Qd}N2k8= z+mKI0nhGruVz3`dB96pF^y|Tr5{fgqW~iZ8o)3_@FZ%ulW!9H_0_mn6Ew!km4?Z=$S7ALfl_`5zu`keC~!(MZ)cRO)j8%6HmVN zM3CIQYG2?o5t_H(pP%xdC}g&QD(r{QgZsnB0%v^74Yn~WSX1J$Ia8ENm1FtL;HSa70`%$&f%T&N z42@cuon5Ta6l=24y4L~~ZrzK4%L~xjXC+eiZh!ulJiN0J=*U=vAZV=$R)%%)ra*qX zybSXrrWjc2y03RLi;tSlDRP)0{;QN6ikbx2ui3#YzcK#)(<#X`5 zAAKt!8$EVyKqxxyFBb?Fo2~t}rHxgMDA>u5LD%Q|2@{pc3!EuH=ao?1(_Y@(Qr%F` zew9|&Yn!`L&{t=Kva04Ws`C?#WTJ*Z}^DLbf^NBVxg z0;T>M5P|IRuzeZZ0|j+f+XSe*(J?bt-cFNU?*NaEIS@c&v%_W`0l!iSbTwng*Qo@@ zM~NK}qTh)pLI6N&j}x6#`ld8Bg0Bx&!wJjk#eS5thxjACO0!j|HyQ~_c->RBF$cC6 zP8T+oq>u7~QxdRCT0xm9L6KVez^sG+3)Y2+UkX?juqu5fKxD)=4g`dmyf!8~voKRl zK%f&T{;{e3`cY@rplu?{7gvCh?Q@;hg5IFC^;@hz+4yq(w zNlqXZi^)-3T;AN=(A?A7tgUZpY-n!nf!v7l##Ue(_nY1P8w1csuL>j3FJ}bdXnjb8 zzX&9D_OORunUF+BsM@S}A20y{d!?Qd4xP~R^A`a6W&`Y>lhT%w6uTs5CkRfzc>p{} zBWg79d;eEd=;IfmuC8D?TK_*ne^mFhT!`#nNP^ru^?XTXVser`W2xDxGmV3D3aSy~ z43^8`a`s?@56$Sr9ehPn3RW-4NqzsWGkWh1el#z|{p%h4Yva+8fsiD0DwP+4dNzmy zGm^&0dq8z&e4L(h^un$WJ?(&A^XLoI+ZPli2Ng(dpdHv7*ci^D;0%OLN#qH0foZKq zNM!=WNIgaqmNg+NX$p!)H7^7S(Yz&LLFkgasm*Qxb-hH~WR4)n~#&y{+Ns%vM1ddgxjHLKNmNiA0Sj zC4tEOEI(>Tr8v+R+&bQKd?ku>NNhAyv>IJ_pp zek~wf1`{j`X`T@(&~N#|0PJ)VGW@OrC1n%DIi1#01K2B8z}Sx*9RT= z9M+Q+8yJYx>4E-H9#839px6p)gM;K~?Q{HaKhV%<%d>nrI=EdRL%NDk8~XV?q6+ z-g^X%W1BGH-zFzo=NA;?9`p+e_CqVu!sXk0c!349i5|$Q&*y|_#kkFEgirLfDwWKwrHsl7`LB?HQ)Y4vlvIDKO|@1hREW0+6)GHv+x) zVn|@Am)qygmQKPMO!%lU%w5qrJ}$7D4$j^fn9>VuZf^z*2O-d=XyEwnkf5}UV{t+5 zu{c4{QKY^x%pXaHKv2Hb3p0ryfL87g6S?=lCpZa_fTx9t=5DQy5iCJUZIS?O`vkb>;ZFp8=m%+-Jj9DM z#zP|si$Hrr!`jf+PX#H+cV3tr9ZLxlp{WI7g>La@g3o!KblnCxubaGMv95x+$)L$F z>>L{*9uW4*iQ|4wtxEO#I<~Dyc~TG?NTH*}Cj|vaJuQqz8&3-EL{-YLBvk)}V4Bvw z^Mzmos=o$A`=uu&5@i2UFojNkDOl`YdP)$^i^VQ1nI5p3MT$DMsNuBWKC*Iw7kec6 zN^qTbu{Fr|Ye6O&4V6j*iCIWSRbLBM%pta zLzSv;1uk^-TY(1s_N^cZ?fyxiLb@}8*HF}V0v(?*6ta9LxEvh`2etUycY<=+O6E3$ z|4O}ZywQ?pg}JEl4KP8I-wVv>_Hv-~nePR~=tx^)AUeHQ2Je-y)>QY%4}#r({8n2T zI3rec^jdIIL&s!+BCO+GgRI`IJ}cPhi=J2!CPiDW2n#@8{wlZ)ZT}6}XnnBAA05A4 z818=MH$fOL$g@8Wo)BjG22!-$p?<=9184Wd{kTB*77rQyg<PLfxv8b(BJ}(X;nkjUP7A8@elQ@rQ5=fkaM(2ks&}YFy;XHDnoaI)$JFY_qni{fCgvn^6L>P}6ql8)8uNRk~{YQl&R8%4fMxwGX zp?jfNxI$gltr{@9z;RYVsH7j485#J#(`^6q=O@7Yw{c* z>B_v6MPSd;(uind+MW*=*Hv4MrpaU=th0~Rf}=F+g8l6t+E1CP%vP%ruSA%D_HK_4n!&`ybfKXl+!GuY z5?_L2iP@%HT2!<^nGEi_(~Ny!&t9a^qtIG9?OZZru_eJ5tnKGu4A{)=ma5dK)yjo2 z=k>``r)t#Mx#X`|ESLRXv2^eKRk$NEJ;`$bhlon43CELRdz^MlkEg!J6Y6p%yGsSf`m5LxTo5w1@lBIzu1q^;#ASpMhZbAE2M2#28Ub$R?Sca6*DW z#CfavFOVWonXD+-2#VlOT?!4xN-~lMciuvHGerqu(NJAf94VFFVY3myT)l7DB zZcnI&u6Unlhn_*bb1-?vgBcr49MRv6CABabgU7)n>j3W3tzJd00>~4@zaXB`LvH@O zjhU+Xzn_wgOvzLg7poF5^4%K3O&fF?NPLr#btFHw44s3|VS zES;>v)jjZv4L;C=0%QZzl{GC5^*z<~?b_OET~AwUMPW~MWqm_)`5LseN)*9cgSJ4C z?@^EhxL4_r`9?s4L0Cqa33og2U#5~^fH!KbJNk*>j73O)Hdq>aftUbb32F%rH0a_@ za+$mPm*DUHnlErpA@-tM1sDAp5Rk{_ls#rl!}Ty$$3pKYSgZFm1MsoS>2BbKJg!Xj zI{fFJ9+;Y#I}W|Sl^2cdVIh2XW@5AVjYm1P|0Dlxlq{6f=D5L!Z$}|2WspHQJ^*a0LTtB zhJ>QZT)4cw_D8si-IN##Cxbfhm3EhfWTKp3Wl~gE7P1&!bDcnfu8M#&``wvAirFr) zYlXl}Smn%uH#uR+7PEC29WDx4hz=%%WM*P|{P%=#$LT^kRH){&@CbYoEWGeZ&}p*t z|J#$G32mJSi9s`KL!>3zIs0?&c@WN&=R%shC9sv`wgE$mL`P7()V*;yr7JzSp9jo6z8=8Y;u#}tp@HzlIGra^uTr|1-gf zsH|UfeUL{`(L4R3wccOu&H+(Q1p3<=ku=C->X3G=C^g#i3s~4W)(29aoP^!NbY&4b zvPKjg;%O(*Xgnf*Zq4sTX>ySEkU+j8Ru<^qwN`XQRz?;M_oxt?>n5P7HDH?IyauJ! zG@)Fn#TgR~RF{I3uXV59CTipT{{acz$B9&M;{pB_b|2kuaiVcSSUPx@Hj6T8Lhq!r zqOBJRWLknbd#!x{Sn3kpt@QTIZmiqT~*IEe*$Az;1bALt4n58%0r(Q0+}D z@SMGHU$}e{NrPv*m!O25A~}lNCCZ!+nqJktirk+PlS7O{4_F48kU)mV2ZTkU&YdC& zdg3P0r*jnd-8)4Sd4Y}aHtD1bWgZi$JkOdmfdVaCq6$P;wuZ)|t3Kj~q5Ut2Lfrb- zLhU7bu?VFCZ8C;P(ZU*W7ihT4YQ*z-i_q?zKp9Hg z5h!%;tr3U&^9e>OmHTkLxLt|%j)_D0OiiObCq=RdN(``>BvR{9=O}bs9OqtO6T1`8 zrkfywwL4#wfZjR67ozUX!2);iR&fOni8etX@cBQ>B`E48h?Lml!~Na1ZQ_6U%@Dq! z(hL5x=M+o4Vb4VLvX7$I(}VS6Pmc=sP(c70`w%M9Hj5d)!-V_45{L}2WFyZ68B1A6 zK3V7llVf%H_a{ThXLK-Fx`-VW@!dC`L&-vT27e}~P?hs#|jUpcu%Y}@@I7YwwxHvk@^Ba5t zSYd+Miq^Ts5m|GW1Q(?q1NwVgN@W$Dcvq}2czVHow-v7SEmQak0$+RdO?g%a_c#4Y z3|yJWW=>SX{fXxXG0=(1i9s`Dl8lTwVW>%2HhZn^L?=EHM+VO#y~zFkGvcF0^jWSr zSOnD|KFo@v6Yr!)qvL9Rr2AM#XnzEWPv$j}E?z@INBk~2=>?QTo{)a2B-SI4Ae=D) z-s&i%zH32%yG9p!yBL*NAmHC$5sL+ClDv3{bUZTv^*G{pQ%!_v@^&-qE{Z9#B8+W(rb9G$B7 zosaCh0}9a3y93J5?zNIE_vz0<9Ra-b?Gnj6bn2_nZug++iTo~$O(R)5) zBjWunAP3bpg$K*AD6;DS7K_7DG%rqKM7_m90qBw!;o=_$gkj!pG^!Ly(S5rGGTth* ztjJeE`jiRJor~PcnysF+H74gkI(E-!Wb=Xhh3y`>#YJgSNfe4d8!AKVUkeBbrA?si z$(c%sG~b7&qWW(`HR+IE0ykPXrv$SNz3U^MP=?(yHUQ^0axY1{pr4EU;{;GKh3{80 z(UJ`z(dgnhNfC;Sm1x{=#7GuN#2$kS%5KE&LUz8N+#R1T3GhSqU}-=gt#&76NopL4 zNsyL-bDFwMlXeR*7+gyOw=pLrI|WWc8*khrao@Nn?ADbE2*SC@RZJ_$f3inesN73_ z4T~;EcYP1{9H+8*foP;Gd>niL!iICNAE;Zu3ig{d&2{+DJ7wXocO}`uS127bTOd`H zOjS+NaF`!D=PJYG1nu~{-sd=*N#}s)4NG7vmO?E3^c#Nu?zeT}-wRO)w=fEQ+7Pab zr#_0=O0uu0w{C(PK+r{G$52{hczFb|J-_RN_T3by5_p)wfetr^M4*on;f6-JK`uqx z8^d2DkxX|_Q}~N^In6-o8^c+vJ$kqe?e9045btRCe96SbL?$$hKpq~g-FY}Xd1Lw$ z;qLS&q{nItF6bQu6L>1;n3Nl`;eUa&&M4rApIG`?spTG1?uMv@R|klUUh`^7&9VupH1<#nM0>9Xh` z@(AP6iwk5??n@WSQsgL1E{jL|cfjq+wL1bO=&41r4zzVgpdxs6G7hvAXJxI)a=*Pu zwxt-|_gh$md;PfV>Hu`38WJbt*2wCS`t4A$dt{A_D+SNyz?G7i5vOx-4Y}4YNrBPH zCB?}ljf>$b+Of2Ib@8%QX(>=e>g*6d@zkZ1EK4cD9S3br7gT1EevON9Fn(!PW@<{w z+7UArC5ZUL-y?b#T){X~gsAbbENcb?PK5_TUNX5jB#g!SfUU;)n>Zf{&iE?8jwVoI zjDQ_g;Gmw{9XCL4>7sB6kv-s#iHwqu%2NFCUZSR>vMvRrh66hoaaw`FVgr$& zJAqz0DtiQW{>4jyjc$KJX7j_@bSGMcS!mN`vU}ZU{wizrMY-wxh=?jk^{9Y2HP~;+ zIayh4Z*!U9KBDqz**EUdzsYXryT3gqlLn$C(Gl~*yfJuBLMYO_A&Wy(0TG?(_BUj) z=*NHvsrzql$PNWipPWRwkN-oKi0*txmI4q+40!SzaDi+)IMhJ*?5^)at_ z5eO@d8{C=ssi`M z0;gG;=SuFb%b>c!jtK_Y2hbfqK*-!_q5d@-Od*)E^n~nEUKlWxc|`A+soG${GmV~JxQ%Kk>dFt<`kdWK=2hO& zFe?e4VX_O^6z6xfp~l~2!gPq)Ls-BC^Y!Qr20)<)N7!)E3Y{N@duz~=-(+zK|8WFb z`kO2^|KD{Z`P3I$Fl2A=+*!+$}Ff!^@MKsInKU;b~_w9>KCy3&=1e%d3BM*9!?hQT$_>^v}%#4(*ARCx)Ka6Y*l@ ziwXcCPl8lGBt)^qGOTGqLC^r~EM7qRBqx}f5fiG2lb6e}VZ+)QoFzf0|g8MaUf~uaJ|jINn=4r}K_^;8mQBlxNK_*ti3>86Mhz zaY-g%SvDzc4ztq+wxxLMFs5W{Gw?5)!#qvYFnqUF?}VgtY<~J6D=$B{ z$5cRq+>n`8W^C!7>GjX+D=FD0xAMVCh~)U*KHeEM8#5j7?Vp5139^HNZSTO13Rb zAkQDWAoF*_anIjD@p7apQeIL?r z06h{95wqj*^2Gwoy<|n`tIK#&A^z#4zeknwdUV}^un-e<#jCtcKz*W?<|%3hkG1VEz;<;_UiCyGVCy%rIKUPzEf zN8~dU4r}s&MZ9M6(L7G4@za|g$!RI@|!PD^M>h>C|a&6!YTlB z03gUn4aW^anF^bug=kKAABdE|qm&oO08am@EOPF)6&G8zbI59S{OJpIW@Z)dbv19C`t%I z%nDzaWw8?T94P62cp2)C8Qn>AA0}C?ho#TkYJ51E1xFduyNP(tIilyCkmO-yp3kgA z%~DtwpcJycxiBg?W9cjq{T)*ITrt?wD3PLuClW;Hctn7JP)#y9rGqE-gg z;%;pALHcl58K)4O$2R5G@nrHY9nA{InHyvkFbFNXJzh5B#R0}*(f3jxa;d$52^V=U z?q_L4I-f(L{{|hyfVl@xi%}ATwmTmu^ZyQ~|7J=IktN3eZaNcbWJ~!EG!P2^o7t?k z3sXA+xmEJ{ewg-lTWoZ-`ORQpX}B(kK)#fVFh+py7zM+YlKBBV%_=PbbKQe)1!rUT5` z+@IJxL!(P13y?S|Lx4VMj}WVI-E26Jr6@GeQzO)Swv56~5nhB|D3ZkBTT|>F5`PbU zk}A)Z&gpdC&_f-=l_ioPVnQJt?VVt^IVDl3W|!n zMGo(B;_DMH!y=7#!8~+nnmin39|KQtZ<;(kcJ8T}#YW-CObLPP5AhY^Iq%ED)}u$> zm&Ndsk>;Q@5&hhfA(+8$PFTrnEG_hakg0@sc$7<*C<`)kNCepc=M(xnFFR8W(OLX< z33*Qjzc5Ak4cuntYyc^{aSWlMv9+hMxuLTQXH3nW{3uJo24i;#tGgMG!iPF?>?Trx zxsL(#LaT2e@USAqp{(;%9@$!tqeWEw`QyoGZCh&Lx1uV^6CU1%(x7@LR- zf~VoK^T3}6y-`b-pX7xuCa8HsZRqMKI8rVufUE7}A)$fcAepl-mqKu0M30&-i-XAT z-f%v9F&f+(c@f=^B+`TAR zTA5#JlB$KIR^g}wL`a_0Y(Khi4nplG^G73JPs){MSS$^`?BC@Z18dXAEX+u zZ|>22`7v2kAbHmy0nK|y22u25G6BjCQAMDEW3mLXN0}t!@hRx}W3n{#@orv;8~pMj z9(rLXTyU#b2T9zwXUYYBB`jr!k_by5oLszqJaoyXj!1J3Lz+ z<3zV#1CIZ)hvWix!)@}$Sai>4^Wm!ip^;MeBS+=){2)IO4uq~`hzMhGnhnRodvCzK zV9g+q9z$M$a)0-vJlekz)Jg*8ZO;ow|JN53(dbKZ8OnG@9^)=~Rz4+M=}FlFH4mYS z%IemhvN~N$Pknj0t_T0pRX1a`BZP1vyv+uoO?bk0qvU~Jp3-NUN)5+W6@(-+fFa;U z^hTaI(*6A3I|b;#M{*@v_mMoy`!j)07$rgJ*8W2-V|5_-E;Ko4W{B^U3qC+CAr}TW#BIRryM>(ad-cM zFm(SL@`E#YUWO4USkKjx6F!~}=}qONjhS_(!e^UsRs;Fxy*xgjVZ!Lu$k=W8Jv5@q z%+69U3HWN&X`aD1J}^K2L;_s!KoTrYeSpx|0%ukxO>}pFNRPl=6Y#MW@`)DDbaUa5 zizBQjX=(#`fe^rAz3)NayG_YBgC5=@#c%b^DDfFLv(JkV286>DWV&t@;}({(J7I(< zcoNyoxf20GbAQ6T3}rF}!Nr5~Iq2b#9GGsq#cY6-enK0%8yA=tEzOQl_yMn>U*D8N zGBb3fsG!~tc`|g*xPbAjvhZHkDuulCc^zaHl>g_nGQe3F6!@BXRoDIiL6SU z-KRgVDo3p`{=Uj|UX?mWeSTGczs3WrST#eZROb~Iv601W%Ixf%{PU^+v3^zsSHc9> zY;|t#`BmAPyz{Em>b|+Xu&x0X9v!o}+~KF?zwl7Ou5chtgD-#PLR{-r#rPu#j0cHJ zG|SlVTDteT8t8~~TPepw2AAiV9l`(x++h>`CvRxr?PUI6zaG6eBo9OHzm*=?d|_>b zbG(5o%jnk_ux%ii;Fk&@HJv6SkAMJpJ7L4bexRd+;U;9?Crv@8n*xH7>?zsSIj@ts z62c?U3;7YD?#gfEkMfYNPLhOX8f6LY)o0|Jcqn^a+B~$WW}(#G@V&ezDC!Td3eadX zTr%%pE)1_}Ca;)OfDWhM9aI`^CZ|;e7MCU+lh1TO{Ob>IGnn{gCZYp}lcL@Gev|L@ zMSm%d(4Y;z5%by241${(P_@1hYvAn}sX*>tp^VrNjXo%jNQ-4uhg0X|R0DP6y}HLO zS`@J|n|}FX*G4(y!;M7DG{Do6*owHF8xVCzsVBg6tutwCr&4(NwoF0r$ z-LFui-T#V}B$`|}&x*d$jlExJWnc4PIU7#2zfz$Qdh0XLiAqJwOm)B4DS_R@*4g_4 zHiW^AIM*L%dErAANq>T8FNkzVc7nI};Z$p4uD%Og0YD>D31A5#kZH@<``n%sEQ&|6DHEpXrTMB^7(y?6lw`~TrwtRxgGt#7D3X_u zdQ&o$_yZ?ilqmrGB@>+91>P8O$KdUA{Cyp`3&AB=MKU>o(hEllQ@?oPhJbhDB$~CycgB=)#SyRbS0%jk(xpq$TVBc zKnaq72H&RvA*Gv#KMVo80_y_;Q-a_*9q!_=59xm%XvyYGQ>K@JfCktL!5eCJI9>475==t? zItu{@AT9u4hJ6EI76QD$;i8)f42KVJt_gT!%?v6EK6QZU0$YMn!fqU{ftwD44L+k~ za6u{`yd(g5LrR=ch8=Ojx6s)^M&Q19L6}wXB{4)8Z2k^7wW6jJMRW+fh-Drb8-ab( zsL=tbLJ=~epJX*fMTqB$jSTi~Z6WCrB_w%|Fa&-#y$u_C?{Bezei_p)#Jf*1f5#Uz!yMUbK;^yXW54)OxI0^i1?-qu+7{Kl1vFjO3)kVI%RR)EN?>egpW)5naSRjEnn=#`44Xuk^9 z`_4;>2m&3cuToT`!rqWC&p}iRtJVoG3j^N7eqn_K2~k$~q>=}(w7Gb}%kt!Q6m%ZY zhQ3>e-zyy(7*tMM%!rp52iwxCXh4!^Sl9Y2MXhytl@coqGQRC56}MEoDzBI?626=$SLnH_&`kL!?Oe3tI{LSMtED1zAUnF_SYk2 zk(J(G?jT*{PXEX{ph2D9q10%Un!Ms{_7wwf%aVK6u(1cG&9Yi*0NysS*wj8mHd3wWy$MV8H! zH4T-OX$>O-+KK8??Lc)?{m^K~P@To#v<%g&wH3xmQ+uaYr(Iq;FfckeJk-$G)TGl+ zmk!l6IVZ}Rx^(SLO_k*nHRZzQ)^clISzD!6(^g(KFkx!0Y}Xj8>WB1|Z3FF($*Cn^%}#7MQv&+t?HZUsHv`NaZW6!kaW7Tnzj`klNO;qyU0~tQE%zWX`j+} zHs@7WG}pCFl~y#V>jv6JipGs)+NnBS^+a81Zl|slO5MOzeXBO79?H538`L#vXkGQR zu)cLz)6j04=$bCCt`u0+-t*&ag*<$Q$p6<#Xw7`s( zzRGq-U$%OnExUcl*jZyYRt*mbo3l0M7G1fcu8cy`PE45pV1e*DR^32zTfMHjqGr4= zr)i*W2%}w^U015DES(yyoDsIv`b;AwL_R1RF3Y$}1x7^(6XdKRJa2c03>ayxI!tCLOTCJ@ttxr=v zT-8+MSY9~VKQU6&R5#X^Q(fCNkd-@7uglidj1@U6?bW8H;VOHpy3D9+>@PHH%L*$y zodr%!O`)}{GQYk|SXZu{sKOi5tZpbR?JBQmsGpjy%x@oQGfn0>>IYlvt7=En8uF)& z`TgkP@hD}8P}@|MRjLIJ87N1~17f0Qs@hx31*YNh^0cv*!LnMXt+}shtZHQWP*+*@ zXz5sGYoTSl$i6~YXDb*^%hMT#vs)V+ea)rK**f!det&J%K=nkuy0UJCtEg08q}OT7 ztBPthj-0mIW~;ho%4RScbyM~UYfVk1u0@;EINfh-6AtLJoc(Fd*6e}+b5ns!jb713 z$0b})B$`{aUXeftZjOpVryC=K(dog+1a#Tg(V>c_>J^i1wWgu$f~ty~wBZ8VL{4d^ zWg2bS7bTlHR}6$$4*oxh!3Cs1h{fO!q@bluU5=$-#v=yPObn*$bahh=y7H;I)-H8@ zI}w9iTqmrbuAcs5G5Fs}fe?!UmV%}K8!>3J)R&$k2Gi*5H__=c4Qh?8t5RE7=vuCC zA1_PORO%e&zM|ZYg6VxP}9W4eY4 z!*tDxmj3bK0pmzR<tR^_>sWobjX^(|vnZThCxPS<#2 zU4B>Eh`pk`vop_9+uY}>HH|kkYRaa^M{?_0ie{R!n_BJ80+`w8G}}9DLQCObl`gB) zR9-!9t!kc5bGD8Rl(yE}Aiz{M(NyQq8iuSx>TE}TZD;m$hpXIW>NlFxrblb~nzYpx zW4*Jsa;nZy-Z4F%RV1vaZpmrR8l5Wa)S0q#UCw%odAPr}u32rU&*|5sW!KD@wDpt5 z<W-v(397AVCRc^Mnt97a++t_N%9n;sF9R)cx23zj9aJqJEEIX~Ky=`!~sxHel z>YCEj4wsqB3|YBd=IM_5?0iGH-cc|*(WcIBnC!|PZmk`64VTpnw>1tFjxKkUmKBT? z4o@^x3^ff19o7A%wVg8sH7kZ;($dP|!YtEZP4$Q)yVOx_YoD65Wtr;w3Ic%M(pp=xM)lTeZGm-@q6sx<*og|Q zwxJBvtah|+a?tL`(^v=Fo9vb21*27^75%mLG@-NIQP?rnm|te?&mF0%EYnry40I0b zM~AbTOt!kl!I|MASFNLO`ADaJY(i66TifYyjTE)yj9Jv$!mcrwNi*2mG1RcU!>q2b z3x}sWN5)oI99@oFTVh&#~HFt!Y`E4b5t&t+2UkKxeG1 z)DLENI)^&5DjPH%{kHc1*V>gpMRld=E*60j5Rd|;?&DEVRG|e_y{#77Rsm5_KorEd z`)Wg!f`Vc-cF3NNS&b9_q>t%#iP#;uW9L7zIFAA#z4QVzHh5{QK%yv5p9DDasO`PwxU3 zTgI)d9a|Evv#sW~^>uIU-r6ZO)p@pc3~bZOP29l9(#G}$A>W$8My@^>8g34SoSjSR zg^m>~hI}KnjVtZF9o{u-f=k=Ob<67)hwEE{J$=I=?@C+kqQS-eZMI?OmLdEbUe@>a ztq!gjk{et>+`}Ww>$mmz8=D$OM_tX$wPU@u;Yi1dEp@@oJ%eJKeYAh|GH*{`WR2jK zmo|wbg5>orbS*%M)#O|h5oNc2xHagLZ0Mrc-_$(NzG_jMXF*r1%e&fEE7rQYg)JVD zTWxpSnp$k#Tdv_3QY#WyBu2l}-@Pe%#M_0(_ z9JNOVB%!V`;L!KFd2dVGa={j^Z*LG6){lfcLnuB%%U9G#2JNd>itfgdrB0!zk@GgU zw5@83aJ6uvV+MhP&l*ob`Is&T?(IQepZRZQq^V{Pc;h)^p>7^L^_H zcKlNhk?}$S1V$LP2L^oj02M-!?)icpQ%_iGYsJgq&u5H(L1Plqlo9Qz9L}6Ip4Z-&Dd*od zJ~IW5WvAz5h9_W=!`-gbA{g0_l#PiJFtDoSp`5ggp%_v*m}cvz&osyH*IJ`7j;fb_ zCF(5GiVd2HYGR`i^podOXB6yKGBV+<4~)+mCt0&56UgRrd>H9~ujkqvwqy zx+H|A>8qJ)guBigr@=!@^l7?&m@}X&ZKqa_t8}?m1vgr+Ld}nVri$JX{bsa2sGh|w zr~r%F11QV|y@lL|((q5sk4sa7R|9H~ITui)X{)NvijpeW>|E{U2zI(b%McP+>x774@6rLyj)^nypc7PBmRlXqW~+YxYcxht_PZv(80XxY`L)Ef-x8Q zsrQ0$B}N!Ty$@op3&^S(A`6^)Z2C;)%mrg(@(g{|+~|Asnu+37(}HkuyQKi8rB2IJ znm;o>m9DgWZIqJXz2oSV`0{7Q+-s-?o!W3zf>jlcYF<&FsLP?I2A%Qhhy9wfoK`gY zA|6oGiZrRMPy-KI3i}6IA!h(l-hNj-H79G>kN6uDD%Og=@9^s9Miv9mxp+}@9u7<> z&QB_S^`5AgG3^uQn6?8q{*Tcc>lhzH)n%h+aY;r0AO;^{G$od%%ojn87J6G#kRbYr zYQMCR>XNptUEwh5j;ay4NlPezzp2KKb^F z)3cOg22!riqoH{HGL6HF)Y5o<4COMF5UV&bLM+DMs6Uz%AV%*siwHIMV;u1tENRNQ zvPGq_gtX~&5?pkV**a-=6+tMj*(4q6OG&xa;gfv6pbsNT1djJgK1t?04w>g#mT zgrH0G%0UnOp^DrJ-&kg3KyevKPq1Dd2N?QAgTE*&DPck7uHWxk!HCQ)iBx^g>0D@#I+ zBu=3AdY*i)QMvqevI)YMh+cVHBJ(qK0+{M>t_6ruodLhABeSfM=#Ybgn@6>h*Dd&* zNG0#`OF_5G7nGc$)9DvoPR;}JZwU(*t4L4^BTThDMsb4GIiM6L+}XuLM26l4XhFvCC3Vpp^->Ss(^9$(gZ^9+Cs9y;-~ZL<2<}1 zIXS0*dNaZAbjYG8dia3DD~O^mD7s}YPJA#vza@!R(dIPK4q|f(IH%dlM*)(mgNJ@b z^7Gx*65S&wC+$4Neij~98~;g!(P&P!gDoU&7r{4PIA5hw45bDxl#A$*ABR3uo#udbGa zODo7r=TftzDuJUCP!MW(RRUhY78UTSg_)|{dyKr5jC9rBLuBRDuSs!|CL1b}B@~XT zbWlmGJdOkYi+{%b!0qrmT#`ul0?CEq&*9(&B-i7V9Ui9-X_aLZA8w7#o@YpLMS?Xn zIumYxZxM4(j!@;`)G0(!&)0}f1^NGB9kjlQY`@`IasjDPP&i~K!P@6ZkGtQ z!8`qakB8&^L8nJ_xH(5ab~u9Wpd`qC(d~E2g2;;=kEC(yPhKR(#H*;nAmI*h0&jvN zhe^hLhf-n9OXO|{n~4d+M@W7;vflO$PRf0gWk4d64d1$FWGcsBCbK5a_^)(8Rx=nt z@hVyKeFHT2buwl5zvGe)N}Mkk6rFD50GCtbd@jEmH%Y$>%@e%r6&+q=eLNS$2T3m> zL}Pz^gGji(I${Zz#t}OSB~OxRaG{*BCCd9br^vj?BcW{k6S>@c^-;)xZ^{^()sM!1 zpI7jDaWnMzq)|J?n?m(4@NZb~RJ0qvAlU;!*BNH4YKCLn;pbDK#bo zR3hGUG2hGPr`?RLf)jbS%f-7&HO#uCf5iDREJy|Mt_;L|*)Bh_; zCD7W!5NK^;GL$8a%v@ceaFqe$aLc3vR*~>In0~D?eJOKmQla#n2DO8$Is*xwR3W#O zaVzE)MwfyI7n}n+g1GzBSxE3`MJG{tdIhs5IZ1TD^~xP{GbD8 z;A&$;j}UJj6(xsiNNZ~97dO-|rOCR~AX%CSt$(1NqTEwu95#kZnJDn> zgi2~&k}E3FNdLL23h(=B(XfpxDm7x3obis}=uYeOF+@vkYp-3=enrt13`R!JAoRMw z>L*>jQX*9v7EJ1CX+PqLsvQMXP1$#;k9LKa9+N7@>!JE7N)zlfQZ%#2rxd~7W0~nN ze+R;j?|$Dr6OMdrn4-M0gY`|()hLxa*)8etS$awyi(WQ3?C2>TCslQZk2&6|F^|fb z2U$!Lxq`*NuQu&ti+IEdLWEv<6kTa??&JRCYs(|+QCHW6PJt@;6sqm0QVjiVXTium z_NRH1>(Mvx{?FMG$lk}6W=?oH3yMlCMas1O>|vdnJ_=qvz~-UiY*RQlKiP+VK=uP{ z(G-;{Dq;9xwm1nDUngee7-+he{!Z#`A7wBM{q=2aqrvq3wfF)vaDZtCI&;??V5e1T z4U|~pQG2xRdevqs_ySu%Y>SsX9=4YO1N;lBN-Gt5267LxJSzS|eD?Y0s< z`t(JyFJSjU7BdPx!b%_lbzf>!MwB)wwr^5#a*ba5l|TLS?B|w68YybVWmj)E+4naSeEkD#*G& z)u=Q-%8uw#XjB%28*GKjmyfac>0td%Q#wLmQ%GjS)&uzx2CF0Z^%TSz%Sp9IRq1qg zVxT~3H`wd={rH!`3s1083M~z3huB(ZImB90)z@(CA$DEPcpV%ZYy}Abyeu@!knJ^G>LRIdGd)=!l`tgs7>WQgDhNHI^7>dVV zsZmjgQixU;Ex-u57d9PXjrrqc=&GXauXO+?AO^y*BkWF%CS}P{){qQV$(kk6WMk3v zVyZoWE)c9~9e*BQU_%J%qyq2!`2M3A1LN%Tmz)p|f zUT#1ygU%rhtq0qRVHm|iZ>`a{w0Cr-jHW@o;w`Rj82vKav^zO%DjYtn*QfOdB8cp{ z7WUl2&M1o}_|Trk676BE4K0Fk32M07-eOD1ZCiiCDqDY3(mM|~)i?jE$u|h|{;B0^!NlMb=|Bgo^6fwpnI- zgc>u)W8di7R14#xS*L}A!O=z236*^RmS#f8I`u7rlU1o0D$`_uCX=b+wvP%;_*1HU zR9JT66PWoIw9Gx1Vy*>+Cdhp?Yua@9dY>%|9=Wb8Uva0JtJg!V*IWWmdCeR=;Kgsb zSG1fU9m`=>>3t+Yz6~q-zhq6ckx|Ygebd~){e1kCudJ*+y zpScl3x$x@2edZlA>6N;+HsZ$CjaC~pYSAZFn6uwpGzC%U@m5*A-@J3m47|E(uWEl4 z6nT$Y%VNPP-`6sHyvICqs@krWvK1VUn-{8|q-=ZKymMCu)#|GDG3DDioFh5y)5 z?#irR*0c=$&Q>&I#z8oT%LDT6D7V3-|ID0`WA&pSd~{7?luHPXJj3Rvs)Yf?OyO8` z-@1YqD{zAf1bVh(#6^E-`_KSt0P%m+`)tQ#e*>f2`$vWbw$uE5+kK;*!(Bbwaj*tQ zW+71CcI4hFUUF7oR@vTu1l6nZ`U11&AwScz0BsKWmu7FCGY=y^^zd4wd?jpZDy>tF z+*RIxM5+92_UshcRA5U{n(Hhj*|~b{*y!yYsjy-kzYm>lS}pl-f0HFo>1ws?(dSlZ zCCt>HUfq#^Y_*u7e63}A3KeT`ZZ!tpw5+v!RZ%vfF}mL&C_qTf=R$92)CFb=PzG+c zloAZb?t9o$2IK)tb_$yJIoP?^!l(&%bCXEnZD;pd@W+Zj75FntIlFiEr!N*@)PRovZ@_G`^}PsH`g=>hawcGVpaII#ZN^l=fpBe*o1K%7*sXX%i50}|Y22n6L(pj6 zTY6K)Ar8u=dog_I??-L=JXNA52SDXjHITy9t*6Uv^?Hkkg;MxpOfOez$s zrB~^4JZ&q2udt0`Q)A~Od;#Z%8H9K_iJkVoYfIB0#Wo^br5vfC zlE2FrXzImufxZ3hcsQ*`ODqNON6A-N^4-_bSIArALBDjY@Sg@%_T4HNA3V;72yr&| znNk89-?be}p?xX2@7W&cOvcryJlRxvVJOMlz5As~Lu$8oFceXCzEIhdB!okV?1|Rj z&B)pu7e#}O|FUjI%P!S46_FMh3z0Vx>X3^it)ZmWhy_HgID{bUm6E6B!UC z)wPIl7{Sm6<&IA(jiZbCZ#TUE`u<0f)}EX*XQ9}BdjEA9di%3~om%|lE!Iutm4>h1 w`@nwo+EZ1}Mb4J2+WZU0t`|>aetLVh{fP5lFa9!Xb4kjNe)OY7b?T9OCwNlIi7zeo@rJ^GpB<5nYRUU4!gW~FE88K zr9UyJ3rK^)Kb&h|WL{oryo~}5Z9HPGzk+L+EA{e<|9QFx(2iv`2>h7){UzeC05T6Q zCH!ZmFzcrfaNqey60|}B3dJcd(hHYgxnviZ^BJVqqJSvzx;oN`%iOx2Rm|>yJTmnC zfa&5Roun0)>J;Dam;<3?_F7>qht?ZmUS20tj<_ZX*(fB0ga>(fHGREXHQDewZ8#p{ z<)xeZd^OV&oRCm$clMa=<<7xDv%{L@>T|6ZkqL2`5O(t5N_u<~SVA7uK_wiLLx5!St(#O-3<1o}peXS`E`fndQ%1rkOiME6 z3gPxApd>F=4E;na{h7>QswB0+h2p9#G8C6S58uk;rhgU11XDVwgz0GLk^W<+|6C zwld?98BA?B)wj9f&D_w#K;?(G*4)a3W=Au-!or!)!^6d&fR-t~5=BUL`evFJD6N;kBIdDHUdU<{H z{I_aOA2CsDqC}*}_9$vZr=w~~O;&U{c^Hna=LXLJ`42o3#hc{F&(TD;{tec1ThO9# zW@8#*RZR@RelcKwP2YDFlMP#9sLk$=p|kuJLq%2|+eoI{9NS7BzK*Tt=FNn8U-*K@ zaDHsLJdm--s0J*TQ7Es-2z`6O&pvcp@jMV|40A#*WJ=_*1X#CxCV6;3UL^L`5QgEh z&GKRe?H1bwR>%63}@MR?uX+-#a@+UI4(>7y{&{>T5#h}afI#z zz>sfl82OQt8g3|xr-CrWHxo4W##6e_#aEGiB5N71UJL|-e9Wl4qY=j z!ySpCVw6PS;c~~@FKz>m5zQP+5;NIJzCl>`-i1+|?p;i*5SLQdjLVMZtgm7A$==NJ zB+C7rNiE!j8^Lk3k9__b*K2;Vkcm%DC6v@AQ<82<&JY`#$qcwuS++gGtV=Cqd{U;6 zu9}oV@^D9ri#$Z8&LS*pwk; z9BJWVS2}6K<&u*h%>?By89MjPcuj z<4j3d44!@IRI2J*r&2rraB4k)k)4rC9{Mx##K%Aj@o-%JRJ!03W`BlSTu;{vTt5Bi zxhOEr861rWCfPn`&Lf<~w`QgjILkBjTMJB-I z12H#z!Ng}#TIOdpk@1JKD#Qs9q#G{he?H}(;Dw0Ix5AXae_ebADyA@NvZ-shJDZNZ zkX=WB)TpXBB%TFAGQW9`TZ%X&O0oi&r&ZMbpHxjJ-DB0%9L;L#;`gd4YtN{uflbpC zi)+9`VE*7TZLe$>r#_greVQNw=}G8yGL!Ve{|6N%f31D(W%v4>l`0o%hV7R!PeeCJew9%jG*OBPv zGiWF++)@GSbqpic2$%PB$E z(Tq4Rl6gNjQ2dFK%#6#Jn)^0_#UwDV#<)HxuUGaLutK z=0rm@lblcOqav#O19? zmT8SPE>eu)7a9qU=GYTkTO$Ahc))!E}{@z4QbRo6KuEGMb0ajv+3@#td zyzwlD%zl9MY@v*s;waE@s-)-!P9boEXc|@RjA>+T+66rxS+K@F*<<@ekAu^wB@3rF z5bn&HPK+f7Lac-9-gt`(jnB;pWL}+41JbXjQ#&b~Q6@IC1Up=&eHHQxC!KRMg3-fw z^Fp~r;5r%!F|W;NgMN}vUizd)4 zZXsxP7gN*UTTIup zLD#|^6(!;X8bRao;LUkQCdY+U(s5apWZUyexR;mxtMdhuvfWWhWqYWS0_0VZl_nUh z?$y+bg`8qz-epz=GvigE977HO*@iVMe`3rPO6G4>)CIj)Rm-(L0dl^k&wmvc1#)t( zCLZiP@L=}TN0OMGWy#F$>Leo3=c{$(fmcHfqqHWU)GVE-IXqGGb4?yMM-^!4Bj-Nf z#BBtc>wKBcS`t_Vf$wHtsdY2Q8snH-Yv~I1Xl<^z9*hC=9G5LAFMrIe12x1$qw7M% zTZ%~|E`K`w;1uS)=D5h}eh;?w9}7tvE`PUyo!RR$2!6NK<&cLr>oUX@uykSgxcvFx zNGazGG5oBVkzxlmW?Z(gwg%WBlF74X1~5xziaDY-gWxw!yY~r(*A$mr2qkXb_9%P1wOE zI`erh7HakjiK%y*tpw2vR9$hYDUl5`hif5V^k=rWP}w}xGL1}sz9mC!dX`L&%jZ9? z`i;rdCvh`4VTRwve!q|D(r1(I>-98gbz`}=EzI0b8KW`; zFh^Ty81q_dfdrHJEO@m${j4ueqydtc(|Q?GUB+Qy<6fj?zgwZWj(zyKAX<({JAeO`vK^CZDFGN+VaZA)=JD5j63sao1Rb< z*q$@VvBP(K^&F);jkK<5r%TWW?Q~_#?jZjEEQoHp-@lFlp)w}B-IuwogF4~AbW+%tc!eZ8@{sI6R>TbGR#vL+>Zp zr*2xi`Y?!3hKDl2v*`{gW43|o@gS)5LzR&iXpdOtZ?mc2`CvBLVXlEi@Rhe>pJqy* z4QCqWBy+<)1;03BRzyG5y%fU9C+1LVxG;wVRKI7y_C&k)1ao|j9PPVH799Bp8DUvH z*Qy_6Hm(n6c64hw;8IA%Bo6-lOH2;hdzUPP5zLL@T0%e~!i}Q}X2EzkQ!{GXE`0E=Tq6W=eHhPcMhu?f@^Z>leOkXH>4~%rRRK^HC3t(Y-A_ z1eZR`0N3>p7|ZX9mfILrPYt8AQjV8eySdg=fWudtx^HIA*=m^Ytu!bR+xiHQB@+*y z+xocvsW5R~{*U)T|61nGUb==K>!o}7oOuh#zH z=ldO`rf|NUJnWr#h#45><{bv*pV9Z3fLUsdGKoBHt#4-HzZ9+Jm| zki_fP`lxF;o2>SPFzY8{*)Tmz>rWrHY2wiPvjSw?m0w9ya z?^w+_CjW&}=A985Bz`kOorQKGQSt#$@;jE^un9~Gy#B&O4)dKbentCtdzlTxuy`z_ z`t-{}I$zx=1+rn3IKqAaBGxWl%K>?5QJAd4)@vIYvT-K}dT}n!~dQ`1AXMojW&ejbI*JN@Ms}meL7BmQewU_Few^ac zKVC;rV8?0b^4&P)ZozWev}!rsYn@z9_T&<%J$h5AYR_$Ct;XbjV zhC@{ZqFJDuH=i-zL%M3OQIZMgT+>D#p17us>uLn<$G+74>txp(uL)!tuBA?A@LI~E zBiB+3lC13D#?1r4uKN1tHYR(067&1YK<1v6Xj_0mA>KXkrFIT;{O;IIr+XG3)k0^hq_giUprROnlr1Y>dshAYu2r%%ssi9>{g!zRGx93e2)=tPh!s9;L8lI z_Gh|oAo-GNXuRo{*g#OsOO5+(AmRBkXzUw0{5EYQv7Hi>^W5#bZ{V0YcXa@Bc1-|N zzeYhYbgdx@zX%lRy@FY(m-V6s22CbjA3NJFp zuSsIgtPwH|Hxdh83k}zN=eU>Ic`%m2)9<{I_|q+T#&0rmnf1$)n6o#gaDrL~6eOeu z4|8aoyD5xmSVt#Xu#WQU_&Rzt5_1!cGMqPc5@KGt$wVHM>#5hjVLdhMlj|v$(>Iim zK7$*m@gJP1xj0cXb0bNC)Pk|kSoKi_tjdriVg$_78>x1m+DKCjQ#X~08?uST*~k8zlZ4t#V$e;v0n(G0hd2-NPKs~kcu|@ zF*hPA`2C3D_Y)%fzbtShKOXcR;NZ{RO1;9;t(2Y@wvwf1Be1UPgR?!Heih$B^eYS; z!3|&j2uWu!pW<6W(Bm&A2Qv@c;!kKfcnb;d%3z9=B>l4x58-Ztu%=;;YnV#{JP%;G zAiU~UlI8dYs5*Dk5ns*)?%FD1p1D;lHh^ouy>PilQ`L*Lf77-pWR|RLR2P?Qqwe9v zHZpG_F#5%RK5>%s@i(p$Gv&7tWARFcs|oAw9O1Nwxs52t3$VfW-!+!TQTX9)G-~tT zPCZQ9cFMS&+o`gCxt(|zozlx|k^RT-nW8&mnNvH3%-kJhOSTerE3XzjP|RuY_8n9L z$9Ir``5!>(i`%B{=NP|zhk&WsN%sZjo$Z9Q!#l|g_W>aqW&J}S>R2Xymt2fPSj-Sy zUO)2l0jBX*+#p3;d!mB;XMk8Zrf%CscQ)sDQ7f<8P06`sH+8?~chkulZl~j!QBUfAm6yphSnQ>d<3qV3FlYO^K=}iJFjmsKh$%A*a6Uz>aTU{!S$4MYHb~GoRc;HV)qc=jKHuf`Q*NmC3y~ zk>J;LFY(Gtz@7SO-`ffDjAh2}4P`#Pmj=5pFmmjfSo*Cc)&^?dLDFfGQ8z;i0E#%r2}ha zOvwXLNM?-+_Wx=^BbEE9B**sCh2zNnN&-P}fUY~X15_iAADF|z{1_6&KP>9r%nTkV zX68Q>O8SpKL_?w%9wMIdodhqhkAt&bCyadf4~)bv|Jc3&M)8Orv-)8gjok7u36W;O z>e&$W(EF6#xEGb|v`1+4x8xCeh;a0gG7hV7V1C0LUbW2JyEw>bxkT>Eta~&RO`iiK zKQ}>g*P}#x*1;VAxY_puX7}z({n7n%qvHJspotT#@Uhv1xPiyWzS{sT8{b}tCJ^H$ zK?Rg&Zjua`=-`gxKsSY4p6{$y`#i?@1C6Zw4M89$NPTCsOetYLB^xsN|O) zqK5s_A-dRx9;VvVc{o=r?jWUdjpL-(XHiuBA=G&U*vTaDNOE(R+ME??ZEUy7Nu>FH4BslQRn`QUFfSj>K!`ozAc z^TjnZfr87wH=T{3lc1NaQ9|bZr-`0zdWO32_GhR#cRxc#{?#*dwJ&&gg1qq8^wFk8x>#2i$q(u@vtSfK|W(s1*;O`x5-Zk!rKl2kp!i`J$L}-eIiabDq(POa}nQ zr{D0S*qII9Z}uYBBi!pHBw(=M6ue>j>ie_CG zy_18{)W>;ZbS_sYMvenge^eV65zL+n}=bpdC&zxvvX*k zGCh5ou)kN4mS7unc^0K5V}P@`j>_4ylrDVAqMV(cj^^g_qtItL@ULPGKS{tHzoezJ zi*)=~1NcULJzH1Imj@wUD}OjB8HRbB7N;FBL&rv?vFPHX;&9(-LeJ15bk-59L}%U) z7oqPO_~GbKEnkE#)bqu{1v=ZnpIcPxQMhfkLAS!w zXZ9!-IfsW79CC_&w_?y~?eAR##-+dnDm+8x-rjzT!s&p{Hbt*}krJshg5uD!PJRed z#=te5xkV8^NVJkKVKq(sTNUgLF8^qTE%8#;KAzY^Il^26EHzw`ew zo2Nv(zYB{%$&j{UH_!HYvXM6%neXODutWFxylG)N{}qZ3P74Y|TOQZ{ z{2(s^{Ubvhf{wlHD`wjazM;3E<`zF+GqxUnINvJ-?ZG=txsaC_1|<$rqLE z3iD$xiUl9@(CkcQI1@J0OHzo+sGn&6ND2V6vZ! zV%HZ6rtnbLyQ%(Y*G;K@=%O?+i2Z4rAR-)vGznt8)2E>WO#%tKsY$Rvz*DfwHbF!% z5>*Ql(ORou1smNfsNjM0Uq2y_WHi8@g6m0QDZ3_Y=X!)H&ANCcGAhFsHOc$JoE{xZMLJtWdg^C$7W++tYip4^tIvXrUMTZ44cF!Tftavn@9V9`gZ2n7n^^0m2b4i73d`D>Bs13>{&ee3UwZvH?J%?D!~>qh0@mUmuBw1f94smCg|CFqVRTU&8TZieE5t{^%Qk z;@69#CdUP%{aZo;$vEgD#-q6ZCA-k+r^T^or!7=~=A0GzqZ8LdM^|eY_*hGb58BTr z3fLW|1`85a|8Z1u%1_3Ep74Fpg*`!` zsOCdqD60H9AdH>+bHHI98u%1Af|+nI z(KQzXde8z>s00aTgtnmW-vbJfE6qQSJ^Xt>f_FK=0!%I~AsO(o47i5}6-$-?)QKNh z)yN!~MeaVcI*t5*iPFUUSzdWPqvp-{cI6rK27C`1_{LJRtY=O;!b7R=QUVIg|p zC`14gt;wuXB;3VE3A=c5zqDkR+0)1V6ee8djXs&?7aCmF?=a)Z(*PO3cXen4+P?>k z97%+CqRk%(r=q(@g;I8zR2aew00roGyKSB{TrK{7>|2q-Z+PtU(ZUD) z*yRf0dpsY!^q{lbrBbvkQJ8{k{D=rqX3;`(zb6fr98Q~&Xs1+yZXEOrK=nyNDcY4J zjAY+V5-NE?m}*bokaM9T*@l+23FFbpW??q!JRvGZyK;oVtRz+FRH5Jcgu%J6aQ7-Q zGaVqyOt;7E8FnjD+*w&!DQ>7$f=GsJgHDgFY{=}e^uf;_?g5!Iges&O5{|Riwh7(i zsNxop6d9gNj%1&BN66E$qTs-5g^`%`o}po|K&;;&s1dUrkYnXBfm@>3k2Qhjc(%|G zI6DG;wp~(;oXLSR-iKp%EfLeP^6q^}5) zA(!6Um)%+x^mkuhPR8hLM^x12d;KFr0d1wlZZi)-aHUwR5O!_e<*z_D)&vFema>o% zd?^M=4gO;Ef)m#L!`2`*dhoE1FS_CSpc3?2j9(J^wKOP$4~#~!ok4|cac@wJ64l)V z)>Cy;&<*LR?D3#M5s3~)Y!0h)sG@&}J@k0cZ6}cSq2L%ac0=&3NWLa`37WG#!;igZ zO>l-3^*xyw!m_)ApN(LTKNf7@p^nFcQ&G$I3<;!g2kx!Z^2)n9fyL0 zS=-aW^L4E3Lhvu4c=6n@G*QA@f^rwh6Cxg$URq%S9V?~|l} z$`jpAQL{}J$J|23gO5B4^42yiw zM|I*H_k#?rIjD@G4Md$^ED1_b6A@=2?6TkZSvUhfgD~Ga#$-|ER1;aM?Ecb8^ z5R{gTokWI0m4WpXLZ*odWj5H^s-V}zY|v|=d%D;cr-nsVqKxkX#c2PIKtFWB6*hwI zUJ&*wVr&6n$n_<-+heEs{_HP9VLu8{{j2gw6u2y`0=brjl`|_pi$XgON`jGYJnR)V zV0qY!E;ROXMDVKS^I>fB`S7ReA#8EAM#Q3$v^W9UKMi7uvw}o__L`!I`$Ey0=qZ6{ z*CFpvRNEXehmYfz6>kK@uq#_4zV~78Fh!I~5Z@l*Kp(E<3CU=cCM<}(V2?;IMAv>A zEMW`oj=0emIUf%MZuSQRpp*H4&A;x8C`HHC`}(lD`y)1%q1f}G!Khg*@GZizg1a~! zwsmP~SyR)~iVM36@k4rXNqTYGtU1}Up)xybN_z3q!G7#fVPS%Q51KuezH|j@Iv0`6 zpQW3Pj(r(E1v$?}h`eX%(97o|;?T=mBNEa29TDZhy4kP~BSU88q7QdOq@bz*FhLj|05d^>-60f-=!@?no@J2WP6meeDKZ4a7?;S@MHJBaddA1>NGOi(7?;du!;0m0()n*wUvT^%QcKOc{ z`vd|SU7_h#K_>dh%#TIM!IDHdt^SK~cw0IcEQtwf?QYgLwRb3Skb|ri$y8)q7Ziz3 zyb}<>Ht;0peNoXT87XM7Nt)<0;_SDg-6p94eI}5|*ie7TQ@-eQpd}lzW1X!DtN)D2o!v=)I@!(P)yv>7XVPv5+ zA?80*g@%T~>tMGQE|CXS!9_;1$78bstJvjG6OT6b@@4G1Ig%H`f`vkh z-RyQNI9)(H@02PL*pkmQtUwj8W~ITB#w}s6qH!TL*AYa6b43uwaF%mr9lFIN(T#H< zo6Ru-UEMZ&Z($*9!nuBxj*&Ey9O-s(nGsO!8iS#ksuAqaIVZ&}mRHP3z;N+42PiEW zz)$q%wmCwwqT%}S|1fWPb#3?mKeJt$>#wF7r!=~+Byz%7{!FENf{Fi&Ooa_nBzT>^ zg7ud;#p$_lezL=PCB!TF?{Hp1tg-2jya%5Fk>2o-Low?TU|{v7DKX#41hdh`G*NoW zsHcf0%gDk>4G@&>G7Un4T8X?Epi>iv)=B2!3%6!Zvw6tPsVW5OSUWOEN(j`N{Du)| zTZ5$7mup3*ZPI87_o$o=kS6;;+?P-%QJ}s$NdgKhDbzcM`p3*T)d%munca}~TFiA{nutc0N_7novFUAi8TC|w)*FZV(V#RVjiUXC{u9h5 zQN||VnAq03VL8Sd3d$C$aKHs{4V*=DnJ)riQXI3A$*|dAH7L#?Ngn@e3~ZyEomv$= zixiXV6vT%4G2LhUX?3#V74Xzqm67)Y1_iSC6rni2UdR z21icd->xDQ06|>AJ+!+;q93PJ5}$_cmFgW)FYKYXL7Wf8&9k_roEogn!y$8d7(&#V zi*>^HNmummtPLhL>Ov4>GB!m!Mp!<{TtaMKSJ&oB<>Y((qOc1cPf~Foyt*gf1IH zYib}hVMj;XCD8&}gO2BetGX#NRGc-5)-bo3BsOuyf^@mJLZvrJ!pA|){*y6bBLGN~ zJthZF>KyJdS$nA;^89mu43kkiPhqfWSyL`V>avj?FfLMsGnI$R8f|D?A zV|MuxF=;Pgo|o4T521ujQzOyb)zavxG$^1ay_6I#C4#qhmuwDTpvEpq99q#ONzc9< zG-l=Hzb82`ffGcUug42xy|c5?irEr1da)>2iZ=9#1?a?V*zRKcjK({%w22Uof&;*K zBYS;tObn-5RN9yA;Xuof*bHw%fqw^9!px9T9JYI4m|s_+n0GABF`-H;R*ftq_HQJg~&P)g8ojdukqaY}vgn$E(Sp2>q#`icv93Sxm0%OT>;KpAhF+OOw zpETl8k@RLE>k}yb(wjRVKG*LDXBuJBhtL7J^kt+FN&V2YbV$GBV?NWVionGd+?bo6>0%vTyJwee8lD^nxTdUvWHyFvnYq|y3xsXQPN zl8E3M6iRm26sf=)`7Mv=LmTdg^k{jOblm$=wBtgrPlNNphC>lWXij06fDKVgujgH! zfyrQR)<`$_qo(On5&PCO=??G7R-6ezdy1qLhz~L*$xU{8f25 z`T2SI`E1LSsGBM-{rt=kRmYqB2?^z>$1nZ**%fu&rC--Ch*~uHixmt-J$`8*`KZW-;emY}y`k zflb|;pQBs#`=%)Pi-x}#_V-P(;vw|5Sau?QB09wgk939XMME2Rb-?4v;ex z3vD~ze7zV6|9^I*%1?}@Z3hF$Z9z<#ro8A7}bGgaza;Tne!XR#=@V9Lbjiva$FXsT}DWEZG~dGLZ`}6sgN^XB2Cb#w3_^MA)HSBRW}{&Rskb4 zTCUq4fECREXgXD%5YzJCi9zo;_&MYMyB`oiHw&1<7Uv*rl5G`m1Ty3_5zS~Gvi0_l z;(o&pNMC}g;O#T6!;xhFsLe_{;>lpym}kSCm5b*nvl5}-yzT|Va85WeCOu1;HcORR z0J-nvrLfK*UY9be;4(S^=pa4Q;&Mf4X^A_6X|EO-kN=Ov-C0C-M#Dmi@o4~l(-2(cn^|0H8; z#Db`ARe9=L)#ZAlwa(CAR-rf58C#8=buFqYqn>M1dCCXWb*)CzLVaapMTr_=~ zTa9&Pohtjx7NaZIYUo?k=rC$Jjdk-4QASJPAN>NAZEjFt~J>RJZ$=C(@bLf}wSPu0Mf5ym#m*XOjF^ktm` z+}QE*7NfdRud-Y*xI;UjhL0Y!TPw-{m|fjwhsoO-b&YNAnQeA`mBpy8=u}xNjM}Oi zqsrdjpwjF0Mpv29Ztw3HYi#YQa(VO)Q$vrbGq+=`uFYnusx=Ib_BE*M7Iw_9Rrgd^ zwKulApnbf~Z7}wk+FD#@eWg)taJb5>Dz&kty{}PUS*0}?^<`GI)zM9f}X3L04T~dXKFJ+U?NZHqq{>Y-!gU%P$k9k%%%^-`3gK zGQVtmLeaFqW}`{fIo4saHd(4`$4m}ao!+Q(cW89_PPK87Y0x#G*Nkeg7Mm;8AQs@1JMm1;we)>_|Uvd?VQj2D<&+xj#e zDqWee%~P%)>{Hq1!9?m#Pn*f2vYK2SpcA0Qt}?7hmO*_@Pfce(DAvM`L0u!X*O=O^ zj!u=yW%x@|uEM4aCTA}%AJA*TOmW}(mbQYrmH{y5@k%g-`Hjw=3hT@^Ls>_Ms&m-f zZm()}lo^aF{bb?c$Qs=@RfP?Xe>9ikK5}z?Nuscg{h)#P}k8gShujD##-N6 z)7Q~psH8xx9EV18tXuxu|sQbwir^(Qz0PXy z1V66^r&d*CwpT3x88^1Hc{nBStTbtj#>Pfy)0PjI!0cR|t>Y$F9cW^IV^x(yZ>%eC zF;#&{S?%Bz%Rv3GpB~WHSi!sDMWk~CoRX2)a-RYGekJ^_hu;nhxKq8cV`huJYChC0 zXff)_J2f8gsvehq%&u*ytSd8`Ow}-XGb~mZUQI)Fr+K{1WUnw)*A1BGS9P?tH4HVF z8i#u{RV~JTkEYeoSJ712*rzXb=FA)`Gqtt#4fHrbA*!v84ntXaOIu^5#Xg|xGz_RZ zjixfIq0?+>x6jwl@2oZtjE=Td^$oPN)s0wc%G!H0jiAIW8ojDz)K<|j)}rya%k7=x zWfq64re5XAF}4*rp>a{`plQ^s>8mrejaK$(V4KhU_S$)q1{tI`jG`R0IW zS9Lf#w5{q+edFLbP+o0OTPjQjv!krNwW4y!&|bN)L2GJlP`f%nzMY*l_O_;qhS8=L z*GNP4Xd7?}fDSBz^}V59GwL#F>|?F=x{)4D$52CCV{5~hslr;_vJjk7d55;FywR?Q zm36eSLsihyQ(@6`f__<5E=Q*k;Emg>9N-?uItSJBfwGE*)`C%k2F%N-8?7HJGuj-k zA=7|W(^QF9`NoP)O_gb;+SJi$(03Trof@;Iaeil8-OQf(bpxGas)F)<9H6K?z=_J* z%El_AU0b_z~%8hnUP3+k2H3?2R?_G zo6`*&F)p13ISjmu7%>lZTZac-mxtRBU~>6DC7iho<8zi-O532h-(G69Le2xS%#$6s zJxtY*bFkafKWOWQlMnzYgo!UhMxmSzac*%kygUM1^{mNc2|*S>;V=vj!#C_-50Qqa z^x?#$1!hX46hSLaI^(L$qQPOeM-QiVb36HQE&n6NzasEUCU;b!jtjcoF8J(QnsRa1 zP;r-IDMX)e@d&O0Z48x4qPYDPmz*W8hCBSx;(A&$O9eSrxHK`r1SQ19ko!tYv8F2$Q{0Jw1=PT&i!K)jT%f-!`sb!e zc8+uRZ6V_4;7!2MK;)eWcR+j1jx1o7ZK%X;8^Ajf8ysL{S#62R%aPI0!LWTu%T%Xx zZ5(U33pT(SC)s#+kLpwf-Q3w=S6UaOO8f2haoaqzT>&?CN?RM%3fRY*75L3U1#EMd z0RHpf(6`@_uE1&md+$LP{8D7%*Bb2o4x6IOIu&!A9CZ>Fr(eaEjS~aTZ!Q(4{`bw@ zgy0d*aG{+tS*e_!8Xgg0)u>pjWXZQzq;w+k|Z3g7@-CB_;|@3C>$SU{3jbVIv`0;KGF z#j;#Js;H2ey%V$0`EprYI*|cfi6~!ag~Usi5^I4HZ~eGy(amu22KJokS&8Vu3Ryu= zmZEi_-{k@e$x38Zm9ozyTuyQAC-M*hw~dCEH;QM;H0Wg}Six?eC4*dJXs>K0ddwzE zg*QU%y|PvwF9#i)Cz}FA!ZO(qn%yVMQugHLTGi^FyqvrOwW`3}qsz(bEyz*ndJ6J1 zDqU`lsz;q?)#jswg|c`g8khB=K$lFIJ=X7n&zRe@uXeckJsISxZjc3E{e*To+^-&x zSD9 zC|sh70a>L;o2k}hs`Cvvb*C%TA>M1UT(n`K>__$)yKFZf7@~evreL$~mVLrQ_92-A z-c8{+r6!!3vsf06*1BaWX$3m1#gbz)Yc)2NMXT=BsPlEYoB~UpRindz%!jQSmic;%wVA5iOs&eG))wmW3w3$u z_?NP4(3usod(qy-QQ_<@qq0-pNcE~Lfso0LFO}Ub1Uip@Dof+5v()Idw`KE?YdtXc z+0{U4PmZ=gr^{32=31;dIl4Tprq`^?v-DW_oO@#$TNgJw@>EFyG+4v|4x>GN*2lj#qhe6CR3v`Xw-#T zO<|4}9lut-49(plOJ{$(UsmUh9(Br0n7R)o681w;c*OqHjhL8&|tU0f6u zz~1$f>@7YBANy@~PK*2;53Ri@bD`Vc1|t9`iC%eI){k63JbbP~g7*CmCU48{$tlp; z%$j_Q+FFp;qXQqO)~Mk)#+ut((4&KlN}dkHj5TU8NBri+@;K#H4nqqDz`tE>=kp`r z;KBVjAqP)BMFd)}2E{k13JP`FLRiPXlwHTl7s?epwEQ)oaDXpAvY;&Rh~7cx3{O)Twqa|dvd|eS>T+(tSQK~6j*!mRQZ?a6(WOB9;&>` zafINj?L0y7LUb%u?uRl~$TP6O5eQs>?g*6Ykz$HGGyud79$sCj!Hd{9nvi-h8y76s z{z2g8FuCwNjtG+4bf z`8I8iO06?nbt-jkZm&htTVTmA$kkXi`Bqi0Ek9Rn>$#-hsBl0ozWQ;*=U?qC)Vfpg zw`}rz<@idilfUHv#B(yN^XQ~KBn`6J$c66X$f&Pw@RK3BTrh_&*&V3j?6lUxGD z>$k}xQ+feiodv8g&!T~&;{vlTM`zRO)DUjv<>zS31z?vw=-jRHLNvHuu0Zo{ly^b^ zn|-@nf_R(cQE68hlIu|ECV4(;S|?utQ{&-KoVZIKdG!G+2Vl-k@@xLjB(OljwLBPp zegomaTDj!vBTqa(yJVyMI&bvGI(Zlu+lB503a&b=!w~LXceC6lQk|uOlVv46;pQ$) zDskL{IikGEu!NHeE4xL08y{K;a;fs_77ZN>h-~)1Vl-yOReRwBcgV}o zvU}yr{=|x__B`**8$aKlzSLTBIzAF!csn2N6NP?TEA@w0H`$@n@$lx`^Hl1n8$KR*+XU#{-2!f%`d zlRSeic*zzXxN%p2pmkhAhq9c$eNd=CyWdTK7tz0ok3x4GnHq*h&c>(y0fFbBN`#Yf zu}FF*J_nUt10q}=9xOyh4+ab1LpJdd?6c?MM-^Sm=L%*wief@xR z0FBOn7cW8eU7_^58u5W*-z|2r4>EoquMu^(R_fuzWfr{RF>eHJ@~{0qzSNiddy z7XXF0Z+-k^q?LOCUkR^?bLm@pGta%{0???qo#z)oVZh6N2d1P(pgZQmD=C#1;x}?I zcy9J2z~i$u7vsO@qhHT}Egh?siB}Eq6|g5ra26wbyHbd5X-|lKtUTcze{{YvL4ry; z5&|GPPsl=<&G1o)qs!rg54!qwBhh#sb8!>GBMu)Q1W3~)H z`W@1b90c0yFL5&9UZvcZS|F>Qj$YFzM5E&^2^FYUpO6}IIi0q2v{|2!iCXTAh#8+m z-^P*lN1RD*J!GyqO8!6NNyh&HIXLm?;>@Ajj{c!ffaKYK64HX!1i3F--=47Tullg# z9SJpm5OLfp-<|~cOJsd}l8%3k`o0&1qwbsG<&b|hCKsT;XF@7!|1yyuThf$VKO1ea zBubGVA3k=J^%T4@e%pP?rMNB!y;}?r+ zzf7LtgD-EQHD4yLi6+-L!R9YrLgg-YCZZ2NNsjcJo?Qe#*@mx@H~OKRmlLGn|FLVA z13D+7Io~E%aq!uDzD?f9u+7(}2pf>96J80APZ0U}^}7x5%_wvrG9U)EESV}n_UWnq z@LqGvZ}56Yu{BlZi_`rjsN0$vi_+dq2|=&FnIb{k#r^`+_-0Bzy8F$Psc7SD_^8%z zZ>B^Bz-l)N|B;J9c&q30c)65-hx|NQA>hIhcOmrJzo&Gf50;7pka!R97kHi?Mm-F7 z9~_n+aHpj4)Bt?HsFe=~PU&x^B%qaishEB`0ceL$X5po3D_>fMR-aagQS)F@Dmrx+ z-w%oJh2DzW1!Zi>+bKi)ec;ljhAq)dP4Z#iUz3^~!cMy-b*m74en+a757}ZA`m$dH ztKF0OV_FveN=dhE6fU>H*&F_W-&=BtTw)x&qMB`ZB{floKKvy$2fbbmDmC}l zRK==azet5YCH$qqUpo8i7gJt&n*Hd3srOd0Km2>DGj0+goe$cToe_$@PRbCYi%A(m z_T{LICJFj?U{V}P&B~}npF9#KLd&u;ReAEQRXI%;@;|-4 i(U>d`sG5?rcl-O#CoQqg|8U;VcKEoPS9MuK+5ZDyWyTi( From 8bdff227d9e0eac3ff2208beba89b143adb88adc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 14 Jun 2024 17:04:22 +0200 Subject: [PATCH 0242/1214] [ticket/17340] Update composer dependencies PHPBB-17340 --- phpBB/composer.lock | 176 ++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/phpBB/composer.lock b/phpBB/composer.lock index b52902590d1..17be00b9410 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -3027,16 +3027,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -3044,11 +3044,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -3074,7 +3075,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -3082,7 +3083,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "phar-io/manifest", @@ -3533,24 +3534,24 @@ }, { "name": "phpspec/prophecy", - "version": "v1.18.0", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c" + "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d4f454f7e1193933f04e6500de3e79191648ed0c", - "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0 || ^5.0", - "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { "phpspec/phpspec": "^6.0 || ^7.0", @@ -3596,9 +3597,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.18.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.19.0" }, - "time": "2023-12-07T16:22:33+00:00" + "time": "2024-02-29T11:52:51+00:00" }, { "name": "phpunit/dbunit", @@ -3726,16 +3727,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + "reference": "69deeb8664f611f156a924154985fbd4911eb36b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b", "shasum": "" }, "require": { @@ -3774,7 +3775,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" }, "funding": [ { @@ -3782,7 +3783,7 @@ "type": "github" } ], - "time": "2021-12-02T12:42:26+00:00" + "time": "2024-03-01T13:39:50+00:00" }, { "name": "phpunit/php-text-template", @@ -3831,16 +3832,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.1.3", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", "shasum": "" }, "require": { @@ -3878,7 +3879,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" }, "funding": [ { @@ -3886,7 +3887,7 @@ "type": "github" } ], - "time": "2020-11-30T08:20:02+00:00" + "time": "2024-03-01T13:42:41+00:00" }, { "name": "phpunit/php-token-stream", @@ -4038,16 +4039,16 @@ }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", "shasum": "" }, "require": { @@ -4081,7 +4082,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -4089,7 +4090,7 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", @@ -4167,16 +4168,16 @@ }, { "name": "sebastian/diff", - "version": "3.0.4", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae" + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae", - "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", "shasum": "" }, "require": { @@ -4221,7 +4222,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" }, "funding": [ { @@ -4229,20 +4230,20 @@ "type": "github" } ], - "time": "2023-05-07T05:30:20+00:00" + "time": "2024-03-02T06:16:36+00:00" }, { "name": "sebastian/environment", - "version": "4.2.4", + "version": "4.2.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + "reference": "56932f6049a0482853056ffd617c91ffcc754205" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", + "reference": "56932f6049a0482853056ffd617c91ffcc754205", "shasum": "" }, "require": { @@ -4284,7 +4285,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" }, "funding": [ { @@ -4292,24 +4293,24 @@ "type": "github" } ], - "time": "2020-11-30T07:53:42+00:00" + "time": "2024-03-01T13:49:59+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.5", + "version": "3.1.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" + "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1939bc8fd1d39adcfa88c5b35335910869214c56", + "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56", "shasum": "" }, "require": { - "php": ">=7.0", + "php": ">=7.2", "sebastian/recursion-context": "^3.0" }, "require-dev": { @@ -4361,7 +4362,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.6" }, "funding": [ { @@ -4369,7 +4370,7 @@ "type": "github" } ], - "time": "2022-09-14T06:00:17+00:00" + "time": "2024-03-02T06:21:38+00:00" }, { "name": "sebastian/global-state", @@ -4428,16 +4429,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", "shasum": "" }, "require": { @@ -4473,7 +4474,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" }, "funding": [ { @@ -4481,20 +4482,20 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2024-03-01T13:54:02+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", "shasum": "" }, "require": { @@ -4528,7 +4529,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" }, "funding": [ { @@ -4536,20 +4537,20 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2024-03-01T13:56:04+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", "shasum": "" }, "require": { @@ -4591,7 +4592,7 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" }, "funding": [ { @@ -4599,20 +4600,20 @@ "type": "github" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2024-03-01T14:07:30+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", "shasum": "" }, "require": { @@ -4642,8 +4643,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" }, "funding": [ { @@ -4651,7 +4651,7 @@ "type": "github" } ], - "time": "2020-11-30T07:30:19+00:00" + "time": "2024-03-01T13:59:09+00:00" }, { "name": "sebastian/version", @@ -4702,16 +4702,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", "shasum": "" }, "require": { @@ -4778,7 +4778,7 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-05-22T21:24:41+00:00" }, { "name": "symfony/browser-kit", @@ -4985,16 +4985,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -5023,7 +5023,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -5031,7 +5031,7 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "webmozart/assert", From df93ffc886cdc85cb20b31a6b7ec5e96cc0042c3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 14 Jun 2024 17:09:55 +0200 Subject: [PATCH 0243/1214] [ticket/17340] Update composer dependencies on master PHPBB-17340 --- phpBB/composer.lock | 939 +++++++++++++++++++++++--------------------- 1 file changed, 485 insertions(+), 454 deletions(-) diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 287f56b08ba..f8b8863dd03 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -213,28 +213,28 @@ }, { "name": "composer/ca-bundle", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", + "phpstan/phpstan": "^1.10", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -269,7 +269,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.0" }, "funding": [ { @@ -285,20 +285,20 @@ "type": "tidelift" } ], - "time": "2024-02-23T10:16:52+00:00" + "time": "2024-03-15T14:00:32+00:00" }, { "name": "composer/class-map-generator", - "version": "1.1.0", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9" + "reference": "b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3", + "reference": "b1b3fd0b4eaf3ddf3ee230bc340bf3fff454a1a3", "shasum": "" }, "require": { @@ -342,7 +342,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.1.0" + "source": "https://github.com/composer/class-map-generator/tree/1.3.4" }, "funding": [ { @@ -358,28 +358,28 @@ "type": "tidelift" } ], - "time": "2023-06-30T13:58:57+00:00" + "time": "2024-06-12T14:13:04+00:00" }, { "name": "composer/composer", - "version": "2.7.1", + "version": "2.7.7", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc" + "reference": "291942978f39435cf904d33739f98d7d4eca7b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc", - "reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc", + "url": "https://api.github.com/repos/composer/composer/zipball/291942978f39435cf904d33739f98d7d4eca7b23", + "reference": "291942978f39435cf904d33739f98d7d4eca7b23", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0", - "composer/class-map-generator": "^1.0", + "composer/class-map-generator": "^1.3.3", "composer/metadata-minifier": "^1.0", "composer/pcre": "^2.1 || ^3.1", - "composer/semver": "^3.2.5", + "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", "justinrainbow/json-schema": "^5.2.11", @@ -398,11 +398,11 @@ "symfony/process": "^5.4 || ^6.0 || ^7" }, "require-dev": { - "phpstan/phpstan": "^1.9.3", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1", - "phpstan/phpstan-symfony": "^1.2.10", + "phpstan/phpstan": "^1.11.0", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1" }, "suggest": { @@ -456,7 +456,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.7.1" + "source": "https://github.com/composer/composer/tree/2.7.7" }, "funding": [ { @@ -472,7 +472,7 @@ "type": "tidelift" } ], - "time": "2024-02-09T14:26:28+00:00" + "time": "2024-06-10T20:11:12+00:00" }, { "name": "composer/installers", @@ -769,16 +769,16 @@ }, { "name": "composer/pcre", - "version": "3.1.1", + "version": "3.1.4", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + "reference": "04229f163664973f68f38f6f73d917799168ef24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", + "reference": "04229f163664973f68f38f6f73d917799168ef24", "shasum": "" }, "require": { @@ -820,7 +820,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.1" + "source": "https://github.com/composer/pcre/tree/3.1.4" }, "funding": [ { @@ -836,7 +836,7 @@ "type": "tidelift" } ], - "time": "2023-10-11T07:11:09+00:00" + "time": "2024-05-27T13:40:54+00:00" }, { "name": "composer/semver", @@ -1001,16 +1001,16 @@ }, { "name": "composer/xdebug-handler", - "version": "3.0.3", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { @@ -1021,7 +1021,7 @@ "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { @@ -1045,9 +1045,9 @@ "performance" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { @@ -1063,7 +1063,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "doctrine/cache", @@ -1410,16 +1410,16 @@ }, { "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.16", + "version": "v1.0.18", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "ecadbdc9052e4ad08c60c8a02268712e50427f7c" + "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/ecadbdc9052e4ad08c60c8a02268712e50427f7c", - "reference": "ecadbdc9052e4ad08c60c8a02268712e50427f7c", + "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", + "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", "shasum": "" }, "require": { @@ -1476,7 +1476,7 @@ ], "support": { "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.16" + "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.18" }, "funding": [ { @@ -1488,7 +1488,7 @@ "type": "tidelift" } ], - "time": "2023-05-24T07:17:17+00:00" + "time": "2024-03-20T12:50:41+00:00" }, { "name": "google/recaptcha", @@ -1846,12 +1846,12 @@ "version": "v5.2.13", "source": { "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", + "url": "https://github.com/jsonrainbow/json-schema.git", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", "shasum": "" }, @@ -1906,8 +1906,8 @@ "schema" ], "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13" + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" }, "time": "2023-09-26T02:20:38+00:00" }, @@ -2103,16 +2103,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", "shasum": "" }, "require": { @@ -2166,7 +2166,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" + "time": "2024-05-08T12:18:48+00:00" }, { "name": "paragonie/random_compat", @@ -2220,16 +2220,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.20.0", + "version": "v1.21.1", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6" + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6", - "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37", + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37", "shasum": "" }, "require": { @@ -2300,9 +2300,9 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0" + "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1" }, - "time": "2023-04-30T00:54:53+00:00" + "time": "2024-04-22T22:05:04+00:00" }, { "name": "psr/cache", @@ -2558,20 +2558,20 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -2595,7 +2595,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -2607,9 +2607,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -2760,16 +2760,16 @@ }, { "name": "react/promise", - "version": "v3.1.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", - "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { @@ -2821,7 +2821,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.1.0" + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, "funding": [ { @@ -2829,7 +2829,7 @@ "type": "open_collective" } ], - "time": "2023-11-16T16:21:57+00:00" + "time": "2024-05-24T10:39:05+00:00" }, { "name": "s9e/regexp-builder", @@ -2875,16 +2875,16 @@ }, { "name": "s9e/sweetdom", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/s9e/SweetDOM.git", - "reference": "b07ae8f5fe4ac40a0b734e0e23dbadcc26a161e3" + "reference": "ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/b07ae8f5fe4ac40a0b734e0e23dbadcc26a161e3", - "reference": "b07ae8f5fe4ac40a0b734e0e23dbadcc26a161e3", + "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd", + "reference": "ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd", "shasum": "" }, "require": { @@ -2892,6 +2892,7 @@ "php": "^8.1" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.52", "phpunit/phpunit": "^10.0", "s9e/repdoc": "dev-wip" }, @@ -2914,22 +2915,22 @@ ], "support": { "issues": "https://github.com/s9e/SweetDOM/issues", - "source": "https://github.com/s9e/SweetDOM/tree/3.4.0" + "source": "https://github.com/s9e/SweetDOM/tree/3.4.1" }, - "time": "2024-01-01T17:22:53+00:00" + "time": "2024-03-23T14:03:01+00:00" }, { "name": "s9e/text-formatter", - "version": "2.16.0", + "version": "2.17.3", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "a78b8f9bc169d0b6dd81ffff3c97479875bd673d" + "reference": "9adf2557f13e45c4524d0dc9886cab22822e337a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/a78b8f9bc169d0b6dd81ffff3c97479875bd673d", - "reference": "a78b8f9bc169d0b6dd81ffff3c97479875bd673d", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/9adf2557f13e45c4524d0dc9886cab22822e337a", + "reference": "9adf2557f13e45c4524d0dc9886cab22822e337a", "shasum": "" }, "require": { @@ -2942,6 +2943,7 @@ }, "require-dev": { "code-lts/doctum": "*", + "friendsofphp/php-cs-fixer": "^3.52", "matthiasmullie/minify": "*", "phpunit/phpunit": "^9.5" }, @@ -2956,7 +2958,7 @@ }, "type": "library", "extra": { - "version": "2.16.0" + "version": "2.17.3" }, "autoload": { "psr-4": { @@ -2988,9 +2990,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.16.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.17.3" }, - "time": "2024-01-07T00:41:30+00:00" + "time": "2024-05-26T00:00:08+00:00" }, { "name": "seld/jsonlint", @@ -3232,16 +3234,16 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.1.1", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "86102bdd19379b2c6e5b0feb94fd490d40e7d133" + "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/86102bdd19379b2c6e5b0feb94fd490d40e7d133", - "reference": "86102bdd19379b2c6e5b0feb94fd490d40e7d133", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/0b10c8b53366729417d6226ae89a665f9e2d61b6", + "reference": "0b10c8b53366729417d6226ae89a665f9e2d61b6", "shasum": "" }, "require": { @@ -3253,7 +3255,7 @@ "ekino/phpstan-banned-code": "^1.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.27", + "infection/infection": "^0.28", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3", "phpstan/phpstan": "^1.8", @@ -3261,8 +3263,8 @@ "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^10.1", - "rector/rector": "^0.19", + "phpunit/phpunit": "^10.1|^11.0", + "rector/rector": "^1.0", "roave/security-advisories": "dev-latest", "symfony/phpunit-bridge": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", @@ -3327,7 +3329,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.1.1" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.1" }, "funding": [ { @@ -3339,20 +3341,20 @@ "type": "patreon" } ], - "time": "2024-02-05T20:37:46+00:00" + "time": "2024-03-30T18:03:49+00:00" }, { "name": "symfony/config", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "206482ff3ed450495b1d5b7bad1bc3a852def96f" + "reference": "12e7e52515ce37191b193cf3365903c4f3951e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/206482ff3ed450495b1d5b7bad1bc3a852def96f", - "reference": "206482ff3ed450495b1d5b7bad1bc3a852def96f", + "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35", + "reference": "12e7e52515ce37191b193cf3365903c4f3951e35", "shasum": "" }, "require": { @@ -3398,7 +3400,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.3" + "source": "https://github.com/symfony/config/tree/v6.4.8" }, "funding": [ { @@ -3414,20 +3416,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T13:26:27+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/console", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e" + "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e", - "reference": "2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e", + "url": "https://api.github.com/repos/symfony/console/zipball/be5854cee0e8c7b110f00d695d11debdfa1a2a91", + "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91", "shasum": "" }, "require": { @@ -3492,7 +3494,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.3" + "source": "https://github.com/symfony/console/tree/v6.4.8" }, "funding": [ { @@ -3508,20 +3510,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "6871811c5a5c5e180244ddb689746446db02c05b" + "reference": "d3b618176e8c3a9e5772151c51eba0c52a0c771c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6871811c5a5c5e180244ddb689746446db02c05b", - "reference": "6871811c5a5c5e180244ddb689746446db02c05b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d3b618176e8c3a9e5772151c51eba0c52a0c771c", + "reference": "d3b618176e8c3a9e5772151c51eba0c52a0c771c", "shasum": "" }, "require": { @@ -3573,7 +3575,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.3" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.8" }, "funding": [ { @@ -3589,20 +3591,20 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -3611,7 +3613,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3640,7 +3642,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -3656,20 +3658,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "6dc3c76a278b77f01d864a6005d640822c6f26a6" + "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/6dc3c76a278b77f01d864a6005d640822c6f26a6", - "reference": "6dc3c76a278b77f01d864a6005d640822c6f26a6", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", + "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", "shasum": "" }, "require": { @@ -3715,7 +3717,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.3" + "source": "https://github.com/symfony/error-handler/tree/v6.4.8" }, "funding": [ { @@ -3731,20 +3733,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T15:40:36+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "ae9d3a6f3003a6caf56acd7466d8d52378d44fef" + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae9d3a6f3003a6caf56acd7466d8d52378d44fef", - "reference": "ae9d3a6f3003a6caf56acd7466d8d52378d44fef", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", "shasum": "" }, "require": { @@ -3795,7 +3797,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" }, "funding": [ { @@ -3811,20 +3813,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { @@ -3834,7 +3836,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3871,7 +3873,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" }, "funding": [ { @@ -3887,20 +3889,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb" + "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d37529150e7081c51b3c5d5718c55a04a9503f3", + "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3", "shasum": "" }, "require": { @@ -3908,6 +3910,9 @@ "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, "type": "library", "autoload": { "psr-4": { @@ -3934,7 +3939,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.3" + "source": "https://github.com/symfony/filesystem/tree/v6.4.8" }, "funding": [ { @@ -3950,20 +3955,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/finder", - "version": "v6.4.0", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", + "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", "shasum": "" }, "require": { @@ -3998,7 +4003,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.0" + "source": "https://github.com/symfony/finder/tree/v6.4.8" }, "funding": [ { @@ -4014,27 +4019,27 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:30:12+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86" + "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/a9034bc119fab8238f76cf49c770f3135f3ead86", - "reference": "a9034bc119fab8238f76cf49c770f3135f3ead86", + "url": "https://api.github.com/repos/symfony/http-client/zipball/61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", + "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3", + "symfony/http-client-contracts": "^3.4.1", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -4052,7 +4057,7 @@ "amphp/http-client": "^4.2.1", "amphp/http-tunnel": "^1.0", "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", + "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", @@ -4091,7 +4096,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.3" + "source": "https://github.com/symfony/http-client/tree/v6.4.8" }, "funding": [ { @@ -4107,20 +4112,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T15:01:07+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "1ee70e699b41909c209a0c930f11034b93578654" + "reference": "20414d96f391677bf80078aa55baece78b82647d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", - "reference": "1ee70e699b41909c209a0c930f11034b93578654", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", "shasum": "" }, "require": { @@ -4129,7 +4134,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4169,7 +4174,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" }, "funding": [ { @@ -4185,20 +4190,20 @@ "type": "tidelift" } ], - "time": "2023-07-30T20:28:31+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5677bdf7cade4619cb17fc9e1e7b31ec392244a9" + "reference": "27de8cc95e11db7a50b027e71caaab9024545947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5677bdf7cade4619cb17fc9e1e7b31ec392244a9", - "reference": "5677bdf7cade4619cb17fc9e1e7b31ec392244a9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/27de8cc95e11db7a50b027e71caaab9024545947", + "reference": "27de8cc95e11db7a50b027e71caaab9024545947", "shasum": "" }, "require": { @@ -4246,7 +4251,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.3" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.8" }, "funding": [ { @@ -4262,20 +4267,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9c6ec4e543044f7568a53a76ab1484ecd30637a2" + "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9c6ec4e543044f7568a53a76ab1484ecd30637a2", - "reference": "9c6ec4e543044f7568a53a76ab1484ecd30637a2", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", + "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", "shasum": "" }, "require": { @@ -4324,12 +4329,13 @@ "symfony/process": "^5.4|^6.0|^7.0", "symfony/property-access": "^5.4.5|^6.0.5|^7.0", "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", "symfony/stopwatch": "^5.4|^6.0|^7.0", "symfony/translation": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.4|^7.0", "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, @@ -4359,7 +4365,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.3" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.8" }, "funding": [ { @@ -4375,20 +4381,20 @@ "type": "tidelift" } ], - "time": "2024-01-31T07:21:29+00:00" + "time": "2024-06-02T16:06:25+00:00" }, { "name": "symfony/mime", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "5017e0a9398c77090b7694be46f20eb796262a34" + "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/5017e0a9398c77090b7694be46f20eb796262a34", - "reference": "5017e0a9398c77090b7694be46f20eb796262a34", + "url": "https://api.github.com/repos/symfony/mime/zipball/618597ab8b78ac86d1c75a9d0b35540cda074f33", + "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33", "shasum": "" }, "require": { @@ -4409,6 +4415,7 @@ "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.3.2|^7.0" @@ -4443,7 +4450,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.3" + "source": "https://github.com/symfony/mime/tree/v6.4.8" }, "funding": [ { @@ -4459,7 +4466,7 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-06-01T07:50:16+00:00" }, { "name": "symfony/polyfill-ctype", @@ -5247,16 +5254,16 @@ }, { "name": "symfony/process", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "31642b0818bfcff85930344ef93193f8c607e0a3" + "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/31642b0818bfcff85930344ef93193f8c607e0a3", - "reference": "31642b0818bfcff85930344ef93193f8c607e0a3", + "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5", "shasum": "" }, "require": { @@ -5288,7 +5295,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.3" + "source": "https://github.com/symfony/process/tree/v6.4.8" }, "funding": [ { @@ -5304,20 +5311,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/proxy-manager-bridge", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "c3f1b7d8f0b567eb960c540567f24219cb759e0a" + "reference": "b8119e0b248ef0711c25cd09acc729102122621c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/c3f1b7d8f0b567eb960c540567f24219cb759e0a", - "reference": "c3f1b7d8f0b567eb960c540567f24219cb759e0a", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/b8119e0b248ef0711c25cd09acc729102122621c", + "reference": "b8119e0b248ef0711c25cd09acc729102122621c", "shasum": "" }, "require": { @@ -5355,7 +5362,7 @@ "description": "Provides integration for ProxyManager with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.3" + "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.8" }, "funding": [ { @@ -5371,20 +5378,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/routing", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b2957ad54902f0f544df83e3d58b38d7e8e5842" + "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b2957ad54902f0f544df83e3d58b38d7e8e5842", - "reference": "3b2957ad54902f0f544df83e3d58b38d7e8e5842", + "url": "https://api.github.com/repos/symfony/routing/zipball/8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", + "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", "shasum": "" }, "require": { @@ -5438,7 +5445,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.3" + "source": "https://github.com/symfony/routing/tree/v6.4.8" }, "funding": [ { @@ -5454,25 +5461,26 @@ "type": "tidelift" } ], - "time": "2024-01-30T13:55:02+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.4.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^1.1|^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -5480,7 +5488,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5520,7 +5528,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -5536,20 +5544,20 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/string", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7a14736fb179876575464e4658fce0c304e8c15b" + "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7a14736fb179876575464e4658fce0c304e8c15b", - "reference": "7a14736fb179876575464e4658fce0c304e8c15b", + "url": "https://api.github.com/repos/symfony/string/zipball/a147c0f826c4a1f3afb763ab8e009e37c877a44d", + "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d", "shasum": "" }, "require": { @@ -5606,7 +5614,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.3" + "source": "https://github.com/symfony/string/tree/v6.4.8" }, "funding": [ { @@ -5622,20 +5630,20 @@ "type": "tidelift" } ], - "time": "2024-01-25T09:26:29+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.4.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "06450585bf65e978026bda220cdebca3f867fde7" + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", - "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "shasum": "" }, "require": { @@ -5644,7 +5652,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5684,7 +5692,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" }, "funding": [ { @@ -5700,20 +5708,20 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/twig-bridge", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "bf6b411a5d9a0ce6ea43cca0fcf5f05f5196a957" + "reference": "57de1b7d7499053a2c5beb9344751e8bfd332649" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/bf6b411a5d9a0ce6ea43cca0fcf5f05f5196a957", - "reference": "bf6b411a5d9a0ce6ea43cca0fcf5f05f5196a957", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/57de1b7d7499053a2c5beb9344751e8bfd332649", + "reference": "57de1b7d7499053a2c5beb9344751e8bfd332649", "shasum": "" }, "require": { @@ -5793,7 +5801,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.3" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.8" }, "funding": [ { @@ -5809,20 +5817,20 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0435a08f69125535336177c29d56af3abc1f69da" + "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0435a08f69125535336177c29d56af3abc1f69da", - "reference": "0435a08f69125535336177c29d56af3abc1f69da", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ad23ca4312395f0a8a8633c831ef4c4ee542ed25", + "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25", "shasum": "" }, "require": { @@ -5878,7 +5886,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.3" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.8" }, "funding": [ { @@ -5894,20 +5902,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:53:30+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "a8c12b5448a5ac685347f5eeb2abf6a571ec16b8" + "reference": "792ca836f99b340f2e9ca9497c7953948c49a504" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/a8c12b5448a5ac685347f5eeb2abf6a571ec16b8", - "reference": "a8c12b5448a5ac685347f5eeb2abf6a571ec16b8", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/792ca836f99b340f2e9ca9497c7953948c49a504", + "reference": "792ca836f99b340f2e9ca9497c7953948c49a504", "shasum": "" }, "require": { @@ -5915,6 +5923,8 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", @@ -5953,7 +5963,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.3" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.8" }, "funding": [ { @@ -5969,20 +5979,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90" + "reference": "52903de178d542850f6f341ba92995d3d63e60c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d75715985f0f94f978e3a8fa42533e10db921b90", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90", + "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9", + "reference": "52903de178d542850f6f341ba92995d3d63e60c9", "shasum": "" }, "require": { @@ -6025,7 +6035,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.3" + "source": "https://github.com/symfony/yaml/tree/v6.4.8" }, "funding": [ { @@ -6041,34 +6051,41 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "twig/twig", - "version": "v3.8.0", + "version": "v3.10.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -6101,7 +6118,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + "source": "https://github.com/twigphp/Twig/tree/v3.10.3" }, "funding": [ { @@ -6113,11 +6130,11 @@ "type": "tidelift" } ], - "time": "2023-11-21T18:54:41+00:00" + "time": "2024-05-16T10:04:27+00:00" }, { "name": "web-token/jwt-key-mgmt", - "version": "3.3.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/web-token/jwt-key-mgmt.git", @@ -6172,7 +6189,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.3.0" + "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.4.3" }, "funding": [ { @@ -6185,16 +6202,16 @@ }, { "name": "web-token/jwt-library", - "version": "3.3.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", - "reference": "5edf0f193425bb9c695a433180ddf9d263f55063" + "reference": "4b09510eec25c328525048cbdf6042a39a7c28d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/5edf0f193425bb9c695a433180ddf9d263f55063", - "reference": "5edf0f193425bb9c695a433180ddf9d263f55063", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/4b09510eec25c328525048cbdf6042a39a7c28d8", + "reference": "4b09510eec25c328525048cbdf6042a39a7c28d8", "shasum": "" }, "require": { @@ -6204,10 +6221,11 @@ "paragonie/constant_time_encoding": "^2.6", "paragonie/sodium_compat": "^1.20", "php": ">=8.1", + "psr/cache": "^3.0", "psr/clock": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "spomky-labs/pki-framework": "^1.0", + "spomky-labs/pki-framework": "^1.2.1", "symfony/console": "^5.4|^6.0|^7.0", "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/polyfill-mbstring": "^1.12" @@ -6266,7 +6284,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.3.0" + "source": "https://github.com/web-token/jwt-library/tree/3.4.3" }, "funding": [ { @@ -6278,11 +6296,11 @@ "type": "patreon" } ], - "time": "2024-02-22T08:15:45+00:00" + "time": "2024-04-17T17:41:33+00:00" }, { "name": "web-token/jwt-signature", - "version": "3.3.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature.git", @@ -6334,7 +6352,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature/tree/3.3.0" + "source": "https://github.com/web-token/jwt-signature/tree/3.4.3" }, "funding": [ { @@ -6347,7 +6365,7 @@ }, { "name": "web-token/jwt-signature-algorithm-ecdsa", - "version": "3.3.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git", @@ -6400,7 +6418,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.3.0" + "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.4.3" }, "funding": [ { @@ -6413,7 +6431,7 @@ }, { "name": "web-token/jwt-util-ecc", - "version": "3.3.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/web-token/jwt-util-ecc.git", @@ -6466,7 +6484,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-util-ecc/tree/3.3.0" + "source": "https://github.com/web-token/jwt-util-ecc/tree/3.4.3" }, "funding": [ { @@ -6481,16 +6499,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { @@ -6502,8 +6520,8 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", "extra": { @@ -6558,7 +6576,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -6566,20 +6584,20 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -6595,11 +6613,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -6623,7 +6636,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -6633,9 +6646,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -6643,7 +6655,7 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -6970,16 +6982,16 @@ }, { "name": "masterminds/html5", - "version": "2.8.1", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", "shasum": "" }, "require": { @@ -6987,7 +6999,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "extra": { @@ -7031,9 +7043,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" }, - "time": "2023-05-10T11:58:31+00:00" + "time": "2024-03-31T07:05:07+00:00" }, { "name": "misantron/dbunit", @@ -7092,16 +7104,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -7109,11 +7121,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -7139,7 +7152,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -7147,7 +7160,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "netresearch/jsonmapper", @@ -7202,21 +7215,21 @@ }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", @@ -7252,26 +7265,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -7312,9 +7326,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -7534,28 +7554,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { @@ -7579,15 +7606,15 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -7649,16 +7676,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.26.0", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { @@ -7690,22 +7717,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2024-02-23T16:05:55+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { @@ -7762,7 +7789,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -7770,7 +7797,7 @@ "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8015,16 +8042,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -8098,7 +8125,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -8114,27 +8141,27 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "psalm/plugin-symfony", - "version": "v5.1.0", + "version": "v5.2.2", "source": { "type": "git", "url": "https://github.com/psalm/psalm-plugin-symfony.git", - "reference": "f23ec3439743fb24f5c1101e52d032f23d5befa6" + "reference": "58e109257764e8e7eab10a43a4212bbd70435f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/f23ec3439743fb24f5c1101e52d032f23d5befa6", - "reference": "f23ec3439743fb24f5c1101e52d032f23d5befa6", + "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/58e109257764e8e7eab10a43a4212bbd70435f67", + "reference": "58e109257764e8e7eab10a43a4212bbd70435f67", "shasum": "" }, "require": { "ext-simplexml": "*", - "php": "^7.4 || ^8.0", + "php": "^8.1", "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", - "vimeo/psalm": "^5.1" + "vimeo/psalm": "^5.24" }, "require-dev": { "doctrine/annotations": "^1.8|^2", @@ -8144,7 +8171,7 @@ "symfony/console": "*", "symfony/form": "^5.0 || ^6.0 || ^7.0", "symfony/messenger": "^5.0 || ^6.0 || ^7.0", - "symfony/security-guard": "*", + "symfony/security-core": "*", "symfony/serializer": "^5.0 || ^6.0 || ^7.0", "symfony/validator": "*", "twig/twig": "^2.10 || ^3.0", @@ -8177,22 +8204,22 @@ "description": "Psalm Plugin for Symfony", "support": { "issues": "https://github.com/psalm/psalm-plugin-symfony/issues", - "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.1.0" + "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.2.2" }, - "time": "2023-11-12T10:04:27+00:00" + "time": "2024-06-06T15:34:33+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -8227,7 +8254,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -8235,7 +8262,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -8481,16 +8508,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -8535,7 +8562,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -8543,7 +8570,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -8610,16 +8637,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -8675,7 +8702,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -8683,20 +8710,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -8739,7 +8766,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -8747,7 +8774,7 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", @@ -8983,16 +9010,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -9004,7 +9031,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9025,8 +9052,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -9034,7 +9060,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -9147,16 +9173,16 @@ }, { "name": "spatie/array-to-xml", - "version": "3.2.3", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab" + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/c95fd4db94ec199f798d4b5b4a81757bd20d88ab", - "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", "shasum": "" }, "require": { @@ -9169,6 +9195,11 @@ "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Spatie\\ArrayToXml\\": "src" @@ -9194,7 +9225,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.2.3" + "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" }, "funding": [ { @@ -9206,20 +9237,20 @@ "type": "github" } ], - "time": "2024-02-07T10:39:02+00:00" + "time": "2024-05-01T10:20:27+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", "shasum": "" }, "require": { @@ -9286,20 +9317,20 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-05-22T21:24:41+00:00" }, { "name": "symfony/browser-kit", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "495ffa2e6d17e199213f93768efa01af32bbf70e" + "reference": "62ab90b92066ef6cce5e79365625b4b1432464c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/495ffa2e6d17e199213f93768efa01af32bbf70e", - "reference": "495ffa2e6d17e199213f93768efa01af32bbf70e", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/62ab90b92066ef6cce5e79365625b4b1432464c8", + "reference": "62ab90b92066ef6cce5e79365625b4b1432464c8", "shasum": "" }, "require": { @@ -9338,7 +9369,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.4.3" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.8" }, "funding": [ { @@ -9354,20 +9385,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/cache", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "49f8cdee544a621a621cd21b6cda32a38926d310" + "reference": "287142df5579ce223c485b3872df3efae8390984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/49f8cdee544a621a621cd21b6cda32a38926d310", - "reference": "49f8cdee544a621a621cd21b6cda32a38926d310", + "url": "https://api.github.com/repos/symfony/cache/zipball/287142df5579ce223c485b3872df3efae8390984", + "reference": "287142df5579ce223c485b3872df3efae8390984", "shasum": "" }, "require": { @@ -9434,7 +9465,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.3" + "source": "https://github.com/symfony/cache/tree/v6.4.8" }, "funding": [ { @@ -9450,20 +9481,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "1d74b127da04ffa87aa940abe15446fa89653778" + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778", - "reference": "1d74b127da04ffa87aa940abe15446fa89653778", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197", "shasum": "" }, "require": { @@ -9473,7 +9504,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -9510,7 +9541,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.0" }, "funding": [ { @@ -9526,20 +9557,20 @@ "type": "tidelift" } ], - "time": "2023-09-25T12:52:38+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229" + "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ee0f7ed5cf298cc019431bb3b3977ebc52b86229", - "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4b61b02fe15db48e3687ce1c45ea385d1780fe08", + "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08", "shasum": "" }, "require": { @@ -9575,7 +9606,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.3" + "source": "https://github.com/symfony/css-selector/tree/v6.4.8" }, "funding": [ { @@ -9591,20 +9622,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/dom-crawler", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "6db31849011fefe091e94d0bb10cba26f7919894" + "reference": "105b56a0305d219349edeb60a800082eca864e4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/6db31849011fefe091e94d0bb10cba26f7919894", - "reference": "6db31849011fefe091e94d0bb10cba26f7919894", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/105b56a0305d219349edeb60a800082eca864e4b", + "reference": "105b56a0305d219349edeb60a800082eca864e4b", "shasum": "" }, "require": { @@ -9642,7 +9673,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.3" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.8" }, "funding": [ { @@ -9658,20 +9689,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.3", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "fb413ac4483803954411966a39f3a9204835848e" + "reference": "7c7739f87f1a8be1c2f5e7d28addfe763a917acb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/fb413ac4483803954411966a39f3a9204835848e", - "reference": "fb413ac4483803954411966a39f3a9204835848e", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/7c7739f87f1a8be1c2f5e7d28addfe763a917acb", + "reference": "7c7739f87f1a8be1c2f5e7d28addfe763a917acb", "shasum": "" }, "require": { @@ -9710,7 +9741,7 @@ "symfony/mime": "<6.4", "symfony/property-access": "<5.4", "symfony/property-info": "<5.4", - "symfony/scheduler": "<6.4.3|>=7.0.0,<7.0.3", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-core": "<5.4", "symfony/security-csrf": "<5.4", "symfony/serializer": "<6.4", @@ -9749,7 +9780,7 @@ "symfony/process": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", "symfony/rate-limiter": "^5.4|^6.0|^7.0", - "symfony/scheduler": "^6.4.3|^7.0.3", + "symfony/scheduler": "^6.4.4|^7.0.4", "symfony/security-bundle": "^5.4|^6.0|^7.0", "symfony/semaphore": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.4|^7.0", @@ -9762,7 +9793,7 @@ "symfony/web-link": "^5.4|^6.0|^7.0", "symfony/workflow": "^6.4|^7.0", "symfony/yaml": "^5.4|^6.0|^7.0", - "twig/twig": "^2.10|^3.0" + "twig/twig": "^2.10|^3.0.4" }, "type": "symfony-bundle", "autoload": { @@ -9790,7 +9821,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v6.4.3" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.8" }, "funding": [ { @@ -9806,20 +9837,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T15:02:55+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -9848,7 +9879,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -9856,20 +9887,20 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "vimeo/psalm", - "version": "5.22.2", + "version": "5.24.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "d768d914152dbbf3486c36398802f74e80cfde48" + "reference": "462c80e31c34e58cc4f750c656be3927e80e550e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/d768d914152dbbf3486c36398802f74e80cfde48", - "reference": "d768d914152dbbf3486c36398802f74e80cfde48", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/462c80e31c34e58cc4f750c656be3927e80e550e", + "reference": "462c80e31c34e58cc4f750c656be3927e80e550e", "shasum": "" }, "require": { @@ -9966,7 +9997,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-02-22T23:39:07+00:00" + "time": "2024-05-01T19:32:08+00:00" }, { "name": "webmozart/assert", From 6b69169e47a15d3a7add460ec5aa5fea5fc2f142 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 14 Jun 2024 21:02:12 +0200 Subject: [PATCH 0244/1214] [ticket/17340] Fix twig to 3.8.0 until performance issues are resolved PHPBB-17340 --- phpBB/composer.json | 2 +- phpBB/composer.lock | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index 6d59cefe266..9d4f28689ce 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -58,7 +58,7 @@ "symfony/routing": "^6.3", "symfony/twig-bridge": "^6.3", "symfony/yaml": "^6.3", - "twig/twig": "^3.0" + "twig/twig": "3.8.0" }, "require-dev": { "laravel/homestead": "~14.4", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index f8b8863dd03..a8651e11a98 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "26143c95732859f6f0849abd2fe74ae5", + "content-hash": "3c21881b6c8a81fbd83d57004763049d", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -6055,37 +6055,30 @@ }, { "name": "twig/twig", - "version": "v3.10.3", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", - "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { - "files": [ - "src/Resources/core.php", - "src/Resources/debug.php", - "src/Resources/escaper.php", - "src/Resources/string_loader.php" - ], "psr-4": { "Twig\\": "src/" } @@ -6118,7 +6111,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.10.3" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -6130,7 +6123,7 @@ "type": "tidelift" } ], - "time": "2024-05-16T10:04:27+00:00" + "time": "2023-11-21T18:54:41+00:00" }, { "name": "web-token/jwt-key-mgmt", From b6c2961568b6e93e21a1740f23e63e8b0bf9912b Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 9 Jun 2024 12:19:47 -0700 Subject: [PATCH 0245/1214] [ticket/17335] Fix language var issues in the extension manager PHPBB-17335 Signed-off-by: Matt Friedman --- phpBB/adm/style/acp_ext_list.html | 39 ++++++++++++++-------------- phpBB/language/en/acp/extensions.php | 37 ++++++++++++++------------ 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index bb22ac861c8..dd006bc2cc6 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -99,25 +99,26 @@

          {L_EXTENSIONS_ADMIN}

          - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
          {L_EXTENSION_INSTALL_HEADLINE}
          {L_EXTENSION_INSTALL_EXPLAIN}
          {L_EXTENSION_UPDATE_HEADLINE}
          {L_EXTENSION_UPDATE_EXPLAIN}
          {L_EXTENSION_REMOVE_HEADLINE}
          {L_EXTENSION_REMOVE_EXPLAIN}
          {L_EXTENSION_INSTALLING_HEADLINE}
          {L_EXTENSION_INSTALLING_EXPLAIN}
          {L_EXTENSION_UPDATING_HEADLINE}
          {L_EXTENSION_UPDATING_EXPLAIN}
          {L_EXTENSION_REMOVING_HEADLINE}
          {L_EXTENSION_REMOVING_EXPLAIN}
          diff --git a/phpBB/language/en/acp/extensions.php b/phpBB/language/en/acp/extensions.php index c6eb147b86e..3a8322ae0bb 100644 --- a/phpBB/language/en/acp/extensions.php +++ b/phpBB/language/en/acp/extensions.php @@ -36,14 +36,15 @@ $lang = array_merge($lang, array( - 'EXTENSION_ALREADY_INSTALLED' => 'The “%s” extension has already been installed.', - 'EXTENSION_ALREADY_INSTALLED_MANUALLY' => 'The “%s” extension has already been installed manually.', - 'EXTENSION_ALREADY_MANAGED' => 'The “%s” extension is already managed.', - 'EXTENSION_CANNOT_MANAGE_FILESYSTEM_ERROR' => 'The “%s” extension cannot be managed because the existing files could not be removed from the filesystem.', - 'EXTENSION_CANNOT_MANAGE_INSTALL_ERROR' => 'The “%s” extension could not be installed. The prior installation of this extension has been restored.', - 'EXTENSION_MANAGED_WITH_CLEAN_ERROR' => 'The “%1$s” extension has been installed but an error occurred and the old files could not be removed. You might want to delete the “%2$s” files manually.', - 'EXTENSION_MANAGED_WITH_ENABLE_ERROR' => 'The “%s” extension has been installed but an error occurred while enabling it.', - 'EXTENSION_NOT_INSTALLED' => 'The “%s” extension is not installed.', + 'EXTENSIONS_ALREADY_INSTALLED' => 'The “%s” extension has already been installed.', + 'EXTENSIONS_ALREADY_INSTALLED_MANUALLY' => 'The “%s” extension has already been installed manually.', + 'EXTENSIONS_ALREADY_MANAGED' => 'The “%s” extension is already managed.', + 'EXTENSIONS_CANNOT_MANAGE_FILESYSTEM_ERROR' => 'The “%s” extension cannot be managed because the existing files could not be removed from the filesystem.', + 'EXTENSIONS_CANNOT_MANAGE_INSTALL_ERROR' => 'The “%s” extension could not be installed. The prior installation of this extension has been restored.', + 'EXTENSIONS_MANAGED_WITH_CLEAN_ERROR' => 'The “%1$s” extension has been installed but an error occurred and the old files could not be removed. You might want to delete the “%2$s” files manually.', + 'EXTENSIONS_MANAGED_WITH_ENABLE_ERROR' => 'The “%s” extension has been installed but an error occurred while enabling it.', + 'EXTENSIONS_NOT_INSTALLED' => 'The “%s” extension is not installed.', + 'EXTENSIONS_NOT_MANAGED' => 'The “%s” extension is not being managed.', 'ENABLING_EXTENSIONS' => 'Enabling extensions', 'DISABLING_EXTENSIONS' => 'Disabling extensions', @@ -74,6 +75,8 @@ 'EXTENSION_DELETE_DATA_EXPLAIN' => 'Deleting an extension’s data removes all of its data and settings. The extension files are retained so it can be enabled again.', 'EXTENSION_DISABLE_EXPLAIN' => 'Disabling an extension retains its files, data and settings but removes any functionality added by the extension.', 'EXTENSION_ENABLE_EXPLAIN' => 'Enabling an extension allows you to use it on your board.', + 'EXTENSION_REMOVE_EXPLAIN' => 'Removing an extension removes all of its files, data and settings.', + 'EXTENSION_UPDATE_EXPLAIN' => 'Updating an extension will install the latest version compatible with your board, removing old files and replacing them with new ones, and updating the database if necessary.', 'EXTENSION_DELETE_DATA_IN_PROGRESS' => 'The extension’s data is currently being deleted. Please do not leave or refresh this page until it is completed.', 'EXTENSION_DISABLE_IN_PROGRESS' => 'The extension is currently being disabled. Please do not leave or refresh this page until it is completed.', @@ -86,25 +89,25 @@ 'EXTENSION_NAME' => 'Extension Name', 'EXTENSION_ACTIONS' => 'Actions', 'EXTENSION_OPTIONS' => 'Options', - 'EXTENSION_INSTALL_HEADLINE'=> 'Installing an extension', - 'EXTENSION_INSTALL_EXPLAIN' => '
            + 'EXTENSION_INSTALLING_HEADLINE'=> 'Installing an extension', + 'EXTENSION_INSTALLING_EXPLAIN' => '
            1. Download an extension from phpBB’s extensions database
            2. Unzip the extension and upload it to the ext/ directory of your phpBB board
            3. Enable the extension, here in the Extensions manager
            ', - 'EXTENSION_UPDATE_HEADLINE' => 'Updating an extension', - 'EXTENSION_UPDATE_EXPLAIN' => '
              + 'EXTENSION_REMOVING_EXPLAIN' => '
              1. Disable the extension
              2. +
              3. Delete the extension’s data
              4. Delete the extension’s files from the filesystem
              5. -
              6. Upload the new files
              7. -
              8. Enable the extension
              ', - 'EXTENSION_REMOVE_HEADLINE' => 'Completely removing an extension from your board', - 'EXTENSION_REMOVE_EXPLAIN' => '
                + 'EXTENSION_UPDATING_HEADLINE' => 'Updating an extension', + 'EXTENSION_UPDATING_EXPLAIN' => '
                1. Disable the extension
                2. -
                3. Delete the extension’s data
                4. Delete the extension’s files from the filesystem
                5. +
                6. Upload the new files
                7. +
                8. Enable the extension
                ', + 'EXTENSION_REMOVING_HEADLINE' => 'Deleting an extension from your board', 'EXTENSION_DELETE_DATA_CONFIRM' => 'Are you sure that you wish to delete the data associated with “%s”?

                This removes all of its data and settings and cannot be undone!', 'EXTENSION_DISABLE_CONFIRM' => 'Are you sure that you wish to disable the “%s” extension?', From 173e1ceeba784b8171102324a8625317225597f2 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 10 Jun 2024 20:46:58 -0700 Subject: [PATCH 0246/1214] [ticket/17335] Move HTML from lang to template PHPBB-17335 Signed-off-by: Matt Friedman --- phpBB/adm/style/acp_ext_list.html | 25 ++++++++++++++++--- phpBB/language/en/acp/extensions.php | 36 ++++++++++++++-------------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index dd006bc2cc6..b8b3d2c68a7 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -104,19 +104,38 @@

                {L_EXTENSIONS_ADMIN}

                {L_EXTENSION_INSTALLING_HEADLINE} - {L_EXTENSION_INSTALLING_EXPLAIN} + +
                  +
                1. {{ lang(['EXTENSION_INSTALLING_EXPLAIN', 'step_1']) }}
                2. +
                3. {{ lang(['EXTENSION_INSTALLING_EXPLAIN', 'step_2']) }}
                4. +
                5. {{ lang(['EXTENSION_INSTALLING_EXPLAIN', 'step_3']) }}
                6. +
                + {L_EXTENSION_UPDATING_HEADLINE} - {L_EXTENSION_UPDATING_EXPLAIN} + +
                  +
                1. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_1']) }}
                2. +
                3. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_2']) }}
                4. +
                5. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_3']) }}
                6. +
                7. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_4']) }}
                8. +
                + {L_EXTENSION_REMOVING_HEADLINE} - {L_EXTENSION_REMOVING_EXPLAIN} + +
                  +
                1. {{ lang(['EXTENSION_REMOVING_EXPLAIN', 'step_1']) }}
                2. +
                3. {{ lang(['EXTENSION_REMOVING_EXPLAIN', 'step_2']) }}
                4. +
                5. {{ lang(['EXTENSION_REMOVING_EXPLAIN', 'step_3']) }}
                6. +
                + diff --git a/phpBB/language/en/acp/extensions.php b/phpBB/language/en/acp/extensions.php index 3a8322ae0bb..311cfa74f32 100644 --- a/phpBB/language/en/acp/extensions.php +++ b/phpBB/language/en/acp/extensions.php @@ -89,25 +89,25 @@ 'EXTENSION_NAME' => 'Extension Name', 'EXTENSION_ACTIONS' => 'Actions', 'EXTENSION_OPTIONS' => 'Options', - 'EXTENSION_INSTALLING_HEADLINE'=> 'Installing an extension', - 'EXTENSION_INSTALLING_EXPLAIN' => '
                  -
                1. Download an extension from phpBB’s extensions database
                2. -
                3. Unzip the extension and upload it to the ext/ directory of your phpBB board
                4. -
                5. Enable the extension, here in the Extensions manager
                6. -
                ', - 'EXTENSION_REMOVING_EXPLAIN' => '
                  -
                1. Disable the extension
                2. -
                3. Delete the extension’s data
                4. -
                5. Delete the extension’s files from the filesystem
                6. -
                ', - 'EXTENSION_UPDATING_HEADLINE' => 'Updating an extension', - 'EXTENSION_UPDATING_EXPLAIN' => '
                  -
                1. Disable the extension
                2. -
                3. Delete the extension’s files from the filesystem
                4. -
                5. Upload the new files
                6. -
                7. Enable the extension
                8. -
                ', + 'EXTENSION_INSTALLING_HEADLINE' => 'Installing an extension', + 'EXTENSION_INSTALLING_EXPLAIN' => [ + 'step_1' => 'Download an extension from phpBB’s extensions database', + 'step_2' => 'Unzip the extension and upload it to the ext/ directory of your phpBB board', + 'step_3' => 'Enable the extension, here in the Extensions manager', + ], 'EXTENSION_REMOVING_HEADLINE' => 'Deleting an extension from your board', + 'EXTENSION_REMOVING_EXPLAIN' => [ + 'step_1' => 'Disable the extension', + 'step_2' => 'Delete the extension’s data', + 'step_3' => 'Delete the extension‘s files from the filesystem', + ], + 'EXTENSION_UPDATING_HEADLINE' => 'Updating an extension', + 'EXTENSION_UPDATING_EXPLAIN' => [ + 'step_1' => 'Disable the extension', + 'step_2' => 'Delete the extension’s files from the filesystem', + 'step_3' => 'Upload the new files', + 'step_4' => 'Enable the extension', + ], 'EXTENSION_DELETE_DATA_CONFIRM' => 'Are you sure that you wish to delete the data associated with “%s”?

                This removes all of its data and settings and cannot be undone!', 'EXTENSION_DISABLE_CONFIRM' => 'Are you sure that you wish to disable the “%s” extension?', From 48cc96b489366641a818888ad45578c2a2948398 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 14 Jun 2024 23:10:09 +0200 Subject: [PATCH 0247/1214] [ticket/17335] Add lang_raw twig function for templates PHPBB-17335 --- phpBB/phpbb/template/twig/extension.php | 13 +++++++++++++ tests/template/template_test.php | 6 +++--- tests/template/templates/lang_twig.html | 4 ++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/phpBB/phpbb/template/twig/extension.php b/phpBB/phpbb/template/twig/extension.php index 2e920dbe4be..7fde9e2cac2 100644 --- a/phpBB/phpbb/template/twig/extension.php +++ b/phpBB/phpbb/template/twig/extension.php @@ -93,6 +93,7 @@ public function getFunctions() new \Twig\TwigFunction('lang', array($this, 'lang')), new \Twig\TwigFunction('lang_defined', array($this, 'lang_defined')), new \Twig\TwigFunction('lang_js', [$this, 'lang_js']), + new \Twig\TwigFunction('lang_raw', [$this, 'lang_raw']), new \Twig\TwigFunction('get_class', 'get_class'), ); } @@ -214,4 +215,16 @@ public function lang_js(): string return twig_escape_filter($this->environment, call_user_func_array([$this, 'lang'], $args), 'js'); } + + /** + * Get raw value associated with lang key + * + * @param string $key + * + * @return array|string Raw value associated with lang key + */ + public function lang_raw(string $key): array|string + { + return call_user_func_array(array($this->language, 'lang_raw'), [$key]); + } } diff --git a/tests/template/template_test.php b/tests/template/template_test.php index 8faa52d8acd..4fc98583669 100644 --- a/tests/template/template_test.php +++ b/tests/template/template_test.php @@ -365,15 +365,15 @@ public function template_data() array(), array(), array(), - "VARIABLE\n1_VARIABLE\nVARIABLE\n1_VARIABLE", + "VARIABLE\n1_VARIABLE\nVARIABLE\n1_VARIABLE\nVARIABLE\n1_VARIABLE\nARY_VARIABLE", ), array( 'lang_twig.html', array(), array(), array(), - "Value'\n1 O'Clock\nValue\\u0027\n1\\u0020O\\u0027Clock", - array('VARIABLE' => "Value'", '1_VARIABLE' => "1 O'Clock"), + "Value'\n1 O'Clock\nValue\\u0027\n1\\u0020O\\u0027Clock\nValue'\n1 O'Clock\nfoo|bar", + array('VARIABLE' => "Value'", '1_VARIABLE' => "1 O'Clock", 'ARY_VARIABLE' => ['foo', 'bar']), ), array( 'loop_nested_multilevel_ref.html', diff --git a/tests/template/templates/lang_twig.html b/tests/template/templates/lang_twig.html index bf31012819c..a331c7f5063 100644 --- a/tests/template/templates/lang_twig.html +++ b/tests/template/templates/lang_twig.html @@ -3,3 +3,7 @@ {{ lang_js('VARIABLE') }} {{ lang_js('1_VARIABLE') }} + +{{ lang_raw('VARIABLE') }} +{{ lang_raw('1_VARIABLE') }} +{{ lang_raw('ARY_VARIABLE')|join('|') }} From 444137531ff0d6650170e4cd76e82049c8656f8c Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Fri, 14 Jun 2024 14:43:40 -0700 Subject: [PATCH 0248/1214] [ticket/17335] Use lang_raw PHPBB-17335 Signed-off-by: Matt Friedman --- phpBB/adm/style/acp_ext_list.html | 19 +++++++++---------- phpBB/language/en/acp/extensions.php | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index b8b3d2c68a7..dc6dcf1797a 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -106,9 +106,9 @@

                {L_EXTENSIONS_ADMIN}

                  -
                1. {{ lang(['EXTENSION_INSTALLING_EXPLAIN', 'step_1']) }}
                2. -
                3. {{ lang(['EXTENSION_INSTALLING_EXPLAIN', 'step_2']) }}
                4. -
                5. {{ lang(['EXTENSION_INSTALLING_EXPLAIN', 'step_3']) }}
                6. + {% for step in lang_raw('EXTENSION_INSTALLING_EXPLAIN') %} +
                7. {{ step }}
                8. + {% endfor %}
                @@ -118,10 +118,9 @@

                {L_EXTENSIONS_ADMIN}

                  -
                1. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_1']) }}
                2. -
                3. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_2']) }}
                4. -
                5. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_3']) }}
                6. -
                7. {{ lang(['EXTENSION_UPDATING_EXPLAIN', 'step_4']) }}
                8. + {% for step in lang_raw('EXTENSION_UPDATING_EXPLAIN') %} +
                9. {{ step }}
                10. + {% endfor %}
                @@ -131,9 +130,9 @@

                {L_EXTENSIONS_ADMIN}

                  -
                1. {{ lang(['EXTENSION_REMOVING_EXPLAIN', 'step_1']) }}
                2. -
                3. {{ lang(['EXTENSION_REMOVING_EXPLAIN', 'step_2']) }}
                4. -
                5. {{ lang(['EXTENSION_REMOVING_EXPLAIN', 'step_3']) }}
                6. + {% for step in lang_raw('EXTENSION_REMOVING_EXPLAIN') %} +
                7. {{ step }}
                8. + {% endfor %}
                diff --git a/phpBB/language/en/acp/extensions.php b/phpBB/language/en/acp/extensions.php index 311cfa74f32..bf854cdb7b4 100644 --- a/phpBB/language/en/acp/extensions.php +++ b/phpBB/language/en/acp/extensions.php @@ -91,22 +91,22 @@ 'EXTENSION_OPTIONS' => 'Options', 'EXTENSION_INSTALLING_HEADLINE' => 'Installing an extension', 'EXTENSION_INSTALLING_EXPLAIN' => [ - 'step_1' => 'Download an extension from phpBB’s extensions database', - 'step_2' => 'Unzip the extension and upload it to the ext/ directory of your phpBB board', - 'step_3' => 'Enable the extension, here in the Extensions manager', + 0 => 'Download an extension from phpBB’s extensions database', + 1 => 'Unzip the extension and upload it to the ext/ directory of your phpBB board', + 2 => 'Enable the extension, here in the Extensions manager', ], 'EXTENSION_REMOVING_HEADLINE' => 'Deleting an extension from your board', 'EXTENSION_REMOVING_EXPLAIN' => [ - 'step_1' => 'Disable the extension', - 'step_2' => 'Delete the extension’s data', - 'step_3' => 'Delete the extension‘s files from the filesystem', + 0 => 'Disable the extension', + 1 => 'Delete the extension’s data', + 2 => 'Delete the extension‘s files from the filesystem', ], 'EXTENSION_UPDATING_HEADLINE' => 'Updating an extension', 'EXTENSION_UPDATING_EXPLAIN' => [ - 'step_1' => 'Disable the extension', - 'step_2' => 'Delete the extension’s files from the filesystem', - 'step_3' => 'Upload the new files', - 'step_4' => 'Enable the extension', + 0 => 'Disable the extension', + 1 => 'Delete the extension’s files from the filesystem', + 2 => 'Upload the new files', + 3 => 'Enable the extension', ], 'EXTENSION_DELETE_DATA_CONFIRM' => 'Are you sure that you wish to delete the data associated with “%s”?

                This removes all of its data and settings and cannot be undone!', From d2cd8ecf6648e90fd0b4a87ff9dc4ddc48a466da Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 15 Jun 2024 11:41:16 +0200 Subject: [PATCH 0249/1214] [ticket/17342] Add tests for PHP 8.4 to GitHub Actions PHPBB-17342 --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cbf31fc2884..9dd53fafa5b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -137,6 +137,10 @@ jobs: db: "mysql:5.7" - php: '8.3' db: "mariadb:10.2" + - php: '8.4' + db: "mysql:8.0" + - php: '8.4' + db: "mariadb:10.3" name: PHP ${{ matrix.php }} - ${{ matrix.db_alias != '' && matrix.db_alias || matrix.db }} @@ -272,6 +276,8 @@ jobs: db: "postgres:9.5" - php: '8.3' db: "postgres:9.5" + - php: '8.4' + db: "postgres:9.5" name: PHP ${{ matrix.php }} - ${{ matrix.db }} From 0fc1a8c1f8c1d7a2781644857ebb58db489c4137 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 16 Jun 2024 08:49:36 -0700 Subject: [PATCH 0250/1214] [ticket/17344] Show an alert when webpush subscribing is denied PHPBB-17344 --- phpBB/assets/javascript/webpush.js | 1 + phpBB/language/en/common.php | 1 + phpBB/styles/prosilver/template/notification_dropdown.html | 2 +- phpBB/styles/prosilver/template/ucp_notifications_options.html | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/phpBB/assets/javascript/webpush.js b/phpBB/assets/javascript/webpush.js index c326fe1cff3..94e542d2aed 100644 --- a/phpBB/assets/javascript/webpush.js +++ b/phpBB/assets/javascript/webpush.js @@ -150,6 +150,7 @@ function PhpbbWebpush() { // Prevent the user from clicking the subscribe button multiple times. const result = await Notification.requestPermission(); if (result === 'denied') { + phpbb.alert(subscribeButton.getAttribute('data-l-err'), subscribeButton.getAttribute('data-l-msg')); return; } diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 5933cc8f5cf..43269679bfd 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -515,6 +515,7 @@ 'NOTIFY_WEB_PUSH_ENABLE' => 'Enable Web Push notifications', 'NOTIFY_WEB_PUSH_SUBSCRIBE' => 'Subscribe', 'NOTIFY_WEB_PUSH_SUBSCRIBED'=> 'Subscribed', + 'NOTIFY_WEB_PUSH_DENIED' => 'You have denied notifications from this site. To subscribe, please allow notifications in your browser settings.', 'NO_ACCESS_ATTACHMENT' => 'You are not allowed to access this file.', 'NO_ACTION' => 'No action specified.', 'NO_ADMINISTRATORS' => 'There are no administrators.', diff --git a/phpBB/styles/prosilver/template/notification_dropdown.html b/phpBB/styles/prosilver/template/notification_dropdown.html index ff52543d0a7..abd5c85bfd4 100644 --- a/phpBB/styles/prosilver/template/notification_dropdown.html +++ b/phpBB/styles/prosilver/template/notification_dropdown.html @@ -47,7 +47,7 @@ {% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %} {% endif %} diff --git a/phpBB/styles/prosilver/template/ucp_notifications_options.html b/phpBB/styles/prosilver/template/ucp_notifications_options.html index b3be983f169..27981581ff2 100644 --- a/phpBB/styles/prosilver/template/ucp_notifications_options.html +++ b/phpBB/styles/prosilver/template/ucp_notifications_options.html @@ -10,7 +10,7 @@

                {{ TITLE }}


                {{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}
                - +
                From f3ca92ffe78b832ac9f39633d5422520bb87fa6e Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 16 Jun 2024 09:01:39 -0700 Subject: [PATCH 0251/1214] [ticket/17344] Responsive mobile friendly webpush sub button PHPBB-17344 --- phpBB/styles/prosilver/theme/common.css | 1 + phpBB/styles/prosilver/theme/responsive.css | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 2300f9c611a..f679df7c9f8 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -1347,6 +1347,7 @@ ul.linklist:after, flex-wrap: nowrap; justify-content: space-between; padding: 5px 10px; + gap: 20px; } .notification-avatar, diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index 5deb7224a68..481b1a23d38 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -20,6 +20,16 @@ } } +@media (max-width: 550px) { + .dropdown-extended .notification-dropdown-footer .notification-subscribe_toggle > span { + display: none; + } + + .dropdown-extended .notification-dropdown-footer .notification-subscribe_toggle > .icon { + font-size: 24px; + } +} + @media (max-width: 430px) { .action-bar .search-box .inputbox { width: 120px; From 16743e934523078b2020b1a975f157900fc1e7ae Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 16 Jun 2024 09:43:02 -0700 Subject: [PATCH 0252/1214] [ticket/17344] Improve webpush class names PHPBB-17344 --- .../prosilver/template/notification_dropdown.html | 6 +++--- phpBB/styles/prosilver/theme/buttons.css | 2 +- phpBB/styles/prosilver/theme/colours.css | 6 +++++- phpBB/styles/prosilver/theme/common.css | 3 +-- phpBB/styles/prosilver/theme/responsive.css | 8 +------- tests/functional/notification_webpush_test.php | 10 +++++----- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/phpBB/styles/prosilver/template/notification_dropdown.html b/phpBB/styles/prosilver/template/notification_dropdown.html index abd5c85bfd4..de5a8c79e6b 100644 --- a/phpBB/styles/prosilver/template/notification_dropdown.html +++ b/phpBB/styles/prosilver/template/notification_dropdown.html @@ -45,10 +45,10 @@ {L_SEE_ALL}

{% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %} - From c2725b441cf40c7f025bbe745e0180223552e1b6 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Mon, 12 Aug 2024 20:57:23 +0100 Subject: [PATCH 0622/1214] [ticket/17326] Change column length of icon and update ACP language PHPBB-17326 --- phpBB/adm/style/acp_bbcodes.html | 2 +- phpBB/includes/acp/acp_bbcodes.php | 13 +++++++------ phpBB/language/en/acp/posting.php | 2 +- .../db/migration/data/v400/add_bbcode_font_icon.php | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/phpBB/adm/style/acp_bbcodes.html b/phpBB/adm/style/acp_bbcodes.html index 8bc0b5fdc2f..af957ed372d 100644 --- a/phpBB/adm/style/acp_bbcodes.html +++ b/phpBB/adm/style/acp_bbcodes.html @@ -50,7 +50,7 @@

{L_ACP_BBCODES}

{{ lang('APPEARANCE') }}
-

{{ lang('BBCODE_FONT_ICON_EXPLAIN') }}
+

{L_BBCODE_FONT_ICON_EXPLAIN}
{{ Icon('font', BBCODE_FONT_ICON, '', false, '', {'id':'bbcode_icon_preview'}) }} diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index 66ef3627aba..e28c1f6155c 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -104,12 +104,13 @@ function main($id, $mode) 'U_BACK' => $this->u_action, 'U_ACTION' => $this->u_action . '&action=' . (($action == 'add') ? 'create' : 'modify') . (($bbcode_id) ? "&bbcode=$bbcode_id" : ''), - 'L_BBCODE_USAGE_EXPLAIN'=> sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), - 'BBCODE_MATCH' => $bbcode_match, - 'BBCODE_TPL' => $bbcode_tpl, - 'BBCODE_HELPLINE' => $bbcode_helpline, - 'BBCODE_FONT_ICON' => $bbcode_font_icon, - 'DISPLAY_ON_POSTING' => $display_on_posting, + 'L_BBCODE_USAGE_EXPLAIN' => sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), + 'L_BBCODE_FONT_ICON_EXPLAIN' => sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), + 'BBCODE_MATCH' => $bbcode_match, + 'BBCODE_TPL' => $bbcode_tpl, + 'BBCODE_HELPLINE' => $bbcode_helpline, + 'BBCODE_FONT_ICON' => $bbcode_font_icon, + 'DISPLAY_ON_POSTING' => $display_on_posting, ); $bbcode_tokens = array('TEXT', 'SIMPLETEXT', 'INTTEXT', 'IDENTIFIER', 'NUMBER', 'EMAIL', 'URL', 'LOCAL_URL', 'RELATIVE_URL', 'COLOR'); diff --git a/phpBB/language/en/acp/posting.php b/phpBB/language/en/acp/posting.php index c219b1a46ab..b03a83956f6 100644 --- a/phpBB/language/en/acp/posting.php +++ b/phpBB/language/en/acp/posting.php @@ -54,7 +54,7 @@ 'BBCODE_HELPLINE_TEXT' => 'Help line text', 'BBCODE_HELPLINE_TOO_LONG' => 'The help line you entered is too long.', 'BBCODE_FONT_ICON' => 'BBCode icon', - 'BBCODE_FONT_ICON_EXPLAIN' => 'Enter the name of a Font Awesome icon (without the fa prefix) to display instead of the BBCode name appearing on the button. Click here to view the list of available icons.', + 'BBCODE_FONT_ICON_EXPLAIN' => 'Enter the name of a Font Awesome icon (without the fa prefix) to display instead of the BBCode name appearing on the button. %1$sClick here%2$s to view the list of available icons. Only solid style icons are supported.', 'BBCODE_FONT_ICON_TOO_LONG' => 'The icon name you have entered is too long.', 'BBCODE_INVALID_TAG_NAME' => 'The BBCode tag name that you selected already exists.', diff --git a/phpBB/phpbb/db/migration/data/v400/add_bbcode_font_icon.php b/phpBB/phpbb/db/migration/data/v400/add_bbcode_font_icon.php index 1a580dde8c3..183b5d91db9 100644 --- a/phpBB/phpbb/db/migration/data/v400/add_bbcode_font_icon.php +++ b/phpBB/phpbb/db/migration/data/v400/add_bbcode_font_icon.php @@ -32,7 +32,7 @@ public function update_schema() return [ 'add_columns' => [ $this->table_prefix . 'bbcodes' => [ - 'bbcode_font_icon' => ['VCHAR:50', ''], + 'bbcode_font_icon' => ['VCHAR:64', ''], ], ], ]; From bb956539a46b53092ff8fd8c0bc61da10a2099a4 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Mon, 12 Aug 2024 21:01:09 +0100 Subject: [PATCH 0623/1214] [ticket/17326] Add php preg check on bbcode font icon name PHPBB-17326 --- phpBB/includes/acp/acp_bbcodes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index e28c1f6155c..d9a4ae98221 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -238,7 +238,7 @@ function main($id, $mode) trigger_error($user->lang['BBCODE_HELPLINE_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } - if (strlen($bbcode_font_icon) > 50) + if (strlen($bbcode_font_icon) > 64 && preg_match('/^[A-Za-z0-9-]+$/', $bbcode_font_icon)) { trigger_error($user->lang['BBCODE_FONT_ICON_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } From 53d7fff391cc9dc0b1dfa097b431ddadd43f544b Mon Sep 17 00:00:00 2001 From: Daniel James Date: Mon, 12 Aug 2024 21:05:09 +0100 Subject: [PATCH 0624/1214] [ticket/17326] Update regex checks for font icon in php and js files PHPBB-17326 --- phpBB/adm/style/admin.js | 5 ++++- phpBB/includes/acp/acp_bbcodes.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/phpBB/adm/style/admin.js b/phpBB/adm/style/admin.js index 1ff1b02d749..2ae75db062e 100644 --- a/phpBB/adm/style/admin.js +++ b/phpBB/adm/style/admin.js @@ -280,7 +280,10 @@ function parse_document(container) // Live update BBCode font icon preview $('#bbcode_font_icon').on('keyup', function(e) { - $('#bbcode_icon_preview').attr('class', "o-icon o-icon-font fa-fw fas icon fa-" + $(this).val()); + const iconName = $(this).val(); + if (iconName.match(/^[\w-]+$/)) { + $('#bbcode_icon_preview').attr('class', "o-icon o-icon-font fa-fw fas icon fa-" + $(this).val()); + } }); }); })(jQuery); diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index d9a4ae98221..58d86c940d4 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -238,7 +238,7 @@ function main($id, $mode) trigger_error($user->lang['BBCODE_HELPLINE_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } - if (strlen($bbcode_font_icon) > 64 && preg_match('/^[A-Za-z0-9-]+$/', $bbcode_font_icon)) + if (strlen($bbcode_font_icon) > 64 && preg_match('/^[\w-]+$/', $bbcode_font_icon)) { trigger_error($user->lang['BBCODE_FONT_ICON_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } From fe3750bfb2a45884a71e91ce3d40f3ff0b13dea3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 30 Aug 2024 21:39:53 +0200 Subject: [PATCH 0625/1214] [ticket/17326] Move HTML to acp_bbcodes.html PHPBB-17326 --- phpBB/adm/style/acp_bbcodes.html | 4 ++-- phpBB/includes/acp/acp_bbcodes.php | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/phpBB/adm/style/acp_bbcodes.html b/phpBB/adm/style/acp_bbcodes.html index af957ed372d..cb3480f9467 100644 --- a/phpBB/adm/style/acp_bbcodes.html +++ b/phpBB/adm/style/acp_bbcodes.html @@ -14,7 +14,7 @@

{L_ACP_BBCODES}

{L_BBCODE_USAGE} -

{L_BBCODE_USAGE_EXPLAIN}

+

{{ lang('BBCODE_USAGE_EXPLAIN', '', '') }}



{L_BBCODE_USAGE_EXAMPLE}
@@ -50,7 +50,7 @@

{L_ACP_BBCODES}

{{ lang('APPEARANCE') }}
-

{L_BBCODE_FONT_ICON_EXPLAIN}
+

{{ lang('BBCODE_FONT_ICON_EXPLAIN', '', '') }}
{{ Icon('font', BBCODE_FONT_ICON, '', false, '', {'id':'bbcode_icon_preview'}) }} diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index 58d86c940d4..ee97631d2e1 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -104,8 +104,6 @@ function main($id, $mode) 'U_BACK' => $this->u_action, 'U_ACTION' => $this->u_action . '&action=' . (($action == 'add') ? 'create' : 'modify') . (($bbcode_id) ? "&bbcode=$bbcode_id" : ''), - 'L_BBCODE_USAGE_EXPLAIN' => sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), - 'L_BBCODE_FONT_ICON_EXPLAIN' => sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), 'BBCODE_MATCH' => $bbcode_match, 'BBCODE_TPL' => $bbcode_tpl, 'BBCODE_HELPLINE' => $bbcode_helpline, From 97728da9be70a90bb7da3786aa9faded1c67eea1 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Sun, 1 Sep 2024 16:10:45 +0100 Subject: [PATCH 0626/1214] [ticket/17326] Replace language string for invalid icon names PHPBB-17326 --- phpBB/includes/acp/acp_bbcodes.php | 4 ++-- phpBB/language/en/acp/posting.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index ee97631d2e1..291151ab6f9 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -236,9 +236,9 @@ function main($id, $mode) trigger_error($user->lang['BBCODE_HELPLINE_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } - if (strlen($bbcode_font_icon) > 64 && preg_match('/^[\w-]+$/', $bbcode_font_icon)) + if (strlen($bbcode_font_icon) > 64 || preg_match('/^[\w-]+$/', $bbcode_font_icon)) { - trigger_error($user->lang['BBCODE_FONT_ICON_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($user->lang['BBCODE_FONT_ICON_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); } /** diff --git a/phpBB/language/en/acp/posting.php b/phpBB/language/en/acp/posting.php index b03a83956f6..5ce331388da 100644 --- a/phpBB/language/en/acp/posting.php +++ b/phpBB/language/en/acp/posting.php @@ -55,7 +55,7 @@ 'BBCODE_HELPLINE_TOO_LONG' => 'The help line you entered is too long.', 'BBCODE_FONT_ICON' => 'BBCode icon', 'BBCODE_FONT_ICON_EXPLAIN' => 'Enter the name of a Font Awesome icon (without the fa prefix) to display instead of the BBCode name appearing on the button. %1$sClick here%2$s to view the list of available icons. Only solid style icons are supported.', - 'BBCODE_FONT_ICON_TOO_LONG' => 'The icon name you have entered is too long.', + 'BBCODE_FONT_ICON_INVALID' => 'The icon name you have entered is too long.', 'BBCODE_INVALID_TAG_NAME' => 'The BBCode tag name that you selected already exists.', 'BBCODE_INVALID' => 'Your BBCode is constructed in an invalid form.', From 220b655150b5c950a7b69a6e428492c772d40ced Mon Sep 17 00:00:00 2001 From: Daniel James Date: Fri, 7 Feb 2025 17:02:26 +0000 Subject: [PATCH 0627/1214] [ticket/17326] Updated js to use same as pages ext and fixed icon bug PHPBB-17326 --- phpBB/adm/style/acp_bbcodes.html | 2 +- phpBB/adm/style/admin.js | 23 ++++++++++++++++++----- phpBB/includes/acp/acp_bbcodes.php | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/phpBB/adm/style/acp_bbcodes.html b/phpBB/adm/style/acp_bbcodes.html index cb3480f9467..aade2f8aac7 100644 --- a/phpBB/adm/style/acp_bbcodes.html +++ b/phpBB/adm/style/acp_bbcodes.html @@ -53,7 +53,7 @@

{L_ACP_BBCODES}


{{ lang('BBCODE_FONT_ICON_EXPLAIN', '', '') }}
- {{ Icon('font', BBCODE_FONT_ICON, '', false, '', {'id':'bbcode_icon_preview'}) }} + {{ Icon('font', ' ', '', false, '', {'id':'bbcode_icon_preview'}) }}
diff --git a/phpBB/adm/style/admin.js b/phpBB/adm/style/admin.js index 2ae75db062e..f53329b5a72 100644 --- a/phpBB/adm/style/admin.js +++ b/phpBB/adm/style/admin.js @@ -279,11 +279,24 @@ function parse_document(container) }); // Live update BBCode font icon preview - $('#bbcode_font_icon').on('keyup', function(e) { - const iconName = $(this).val(); - if (iconName.match(/^[\w-]+$/)) { - $('#bbcode_icon_preview').attr('class', "o-icon o-icon-font fa-fw fas icon fa-" + $(this).val()); - } + const updateIconClass = (element, newClass) => { + element.classList.forEach(className => { + if (className.startsWith('fa-') && className !== 'fa-fw') { + element.classList.remove(className); + } + }); + + element.classList.add(`fa-${newClass}`); + }; + + const pageIconFont = document.getElementById('bbcode_font_icon'); + + pageIconFont.addEventListener('keyup', function() { + updateIconClass(this.nextElementSibling, this.value); + }); + + pageIconFont.addEventListener('blur', function() { + updateIconClass(this.nextElementSibling, this.value); }); }); })(jQuery); diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index 291151ab6f9..6fb2dfbb9a7 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -103,7 +103,7 @@ function main($id, $mode) 'S_EDIT_BBCODE' => true, 'U_BACK' => $this->u_action, 'U_ACTION' => $this->u_action . '&action=' . (($action == 'add') ? 'create' : 'modify') . (($bbcode_id) ? "&bbcode=$bbcode_id" : ''), - + 'L_BBCODE_USAGE_EXPLAIN' => sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), 'BBCODE_MATCH' => $bbcode_match, 'BBCODE_TPL' => $bbcode_tpl, 'BBCODE_HELPLINE' => $bbcode_helpline, From bf15d1ea420ebe7bd69bcd58520d73197ef2d871 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 15 Feb 2025 10:42:47 +0100 Subject: [PATCH 0628/1214] [ticket/17326] Check for existence of font icon element before use PHPBB-17326 --- phpBB/adm/style/admin.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/phpBB/adm/style/admin.js b/phpBB/adm/style/admin.js index f53329b5a72..f308bf9c121 100644 --- a/phpBB/adm/style/admin.js +++ b/phpBB/adm/style/admin.js @@ -291,12 +291,14 @@ function parse_document(container) const pageIconFont = document.getElementById('bbcode_font_icon'); - pageIconFont.addEventListener('keyup', function() { - updateIconClass(this.nextElementSibling, this.value); - }); + if (pageIconFont) { + pageIconFont.addEventListener('keyup', function () { + updateIconClass(this.nextElementSibling, this.value); + }); - pageIconFont.addEventListener('blur', function() { - updateIconClass(this.nextElementSibling, this.value); - }); + pageIconFont.addEventListener('blur', function () { + updateIconClass(this.nextElementSibling, this.value); + }); + } }); })(jQuery); From 29c19bb764da89eea691bdd9aeabcfaa13d1cc9a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 15 Feb 2025 10:44:08 +0100 Subject: [PATCH 0629/1214] [ticket/17326] Differentiate between invalid and too long icon name PHPBB-17326 --- phpBB/includes/acp/acp_bbcodes.php | 7 ++++++- phpBB/language/en/acp/posting.php | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index 6fb2dfbb9a7..ee3dffd4614 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -236,7 +236,12 @@ function main($id, $mode) trigger_error($user->lang['BBCODE_HELPLINE_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } - if (strlen($bbcode_font_icon) > 64 || preg_match('/^[\w-]+$/', $bbcode_font_icon)) + if (strlen($bbcode_font_icon) > 64) + { + trigger_error($user->lang['BBCODE_FONT_ICON_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!empty($bbcode_font_icon) && !preg_match('/^[\w-]+$/', $bbcode_font_icon)) { trigger_error($user->lang['BBCODE_FONT_ICON_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); } diff --git a/phpBB/language/en/acp/posting.php b/phpBB/language/en/acp/posting.php index 5ce331388da..6420f9aa199 100644 --- a/phpBB/language/en/acp/posting.php +++ b/phpBB/language/en/acp/posting.php @@ -55,8 +55,8 @@ 'BBCODE_HELPLINE_TOO_LONG' => 'The help line you entered is too long.', 'BBCODE_FONT_ICON' => 'BBCode icon', 'BBCODE_FONT_ICON_EXPLAIN' => 'Enter the name of a Font Awesome icon (without the fa prefix) to display instead of the BBCode name appearing on the button. %1$sClick here%2$s to view the list of available icons. Only solid style icons are supported.', - 'BBCODE_FONT_ICON_INVALID' => 'The icon name you have entered is too long.', - + 'BBCODE_FONT_ICON_INVALID' => 'The icon name you have entered is invalid.', + 'BBCODE_FONT_ICON_TOO_LONG' => 'The icon name you have entered is too long.', 'BBCODE_INVALID_TAG_NAME' => 'The BBCode tag name that you selected already exists.', 'BBCODE_INVALID' => 'Your BBCode is constructed in an invalid form.', 'BBCODE_INVALID_TEMPLATE' => 'Your BBCode’s template is invalid.', From 68c4e22886ec239f6d45f3f432df1eabebd8f5fa Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 15 Feb 2025 11:09:22 +0100 Subject: [PATCH 0630/1214] [ticket/17326] Extend acp bbcodes test for font icon PHPBB-17326 --- tests/functional/acp_bbcodes_test.php | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/functional/acp_bbcodes_test.php b/tests/functional/acp_bbcodes_test.php index bdf79206150..ac73091a79b 100644 --- a/tests/functional/acp_bbcodes_test.php +++ b/tests/functional/acp_bbcodes_test.php @@ -24,8 +24,9 @@ public function test_htmlspecialchars() // Create the BBCode $crawler = self::request('GET', 'adm/index.php?i=acp_bbcodes&sid=' . $this->sid . '&mode=bbcodes&action=add'); $form = $crawler->selectButton('Submit')->form(array( - 'bbcode_match' => '[mod="{TEXT1}"]{TEXT2}[/mod]', - 'bbcode_tpl' => '
{TEXT1}
{TEXT2}
' + 'bbcode_match' => '[mod="{TEXT1}"]{TEXT2}[/mod]', + 'bbcode_tpl' => '
{TEXT1}
{TEXT2}
', + 'bbcode_font_icon' => 'user', )); self::submit($form); @@ -47,15 +48,16 @@ public function test_htmlspecialchars() /** * @dataProvider get_bbcode_error_tests */ - public function test_bbcode_error($match, $tpl, $error) + public function test_bbcode_error($match, $tpl, $icon, $error) { $this->login(); $this->admin_login(); $crawler = self::request('GET', 'adm/index.php?i=acp_bbcodes&sid=' . $this->sid . '&mode=bbcodes&action=add'); $form = $crawler->selectButton('Submit')->form([ - 'bbcode_match' => $match, - 'bbcode_tpl' => $tpl + 'bbcode_match' => $match, + 'bbcode_tpl' => $tpl, + 'bbcode_font_icon' => $icon, ]); $crawler = self::submit($form); @@ -69,18 +71,33 @@ public function get_bbcode_error_tests() [ 'XXX', '', + '', 'BBCode is constructed in an invalid form' ], [ '[x]{TEXT}[/x]', '{TEXT}', + '', 'unsafe' ], + 'icon name too long' => [ + '[mod2="{TEXT1}"]{TEXT2}[/mod2]', + '
{TEXT1}
{TEXT2}
', + str_repeat('a', 65), + 'is too long', + ], + 'icon name invalid' => [ + '[mod2="{TEXT1}"]{TEXT2}[/mod2]', + '
{TEXT1}
{TEXT2}
', + 'Not a valid icon name', + 'is invalid', + ], ]; } } From 67e2b328163f63af0949b54c023206e6fbab6aa6 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 15 Feb 2025 20:21:25 +0100 Subject: [PATCH 0631/1214] [ticket/17326] Ignore invalid class names PHPBB-17326 --- phpBB/adm/style/admin.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpBB/adm/style/admin.js b/phpBB/adm/style/admin.js index f308bf9c121..3d37b0c331d 100644 --- a/phpBB/adm/style/admin.js +++ b/phpBB/adm/style/admin.js @@ -280,6 +280,12 @@ function parse_document(container) // Live update BBCode font icon preview const updateIconClass = (element, newClass) => { + // Ignore invalid class names + const faIconRegex = /^(?!-)(?!.*--)[a-z0-9-]+(? { if (className.startsWith('fa-') && className !== 'fa-fw') { element.classList.remove(className); From 1e76b0df0e1450c35bab6160b703d32bfb267324 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 15 Feb 2025 20:24:07 +0100 Subject: [PATCH 0632/1214] [ticket/17326] Use same regex in icon name validation in PHP PHPBB-17326 --- phpBB/includes/acp/acp_bbcodes.php | 2 +- tests/functional/acp_bbcodes_test.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index ee3dffd4614..723a13d1db0 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -241,7 +241,7 @@ function main($id, $mode) trigger_error($user->lang['BBCODE_FONT_ICON_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); } - if (!empty($bbcode_font_icon) && !preg_match('/^[\w-]+$/', $bbcode_font_icon)) + if (!empty($bbcode_font_icon) && !preg_match('/^(?!-)(?!.*--)[a-z0-9-]+(?lang['BBCODE_FONT_ICON_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); } diff --git a/tests/functional/acp_bbcodes_test.php b/tests/functional/acp_bbcodes_test.php index ac73091a79b..dd0d356a58e 100644 --- a/tests/functional/acp_bbcodes_test.php +++ b/tests/functional/acp_bbcodes_test.php @@ -98,6 +98,18 @@ public function get_bbcode_error_tests() 'Not a valid icon name', 'is invalid', ], + 'icon name invalid double dash' => [ + '[mod2="{TEXT1}"]{TEXT2}[/mod2]', + '
{TEXT1}
{TEXT2}
', + 'us--er', + 'is invalid', + ], + 'icon name invalid trailing dash' => [ + '[mod2="{TEXT1}"]{TEXT2}[/mod2]', + '
{TEXT1}
{TEXT2}
', + 'user-', + 'is invalid', + ], ]; } } From bfdf1729923c72facab81d2499ea72ce039594ad Mon Sep 17 00:00:00 2001 From: Neo-CTC <92761505+Neo-CTC@users.noreply.github.com> Date: Sun, 16 Feb 2025 00:09:45 -0600 Subject: [PATCH 0633/1214] [ticket/17470] Feed enable config not enforced The 'feed_enable' config is never checked before generating the feeds. The effective result is that feeds are always enabled, regardless of the setting. PHPBB-17470 --- phpBB/phpbb/feed/controller/feed.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpBB/phpbb/feed/controller/feed.php b/phpBB/phpbb/feed/controller/feed.php index 7e88f18a980..b65e1d9c9bc 100644 --- a/phpBB/phpbb/feed/controller/feed.php +++ b/phpBB/phpbb/feed/controller/feed.php @@ -123,6 +123,12 @@ public function __construct(Environment $twig, symfony_request $request, control $this->template = $twig; $this->language = $language; $this->phpbb_dispatcher = $phpbb_dispatcher; + + // Feeds are disabled, no need to continue + if (!$this->config['feed_enable']) + { + $this->send_unavailable(); + } } /** From 183110789a05d9a3ae8f6739c64c39e9b5c109ff Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 16 Feb 2025 09:13:26 +0100 Subject: [PATCH 0634/1214] [ticket/17326] Add changed info for core.acp_bbcodes_modify_create event PHPBB-17326 --- phpBB/includes/acp/acp_bbcodes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index 723a13d1db0..b49d74985f5 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -163,6 +163,7 @@ function main($id, $mode) * @var array hidden_fields Array of hidden fields for use when * submitting form when $warn_unsafe is true * @since 3.1.0-a3 + * @changed 4.0.0-a1 Added bbcode_font_icon */ $vars = array( 'action', From f42c4be939368e0d397b6af9965d8cdedb1107e7 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 16 Feb 2025 12:00:36 +0100 Subject: [PATCH 0635/1214] [ticket/17470] Check feed enabled in each handler and add tests PHPBB-17470 --- phpBB/phpbb/feed/controller/feed.php | 38 +++++++++++++++++++++++----- tests/functional/feed_test.php | 23 +++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/phpBB/phpbb/feed/controller/feed.php b/phpBB/phpbb/feed/controller/feed.php index b65e1d9c9bc..a12e12756de 100644 --- a/phpBB/phpbb/feed/controller/feed.php +++ b/phpBB/phpbb/feed/controller/feed.php @@ -123,12 +123,6 @@ public function __construct(Environment $twig, symfony_request $request, control $this->template = $twig; $this->language = $language; $this->phpbb_dispatcher = $phpbb_dispatcher; - - // Feeds are disabled, no need to continue - if (!$this->config['feed_enable']) - { - $this->send_unavailable(); - } } /** @@ -140,6 +134,8 @@ public function __construct(Environment $twig, symfony_request $request, control */ public function forums() { + $this->check_enabled(); + if (!$this->config['feed_overall_forums']) { $this->send_unavailable(); @@ -157,6 +153,8 @@ public function forums() */ public function news() { + $this->check_enabled(); + // Get at least one news forum $sql = 'SELECT forum_id FROM ' . FORUMS_TABLE . ' @@ -182,6 +180,8 @@ public function news() */ public function topics() { + $this->check_enabled(); + if (!$this->config['feed_topics_new']) { $this->send_unavailable(); @@ -199,6 +199,8 @@ public function topics() */ public function topics_new() { + $this->check_enabled(); + return $this->topics(); } @@ -211,6 +213,8 @@ public function topics_new() */ public function topics_active() { + $this->check_enabled(); + if (!$this->config['feed_topics_active']) { $this->send_unavailable(); @@ -230,6 +234,8 @@ public function topics_active() */ public function forum($forum_id) { + $this->check_enabled(); + if (!$this->config['feed_forum']) { $this->send_unavailable(); @@ -249,6 +255,8 @@ public function forum($forum_id) */ public function topic($topic_id) { + $this->check_enabled(); + if (!$this->config['feed_topic']) { $this->send_unavailable(); @@ -266,6 +274,8 @@ public function topic($topic_id) */ public function overall() { + $this->check_enabled(); + if (!$this->config['feed_overall']) { $this->send_unavailable(); @@ -413,6 +423,22 @@ protected function send_feed_do(feed_interface $feed) return $response; } + /** + * Check if feeds are enabled in the configuration. + * + * @throws http_exception If feeds are disabled. + * + * @return void + */ + protected function check_enabled() + { + // Feeds are disabled, no need to continue + if (!$this->config['feed_enable']) + { + throw new http_exception(404, 'NO_FEED_ENABLED'); + } + } + /** * Throw and exception saying that the feed isn't available * diff --git a/tests/functional/feed_test.php b/tests/functional/feed_test.php index 157aaae9e86..37dea63a66f 100644 --- a/tests/functional/feed_test.php +++ b/tests/functional/feed_test.php @@ -261,6 +261,29 @@ public function test_create_exclude_topic() $this->data['topics']['Feeds #exclude - Topic #1'] = (int) $post['topic_id']; } + public function test_feeds_disabled() + { + $this->login(); + $this->admin_login(); + + // Disable feeds in ACP + $crawler = self::request('GET', "adm/index.php?sid={$this->sid}&i=acp_board&mode=feed"); + $form = $crawler->selectButton('Submit')->form(); + $crawler = self::submit($form, ['config[feed_enable]' => false]); + self::assertContainsLang('CONFIG_UPDATED', $crawler->filter('.successbox')->text()); + + // Assert that feeds aren't available + $crawler = self::request('GET', 'app.php/feed/overall', array(), false); + self::assert_response_status_code(404); + $this->assertContainsLang('NO_FEED_ENABLED', $crawler->text()); + + // Enable feeds again in ACP + $crawler = self::request('GET', "adm/index.php?sid={$this->sid}&i=acp_board&mode=feed"); + $form = $crawler->selectButton('Submit')->form(); + $crawler = self::submit($form, ['config[feed_enable]' => true]); + self::assertContainsLang('CONFIG_UPDATED', $crawler->filter('.successbox')->text()); + } + public function test_feeds_exclude() { $this->load_ids(array( From 77f83306b568f92cbc6d7b59a218b4c60d9ad7a5 Mon Sep 17 00:00:00 2001 From: cabot Date: Sun, 16 Feb 2025 16:08:49 +0100 Subject: [PATCH 0636/1214] [ticket/17471] Fix feed link href for feed icon PHPBB-17471 --- phpBB/styles/prosilver/template/forumlist_body.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/template/forumlist_body.html b/phpBB/styles/prosilver/template/forumlist_body.html index 785e02c30a5..40ffee77bc8 100644 --- a/phpBB/styles/prosilver/template/forumlist_body.html +++ b/phpBB/styles/prosilver/template/forumlist_body.html @@ -37,7 +37,7 @@
From 2fa5da145437eb23363aa4a5e76be3ce57db1ccc Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 19 Feb 2025 21:45:47 +0700 Subject: [PATCH 0637/1214] [ticket/17470] Fix feed functional tests for master Symfony DomCrawler component validates form values and throws InvalidArgumentException for impossible values set. PHPBB-17470 --- tests/functional/feed_test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/feed_test.php b/tests/functional/feed_test.php index 37dea63a66f..ee3c8a1085b 100644 --- a/tests/functional/feed_test.php +++ b/tests/functional/feed_test.php @@ -269,7 +269,7 @@ public function test_feeds_disabled() // Disable feeds in ACP $crawler = self::request('GET', "adm/index.php?sid={$this->sid}&i=acp_board&mode=feed"); $form = $crawler->selectButton('Submit')->form(); - $crawler = self::submit($form, ['config[feed_enable]' => false]); + $crawler = self::submit($form, ['config[feed_enable]' => 0]); self::assertContainsLang('CONFIG_UPDATED', $crawler->filter('.successbox')->text()); // Assert that feeds aren't available @@ -280,7 +280,7 @@ public function test_feeds_disabled() // Enable feeds again in ACP $crawler = self::request('GET', "adm/index.php?sid={$this->sid}&i=acp_board&mode=feed"); $form = $crawler->selectButton('Submit')->form(); - $crawler = self::submit($form, ['config[feed_enable]' => true]); + $crawler = self::submit($form, ['config[feed_enable]' => 1]); self::assertContainsLang('CONFIG_UPDATED', $crawler->filter('.successbox')->text()); } From 8adc853deab024e1fd562a728427c7d667f970ea Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 22 Feb 2025 13:28:43 +0700 Subject: [PATCH 0638/1214] [ticket/17461] Add core events to acp_main PHPBB-17461 --- phpBB/includes/acp/acp_main.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index c39e984f736..e284b291466 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -100,6 +100,20 @@ function main($id, $mode) default: $confirm = true; $confirm_lang = 'CONFIRM_OPERATION'; + + /** + * Event to add confirm box for custom ACP quick actions + * + * @event core.acp_main_add_actions_confirm + * @var string id The module ID + * @var string mode The module mode + * @var string action Custom action type name + * @var boolean confirm Do we display the confirm box to run the custom action + * @var string confirm_lang Lang var name to display in confirm box + * @since 3.3.15-RC1 + */ + $vars = ['id', 'mode', 'action', 'confirm', 'confirm_lang']; + extract($phpbb_dispatcher->trigger_event('core.acp_main_add_actions_confirm', compact($vars))); } if ($confirm) @@ -423,6 +437,19 @@ function main($id, $mode) trigger_error('PURGE_SESSIONS_SUCCESS'); } break; + + default: + /** + * Event to add custom ACP quick actions + * + * @event core.acp_main_add_actions + * @var string id The module ID + * @var string mode The module mode + * @var string action Custom action type name + * @since 3.3.15-RC1 + */ + $vars = ['id', 'mode', 'action']; + extract($phpbb_dispatcher->trigger_event('core.acp_main_add_actions', compact($vars))); } } } From 29730e49ce81d6ecbe2922085ecaa714c1bfb178 Mon Sep 17 00:00:00 2001 From: battye Date: Fri, 28 Feb 2025 03:22:08 +0000 Subject: [PATCH 0639/1214] [ticket/17381] Add migration for topic_views to ULINT PHPBB-17381 --- .../data/v33x/topic_views_update.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/topic_views_update.php diff --git a/phpBB/phpbb/db/migration/data/v33x/topic_views_update.php b/phpBB/phpbb/db/migration/data/v33x/topic_views_update.php new file mode 100644 index 00000000000..8402d548c45 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/topic_views_update.php @@ -0,0 +1,47 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v33x; + +class topic_views_update extends \phpbb\db\migration\migration +{ + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3314', + ]; + } + + public function update_schema(): array + { + // This extends the topic view count field so we can support much larger values. + return [ + 'change_columns' => [ + $this->table_prefix . 'topics' => [ + 'topic_views' => ['ULINT', 0], + ], + ] + ]; + } + + public function revert_schema(): array + { + return [ + 'change_columns' => [ + $this->table_prefix . 'topics' => [ + 'topic_views' => ['UINT', 0], + ], + ] + ]; + } +} From c3bb5e1bec720abb69fff57b2efa33cf911ec5cc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 28 Feb 2025 20:33:41 +0100 Subject: [PATCH 0640/1214] [ticket/security-283] Ensure text is properly handled for responsiveness SECURITY-283 --- phpBB/styles/prosilver/template/forum_fn.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index 009a9de6219..ed273c8da38 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -650,7 +650,7 @@ function parseDocument($container) { html = $children.html(); } - $block.append((first ? '' : '
') + html); + $block.append((first ? '' : '
') + html); first = false; }); @@ -670,7 +670,7 @@ function parseDocument($container) { // Find all headers, get contents $list.prev('.topiclist').find('li.header dd').not('.mark').each(function() { - headers.push($(this).text()); + headers.push($("
").text($(this).text()).html()); headersLength++; }); @@ -707,7 +707,7 @@ function parseDocument($container) { html = headers[i] + ': ' + html + ''; } - $block.append((first ? '' : '
') + html); + $block.append((first ? '' : '
') + html); first = false; }); @@ -773,7 +773,8 @@ function parseDocument($container) { } if ((text.length && text !== '-') || cell.children().length) { - cell.prepend('' + headers[column] + ''); + const $dfnElement = $("").css('display', 'none').text(headers[column]); + cell.prepend($dfnElement); } else { cell.addClass('empty'); } From 17480d7d073b344792c3a57a613db434f358646b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 09:22:23 +0100 Subject: [PATCH 0641/1214] [ticket/security-283] Unify behavior between adm and prosilver, clean up SECURITY-283 --- phpBB/adm/style/admin.js | 14 +++++----- phpBB/adm/style/ajax.js | 29 +++++++++++++++------ phpBB/styles/prosilver/template/forum_fn.js | 5 ++-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/phpBB/adm/style/admin.js b/phpBB/adm/style/admin.js index 23bd4a116b8..10d2f398e06 100644 --- a/phpBB/adm/style/admin.js +++ b/phpBB/adm/style/admin.js @@ -5,7 +5,7 @@ /** * Parse document block */ -function parse_document(container) +function parse_document(container) { var test = document.createElement('div'), oldBrowser = (typeof test.style.borderRadius == 'undefined'); @@ -90,7 +90,7 @@ function parse_document(container) } }); } - + headersLength = headers.length; // Add header text to each cell as @@ -121,8 +121,8 @@ function parse_document(container) } if ((text.length && text !== '-') || cell.children().length) { - if (headers[column] != '') { - cell.prepend('' + headers[column] + ''); + if (headers[column].length) { + cell.prepend($("").css('display', 'none').text(headers[column])); } } else { @@ -143,7 +143,7 @@ function parse_document(container) */ container.find('table.responsive > tbody').each(function() { var items = $(this).children('tr'); - if (items.length == 0) + if (!items.length) { $(this).parent('table:first').addClass('responsive-hide'); } @@ -157,7 +157,7 @@ function parse_document(container) if ($this.html() == ' ') { $this.addClass('responsive-hide'); } - + }); /** @@ -184,7 +184,7 @@ function parse_document(container) var width = $body.width(), height = $this.height(); - if (arguments.length == 0 && (!responsive || width <= lastWidth) && height <= maxHeight) { + if (!arguments.length && (!responsive || width <= lastWidth) && height <= maxHeight) { return; } diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index d1007d0173c..6d6822df4d3 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -235,14 +235,20 @@ function submitPermissions() { if ($alertBoxLink) { // Remove forum_id[] from URL $alertBoxLink.attr('href', $alertBoxLink.attr('href').replace(/(&forum_id\[\]=[0-9]+)/g, '')); - var previousPageForm = '
'; + const $previousPageForm = $('').attr({ + action: $alertBoxLink.attr('href'), + method: 'post' + }); + $.each(forumIds, function (key, value) { - previousPageForm += ''; + $previousPageForm.append($('').attr({ + type: 'text', + name: 'forum_id[]', + value: value + })); }); - previousPageForm += '
'; $alertBoxLink.on('click', function (e) { - var $previousPageForm = $(previousPageForm); $('body').append($previousPageForm); e.preventDefault(); $previousPageForm.submit(); @@ -257,12 +263,19 @@ function submitPermissions() { setTimeout(function () { // Create forum to submit using POST. This will prevent // exceeding the maximum length of URLs - var form = '
'; + const $form = $('').attr({ + action: res.REFRESH_DATA.url.replace(/(&forum_id\[\]=[0-9]+)/g, ''), + method: 'post' + }); + $.each(forumIds, function (key, value) { - form += ''; + $form.append($('').attr({ + type: 'text', + name: 'forum_id[]', + value: value + })); }); - form += '
'; - $form = $(form); + $('body').append($form); // Hide the alert even if we refresh the page, in case the user diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index ed273c8da38..51478d86419 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -773,8 +773,9 @@ function parseDocument($container) { } if ((text.length && text !== '-') || cell.children().length) { - const $dfnElement = $("").css('display', 'none').text(headers[column]); - cell.prepend($dfnElement); + if (headers[column].length) { + cell.prepend($("").css('display', 'none').text(headers[column])); + } } else { cell.addClass('empty'); } From cedbbb0c769223a679f1af63c0c03b1519035606 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 16:45:36 +0100 Subject: [PATCH 0642/1214] [ticket/17478] Add security policy file PHPBB-17478 --- SECURITY.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..09762ad9ac8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policy + +## Supported Versions + +Only phpBB 3.3 is currently supported. The table below lists provides an overview of phpBB versions and their current support state: + +| Version | Supported | Comment | +|---------|--------------------|----------------------------------------------------------------------------------------------------| +| 4.0.x | :x: | Not released yet, still in development | +| 3.3.x | :white_check_mark: | Current stable release | +| 3.2.x | :x: | [End of Life (EoL) in Nov 2020](https://www.phpbb.com/community/viewtopic.php?t=2573411) | +| 3.1.x | :x: | [End of Life (EoL) in December 2017](https://www.phpbb.com/community/viewtopic.php?t=2453376) | +| 3.0.x | :x: | [End of Life (EoL) in November 2015](https://www.phpbb.com/community/viewtopic.php?f=14&t=2302466) | +| < 3.0 | :x: | [End of Life (EoL) in October 2008](https://www.phpbb.com/community/viewtopic.php?t=900655) | + +## Reporting a Vulnerability + +There are multiple ways a potential security vulnerability can be reported: + +- HackerOne: [phpBB | Vulnerability Disclosure Program | HackerOne](https://hackerone.com/phpbb) +- Send an email: [security@phpbb.com](mailto:security@phpbb.com) +- Create a report in the security tracker: [Security Tracker](https://www.phpbb.com/security/) + +Please provide as much detail as possible when reporting a vulnerability. You can expect to receive an update on your report within a few days. If the vulnerability is accepted, we will work on a fix and keep you informed of the progress. If the vulnerability is declined, we will provide an explanation. From be67bfbe632422ca482e529a936e9523ac7cdc8a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 17:04:16 +0100 Subject: [PATCH 0643/1214] [ticket/17478] Remove versions and reword reporting section [skip ci] PHPBB-17478 --- SECURITY.md | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 09762ad9ac8..b62f160225e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,24 +1,13 @@ # Security Policy -## Supported Versions - -Only phpBB 3.3 is currently supported. The table below lists provides an overview of phpBB versions and their current support state: - -| Version | Supported | Comment | -|---------|--------------------|----------------------------------------------------------------------------------------------------| -| 4.0.x | :x: | Not released yet, still in development | -| 3.3.x | :white_check_mark: | Current stable release | -| 3.2.x | :x: | [End of Life (EoL) in Nov 2020](https://www.phpbb.com/community/viewtopic.php?t=2573411) | -| 3.1.x | :x: | [End of Life (EoL) in December 2017](https://www.phpbb.com/community/viewtopic.php?t=2453376) | -| 3.0.x | :x: | [End of Life (EoL) in November 2015](https://www.phpbb.com/community/viewtopic.php?f=14&t=2302466) | -| < 3.0 | :x: | [End of Life (EoL) in October 2008](https://www.phpbb.com/community/viewtopic.php?t=900655) | - ## Reporting a Vulnerability +Please do not post potential security vulnerabilities publicly. Instead, report them to the phpBB team. +We take security very seriously and will respond to reports about potential security vulnerabilities as quickly as possible. There are multiple ways a potential security vulnerability can be reported: - HackerOne: [phpBB | Vulnerability Disclosure Program | HackerOne](https://hackerone.com/phpbb) -- Send an email: [security@phpbb.com](mailto:security@phpbb.com) - Create a report in the security tracker: [Security Tracker](https://www.phpbb.com/security/) +- Send an email: [security@phpbb.com](mailto:security@phpbb.com) Please provide as much detail as possible when reporting a vulnerability. You can expect to receive an update on your report within a few days. If the vulnerability is accepted, we will work on a fix and keep you informed of the progress. If the vulnerability is declined, we will provide an explanation. From ae40c6365ec1a5a621bb431519258aa973f70967 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 21:41:28 +0100 Subject: [PATCH 0644/1214] [prep-release-3.3.15] Update version numbers to 3.3.15-RC1 --- build/build.xml | 2 +- phpBB/includes/constants.php | 2 +- phpBB/install/phpbbcli.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.xml b/build/build.xml index 3c5dafcf86d..48e58c2217a 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2,7 +2,7 @@ - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 3734899352d..5502664c827 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '3.3.15-dev'); +@define('PHPBB_VERSION', '3.3.15-RC1'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/phpbbcli.php b/phpBB/install/phpbbcli.php index 58bb9843701..bcb42920c13 100755 --- a/phpBB/install/phpbbcli.php +++ b/phpBB/install/phpbbcli.php @@ -23,7 +23,7 @@ define('IN_PHPBB', true); define('IN_INSTALL', true); define('PHPBB_ENVIRONMENT', 'production'); -define('PHPBB_VERSION', '3.3.14'); +define('PHPBB_VERSION', '3.3.15-RC1'); $phpbb_root_path = __DIR__ . '/../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index ed73c1d5328..c848e12e8e6 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -316,7 +316,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.15-dev'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.15-RC1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); From 24fc7d5388b2a0d772c3d6a2cefd1f0b31482d47 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 21:41:36 +0100 Subject: [PATCH 0645/1214] [prep-release-3.3.15] Update version numbers to 3.3.15 --- phpBB/install/convertors/convert_phpbb20.php | 2 +- phpBB/styles/prosilver/style.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 014dc0e3702..7d4ed05bd9f 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -38,7 +38,7 @@ $convertor_data = array( 'forum_name' => 'phpBB 2.0.x', 'version' => '1.0.3', - 'phpbb_version' => '3.3.14', + 'phpbb_version' => '3.3.15', 'author' => 'phpBB Limited', 'dbms' => $dbms, 'dbhost' => $dbhost, diff --git a/phpBB/styles/prosilver/style.cfg b/phpBB/styles/prosilver/style.cfg index 1dfc8230b78..cf175b6d25e 100644 --- a/phpBB/styles/prosilver/style.cfg +++ b/phpBB/styles/prosilver/style.cfg @@ -21,8 +21,8 @@ # General Information about this style name = prosilver copyright = © phpBB Limited, 2007 -style_version = 3.3.14 -phpbb_version = 3.3.14 +style_version = 3.3.15 +phpbb_version = 3.3.15 # Defining a different template bitfield # template_bitfield = //g= From d9e4ce6ddef81acd034b3a7eb2212ed478b2537c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 21:45:21 +0100 Subject: [PATCH 0646/1214] [prep-release-3.3.15] Add migration for 3.3.15-RC1 --- .../phpbb/db/migration/data/v33x/v3315rc1.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/v3315rc1.php diff --git a/phpBB/phpbb/db/migration/data/v33x/v3315rc1.php b/phpBB/phpbb/db/migration/data/v33x/v3315rc1.php new file mode 100644 index 00000000000..71ab26c908b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3315rc1.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3315rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.15-RC1', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\topic_views_update', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.15-RC1']], + ]; + } +} From 66650cb0e2cfbb81b119a4019b086e69335339b2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 1 Mar 2025 21:55:15 +0100 Subject: [PATCH 0647/1214] [prep-release-3.3.15] Update changelog for 3.3.15-RC1 --- phpBB/docs/CHANGELOG.html | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 4674867ecd8..06cd3c93169 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -50,6 +50,7 @@

Changelog

  1. Changelog
      +
    • Changes since 3.3.14
    • Changes since 3.3.14-RC1
    • Changes since 3.3.13
    • Changes since 3.3.13-RC1
    • @@ -173,6 +174,32 @@

      Changelog

      +

      Changes since 3.3.14

      +

      Bug

      +
        +
      • [PHPBB-17227] - Member list sorting bug - repeating users on several pages
      • +
      • [PHPBB-17381] - 'topic_views' column overflow blocks access to the topic
      • +
      • [PHPBB-17417] - Day selection not visible when no results
      • +
      • [PHPBB-17422] - Ascending posts pagination
      • +
      • [PHPBB-17436] - PHP fatal error while converting from phpBB 2.0 with Attachment MOD
      • +
      • [PHPBB-17455] - PHP warning on MySQLi connection failure
      • +
      • [PHPBB-17463] - Extra & in unread posts search pagination
      • +
      • [PHPBB-17468] - Reset password feature is not restricted to email
      • +
      • [PHPBB-17470] - Enable feeds setting not enforced
      • +
      +

      Improvement

      +
        +
      • [PHPBB-17429] - Adding event before users have been added to a group
      • +
      • [PHPBB-17431] - Add more vars to memberlist event
      • +
      • [PHPBB-17433] - Unclear instructions in ACP, Server settings
      • +
      • [PHPBB-17443] - Various Guzzle client issues for version checks
      • +
      • [PHPBB-17446] - Add acp_account_activation_edit_add event
      • +
      • [PHPBB-17461] - Add php events for ACP main actions
      • +
      • [PHPBB-17467] - Add TLS v.1.3 support to email messenger connection
      • +
      • [PHPBB-17471] - Forum feed link in forumlist_body does not return the correct URL
      • +
      • [PHPBB-17478] - Add security policy to repository
      • +
      +

      Changes since 3.3.14-RC1

      Improvement

        From bb3d41e193547d20b42d372faed9a3d277449133 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 2 Mar 2025 17:11:26 +0100 Subject: [PATCH 0648/1214] [3.3.x] Update version numbers to 3.3.16-dev --- build/build.xml | 2 +- phpBB/includes/constants.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/build.xml b/build/build.xml index 48e58c2217a..83a492625fd 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2,7 +2,7 @@ - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 5502664c827..89ace2e439e 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '3.3.15-RC1'); +@define('PHPBB_VERSION', '3.3.16-dev'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index c848e12e8e6..f6938204c7e 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -316,7 +316,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.15-RC1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.16-dev'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); From 7fd634b420b0114071b32f518d4497d7e042de49 Mon Sep 17 00:00:00 2001 From: rxu Date: Mon, 3 Mar 2025 23:33:58 +0700 Subject: [PATCH 0649/1214] [ticket/17480] Fix PHP fatal error in version check failure PHPBB-17480 --- phpBB/includes/acp/acp_main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index e284b291466..2d4cb10c734 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -485,7 +485,7 @@ function main($id, $mode) 'UPGRADE_INSTRUCTIONS' => !empty($upgrades_available) ? $user->lang('UPGRADE_INSTRUCTIONS', $upgrades_available['current'], $upgrades_available['announcement']) : false, )); } - catch (\RuntimeException $e) + catch (\phpbb\exception\runtime_exception $e) { $message = call_user_func_array(array($user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); $template->assign_vars(array( From d9136469ea6a3833cf1ba7776717ec32b8bafa03 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 4 Mar 2025 01:06:46 +0700 Subject: [PATCH 0650/1214] [ticket/17480] Catch runtime_exception instead of exception_interface Catch runtime_exception instead of exception_interface for extensions version check failures. PHPBB-17480 --- phpBB/includes/acp/acp_extensions.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php index 277e7bdf487..0c9cdbeff4a 100644 --- a/phpBB/includes/acp/acp_extensions.php +++ b/phpBB/includes/acp/acp_extensions.php @@ -12,6 +12,7 @@ */ use phpbb\exception\exception_interface; +use phpbb\exception\runtime_exception; use phpbb\exception\version_check_exception; /** @@ -344,7 +345,7 @@ function main($id, $mode) $this->template->assign_block_vars('updates_available', $updates_available); } - catch (exception_interface $e) + catch (runtime_exception $e) { $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); @@ -423,7 +424,7 @@ public function list_enabled_exts() $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = true; $enabled_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&action=details&versioncheck_force=1&ext_name=' . urlencode($md_manager->get_metadata('name')); } - catch (exception_interface $e) + catch (runtime_exception $e) { // Ignore exceptions due to the version check } @@ -433,7 +434,7 @@ public function list_enabled_exts() $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; } } - catch (exception_interface $e) + catch (runtime_exception $e) { $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); $this->template->assign_block_vars('disabled', array( @@ -501,7 +502,7 @@ public function list_disabled_exts() { $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; } - catch (exception_interface $e) + catch (runtime_exception $e) { $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); $this->template->assign_block_vars('disabled', array( @@ -572,7 +573,7 @@ public function list_available_exts() { $available_extension_meta_data[$name]['S_VERSIONCHECK'] = false; } - catch (exception_interface $e) + catch (runtime_exception $e) { $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); $this->template->assign_block_vars('not_installed', array( From 9bdb88879da75e31883817f42b2f1f8ea54f3626 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 4 Mar 2025 09:53:19 +0700 Subject: [PATCH 0651/1214] [ticket/17480] Properly catch RuntimeException if no http handler is available PHPBB-17480 --- phpBB/phpbb/file_downloader.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/file_downloader.php b/phpBB/phpbb/file_downloader.php index 0dcb3e58543..3f87c9c5121 100644 --- a/phpBB/phpbb/file_downloader.php +++ b/phpBB/phpbb/file_downloader.php @@ -75,8 +75,15 @@ protected function create_client(string $host, int $port = 443, int $timeout = 6 */ public function get(string $host, string $directory, string $filename, int $port = 443, int $timeout = 6) { - // Initialize Guzzle client - $client = $this->create_client($host, $port, $timeout); + try + { + // Initialize Guzzle client + $client = $this->create_client($host, $port, $timeout); + } + catch (\RuntimeException $exception) + { + throw new runtime_exception($exception->getMessage()); + } // Set default values for error variables $this->error_number = 0; From 1ea59731f047ae281d95854e4b06cf6e85b76a97 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 5 Mar 2025 10:10:29 +0700 Subject: [PATCH 0652/1214] [ticket/17480] Use phpBB error message text instead of thrown by GuzzleHTTP PHPBB-17480 --- phpBB/language/en/common.php | 1 + phpBB/phpbb/file_downloader.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 5694bbd5dcf..5cdba377f59 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -357,6 +357,7 @@ 'HIDE_ME' => 'Hide my online status this session', 'HOURS' => 'Hours', 'HOME' => 'Home', + 'HTTP_HANDLER_NOT_FOUND' => 'The operation could not be completed because cURL PHP extension and allow_url_fopen PHP ini setting have been disabled and no other HTTP handler could be found.', 'ICQ' => 'ICQ', 'IF' => 'If', diff --git a/phpBB/phpbb/file_downloader.php b/phpBB/phpbb/file_downloader.php index 3f87c9c5121..e2becbfbfa1 100644 --- a/phpBB/phpbb/file_downloader.php +++ b/phpBB/phpbb/file_downloader.php @@ -82,7 +82,7 @@ public function get(string $host, string $directory, string $filename, int $port } catch (\RuntimeException $exception) { - throw new runtime_exception($exception->getMessage()); + throw new runtime_exception('HTTP_HANDLER_NOT_FOUND'); } // Set default values for error variables From 0826a41da86e5ba9ef7e36a30cbf7c811ee7b9b5 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 8 Mar 2025 11:01:47 +0700 Subject: [PATCH 0653/1214] [ticket/17480] Adjust message wording PHPBB-17480 --- phpBB/language/en/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 5cdba377f59..f5fad1b28e3 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -357,7 +357,7 @@ 'HIDE_ME' => 'Hide my online status this session', 'HOURS' => 'Hours', 'HOME' => 'Home', - 'HTTP_HANDLER_NOT_FOUND' => 'The operation could not be completed because cURL PHP extension and allow_url_fopen PHP ini setting have been disabled and no other HTTP handler could be found.', + 'HTTP_HANDLER_NOT_FOUND' => 'The operation could not be completed because the cURL PHP extension and allow_url_fopen PHP ini setting have been disabled and no other HTTP handler could be found.', 'ICQ' => 'ICQ', 'IF' => 'If', From 14a6322b4fc88b8cd42ee455ab940081de3d7b1d Mon Sep 17 00:00:00 2001 From: rxu Date: Mon, 10 Mar 2025 21:24:15 +0700 Subject: [PATCH 0654/1214] [ticket/17475] Fix MSSQL arithmetic overflow error on counting attachments size PHPBB-17475 --- phpBB/includes/acp/acp_attachments.php | 2 +- phpBB/includes/acp/acp_forums.php | 2 +- phpBB/includes/acp/acp_main.php | 2 +- phpBB/includes/functions_convert.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index 6b7d9f7b44d..5813f719be0 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -1077,7 +1077,7 @@ function main($id, $mode) $attachments_per_page = (int) $config['topics_per_page']; // Get total number or orphans older than 3 hours - $sql = 'SELECT COUNT(attach_id) as num_files, SUM(filesize) as total_size + $sql = 'SELECT COUNT(attach_id) as num_files, SUM(' . $this->db->cast_expr_to_bigint('filesize') . ') as total_size FROM ' . ATTACHMENTS_TABLE . ' WHERE is_orphan = 1 AND filetime < ' . (time() - 3*60*60); diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php index ba3901f67a1..57677544fa4 100644 --- a/phpBB/includes/acp/acp_forums.php +++ b/phpBB/includes/acp/acp_forums.php @@ -2071,7 +2071,7 @@ function delete_forum_content($forum_id) $config->set('num_files', (int) $row['stat'], false); - $sql = 'SELECT SUM(filesize) as stat + $sql = 'SELECT SUM(' . $db->cast_expr_to_bigint('filesize') . ') as stat FROM ' . ATTACHMENTS_TABLE; $result = $db->sql_query($sql); $row = $db->sql_fetchrow($result); diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index 2d4cb10c734..9ed450ca36d 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -182,7 +182,7 @@ function main($id, $mode) $config->set('num_files', (int) $db->sql_fetchfield('stat'), false); $db->sql_freeresult($result); - $sql = 'SELECT SUM(filesize) as stat + $sql = 'SELECT SUM(' . $db->cast_expr_to_bigint('filesize') . ') as stat FROM ' . ATTACHMENTS_TABLE . ' WHERE is_orphan = 0'; $result = $db->sql_query($sql); diff --git a/phpBB/includes/functions_convert.php b/phpBB/includes/functions_convert.php index 1b499293d7c..f980ab200d1 100644 --- a/phpBB/includes/functions_convert.php +++ b/phpBB/includes/functions_convert.php @@ -1998,7 +1998,7 @@ function update_dynamic_config() $config->set('num_files', (int) $db->sql_fetchfield('stat'), false); $db->sql_freeresult($result); - $sql = 'SELECT SUM(filesize) as stat + $sql = 'SELECT SUM(' . $db->cast_expr_to_bigint('filesize') . ') as stat FROM ' . ATTACHMENTS_TABLE . ' WHERE is_orphan = 0'; $result = $db->sql_query($sql); From 51465670f6802c20519fd7cafb7ec76b37014b68 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 23 Mar 2025 19:39:07 +0100 Subject: [PATCH 0655/1214] [prep-release-3.3.15] Update version numbers to 3.3.15 --- build/build.xml | 4 ++-- phpBB/includes/constants.php | 2 +- phpBB/install/phpbbcli.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/build.xml b/build/build.xml index 48e58c2217a..7d7d9eee64b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2,9 +2,9 @@ - + - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 5502664c827..7b2e8cca221 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '3.3.15-RC1'); +@define('PHPBB_VERSION', '3.3.15'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/phpbbcli.php b/phpBB/install/phpbbcli.php index bcb42920c13..6291bbbad59 100755 --- a/phpBB/install/phpbbcli.php +++ b/phpBB/install/phpbbcli.php @@ -23,7 +23,7 @@ define('IN_PHPBB', true); define('IN_INSTALL', true); define('PHPBB_ENVIRONMENT', 'production'); -define('PHPBB_VERSION', '3.3.15-RC1'); +define('PHPBB_VERSION', '3.3.15'); $phpbb_root_path = __DIR__ . '/../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index c848e12e8e6..338af1160a2 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -316,7 +316,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.15-RC1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.3.15'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); From fb46aa38b83a68583a093d68bdb7a6889728cb62 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 23 Mar 2025 19:41:47 +0100 Subject: [PATCH 0656/1214] [prep-release-3.3.15] Add migration for 3.3.15 --- phpBB/phpbb/db/migration/data/v33x/v3315.php | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v33x/v3315.php diff --git a/phpBB/phpbb/db/migration/data/v33x/v3315.php b/phpBB/phpbb/db/migration/data/v33x/v3315.php new file mode 100644 index 00000000000..23028602f65 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v33x/v3315.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v33x; + +class v3315 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.3.15', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3315rc1', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '3.3.15']], + ]; + } +} From b92ca5d1f808fe9f5b04b32a492abaee2e38544a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 23 Mar 2025 20:20:03 +0100 Subject: [PATCH 0657/1214] [prep-release-3.3.15] Update changelog for 3.3.15 --- phpBB/docs/CHANGELOG.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 06cd3c93169..3b4a42bed33 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -50,6 +50,7 @@

        Changelog

        1. Changelog
            +
          • Changes since 3.3.15-RC1
          • Changes since 3.3.14
          • Changes since 3.3.14-RC1
          • Changes since 3.3.13
          • @@ -174,6 +175,16 @@

            Changelog

            +

            Changes since 3.3.15-RC1

            +

            Bug

            +
              +
            • [PHPBB-17480] - PHP fatal error in version check failure
            • +
            +

            Security Issue

            +
              +
            • [SECURITY-283] - Use jQuery to generate HTML from page data
            • +
            +

            Changes since 3.3.14

            Bug

              From fabef37d7150956508e92b0b75bc48750ba4bf0d Mon Sep 17 00:00:00 2001 From: IdfbAn Date: Mon, 31 Mar 2025 20:36:21 +0400 Subject: [PATCH 0658/1214] [ticket/17110] Change "slanderous" to "libellous" in T&C Slander is spoken and libel is written, so "libellous" is more accurate here. PHPBB-17110 --- phpBB/language/en/ucp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 16c2568f4b4..1f52db2901b 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -42,7 +42,7 @@

              Our forums are powered by phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) which is a bulletin board solution released under the “GNU General Public License v2” (hereinafter “GPL”) and can be downloaded from www.phpbb.com. The phpBB software only facilitates internet based discussions; phpBB Limited is not responsible for what we allow and/or disallow as permissible content and/or conduct. For further information about phpBB, please see: https://www.phpbb.com/.

              - You agree not to post any abusive, obscene, vulgar, slanderous, hateful, threatening, sexually-orientated or any other material that may violate any laws be it of your country, the country where “%1$s” is hosted or International Law. Doing so may lead to you being immediately and permanently banned, with notification of your Internet Service Provider if deemed required by us. The IP address of all posts are recorded to aid in enforcing these conditions. You agree that “%1$s” have the right to remove, edit, move or close any topic at any time should we see fit. As a user you agree to any information you have entered to being stored in a database. While this information will not be disclosed to any third party without your consent, neither “%1$s” nor phpBB shall be held responsible for any hacking attempt that may lead to the data being compromised. + You agree not to post any abusive, obscene, vulgar, libellous, hateful, threatening, sexually-orientated or any other material that may violate any laws be it of your country, the country where “%1$s” is hosted or International Law. Doing so may lead to you being immediately and permanently banned, with notification of your Internet Service Provider if deemed required by us. The IP address of all posts are recorded to aid in enforcing these conditions. You agree that “%1$s” have the right to remove, edit, move or close any topic at any time should we see fit. As a user you agree to any information you have entered to being stored in a database. While this information will not be disclosed to any third party without your consent, neither “%1$s” nor phpBB shall be held responsible for any hacking attempt that may lead to the data being compromised. ', 'PRIVACY_POLICY' => 'This policy explains in detail how “%1$s” along with its affiliated companies (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”) and phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) use any information collected during any session of usage by you (hereinafter “your information”). From 98a8999283727799375f78cd1572b67a16945b24 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 1 Apr 2025 10:46:29 +0700 Subject: [PATCH 0659/1214] [ticket/17486] Fix SQL error on phpBB v.3.0 to v.3.3 upgrade Ensure v310/bot_update runs after v310/avatars migration which changes user_avatar_type column type from tinyint(2) to varchar(255). PHPBB-17486 --- phpBB/phpbb/db/migration/data/v310/bot_update.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/db/migration/data/v310/bot_update.php b/phpBB/phpbb/db/migration/data/v310/bot_update.php index 39b16c68f81..73c3785a013 100644 --- a/phpBB/phpbb/db/migration/data/v310/bot_update.php +++ b/phpBB/phpbb/db/migration/data/v310/bot_update.php @@ -17,7 +17,10 @@ class bot_update extends \phpbb\db\migration\migration { static public function depends_on() { - return array('\phpbb\db\migration\data\v310\rc6'); + return array( + '\phpbb\db\migration\data\v310\rc6', + '\phpbb\db\migration\data\v310\avatars', + ); } public function update_data() From 1f1a02d086df4d650feb54d6e6da978096103eb8 Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 15:46:14 +0700 Subject: [PATCH 0660/1214] [ticket/17489] Fix messenger queue won't be saved PHPBB-17489 --- phpBB/phpbb/notification/method/messenger_base.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/phpBB/phpbb/notification/method/messenger_base.php b/phpBB/phpbb/notification/method/messenger_base.php index e4b97b7cacf..7acc608cc25 100644 --- a/phpBB/phpbb/notification/method/messenger_base.php +++ b/phpBB/phpbb/notification/method/messenger_base.php @@ -126,16 +126,13 @@ protected function notify_using_messenger($notify_method, $template_dir_prefix = ], $notification->get_email_template_variables())); $messenger_method->send(); + + // Save the queue in the messenger method class (has to be called or these messages could be lost) + $messenger_method->save_queue(); } } } - // Save the queue in the messenger method class (has to be called or these messages could be lost) - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->save_queue(); - } - // We're done, empty the queue $this->empty_queue(); } From 7467b567e869f940e2298cae27d71a9c0fe3aa99 Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 16:03:33 +0700 Subject: [PATCH 0661/1214] [ticket/17489] Do not always set use_queue flag to true PHPBB-17489 --- phpBB/phpbb/messenger/method/email.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 1bad23760c6..adf0951fec2 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -105,7 +105,7 @@ public function init(): void $this->mail_priority = symfony_email::PRIORITY_NORMAL; $this->additional_headers = []; - $this->use_queue = true; + $this->set_use_queue(); unset($this->template, $this->reply_to, $this->from); } From c23504e3c7694c4d40cacb2353b3d2cc256363c1 Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 19:16:08 +0700 Subject: [PATCH 0662/1214] [ticket/17489] Fix queue for ucp_resend, add type hints to $messenger_method PHPBB-17489 --- phpBB/includes/acp/acp_email.php | 2 ++ phpBB/includes/acp/acp_inactive.php | 2 ++ phpBB/includes/ucp/ucp_activate.php | 2 ++ phpBB/includes/ucp/ucp_resend.php | 10 ++++------ phpBB/phpbb/message/message.php | 2 ++ phpBB/phpbb/messenger/queue.php | 2 ++ phpBB/phpbb/notification/method/messenger_base.php | 1 + 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index 00ce89317cd..1e412d04619 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -227,6 +227,7 @@ function main($id, $mode) $used_lang = $email_list[$i][0]['lang']; $used_method = $email_list[$i][0]['method']; + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { $notify_method = $messenger_method->get_id(); @@ -272,6 +273,7 @@ function main($id, $mode) if ($use_queue) { + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->save_queue(); diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 2436277e15c..641beb4d0e6 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -205,6 +205,7 @@ function main($id, $mode) do { + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { if ($messenger_method->get_id() == $user_row['user_notify_type'] || $user_row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) @@ -227,6 +228,7 @@ function main($id, $mode) } while ($row = $db->sql_fetchrow($result)); + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->save_queue(); diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php index c9dd3e553e2..55006047218 100644 --- a/phpBB/includes/ucp/ucp_activate.php +++ b/phpBB/includes/ucp/ucp_activate.php @@ -133,6 +133,8 @@ function main($id, $mode) $messenger = $phpbb_container->get('messenger.method_collection'); $messenger_collection_iterator = $messenger->getIterator(); + + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { if ($messenger_method->get_id() == $user_row['user_notify_type'] || $user_row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 76e748e85d3..259eaf97ef6 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -138,6 +138,7 @@ function main($id, $mode) $messenger_collection_iterator = $messenger->getIterator(); while ($row = $db->sql_fetchrow($result)) { + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->set_use_queue(false); @@ -153,16 +154,13 @@ function main($id, $mode) ]); $messenger_method->send(); + + // Save the queue in the messenger method class (has to be called or these messages could be lost) + $messenger_method->save_queue(); } } } $db->sql_freeresult($result); - - // Save the queue in the messenger method class (has to be called or these messages could be lost) - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->save_queue(); - } } $this->update_activation_expiration(); diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index 90b8a1ff5f6..581ba17e46c 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -249,6 +249,8 @@ public function send(\phpbb\di\service_collection $messenger, $contact) { /** @psalm-suppress InvalidTemplateParam */ $messenger_collection_iterator = $messenger->getIterator(); + + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->set_use_queue(false); diff --git a/phpBB/phpbb/messenger/queue.php b/phpBB/phpbb/messenger/queue.php index abcc29c1bd7..50384bfd820 100644 --- a/phpBB/phpbb/messenger/queue.php +++ b/phpBB/phpbb/messenger/queue.php @@ -116,6 +116,8 @@ public function process(): void /** @psalm-suppress InvalidTemplateParam */ $messenger_collection_iterator = $this->messenger_method_collection->getIterator(); + + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { if (isset($this->queue_data[$messenger_method->get_queue_object_name()])) diff --git a/phpBB/phpbb/notification/method/messenger_base.php b/phpBB/phpbb/notification/method/messenger_base.php index 7acc608cc25..e59176a8a20 100644 --- a/phpBB/phpbb/notification/method/messenger_base.php +++ b/phpBB/phpbb/notification/method/messenger_base.php @@ -114,6 +114,7 @@ protected function notify_using_messenger($notify_method, $template_dir_prefix = continue; } + /** @var \phpbb\messenger\method\base $messenger_method */ foreach ($messenger_collection_iterator as $messenger_method) { if ($messenger_method->get_id() == $notify_method || $notify_method == $messenger_method::NOTIFY_BOTH) From d99c3838dfbcb3acf86db3eb087384863cd89f2b Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 20:57:24 +0700 Subject: [PATCH 0663/1214] [ticket/17489] Ignore Psalm UndefinedMethod issue Psalm fails with UndefinedMethod issue when a method of a child class does not exist in parent/abstract/interface class. Ignore the issue. PHPBB-17489 --- phpBB/includes/acp/acp_email.php | 10 ++++++++-- phpBB/includes/acp/acp_inactive.php | 10 ++++++++-- phpBB/includes/ucp/ucp_activate.php | 5 ++++- phpBB/includes/ucp/ucp_resend.php | 5 ++++- phpBB/phpbb/message/message.php | 5 ++++- phpBB/phpbb/messenger/queue.php | 5 ++++- phpBB/phpbb/notification/method/messenger_base.php | 5 ++++- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index 1e412d04619..591700f66d6 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -227,7 +227,10 @@ function main($id, $mode) $used_lang = $email_list[$i][0]['lang']; $used_method = $email_list[$i][0]['method']; - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { $notify_method = $messenger_method->get_id(); @@ -273,7 +276,10 @@ function main($id, $mode) if ($use_queue) { - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->save_queue(); diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 641beb4d0e6..935df27c14a 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -205,7 +205,10 @@ function main($id, $mode) do { - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { if ($messenger_method->get_id() == $user_row['user_notify_type'] || $user_row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) @@ -228,7 +231,10 @@ function main($id, $mode) } while ($row = $db->sql_fetchrow($result)); - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->save_queue(); diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php index 55006047218..81036c19568 100644 --- a/phpBB/includes/ucp/ucp_activate.php +++ b/phpBB/includes/ucp/ucp_activate.php @@ -134,7 +134,10 @@ function main($id, $mode) $messenger = $phpbb_container->get('messenger.method_collection'); $messenger_collection_iterator = $messenger->getIterator(); - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { if ($messenger_method->get_id() == $user_row['user_notify_type'] || $user_row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 259eaf97ef6..4ae14e0ba88 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -138,7 +138,10 @@ function main($id, $mode) $messenger_collection_iterator = $messenger->getIterator(); while ($row = $db->sql_fetchrow($result)) { - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->set_use_queue(false); diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index 581ba17e46c..b1d95cf5b83 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -250,7 +250,10 @@ public function send(\phpbb\di\service_collection $messenger, $contact) /** @psalm-suppress InvalidTemplateParam */ $messenger_collection_iterator = $messenger->getIterator(); - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->set_use_queue(false); diff --git a/phpBB/phpbb/messenger/queue.php b/phpBB/phpbb/messenger/queue.php index 50384bfd820..648dd751820 100644 --- a/phpBB/phpbb/messenger/queue.php +++ b/phpBB/phpbb/messenger/queue.php @@ -117,7 +117,10 @@ public function process(): void /** @psalm-suppress InvalidTemplateParam */ $messenger_collection_iterator = $this->messenger_method_collection->getIterator(); - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { if (isset($this->queue_data[$messenger_method->get_queue_object_name()])) diff --git a/phpBB/phpbb/notification/method/messenger_base.php b/phpBB/phpbb/notification/method/messenger_base.php index e59176a8a20..165735e56c4 100644 --- a/phpBB/phpbb/notification/method/messenger_base.php +++ b/phpBB/phpbb/notification/method/messenger_base.php @@ -114,7 +114,10 @@ protected function notify_using_messenger($notify_method, $template_dir_prefix = continue; } - /** @var \phpbb\messenger\method\base $messenger_method */ + /** + * @var \phpbb\messenger\method\messenger_interface $messenger_method + * @psalm-suppress UndefinedMethod + */ foreach ($messenger_collection_iterator as $messenger_method) { if ($messenger_method->get_id() == $notify_method || $notify_method == $messenger_method::NOTIFY_BOTH) From 67265efc0d1f074b9549075de1382a211d65b7bd Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 10:26:50 +0700 Subject: [PATCH 0664/1214] [ticket/17487] Fix PHP uncaught exception on sending email via board PHPBB-17487 --- phpBB/phpbb/message/message.php | 20 ++++++++++++-------- phpBB/phpbb/messenger/method/email.php | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index b1d95cf5b83..ea6da8419a9 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -263,15 +263,19 @@ public function send(\phpbb\di\service_collection $messenger, $contact) $messenger_method->set_addresses($recipient); $messenger_method->reply_to($this->sender_address); - $messenger_method->header('X-AntiAbuse', 'Board servername - ' . $this->server_name); - $messenger_method->header('X-AntiAbuse', 'User IP - ' . $this->sender_ip); - if ($this->sender_id) + if (isset($messenger_method->headers) && $messenger_method->headers instanceof \Symfony\Component\Mime\Header\Headers) { - $messenger_method->header('X-AntiAbuse', 'User_id - ' . $this->sender_id); - } - if ($this->sender_username) - { - $messenger_method->header('X-AntiAbuse', 'Username - ' . $this->sender_username); + $messenger_method->headers->addHeader('X-AntiAbuse', 'Board servername - ' . $this->server_name); + $messenger_method->headers->addHeader('X-AntiAbuse', 'User IP - ' . $this->sender_ip); + if ($this->sender_id) + { + $messenger_method->headers->addHeader('X-AntiAbuse', 'User_id - ' . $this->sender_id); + } + + if ($this->sender_username) + { + $messenger_method->headers->addHeader('X-AntiAbuse', 'Username - ' . $this->sender_username); + } } $messenger_method->subject(html_entity_decode($this->subject, ENT_COMPAT)); diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index adf0951fec2..78059174b32 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -47,7 +47,7 @@ class email extends base protected $from; /** @var Headers */ - protected $headers; + public $headers; /** * @var int From 21314a34a64c1bbf14e149c7602ca8d7d437a881 Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 11:41:59 +0700 Subject: [PATCH 0665/1214] [ticket/17487] Add functional test for sending email via board PHPBB-17487 --- tests/functional/memberlist_test.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/functional/memberlist_test.php b/tests/functional/memberlist_test.php index 3e40ddc528a..5497c240375 100644 --- a/tests/functional/memberlist_test.php +++ b/tests/functional/memberlist_test.php @@ -134,4 +134,29 @@ public function test_group_rank() unlink(__DIR__ . '/../../phpBB/images/ranks/valid.jpg'); } + + public function test_email() + { + $this->login(); + $this->admin_login(); + $this->add_lang(['acp/board', 'acp/common', 'memberlist']); + + $crawler = self::request('GET', "adm/index.php?sid={$this->sid}&i=acp_board&mode=email"); + $form = $crawler->selectButton('Submit')->form([ + 'config[board_email_form]' => 1, + ]); + $crawler = self::submit($form); + $this->assertContainsLang('CONFIG_UPDATED', $crawler->filter('.successbox')->text()); + + $crawler = self::request('GET', 'memberlist.php?mode=email&u=2'); + $this->assertStringContainsString($this->lang('SEND_EMAIL_USER', 'admin'), $crawler->filter('.titlespace')->text()); + + $form = $crawler->selectButton($this->lang('SEND_EMAIL'))->form([ + 'subject' => 'Test email form message', + 'message' => 'This is a test email message sent from a member profile email form.', + ]); + $crawler = self::submit($form); + + $this->assertContainsLang('EMAIL_SENT', $crawler->text()); + } } From cebc19f2ed201a11078ca538dc377be868e580c0 Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 3 Apr 2025 22:15:37 +0700 Subject: [PATCH 0666/1214] [ticket/17487] Add wrapper method to add headers to interface PHPBB-17487 --- phpBB/phpbb/message/message.php | 8 ++++---- phpBB/phpbb/messenger/method/base.php | 8 ++++++++ phpBB/phpbb/messenger/method/email.php | 20 +++++++++++++------ .../messenger/method/messenger_interface.php | 11 ++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index ea6da8419a9..1248e3b39df 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -265,16 +265,16 @@ public function send(\phpbb\di\service_collection $messenger, $contact) if (isset($messenger_method->headers) && $messenger_method->headers instanceof \Symfony\Component\Mime\Header\Headers) { - $messenger_method->headers->addHeader('X-AntiAbuse', 'Board servername - ' . $this->server_name); - $messenger_method->headers->addHeader('X-AntiAbuse', 'User IP - ' . $this->sender_ip); + $messenger_method->header('X-AntiAbuse', 'Board servername - ' . $this->server_name); + $messenger_method->header('X-AntiAbuse', 'User IP - ' . $this->sender_ip); if ($this->sender_id) { - $messenger_method->headers->addHeader('X-AntiAbuse', 'User_id - ' . $this->sender_id); + $messenger_method->header('X-AntiAbuse', 'User_id - ' . $this->sender_id); } if ($this->sender_username) { - $messenger_method->headers->addHeader('X-AntiAbuse', 'Username - ' . $this->sender_username); + $messenger_method->header('X-AntiAbuse', 'Username - ' . $this->sender_username); } } diff --git a/phpBB/phpbb/messenger/method/base.php b/phpBB/phpbb/messenger/method/base.php index 46d676013cf..c0c2a94d0d9 100644 --- a/phpBB/phpbb/messenger/method/base.php +++ b/phpBB/phpbb/messenger/method/base.php @@ -481,4 +481,12 @@ protected function set_template_paths(string|array $path_name, string|array $pat $this->setup_template(); $this->template->set_custom_style($path_name, $paths); } + + /** + * {@inheritdoc} + */ + public function header(string $header_name, mixed $header_value): void + { + return; + } } diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 78059174b32..87ad9114c1d 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -47,7 +47,7 @@ class email extends base protected $from; /** @var Headers */ - public $headers; + protected $headers; /** * @var int @@ -242,10 +242,10 @@ public function subject(string $subject = ''): void */ public function anti_abuse_headers(\phpbb\config\config $config, \phpbb\user $user): void { - $this->headers->addHeader('X-AntiAbuse', 'Board servername - ' . $config['server_name']); - $this->headers->addHeader('X-AntiAbuse', 'User_id - ' . $user->data['user_id']); - $this->headers->addHeader('X-AntiAbuse', 'Username - ' . $user->data['username']); - $this->headers->addHeader('X-AntiAbuse', 'User IP - ' . $user->ip); + $this->header('X-AntiAbuse', 'Board servername - ' . $config['server_name']); + $this->header('X-AntiAbuse', 'User_id - ' . $user->data['user_id']); + $this->header('X-AntiAbuse', 'Username - ' . $user->data['username']); + $this->header('X-AntiAbuse', 'User IP - ' . $user->ip); } /** @@ -313,7 +313,7 @@ protected function build_headers(): void foreach ($headers as $header => $value) { - $this->headers->addHeader($header, $value); + $this->header($header, $value); } } @@ -576,4 +576,12 @@ public function send(): bool return true; } + + /** + * {@inheritdoc} + */ + public function header(string $header_name, mixed $header_value): void + { + $this->headers->addHeader($header_name, $header_value); + } } diff --git a/phpBB/phpbb/messenger/method/messenger_interface.php b/phpBB/phpbb/messenger/method/messenger_interface.php index b733f3d3d4d..6d82f89b6f3 100644 --- a/phpBB/phpbb/messenger/method/messenger_interface.php +++ b/phpBB/phpbb/messenger/method/messenger_interface.php @@ -64,5 +64,16 @@ public function send(): bool; * * @return void */ + public function error(string $msg): void; + + /** + * Add message header + * + * @param string $header_name Message header name + * @param mixed $header_value Message header value + * + * @return void + */ + public function header(string $header_name, mixed $header_value): void; } From f9e6385e84e6f8102b347eade20fd9595c545366 Mon Sep 17 00:00:00 2001 From: rxu Date: Fri, 4 Apr 2025 00:04:45 +0700 Subject: [PATCH 0667/1214] [ticket/17489] Further queue logic adjustments Saving queue is not needed if use_queue flag is set to false as in this case the message should be set immediately in the runtime. PHPBB-17487 PHPBB-17489 --- phpBB/includes/acp/acp_email.php | 13 +------------ phpBB/includes/acp/acp_inactive.php | 10 +--------- phpBB/includes/ucp/ucp_resend.php | 3 --- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index 591700f66d6..dfe58eb3187 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -269,23 +269,12 @@ function main($id, $mode) } $errored = !$messenger_method->send() || $errored; + $messenger_method->save_queue(); } } } unset($email_list); - if ($use_queue) - { - /** - * @var \phpbb\messenger\method\messenger_interface $messenger_method - * @psalm-suppress UndefinedMethod - */ - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->save_queue(); - } - } - if ($generate_log_entry) { if (!empty($usernames)) diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 935df27c14a..e1a511852bc 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -223,6 +223,7 @@ function main($id, $mode) ]); $messenger_method->send(); + $messenger_method->save_queue(); } } @@ -231,15 +232,6 @@ function main($id, $mode) } while ($row = $db->sql_fetchrow($result)); - /** - * @var \phpbb\messenger\method\messenger_interface $messenger_method - * @psalm-suppress UndefinedMethod - */ - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->save_queue(); - } - // Add the remind state to the database and increase activation expiration by one day $sql = 'UPDATE ' . USERS_TABLE . ' SET user_reminded = user_reminded + 1, diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 4ae14e0ba88..43e26567e5e 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -157,9 +157,6 @@ function main($id, $mode) ]); $messenger_method->send(); - - // Save the queue in the messenger method class (has to be called or these messages could be lost) - $messenger_method->save_queue(); } } } From 43d34880ac55c737e38d7b532b0558841da7b8fd Mon Sep 17 00:00:00 2001 From: rxu Date: Fri, 4 Apr 2025 00:50:15 +0700 Subject: [PATCH 0668/1214] [ticket/17489] Remove unneeded headers check PHPBB-17487 PHPBB-17489 --- phpBB/phpbb/message/message.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index 1248e3b39df..900ef103c58 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -263,19 +263,16 @@ public function send(\phpbb\di\service_collection $messenger, $contact) $messenger_method->set_addresses($recipient); $messenger_method->reply_to($this->sender_address); - if (isset($messenger_method->headers) && $messenger_method->headers instanceof \Symfony\Component\Mime\Header\Headers) + $messenger_method->header('X-AntiAbuse', 'Board servername - ' . $this->server_name); + $messenger_method->header('X-AntiAbuse', 'User IP - ' . $this->sender_ip); + if ($this->sender_id) { - $messenger_method->header('X-AntiAbuse', 'Board servername - ' . $this->server_name); - $messenger_method->header('X-AntiAbuse', 'User IP - ' . $this->sender_ip); - if ($this->sender_id) - { - $messenger_method->header('X-AntiAbuse', 'User_id - ' . $this->sender_id); - } + $messenger_method->header('X-AntiAbuse', 'User_id - ' . $this->sender_id); + } - if ($this->sender_username) - { - $messenger_method->header('X-AntiAbuse', 'Username - ' . $this->sender_username); - } + if ($this->sender_username) + { + $messenger_method->header('X-AntiAbuse', 'Username - ' . $this->sender_username); } $messenger_method->subject(html_entity_decode($this->subject, ENT_COMPAT)); From 692c96978a1358fd7cd5efb917f238a233231c0e Mon Sep 17 00:00:00 2001 From: rxu Date: Fri, 4 Apr 2025 12:26:08 +0700 Subject: [PATCH 0669/1214] [ticket/17488] Fix PHP error when MySQL PDO driver is not enabled Check pdo_mysql extension for being loaded to use respective \PDO::MYSQL_ATTR_FOUND_ROWS constant. PHPBB-17488 --- phpBB/phpbb/db/doctrine/connection_parameter_factory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php index 4899f916f48..f9008616ca5 100644 --- a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php @@ -164,7 +164,7 @@ private static function enrich_parameters(array $params) : array ], ]; - if ($params['driver'] === 'pdo_mysql') + if ($params['driver'] === 'pdo_mysql' && extension_loaded('pdo_mysql')) { $enrichment_tags['pdo_mysql'][\PDO::MYSQL_ATTR_FOUND_ROWS] = true; } From 2e08d01b5e689d31565239cf09ee61278b19cce5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 6 Apr 2025 14:16:11 +0200 Subject: [PATCH 0670/1214] [ticket/17490] Add unit tests file for email method PHPBB-17490 --- tests/messenger/method_email_test.php | 389 ++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 tests/messenger/method_email_test.php diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php new file mode 100644 index 00000000000..a053658378b --- /dev/null +++ b/tests/messenger/method_email_test.php @@ -0,0 +1,389 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\config\config; +use phpbb\language\language; +use phpbb\language\language_file_loader; +use phpbb\messenger\method\email; +use phpbb\messenger\queue; +use phpbb\path_helper; +use phpbb\symfony_request; +use phpbb\template\assets_bag; + +class phpbb_messenger_method_email_test extends \phpbb_test_case +{ + protected config $config; + protected queue $queue; + protected $request; + + public function setUp(): void + { + global $phpbb_root_path, $phpEx; + + $assets_bag = new assets_bag(); + $cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; + $this->config = new config([]); + $dispatcher = new \phpbb_mock_event_dispatcher(); + $filesystem = new \phpbb\filesystem\filesystem(); + $language = new language(new language_file_loader($phpbb_root_path, $phpEx)); + $this->queue = $this->createMock(queue::class); + $this->request = new phpbb_mock_request(); + $user = new \phpbb\user($language, '\phpbb\datetime'); + $path_helper = new path_helper( + new symfony_request( + new phpbb_mock_request() + ), + $this->request, + $phpbb_root_path, + $phpEx + ); + $phpbb_container = new phpbb_mock_container_builder; + $twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); + $twig = new \phpbb\template\twig\environment( + $assets_bag, + $this->config, + $filesystem, + $path_helper, + $cache_path, + null, + new \phpbb\template\twig\loader(''), + $dispatcher, + array( + 'cache' => false, + 'debug' => false, + 'auto_reload' => true, + 'autoescape' => false, + ) + ); + $twig_lexer = new \phpbb\template\twig\lexer($twig); + $extension_manager = new phpbb_mock_extension_manager( + __DIR__ . '/', + array( + 'vendor2/foo' => array( + 'ext_name' => 'vendor2/foo', + 'ext_active' => '1', + 'ext_path' => 'ext/vendor2/foo/', + ), + ) + ); + $log = $this->createMock(\phpbb\log\log_interface::class); + + $this->method_email = new email( + $assets_bag, + $this->config, + $dispatcher, + $language, + $this->queue, + $path_helper, + $this->request, + $twig_extensions_collection, + $twig_lexer, + $user, + $phpbb_root_path, + $cache_path, + $extension_manager, + $log + ); + } + + public function test_miscellaneous(): void + { + $this->assertEquals(email::NOTIFY_EMAIL, $this->method_email->get_id()); + $this->assertEquals('email', $this->method_email->get_queue_object_name()); + $this->assertFalse($this->method_email->is_enabled()); + $this->config->offsetSet('email_enable', true); + $this->assertTrue($this->method_email->is_enabled()); + } + + public function test_set_dsn_from_config() + { + $config_values = [ + 'smtp_delivery' => true, + 'smtp_host' => 'smtp.example.com', + 'smtp_username' => 'user', + 'smtp_password' => 'pass', + 'smtp_port' => 587, + ]; + foreach ($config_values as $key => $value) + { + $this->config->set($key, $value); + } + + $this->method_email->set_dsn(); + $this->assertEquals('smtp://user:pass@smtp.example.com:587', $this->method_email->get_dsn()); + + $this->config->set('smtp_host', ''); + $this->method_email->set_dsn(); + $this->assertEquals('null://null', $this->method_email->get_dsn()); + } + + public function test_set_dns() + { + $this->assertEquals('', $this->method_email->get_dsn()); + $this->method_email->set_dsn(''); + $this->assertEquals('sendmail://default', $this->method_email->get_dsn()); + + $this->method_email->set_dsn('smtp://user:pass1@smtp.example.com:587'); + $this->assertEquals('smtp://user:pass1@smtp.example.com:587', $this->method_email->get_dsn()); + } + + public function test_set_transport() + { + $this->assertNull($this->method_email->get_transport()); + $this->assertEmpty($this->method_email->get_dsn()); + + $config_values = [ + 'smtp_delivery' => true, + 'smtp_host' => 'smtp.example.com', + 'smtp_username' => 'user', + 'smtp_password' => 'pass', + 'smtp_port' => 587, + 'smtp_verify_peer' => true, + 'smtp_verify_peer_name' => true, + 'smtp_allow_self_signed' => false, + ]; + foreach ($config_values as $key => $value) + { + $this->config->set($key, $value); + } + + $this->method_email->set_transport(); + + // set_dsn() should have been called in set_transport() + $this->assertEquals('smtp://user:pass@smtp.example.com:587', $this->method_email->get_dsn()); + + $transport = $this->method_email->get_transport(); + $this->assertInstanceOf('\Symfony\Component\Mailer\Transport\Smtp\SmtpTransport', $transport); + if (method_exists($transport->getStream(), 'getStreamOptions')) + { + $this->assertEquals([ + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => false, + ], $transport->getStream()->getStreamOptions()['ssl'] ?? null); + } + } + + public function test_init() + { + $this->config->set('email_package_size', 100); + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->assertNull($email_property->getValue($this->method_email)); + + $use_queue_property = $email_reflection->getProperty('use_queue'); + $this->assertFalse($use_queue_property->getValue($this->method_email)); + + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + $this->assertTrue($use_queue_property->getValue($this->method_email)); + $this->assertEmpty($email->getTo()); + + $this->method_email->to('foo@bar.com'); + $this->assertNotEmpty($email->getTo()); + + $this->method_email->init(); + + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + $this->assertEmpty($email->getTo()); + } + + public function test_set_addresses() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + $this->method_email->set_addresses([]); + $this->assertEmpty($email->getTo()); + + $this->method_email->set_addresses(['user_email' => 'foo@bar.com']); + $this->assertNotEmpty($email->getTo()); + $this->assertEquals('foo@bar.com', $email->getTo()[0]->getAddress()); + $this->assertEmpty($email->getTo()[0]->getName()); + + $this->method_email->set_addresses(['user_email' => 'bar@foo.com', 'username' => 'Bar Foo']); + + $this->assertEquals('bar@foo.com', $email->getTo()[1]->getAddress()); + $this->assertEquals('Bar Foo', $email->getTo()[1]->getName()); + } + + public function test_to() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Empty address + $this->assertEmpty($email->getTo()); + $this->method_email->to(''); + $this->assertEmpty($email->getTo()); + + // Valid address + $this->method_email->to('foo@bar.com'); + $this->assertNotEmpty($email->getTo()); + $this->assertEquals('foo@bar.com', $email->getTo()[0]->getAddress()); + $this->assertEmpty($email->getTo()[0]->getName()); + + // Valid address with name + $this->method_email->to('bar@foo.com', 'Bar Foo'); + $this->assertEquals('bar@foo.com', $email->getTo()[1]->getAddress()); + if (DIRECTORY_SEPARATOR == '\\') + { + $this->assertEmpty($email->getTo()[1]->getName()); + } + else + { + $this->assertEquals('Bar Foo', $email->getTo()[1]->getName()); + } + } + + public function test_cc() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Empty address + $this->assertEmpty($email->getCc()); + $this->method_email->cc(''); + $this->assertEmpty($email->getCc()); + + // Valid address + $this->method_email->cc('foo@bar.com'); + $this->assertNotEmpty($email->getCc()); + $this->assertEquals('foo@bar.com', $email->getCc()[0]->getAddress()); + $this->assertEmpty($email->getCc()[0]->getName()); + + // Valid address with name + $this->method_email->cc('bar@foo.com', 'Bar Foo'); + $this->assertEquals('bar@foo.com', $email->getCc()[1]->getAddress()); + $this->assertEquals('Bar Foo', $email->getCc()[1]->getName()); + } + + public function test_bcc() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Empty address + $this->assertEmpty($email->getBcc()); + $this->method_email->bcc(''); + $this->assertEmpty($email->getBcc()); + + // Valid address + $this->method_email->bcc('foo@bar.com'); + $this->assertNotEmpty($email->getBcc()); + $this->assertEquals('foo@bar.com', $email->getBcc()[0]->getAddress()); + $this->assertEmpty($email->getBcc()[0]->getName()); + + // Valid address with name + $this->method_email->bcc('bar@foo.com', 'Bar Foo'); + $this->assertEquals('bar@foo.com', $email->getBcc()[1]->getAddress()); + $this->assertEquals('Bar Foo', $email->getBcc()[1]->getName()); + } + + public function test_reply_to() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Empty address + $this->assertEmpty($email->getReplyTo()); + $this->method_email->reply_to(''); + $this->assertEmpty($email->getReplyTo()); + + // Valid address + $this->method_email->reply_to('foo@bar.com'); + $this->assertNotEmpty($email->getReplyTo()); + $this->assertEquals('foo@bar.com', $email->getReplyTo()[0]->getAddress()); + $this->assertEmpty($email->getReplyTo()[0]->getName()); + + // Valid address with name + $this->method_email->reply_to('bar@foo.com', 'Bar Foo'); + $this->assertEquals('bar@foo.com', $email->getReplyTo()[1]->getAddress()); + $this->assertEquals('Bar Foo', $email->getReplyTo()[1]->getName()); + } + + public function test_from() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Empty address + $this->assertEmpty($email->getFrom()); + $this->method_email->from(''); + $this->assertEmpty($email->getFrom()); + + // Valid address + $this->method_email->from('foo@bar.com'); + $this->assertNotEmpty($email->getFrom()); + $this->assertEquals('foo@bar.com', $email->getFrom()[0]->getAddress()); + $this->assertEmpty($email->getFrom()[0]->getName()); + + // Valid address with name + $this->method_email->from('bar@foo.com', 'Bar Foo'); + $this->assertEquals('bar@foo.com', $email->getFrom()[1]->getAddress()); + $this->assertEquals('Bar Foo', $email->getFrom()[1]->getName()); + } + + public function test_subject() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Empty subject + $this->assertEmpty($email->getSubject()); + $this->method_email->subject(''); + $this->assertEmpty($email->getSubject()); + + // Test subject + $this->method_email->subject('Test email'); + $this->assertNotEmpty($email->getSubject()); + $this->assertEquals('Test email', $email->getSubject()); + + // Overwrite subject + $this->method_email->subject('Reply to test email'); + $this->assertNotEmpty($email->getSubject()); + $this->assertEquals('Reply to test email', $email->getSubject()); + } +} From 930c87e97af0484b7fa5b03faea439370a70fe7f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 6 Apr 2025 14:16:59 +0200 Subject: [PATCH 0671/1214] [ticket/17490] Improve expected types and docblock info PHPBB-17490 --- phpBB/phpbb/messenger/method/email.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 87ad9114c1d..9e972fa8556 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -15,6 +15,7 @@ use Symfony\Component\Mailer\Transport; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email as symfony_email; use Symfony\Component\Mime\Header\Headers; @@ -43,10 +44,10 @@ class email extends base /** @var symfony_email */ protected $email; - /** @var Address */ + /** @var Address From address */ protected $from; - /** @var Headers */ + /** @var Headers Email headers */ protected $headers; /** @@ -124,7 +125,7 @@ public function set_addresses(array $user_row): void { if (!empty($user_row['user_email'])) { - $this->to($user_row['user_email'], $user_row['username'] ?: ''); + $this->to($user_row['user_email'], $user_row['username'] ?? ''); } } @@ -453,9 +454,9 @@ public function process_queue(array &$queue_data): void /** * Get mailer transport object * - * @return \Symfony\Component\Mailer\Transport\TransportInterface Symfony Mailer transport object + * @return ?TransportInterface Symfony Mailer transport object */ - public function get_transport(): \Symfony\Component\Mailer\Transport\TransportInterface + public function get_transport(): ?TransportInterface { return $this->transport; } From 91c325174f44308c6ba46c80e9cdd065a022243f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 6 Apr 2025 14:20:04 +0200 Subject: [PATCH 0672/1214] [ticket/17490] Add type hints to enforce some types PHPBB-17490 --- phpBB/phpbb/messenger/method/email.php | 21 +++++++++++---------- tests/messenger/method_email_test.php | 2 -- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 9e972fa8556..6fb3e20e683 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -15,6 +15,7 @@ use Symfony\Component\Mailer\Transport; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email as symfony_email; @@ -39,16 +40,16 @@ class email extends base * * Symfony Mailer transport DSN */ - protected $dsn = ''; + protected string $dsn = ''; /** @var symfony_email */ - protected $email; + protected symfony_email $email; /** @var Address From address */ - protected $from; + protected Address $from; /** @var Headers Email headers */ - protected $headers; + protected Headers $headers; /** * @var int @@ -60,16 +61,16 @@ class email extends base * symfony_email::PRIORITY_LOW * symfony_email::PRIORITY_LOWEST */ - protected $mail_priority = symfony_email::PRIORITY_NORMAL; + protected int $mail_priority = symfony_email::PRIORITY_NORMAL; /** @var \phpbb\messenger\queue */ protected $queue; /** @var Address */ - protected $reply_to; + protected Address $reply_to; - /** @var \Symfony\Component\Mailer\Transport\AbstractTransport */ - protected $transport; + /** @var AbstractTransport */ + protected AbstractTransport $transport; /** * {@inheritDoc} @@ -454,9 +455,9 @@ public function process_queue(array &$queue_data): void /** * Get mailer transport object * - * @return ?TransportInterface Symfony Mailer transport object + * @return TransportInterface Symfony Mailer transport object */ - public function get_transport(): ?TransportInterface + public function get_transport(): TransportInterface { return $this->transport; } diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index a053658378b..a1d82560702 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -139,7 +139,6 @@ public function test_set_dns() public function test_set_transport() { - $this->assertNull($this->method_email->get_transport()); $this->assertEmpty($this->method_email->get_dsn()); $config_values = [ @@ -179,7 +178,6 @@ public function test_init() $this->config->set('email_package_size', 100); $email_reflection = new \ReflectionClass($this->method_email); $email_property = $email_reflection->getProperty('email'); - $this->assertNull($email_property->getValue($this->method_email)); $use_queue_property = $email_reflection->getProperty('use_queue'); $this->assertFalse($use_queue_property->getValue($this->method_email)); From 8dbe499e3dfdb03c5f01c7c422961373b3509afd Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 6 Apr 2025 21:25:16 +0200 Subject: [PATCH 0673/1214] [ticket/17490] Add extra method to support unit testing mailer Also increased unit test coverage of email method to 100%. PHPBB-17490 --- phpBB/phpbb/messenger/method/email.php | 22 +- tests/messenger/method_email_test.php | 466 +++++++++++++++++++++++-- 2 files changed, 450 insertions(+), 38 deletions(-) diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 6fb3e20e683..3b580b6a69e 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -13,6 +13,8 @@ namespace phpbb\messenger\method; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\Transport; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\Transport\AbstractTransport; @@ -275,7 +277,6 @@ public function set_mail_priority(int $priority = symfony_email::PRIORITY_NORMAL */ protected function build_headers(): void { - $board_contact = trim($this->config['board_contact']); $contact_name = html_entity_decode($this->config['board_contact_name'], ENT_COMPAT); @@ -317,7 +318,6 @@ protected function build_headers(): void { $this->header($header, $value); } - } /** @@ -408,7 +408,7 @@ public function process_queue(array &$queue_data): void $package_size = $queue_data[$queue_object_name]['package_size'] ?? 0; $num_items = (!$package_size || $messages_count < $package_size) ? $messages_count : $package_size; - $mailer = new Mailer($this->transport); + $mailer = $this->get_mailer(); for ($i = 0; $i < $num_items; $i++) { @@ -437,7 +437,7 @@ public function process_queue(array &$queue_data): void { $mailer->send($email); } - catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) + catch (TransportExceptionInterface $e) { $this->error($e->getDebug()); continue; @@ -452,6 +452,16 @@ public function process_queue(array &$queue_data): void } } + /** + * Get mailer object + * + * @return MailerInterface Symfony Mailer object + */ + protected function get_mailer(): MailerInterface + { + return new Mailer($this->transport); + } + /** * Get mailer transport object * @@ -506,7 +516,7 @@ public function send(): bool // Send message ... if (!$this->use_queue) { - $mailer = new Mailer($this->transport); + $mailer = $this->get_mailer(); $subject = $this->subject; $msg = $this->msg; @@ -540,7 +550,7 @@ public function send(): bool { $mailer->send($this->email); } - catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) + catch (TransportExceptionInterface $e) { $this->error($e->getDebug()); return false; diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index a1d82560702..e0dfae93b59 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -19,45 +19,65 @@ use phpbb\path_helper; use phpbb\symfony_request; use phpbb\template\assets_bag; +use Symfony\Component\Mime\RawMessage; class phpbb_messenger_method_email_test extends \phpbb_test_case { + protected $assets_bag; + protected $cache_path; protected config $config; + protected $dispatcher; + protected $extension_manager; + protected $language; + protected $log; + protected $path_helper; protected queue $queue; protected $request; + protected $twig_extensions_collection; + protected $twig_lexer; + protected $user; public function setUp(): void { - global $phpbb_root_path, $phpEx; - - $assets_bag = new assets_bag(); - $cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; - $this->config = new config([]); - $dispatcher = new \phpbb_mock_event_dispatcher(); - $filesystem = new \phpbb\filesystem\filesystem(); - $language = new language(new language_file_loader($phpbb_root_path, $phpEx)); + global $config, $request, $symfony_request, $user, $phpbb_root_path, $phpEx; + + $this->assets_bag = new assets_bag(); + $this->cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; + $this->config = new config([ + 'force_server_vars' => false, + ]); + $config = $this->config; + $this->dispatcher = $this->getMockBuilder('\phpbb\event\dispatcher') + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem = new \phpbb\filesystem\filesystem(); + $this->language = new language(new language_file_loader($phpbb_root_path, $phpEx)); $this->queue = $this->createMock(queue::class); $this->request = new phpbb_mock_request(); - $user = new \phpbb\user($language, '\phpbb\datetime'); - $path_helper = new path_helper( - new symfony_request( - new phpbb_mock_request() - ), + $request = $this->request; + $this->symfony_request = new symfony_request(new phpbb_mock_request()); + $symfony_request = $this->symfony_request; + $this->user = new \phpbb\user($this->language, '\phpbb\datetime'); + $user = $this->user; + $user->page['root_script_path'] = 'phpbb/'; + $this->user->host = 'yourdomain.com'; + $this->path_helper = new path_helper( + $this->symfony_request, $this->request, $phpbb_root_path, $phpEx ); $phpbb_container = new phpbb_mock_container_builder; - $twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); + $this->twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); $twig = new \phpbb\template\twig\environment( - $assets_bag, + $this->assets_bag, $this->config, - $filesystem, - $path_helper, - $cache_path, + $this->filesystem, + $this->path_helper, + $this->cache_path, null, new \phpbb\template\twig\loader(''), - $dispatcher, + $this->dispatcher, array( 'cache' => false, 'debug' => false, @@ -65,8 +85,8 @@ public function setUp(): void 'autoescape' => false, ) ); - $twig_lexer = new \phpbb\template\twig\lexer($twig); - $extension_manager = new phpbb_mock_extension_manager( + $this->twig_lexer = new \phpbb\template\twig\lexer($twig); + $this->extension_manager = new phpbb_mock_extension_manager( __DIR__ . '/', array( 'vendor2/foo' => array( @@ -76,23 +96,23 @@ public function setUp(): void ), ) ); - $log = $this->createMock(\phpbb\log\log_interface::class); + $this->log = $this->createMock(\phpbb\log\log_interface::class); $this->method_email = new email( - $assets_bag, + $this->assets_bag, $this->config, - $dispatcher, - $language, + $this->dispatcher, + $this->language, $this->queue, - $path_helper, + $this->path_helper, $this->request, - $twig_extensions_collection, - $twig_lexer, - $user, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, $phpbb_root_path, - $cache_path, - $extension_manager, - $log + $this->cache_path, + $this->extension_manager, + $this->log ); } @@ -200,6 +220,17 @@ public function test_init() $this->assertEmpty($email->getTo()); } + public function test_get_mailer() + { + $email_reflection = new \ReflectionClass($this->method_email); + $this->method_email->init(); + $this->method_email->set_transport(); + $mailer_method = $email_reflection->getMethod('get_mailer'); + + $mailer = $mailer_method->invoke($this->method_email); + $this->assertInstanceOf(\Symfony\Component\Mailer\Mailer::class, $mailer); + } + public function test_set_addresses() { $email_reflection = new \ReflectionClass($this->method_email); @@ -384,4 +415,375 @@ public function test_subject() $this->assertNotEmpty($email->getSubject()); $this->assertEquals('Reply to test email', $email->getSubject()); } + + public function test_anti_abuse_headers() + { + $email_reflection = new \ReflectionClass($this->method_email); + $headers_property = $email_reflection->getProperty('headers'); + $this->method_email->init(); + + /** @var \Symfony\Component\Mime\Header\Headers $headers */ + $headers = $headers_property->getValue($this->method_email); + + $this->config->set('server_name', 'yourdomain.com'); + $this->user->data['user_id'] = 2; + $this->user->data['username'] = 'admin'; + $this->user->ip = '127.0.0.1'; + + $this->assertEmpty($headers->toArray()); + $this->method_email->anti_abuse_headers($this->config, $this->user); + + $this->assertEquals( + [ + 'X-AntiAbuse: Board servername - yourdomain.com', + 'X-AntiAbuse: User_id - 2', + 'X-AntiAbuse: Username - admin', + 'X-AntiAbuse: User IP - 127.0.0.1', + ], + $headers->toArray() + ); + } + + public function test_set_mail_priority() + { + $email_reflection = new \ReflectionClass($this->method_email); + $email_property = $email_reflection->getProperty('email'); + $this->method_email->init(); + /** @var \Symfony\Component\Mime\Email $email */ + $email = $email_property->getValue($this->method_email); + $this->assertNotNull($email); + + // Default priority + $this->assertEquals(\Symfony\Component\Mime\Email::PRIORITY_NORMAL, $email->getPriority()); + + // Highest priority + $this->method_email->set_mail_priority(\Symfony\Component\Mime\Email::PRIORITY_HIGHEST); + $this->assertEquals(\Symfony\Component\Mime\Email::PRIORITY_HIGHEST, $email->getPriority()); + } + + public function test_process_queue_not_enabled() + { + $this->method_email->init(); + + $queue_data = [ + 'email' => [ + 'data' => [ + 'message_one', + 'message_two', + ] + ] + ]; + + // Process queue will remove emails if email method is not enabled + $this->method_email->process_queue($queue_data); + $this->assertEmpty($queue_data); + } + + public function test_process_queue() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $sent_emails = 0; + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + if ($mail->toString() === 'throw_exception') + { + throw new \Symfony\Component\Mailer\Exception\TransportException('exception'); + } + + $sent_emails++; + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + if ($event_name === 'core.notification_message_process' && $value_array['email']->toString() == 'message_three') + { + $value_array['break'] = true; + } + + return $value_array; + }); + + $queue_data = [ + 'email' => [ + 'data' => [ + ['email' => new RawMessage('message_one')], + ['email' => new RawMessage('message_two')], + ['email' => new RawMessage('message_three')], + ['email' => new RawMessage('throw_exception')], + ['email' => new RawMessage('message_four')], + ] + ] + ]; + + $this->assertEmpty($errors); + $this->method_email->process_queue($queue_data); + $this->assertEmpty($queue_data); + $this->assertEquals(3, $sent_emails); + + $this->assertEquals(['EMAIL



              '], $errors); + } + + public function test_send_break() + { + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + if ($event_name !== 'core.notification_message_email') + { + return $value_array; + } + + $value_array['break'] = true; + return $value_array; + }); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->method_email->send(); + } + + public function test_send_no_queue() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $sent_emails = 0; + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + $sent_emails++; + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + return $value_array; + }); + + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->assertTrue($this->method_email->send()); + $this->assertEquals(1, $sent_emails); + + $this->assertEmpty($errors); + } + + public function test_send_exception() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + throw new \Symfony\Component\Mailer\Exception\TransportException('exception'); + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + return $value_array; + }); + + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->assertFalse($this->method_email->send()); + + $this->assertEquals(['EMAIL



              '], $errors); + } + + public function test_send_queue() + { + global $phpbb_root_path; + + $this->config->set('email_enable', true); + $this->config->set('email_package_size', 100); + $this->user->data['user_id'] = 2; + $this->user->session_id = 'abcdef'; + + $this->method_email = $this->getMockBuilder($this->method_email::class) + ->setConstructorArgs([$this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->onlyMethods(['get_mailer']) + ->getMock(); + + $mailer_mock = $this->getMockBuilder(\Symfony\Component\Mailer\MailerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['send']) + ->getMock(); + $mailer_mock->method('send') + ->willReturnCallback(function(RawMessage $mail) use(&$sent_emails) { + throw new \Symfony\Component\Mailer\Exception\TransportException('exception'); + }); + $this->method_email->method('get_mailer')->willReturn($mailer_mock); + + $this->config->set('board_email', 'admin@yourdomain.com'); + + $this->method_email->init(); + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->dispatcher + ->expects($this->atLeastOnce()) + ->method('trigger_event') + ->willReturnCallback(function($event_name, $value_array) { + return $value_array; + }); + + // Mock queue methods + $this->queue->method('init') + ->willReturnCallback(function(string $object, int $package_size) { + $this->assertEquals('email', $object); + $this->assertEquals($this->config['email_package_size'], $package_size); + }); + $this->queue->method('put') + ->willReturnCallback(function(string $object, array $message_data) { + $this->assertEquals('email', $object); + $this->assertStringContainsString('phpBB is correctly configured to send emails', $message_data['email']->getSubject()); + }); + + $this->method_email->to('foo@bar.com'); + $this->method_email->subject('Test email'); + $this->method_email->template('test', 'en'); + + $this->assertTrue($this->method_email->send()); + + $this->assertEmpty($errors); + } } From d5717b541e1d6c3164edd835273775f71ce121bf Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 7 Apr 2025 17:11:31 +0200 Subject: [PATCH 0674/1214] [ticket/17490] Remove name handling for mails on windows PHPBB-17490 --- phpBB/phpbb/messenger/method/email.php | 5 +---- tests/messenger/method_email_test.php | 9 +-------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 3b580b6a69e..7bd92538c2d 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -146,10 +146,7 @@ public function to(string $address, string $realname = ''): void return; } - // If empty sendmail_path on windows, PHP changes the to line - $windows_empty_sendmail_path = !$this->config['smtp_delivery'] && DIRECTORY_SEPARATOR == '\\'; - - $to = new Address($address, $windows_empty_sendmail_path ? '' : trim($realname)); + $to = new Address($address, trim($realname)); $this->email->getTo() ? $this->email->addTo($to) : $this->email->to($to); } diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index e0dfae93b59..432e90939b7 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -277,14 +277,7 @@ public function test_to() // Valid address with name $this->method_email->to('bar@foo.com', 'Bar Foo'); $this->assertEquals('bar@foo.com', $email->getTo()[1]->getAddress()); - if (DIRECTORY_SEPARATOR == '\\') - { - $this->assertEmpty($email->getTo()[1]->getName()); - } - else - { - $this->assertEquals('Bar Foo', $email->getTo()[1]->getName()); - } + $this->assertEquals('Bar Foo', $email->getTo()[1]->getName()); } public function test_cc() From e4c8984bf3a26d0833b0295c700dd8aa06959907 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 7 Apr 2025 19:39:07 +0200 Subject: [PATCH 0675/1214] [ticket/17490] Remove not needed returns PHPBB-17490 --- phpBB/phpbb/messenger/method/base.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/phpBB/phpbb/messenger/method/base.php b/phpBB/phpbb/messenger/method/base.php index c0c2a94d0d9..0fb46b2cb38 100644 --- a/phpBB/phpbb/messenger/method/base.php +++ b/phpBB/phpbb/messenger/method/base.php @@ -216,9 +216,9 @@ abstract public function process_queue(array &$queue_data): void; * @param string $template_path Email template path * @param string $template_dir_prefix Email template directory prefix * - * @return bool + * @return void */ - public function template(string $template_file, string $template_lang = '', string $template_path = '', string $template_dir_prefix = ''): bool + public function template(string $template_file, string $template_lang = '', string $template_path = '', string $template_dir_prefix = ''): void { $template_dir_prefix = (!$template_dir_prefix || $template_dir_prefix[0] === '/') ? $template_dir_prefix : '/' . $template_dir_prefix; @@ -290,8 +290,6 @@ public function template(string $template_file, string $template_lang = '', stri $this->template->set_filenames([ 'body' => $template_file . '.txt', ]); - - return true; } /** @@ -487,6 +485,5 @@ protected function set_template_paths(string|array $path_name, string|array $pat */ public function header(string $header_name, mixed $header_value): void { - return; } } From 5bc4c5765e2a64eb92b1128f104691fecc0049ef Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 7 Apr 2025 19:39:23 +0200 Subject: [PATCH 0676/1214] [ticket/17490] Use isset instead of !empty() PHPBB-17490 --- phpBB/phpbb/messenger/method/base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/messenger/method/base.php b/phpBB/phpbb/messenger/method/base.php index 0fb46b2cb38..c8c7129cafc 100644 --- a/phpBB/phpbb/messenger/method/base.php +++ b/phpBB/phpbb/messenger/method/base.php @@ -423,7 +423,7 @@ public function error(string $msg): void */ public function save_queue(): void { - if ($this->use_queue && !empty($this->queue)) + if ($this->use_queue && isset($this->queue)) { $this->queue->save(); } From aa2da5b91676311050f29efbbddb59b8dbda58fa Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 7 Apr 2025 19:39:37 +0200 Subject: [PATCH 0677/1214] [ticket/17490] Add missing property declaration in test PHPBB-17490 --- tests/messenger/method_email_test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index 432e90939b7..be77da02dec 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -28,6 +28,7 @@ class phpbb_messenger_method_email_test extends \phpbb_test_case protected config $config; protected $dispatcher; protected $extension_manager; + protected email $method_email; protected $language; protected $log; protected $path_helper; From 63203f42f78612e5e833e09296562b5bb4aebfaa Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 7 Apr 2025 20:37:52 +0200 Subject: [PATCH 0678/1214] [ticket/17490] Add test coverage for remainder of base test PHPBB-17490 --- tests/messenger/method_base_test.php | 274 ++++++++++++++++++++++++++ tests/messenger/method_email_test.php | 17 +- 2 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 tests/messenger/method_base_test.php diff --git a/tests/messenger/method_base_test.php b/tests/messenger/method_base_test.php new file mode 100644 index 00000000000..38e7a921b17 --- /dev/null +++ b/tests/messenger/method_base_test.php @@ -0,0 +1,274 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\config\config; +use phpbb\language\language; +use phpbb\language\language_file_loader; +use phpbb\messenger\method\email; +use phpbb\messenger\queue; +use phpbb\path_helper; +use phpbb\symfony_request; +use phpbb\template\assets_bag; + +class phpbb_messenger_method_base_test extends \phpbb_test_case +{ + protected $assets_bag; + protected $cache_path; + protected config $config; + protected $dispatcher; + protected $extension_manager; + protected email $method_email; + protected $method_base; + protected $language; + protected $log; + protected $path_helper; + protected queue $queue; + protected $request; + protected $twig_extensions_collection; + protected $twig_lexer; + protected $user; + + public function setUp(): void + { + global $config, $request, $symfony_request, $user, $phpbb_root_path, $phpEx; + + $this->assets_bag = new assets_bag(); + $this->cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; + $this->config = new config([ + 'force_server_vars' => false, + ]); + $config = $this->config; + $this->dispatcher = $this->getMockBuilder('\phpbb\event\dispatcher') + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem = new \phpbb\filesystem\filesystem(); + $this->language = new language(new language_file_loader($phpbb_root_path, $phpEx)); + $this->queue = $this->createMock(queue::class); + $this->request = new phpbb_mock_request(); + $request = $this->request; + $this->symfony_request = new symfony_request(new phpbb_mock_request()); + $symfony_request = $this->symfony_request; + $this->user = $this->getMockBuilder('\phpbb\user') + ->setConstructorArgs([$this->language, '\phpbb\datetime']) + ->getMock(); + $user = $this->user; + $user->page['root_script_path'] = 'phpbb/'; + $this->user->host = 'yourdomain.com'; + $this->path_helper = new path_helper( + $this->symfony_request, + $this->request, + $phpbb_root_path, + $phpEx + ); + $phpbb_container = new phpbb_mock_container_builder; + $this->twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); + $twig = new \phpbb\template\twig\environment( + $this->assets_bag, + $this->config, + $this->filesystem, + $this->path_helper, + $this->cache_path, + null, + new \phpbb\template\twig\loader(''), + $this->dispatcher, + array( + 'cache' => false, + 'debug' => false, + 'auto_reload' => true, + 'autoescape' => false, + ) + ); + $this->twig_lexer = new \phpbb\template\twig\lexer($twig); + $this->extension_manager = new phpbb_mock_extension_manager( + __DIR__ . '/', + array( + 'vendor2/foo' => array( + 'ext_name' => 'vendor2/foo', + 'ext_active' => '1', + 'ext_path' => 'ext/vendor2/foo/', + ), + ) + ); + $this->log = $this->createMock(\phpbb\log\log_interface::class); + + $this->method_email = new email( + $this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ); + + $this->method_base = $this->getMockBuilder(\phpbb\messenger\method\base::class) + ->setConstructorArgs([ + $this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ]) + ->getMockForAbstractClass(); + } + + public function test_header() + { + $this->method_base->header('X-AntiAbuse', 'Board servername - ' . $this->user->host); + $this->assertTrue(true); // No exception should be thrown + } + + public function test_set_use_queue() + { + $use_queue_property = new \ReflectionProperty($this->method_base, 'use_queue'); + $this->method_base->set_use_queue(); + $this->assertTrue($use_queue_property->getValue($this->method_base)); + $this->method_base->set_use_queue(false); + $this->assertFalse($use_queue_property->getValue($this->method_base)); + } + + public function test_error_wout_session() + { + $errors = []; + $this->log->method('add') + ->willReturnCallback(function($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = []) use (&$errors) { + $errors[] = $additional_data[0]; + }); + + $this->user->data['user_id'] = 2; + $this->user->session_id = ''; + $this->user + ->expects($this->once()) + ->method('session_begin') + ->willReturnCallback(function() { + $this->assertTrue(true); + }); + + $this->method_base->error('Test error message'); + + $this->assertCount(1, $errors); + $this->assertEquals('


              Test error message
              ', $errors[0]); + } + + public function test_save_queue() + { + $this->queue->expects($this->once()) + ->method('save'); + $this->method_base->set_use_queue(false); + $this->method_base->save_queue(); + $this->method_base->set_use_queue(true); + $this->method_base->save_queue(); + } + + public function test_template_no_lang() + { + $template_mock = $this->getMockBuilder(\phpbb\template\template::class) + ->disableOriginalConstructor() + ->getMock(); + $filenames = []; + $template_mock->method('set_filenames') + ->willReturnCallback(function($filename_array) use (&$filenames, $template_mock) { + $filenames = array_merge($filenames, $filename_array); + + return $template_mock; + }); + + $base_reflection = new \ReflectionClass($this->method_base); + $template_reflection = $base_reflection->getProperty('template'); + $template_reflection->setValue($this->method_base, $template_mock); + + $this->config->set('default_lang', 'en'); + $this->method_base->template('test'); + $this->assertEquals(['body' => 'test.txt'], $filenames); + } + + public function test_template_template_path() + { + global $phpbb_root_path; + + $template_mock = $this->getMockBuilder(\phpbb\template\template::class) + ->disableOriginalConstructor() + ->getMock(); + $filenames = []; + $template_mock->method('set_filenames') + ->willReturnCallback(function($filename_array) use (&$filenames, $template_mock) { + $filenames = array_merge($filenames, $filename_array); + + return $template_mock; + }); + $template_mock->method('set_custom_style') + ->willReturnCallback(function($path_name, $paths) use($phpbb_root_path) { + $this->assertEquals([['name' => 'en_email', 'ext_path' => 'language/en/email']], $path_name); + $this->assertEquals([$phpbb_root_path . 'language/en/email'], $paths); + }); + + $base_reflection = new \ReflectionClass($this->method_base); + $template_reflection = $base_reflection->getProperty('template'); + $template_reflection->setValue($this->method_base, $template_mock); + + $this->config->set('default_lang', 'en'); + $this->method_base->template('test', '', $phpbb_root_path . 'language/en/email'); + $this->assertEquals(['body' => 'test.txt'], $filenames); + } + + public function test_template_path_fallback() + { + global $phpbb_root_path; + + $template_mock = $this->getMockBuilder(\phpbb\template\template::class) + ->disableOriginalConstructor() + ->getMock(); + $filenames = []; + $template_mock->method('set_filenames') + ->willReturnCallback(function($filename_array) use (&$filenames, $template_mock) { + $filenames = array_merge($filenames, $filename_array); + + return $template_mock; + }); + $template_mock->method('set_custom_style') + ->willReturnCallback(function($path_name, $paths) use($phpbb_root_path) { + $this->assertEquals([ + ['name' => 'de_email', 'ext_path' => 'language/de/email'], + ['name' => 'en_email', 'ext_path' => 'language/en/email'], + ], $path_name); + $this->assertEquals([ + $phpbb_root_path . 'language/de/email', + $phpbb_root_path . 'language/en/email' + ], $paths); + }); + + $base_reflection = new \ReflectionClass($this->method_base); + $template_reflection = $base_reflection->getProperty('template'); + $template_reflection->setValue($this->method_base, $template_mock); + + $this->config->set('default_lang', 'de'); + $this->method_base->template('test', 'de'); + $this->assertEquals(['body' => 'test.txt'], $filenames); + } +} diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index be77da02dec..c21a883f74f 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -576,11 +576,24 @@ public function test_send_break() $this->method_email->to('foo@bar.com'); $this->method_email->subject('Test email'); $this->method_email->template('test', 'en'); + $this->method_email->assign_block_vars('foo', ['bar' => 'baz']); $this->method_email->send(); } - public function test_send_no_queue() + public function email_template_data(): array + { + return [ + ['test'], + ['admin_send_email'], + ['topic_notify'], + ]; + } + + /** + * @dataProvider email_template_data + */ + public function test_send_no_queue($email_template) { global $phpbb_root_path; @@ -636,7 +649,7 @@ public function test_send_no_queue() $this->method_email->to('foo@bar.com'); $this->method_email->subject('Test email'); - $this->method_email->template('test', 'en'); + $this->method_email->template($email_template, 'en'); $this->assertTrue($this->method_email->send()); $this->assertEquals(1, $sent_emails); From a63d423c8632f4a328d17c744984a05c22d84df7 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 8 Apr 2025 22:37:03 +0200 Subject: [PATCH 0679/1214] [ticket/17490] Start implementing unit tests for jabber PHPBB-17490 --- tests/messenger/method_jabber_test.php | 311 +++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 tests/messenger/method_jabber_test.php diff --git a/tests/messenger/method_jabber_test.php b/tests/messenger/method_jabber_test.php new file mode 100644 index 00000000000..d61cd9b1b15 --- /dev/null +++ b/tests/messenger/method_jabber_test.php @@ -0,0 +1,311 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +class phpbb_messenger_method_jabber_test extends \phpbb_test_case +{ + protected $assets_bag; + protected $cache_path; + protected config $config; + protected $dispatcher; + protected $extension_manager; + protected jabber $method_jabber; + protected $method_base; + protected $language; + protected $log; + protected $path_helper; + protected queue $queue; + protected $request; + protected $twig_extensions_collection; + protected $twig_lexer; + protected $user; + + public function setUp(): void + { + global $config, $request, $symfony_request, $user, $phpbb_root_path, $phpEx; + + $this->assets_bag = new assets_bag(); + $this->cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; + $this->config = new config([ + 'force_server_vars' => false, + 'jab_username' => 'test', + 'jab_password' => 'password', + 'jab_use_ssl' => false, + 'jab_host' => 'localhost', + 'jab_port' => 5222, + 'jab_verify_peer' => true, + 'jab_verify_peer_name' => true, + 'jab_allow_self_signed' => false, + ]); + $config = $this->config; + $this->dispatcher = $this->getMockBuilder('\phpbb\event\dispatcher') + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem = new \phpbb\filesystem\filesystem(); + $this->language = new language(new language_file_loader($phpbb_root_path, $phpEx)); + $this->queue = $this->createMock(queue::class); + $this->request = new phpbb_mock_request(); + $request = $this->request; + $this->symfony_request = new symfony_request(new phpbb_mock_request()); + $symfony_request = $this->symfony_request; + $this->user = $this->getMockBuilder('\phpbb\user') + ->setConstructorArgs([$this->language, '\phpbb\datetime']) + ->getMock(); + $user = $this->user; + $user->page['root_script_path'] = 'phpbb/'; + $this->user->host = 'yourdomain.com'; + $this->path_helper = new path_helper( + $this->symfony_request, + $this->request, + $phpbb_root_path, + $phpEx + ); + $phpbb_container = new phpbb_mock_container_builder; + $this->twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); + $twig = new \phpbb\template\twig\environment( + $this->assets_bag, + $this->config, + $this->filesystem, + $this->path_helper, + $this->cache_path, + null, + new \phpbb\template\twig\loader(''), + $this->dispatcher, + array( + 'cache' => false, + 'debug' => false, + 'auto_reload' => true, + 'autoescape' => false, + ) + ); + $this->twig_lexer = new \phpbb\template\twig\lexer($twig); + $this->extension_manager = new phpbb_mock_extension_manager( + __DIR__ . '/', + array( + 'vendor2/foo' => array( + 'ext_name' => 'vendor2/foo', + 'ext_active' => '1', + 'ext_path' => 'ext/vendor2/foo/', + ), + ) + ); + $this->log = $this->createMock(\phpbb\log\log_interface::class); + + $this->method_jabber = new jabber( + $this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + $phpbb_root_path, + $this->cache_path, + $this->extension_manager, + $this->log + ); + } + + public function test_miscellaneous() + { + $this->method_jabber->init(); + $this->assertEquals(messenger_interface::NOTIFY_IM, $this->method_jabber->get_id()); + $this->assertEquals('jabber', $this->method_jabber->get_queue_object_name()); + $this->assertFalse($this->method_jabber->is_enabled()); + $this->config->set('jab_enable', true); + $this->assertTrue($this->method_jabber->is_enabled()); + $this->assertEquals(@extension_loaded('openssl'), $this->method_jabber->can_use_ssl()); + } + + public function test_stream_options() + { + $this->method_jabber->init(); + $this->assertEquals($this->method_jabber, $this->method_jabber->stream_options([ + 'allow_self_signed' => true, + ])); + + $stream_options_reflection = new \ReflectionProperty($this->method_jabber, 'stream_options'); + $stream_options = $stream_options_reflection->getValue($this->method_jabber); + $this->assertEquals([ + 'ssl' => [ + 'allow_self_signed' => false, + 'verify_peer' => true, + 'verify_peer_name' => true, + ], + ], $stream_options); + + $this->method_jabber->ssl(true); + + $this->assertEquals($this->method_jabber, $this->method_jabber->stream_options([ + 'allow_self_signed' => true, + ])); + $stream_options = $stream_options_reflection->getValue($this->method_jabber); + $this->assertEquals([ + 'ssl' => [ + 'allow_self_signed' => true, + 'verify_peer' => true, + 'verify_peer_name' => true, + ], + ], $stream_options); + } + + public function test_port_ssl_switch() + { + $port_reflection = new \ReflectionProperty($this->method_jabber, 'port'); + + $this->method_jabber->port(); + $this->assertEquals(5222, $port_reflection->getValue($this->method_jabber)); + + $this->method_jabber->ssl(true) + ->port(); + $this->assertEquals(5223, $port_reflection->getValue($this->method_jabber)); + } + + public function test_username() + { + $jabber_reflection = new \ReflectionClass($this->method_jabber); + $username_reflection = $jabber_reflection->getProperty('username'); + $jid_reflection = $jabber_reflection->getProperty('jid'); + + $this->method_jabber->username('foo@bar'); + $this->assertEquals(['foo', 'bar'], $jid_reflection->getValue($this->method_jabber)); + $this->assertEquals('foo', $username_reflection->getValue($this->method_jabber)); + + $this->method_jabber->username('bar@baz@qux'); + $this->assertEquals(['bar', 'baz@qux'], $jid_reflection->getValue($this->method_jabber)); + $this->assertEquals('bar', $username_reflection->getValue($this->method_jabber)); + } + + public function test_server() + { + $jabber_reflection = new \ReflectionClass($this->method_jabber); + $connect_server_reflection = $jabber_reflection->getProperty('connect_server'); + $server_reflection = $jabber_reflection->getProperty('server'); + + $this->method_jabber->server(); + $this->assertEquals('localhost', $connect_server_reflection->getValue($this->method_jabber)); + $this->assertEquals('localhost', $server_reflection->getValue($this->method_jabber)); + + $this->method_jabber->server('foobar.com'); + $this->assertEquals('foobar.com', $connect_server_reflection->getValue($this->method_jabber)); + $this->assertEquals('foobar.com', $server_reflection->getValue($this->method_jabber)); + + $this->method_jabber->username('foo@bar.com'); + $this->method_jabber->server('foobar.com'); + $this->assertEquals('foobar.com', $connect_server_reflection->getValue($this->method_jabber)); + $this->assertEquals('bar.com', $server_reflection->getValue($this->method_jabber)); + } + + public function test_encrypt_password() + { + $this->method_jabber->init(); + $this->method_jabber->password('password'); + $data = [ + 'realm' => 'example.com', + 'nonce' => '12345', + 'cnonce' => 'abcde', + 'digest-uri' => 'xmpp/example.com', + 'nc' => '00000001', + 'qop' => 'auth', + ]; + + $expected = md5(sprintf( + '%s:%s:%s:%s:%s:%s', + md5(pack('H32', md5('test:example.com:password')) . ':12345:abcde'), + $data['nonce'], + $data['nc'], + $data['cnonce'], + $data['qop'], + md5('AUTHENTICATE:xmpp/example.com') + )); + $this->assertEquals($expected, $this->method_jabber->encrypt_password($data)); + } + + public function test_parse_data() + { + $data = 'key1="value1",key2="value2",key3="value3"'; + $expected = [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ]; + + $this->assertEquals($expected, $this->method_jabber->parse_data($data)); + } + + public function test_implode_data() + { + $data = [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ]; + $expected = 'key1="value1",key2="value2",key3="value3"'; + + $this->assertEquals($expected, $this->method_jabber->implode_data($data)); + } + + public function test_xmlize() + { + $xml = 'content'; + $result = $this->method_jabber->xmlize($xml); + + $this->assertArrayHasKey('root', $result); + $this->assertArrayHasKey('child', $result['root'][0]['#']); + $this->assertEquals('content', $result['root'][0]['#']['child'][0]['#']); + $this->assertEquals(['key' => 'value'], $result['root'][0]['#']['child'][0]['@']); + } + + public function test_send_xml() + { + $jabber_mock = $this->getMockBuilder(jabber::class) + ->setConstructorArgs([ + $this->assets_bag, + $this->config, + $this->dispatcher, + $this->language, + $this->queue, + $this->path_helper, + $this->request, + $this->twig_extensions_collection, + $this->twig_lexer, + $this->user, + '', + '', + $this->extension_manager, + $this->log, + ]) + ->onlyMethods(['send_xml']) + ->getMock(); + + $jabber_mock->expects($this->once()) + ->method('send_xml') + ->with('Test') + ->willReturn(true); + + $this->assertTrue($jabber_mock->send_xml('Test')); + } +} From 6fcac2c40f4519a9d4755d1ed211fb4b03252284 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 10 Apr 2025 20:59:30 +0200 Subject: [PATCH 0680/1214] [ticket/17490] Add unit tests for messenger queue PHPBB-17490 --- tests/messenger/queue_test.php | 344 +++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 tests/messenger/queue_test.php diff --git a/tests/messenger/queue_test.php b/tests/messenger/queue_test.php new file mode 100644 index 00000000000..e99893d5778 --- /dev/null +++ b/tests/messenger/queue_test.php @@ -0,0 +1,344 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use phpbb\config\config; +use phpbb\filesystem\exception\filesystem_exception; +use phpbb\messenger\queue; + +class phpbb_messenger_queue_test extends phpbb_test_case +{ + protected $config; + protected $dispatcher; + protected $service_collection; + + /** @var queue */ + protected $messenger_queue; + + /** + * Set up the test case + */ + protected function setUp(): void + { + $this->config = new config([ + 'last_queue_run' => time() - 30, + 'queue_interval' => 600, + ]); + $this->dispatcher = $this->getMockBuilder('phpbb\event\dispatcher') + ->disableOriginalConstructor() + ->getMock(); + $this->service_collection = $this->getMockBuilder('phpbb\di\service_collection') + ->disableOriginalConstructor() + ->onlyMethods(['getIterator', 'add_service_class']) + ->getMock(); + + $this->cache_file = __DIR__ . '/../tmp/queue_test'; + + if (file_exists($this->cache_file)) + { + @unlink($this->cache_file); + } + + $this->messenger_queue = $this->getMockBuilder('phpbb\messenger\queue') + ->setConstructorArgs([ + $this->config, + $this->dispatcher, + $this->service_collection, + $this->cache_file + ]) + ->addMethods(['get_data']) + ->getMock(); + + $this->messenger_queue->method('get_data') + ->willReturnCallback(function(){ + $data_reflection = new \ReflectionProperty(queue::class, 'data'); + return $data_reflection->getValue($this->messenger_queue); + }); + } + + public function test_init() + { + $this->messenger_queue->init('email', 5); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->init('jabber', 9); + + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ], + 'jabber' => [ + 'package_size' => 9, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + } + + public function test_put() + { + $this->messenger_queue->init('email', 5); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [ + ['data1'], + ], + ], + ], $this->messenger_queue->get_data()); + } + + public function test_process_no_cache_file() + { + $this->assertFileDoesNotExist($this->cache_file); + $this->assertGreaterThan(5, time() - $this->config['last_queue_run']); + $this->messenger_queue->init('email', 5); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->process(); + $this->assertFileDoesNotExist($this->cache_file); + $this->assertLessThan(5, time() - $this->config['last_queue_run']); + } + + public function test_process_no_queue_handling() + { + // First save queue data + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->init('email', 5); + $this->messenger_queue->init('jabber', 10); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ], + 'jabber' => [ + 'package_size' => 10, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + $this->messenger_queue->put('jabber', ['data2']); + $this->messenger_queue->save(); + $this->assertFileExists($this->cache_file); + $this->assertEquals([], $this->messenger_queue->get_data()); + + $this->config['last_queue_run'] = time() - 1000; + + // Process the queue + $this->messenger_queue->process(); + } + + public function test_process_no_queue_handling_chmod_exception() + { + // First save queue data + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->init('email', 5); + $this->messenger_queue->init('jabber', 10); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ], + 'jabber' => [ + 'package_size' => 10, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + $this->messenger_queue->put('jabber', ['data2']); + $this->messenger_queue->save(); + $this->assertFileExists($this->cache_file); + $this->assertEquals([], $this->messenger_queue->get_data()); + + $this->config['last_queue_run'] = time() - 1000; + + // Override the filesystem to simulate a chmod failure + $filesystem = $this->getMockBuilder('phpbb\filesystem\filesystem') + ->disableOriginalConstructor() + ->onlyMethods(['phpbb_chmod']) + ->getMock(); + $filesystem->method('phpbb_chmod') + ->will($this->throwException(new filesystem_exception('Chmod failed'))); + $filesystem_reflection = new \ReflectionProperty(queue::class, 'filesystem'); + $filesystem_reflection->setAccessible(true); + $filesystem_reflection->setValue($this->messenger_queue, $filesystem); + + // Process the queue + $this->messenger_queue->process(); + } + + public function test_process_complete() + { + // First save queue data + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->init('email', 5); + $this->messenger_queue->init('jabber', 10); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ], + 'jabber' => [ + 'package_size' => 10, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + $this->messenger_queue->put('jabber', ['data2']); + $this->messenger_queue->save(); + $this->assertFileExists($this->cache_file); + $this->assertEquals([], $this->messenger_queue->get_data()); + + $this->config['last_queue_run'] = time() - 1000; + + // Prepare service iterator and messenger methods + $email_method = $this->getMockBuilder('phpbb\messenger\method\email') + ->disableOriginalConstructor() + ->onlyMethods(['get_queue_object_name', 'process_queue']) + ->getMock(); + $email_method->method('get_queue_object_name') + ->willReturn('email'); + $email_method->method('process_queue') + ->willReturnCallback(function(array &$queue_data) { + $this->assertEquals([ + 'package_size' => 5, + 'data' => [ + ['data1'], + ], + ], $queue_data['email']); + unset($queue_data['email']); + }); + $jabber_method = $this->getMockBuilder('phpbb\messenger\method\jabber') + ->disableOriginalConstructor() + ->onlyMethods(['get_queue_object_name', 'process_queue']) + ->getMock(); + $jabber_method->method('get_queue_object_name') + ->willReturn('jabber'); + $jabber_method->method('process_queue') + ->willReturnCallback(function(array &$queue_data) { + $this->assertEquals([ + 'package_size' => 10, + 'data' => [ + ['data2'], + ], + ], $queue_data['jabber']); + unset($queue_data['jabber']); + }); + + $this->service_collection->method('getIterator') + ->willReturn(new \ArrayIterator([ + 'email' => $email_method, + 'jabber' => $jabber_method, + ])); + + // Process the queue + $this->messenger_queue->process(); + $this->assertFileDoesNotExist($this->cache_file); + } + + public function test_save_no_data() + { + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->save(); + $this->assertFileDoesNotExist($this->cache_file); + } + + public function test_save() + { + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->init('email', 5); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + $this->messenger_queue->save(); + $this->assertFileExists($this->cache_file); + $this->assertEquals([], $this->messenger_queue->get_data()); + } + + public function test_save_twice() + { + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->init('email', 5); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + $this->messenger_queue->put('jabber', ['data2']); + $this->messenger_queue->save(); + $this->assertFileExists($this->cache_file); + $this->assertEquals([], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data3']); + $this->messenger_queue->save(); + $this->assertEquals([], $this->messenger_queue->get_data()); + } + + public function test_save_chmod_fail() + { + $this->assertFileDoesNotExist($this->cache_file); + $this->messenger_queue->init('email', 5); + $this->assertEquals([ + 'email' => [ + 'package_size' => 5, + 'data' => [], + ] + ], $this->messenger_queue->get_data()); + + $this->messenger_queue->put('email', ['data1']); + + // Override the filesystem to simulate a chmod failure + $filesystem = $this->getMockBuilder('phpbb\filesystem\filesystem') + ->disableOriginalConstructor() + ->onlyMethods(['phpbb_chmod']) + ->getMock(); + $filesystem->method('phpbb_chmod') + ->will($this->throwException(new filesystem_exception('Chmod failed'))); + $filesystem_reflection = new \ReflectionProperty(queue::class, 'filesystem'); + $filesystem_reflection->setAccessible(true); + $filesystem_reflection->setValue($this->messenger_queue, $filesystem); + + $this->messenger_queue->save(); + $this->assertFileExists($this->cache_file); + $this->assertEquals([], $this->messenger_queue->get_data()); + } +} From bf2dd2cd75687c3b8ca393ae631b02fc8deee52f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 12 Apr 2025 09:02:14 +0200 Subject: [PATCH 0681/1214] [ticket/17490] Remove not needed isset PHPBB-17490 --- phpBB/phpbb/messenger/method/base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/messenger/method/base.php b/phpBB/phpbb/messenger/method/base.php index c8c7129cafc..f3db4155cad 100644 --- a/phpBB/phpbb/messenger/method/base.php +++ b/phpBB/phpbb/messenger/method/base.php @@ -423,7 +423,7 @@ public function error(string $msg): void */ public function save_queue(): void { - if ($this->use_queue && isset($this->queue)) + if ($this->use_queue) { $this->queue->save(); } From cac3d81e73e4124e5d6e9e8c915d9bf532bb1df0 Mon Sep 17 00:00:00 2001 From: LukeWCS Date: Thu, 10 Apr 2025 18:38:29 +0200 Subject: [PATCH 0682/1214] [ticket/17492] Unintuitive checkbox status inverted for topic subscriptions PHPBB-17492 --- .../styles/prosilver/template/viewtopic_topic_tools.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/styles/prosilver/template/viewtopic_topic_tools.html b/phpBB/styles/prosilver/template/viewtopic_topic_tools.html index 272a434f6a2..78dbae2dd46 100644 --- a/phpBB/styles/prosilver/template/viewtopic_topic_tools.html +++ b/phpBB/styles/prosilver/template/viewtopic_topic_tools.html @@ -8,13 +8,13 @@
-
-

{L_NOTIFY_METHOD_EXPLAIN}
-
- -
-
diff --git a/phpBB/adm/style/acp_users_profile.html b/phpBB/adm/style/acp_users_profile.html index 9296638ff6e..54f7245f813 100644 --- a/phpBB/adm/style/acp_users_profile.html +++ b/phpBB/adm/style/acp_users_profile.html @@ -3,10 +3,6 @@
{L_USER_PROFILE} -
-
-
-

{L_BIRTHDAY_EXPLAIN}
{L_DAY}{L_COLON} {L_MONTH}{L_COLON} {L_YEAR}{L_COLON}
diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index dfe58eb3187..dc62e385cd9 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -76,7 +76,7 @@ function main($id, $mode) { // If giving usernames the admin is able to email inactive users too... $sql_ary = array( - 'SELECT' => 'user_id, username, user_email, user_jabber, user_notify_type, user_lang', + 'SELECT' => 'user_id, username, user_email, user_notify_type, user_lang', 'FROM' => array( USERS_TABLE => '', ), @@ -90,7 +90,7 @@ function main($id, $mode) if ($group_id) { $sql_ary = array( - 'SELECT' => 'u.user_id, u.user_email, u.username, u.username_clean, u.user_lang, u.user_jabber, u.user_notify_type', + 'SELECT' => 'u.user_id, u.user_email, u.username, u.username_clean, u.user_lang, u.user_notify_type', 'FROM' => array( USERS_TABLE => 'u', USER_GROUP_TABLE => 'ug', @@ -106,7 +106,7 @@ function main($id, $mode) else { $sql_ary = array( - 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_email, u.user_jabber, u.user_lang, u.user_notify_type', + 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_email, u.user_lang, u.user_notify_type', 'FROM' => array( USERS_TABLE => 'u', ), diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index e1a511852bc..138e06e5258 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -185,7 +185,7 @@ function main($id, $mode) trigger_error($user->lang['EMAIL_DISABLED'] . adm_back_link($this->u_action), E_USER_WARNING); } - $sql = 'SELECT user_id, username, user_email, user_lang, user_jabber, user_notify_type, user_regdate, user_actkey + $sql = 'SELECT user_id, username, user_email, user_lang, user_notify_type, user_regdate, user_actkey FROM ' . USERS_TABLE . ' WHERE ' . $db->sql_in_set('user_id', $mark) . ' AND user_inactive_reason'; diff --git a/phpBB/includes/acp/acp_jabber.php b/phpBB/includes/acp/acp_jabber.php deleted file mode 100644 index fce1dee1df5..00000000000 --- a/phpBB/includes/acp/acp_jabber.php +++ /dev/null @@ -1,139 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -/** -* @todo Check/enter/update transport info -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -class acp_jabber -{ - var $u_action; - - function main($id, $mode) - { - global $db, $user, $template, $phpbb_log, $request; - global $config, $phpbb_container, $phpbb_root_path, $phpEx; - - $jabber = $phpbb_container->get('messenger.method.jabber'); - - $user->add_lang('acp/board'); - - $submit = (isset($_POST['submit'])) ? true : false; - - if ($mode != 'settings') - { - return; - } - - $this->tpl_name = 'acp_jabber'; - $this->page_title = 'ACP_JABBER_SETTINGS'; - - $jab_enable = $request->variable('jab_enable', (bool) $config['jab_enable']); - $jab_host = $request->variable('jab_host', (string) $config['jab_host']); - $jab_port = $request->variable('jab_port', (int) $config['jab_port']); - $jab_username = $request->variable('jab_username', (string) $config['jab_username']); - $jab_password = $request->variable('jab_password', (string) $config['jab_password']); - $jab_package_size = $request->variable('jab_package_size', (int) $config['jab_package_size']); - $jab_use_ssl = $request->variable('jab_use_ssl', (bool) $config['jab_use_ssl']); - $jab_verify_peer = $request->variable('jab_verify_peer', (bool) $config['jab_verify_peer']); - $jab_verify_peer_name = $request->variable('jab_verify_peer_name', (bool) $config['jab_verify_peer_name']); - $jab_allow_self_signed = $request->variable('jab_allow_self_signed', (bool) $config['jab_allow_self_signed']); - - $form_name = 'acp_jabber'; - add_form_key($form_name); - - if ($submit) - { - if (!check_form_key($form_name)) - { - trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); - } - - $message = $user->lang['JAB_SETTINGS_CHANGED']; - $log = 'JAB_SETTINGS_CHANGED'; - - // Is this feature enabled? Then try to establish a connection - if ($jabber->is_enabled()) - { - if (!$jabber->connect()) - { - trigger_error($user->lang['ERR_JAB_CONNECT'] . '

' . $jabber->get_log() . adm_back_link($this->u_action), E_USER_WARNING); - } - - // We'll try to authorise using this account - if (!$jabber->login()) - { - trigger_error($user->lang['ERR_JAB_AUTH'] . '

' . $jabber->get_log() . adm_back_link($this->u_action), E_USER_WARNING); - } - - $jabber->disconnect(); - } - else - { - // This feature is disabled. - // We update the user table to be sure all users that have IM as notify type are set to both as notify type - // We set this to both because users still have their jabber address entered and may want to receive jabber notifications again once it is re-enabled. - $sql_ary = array( - 'user_notify_type' => $jabber::NOTIFY_BOTH, - ); - - $sql = 'UPDATE ' . USERS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_notify_type = ' . $jabber::NOTIFY_IM; - $db->sql_query($sql); - } - - $config->set('jab_enable', $jab_enable); - $config->set('jab_host', $jab_host); - $config->set('jab_port', $jab_port); - $config->set('jab_username', $jab_username); - if ($jab_password !== '********') - { - $config->set('jab_password', $jab_password); - } - $config->set('jab_package_size', $jab_package_size); - $config->set('jab_use_ssl', $jab_use_ssl); - $config->set('jab_verify_peer', $jab_verify_peer); - $config->set('jab_verify_peer_name', $jab_verify_peer_name); - $config->set('jab_allow_self_signed', $jab_allow_self_signed); - - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_' . $log); - trigger_error($message . adm_back_link($this->u_action)); - } - - $template->assign_vars(array( - 'U_ACTION' => $this->u_action, - 'JAB_ENABLE' => $jab_enable, - 'L_JAB_SERVER_EXPLAIN' => sprintf($user->lang['JAB_SERVER_EXPLAIN'], '', ''), - 'JAB_HOST' => $jab_host, - 'JAB_PORT' => ($jab_port) ? $jab_port : '', - 'JAB_USERNAME' => $jab_username, - 'JAB_PASSWORD' => $jab_password !== '' ? '********' : '', - 'JAB_PACKAGE_SIZE' => $jab_package_size, - 'JAB_USE_SSL' => $jab_use_ssl, - 'JAB_VERIFY_PEER' => $jab_verify_peer, - 'JAB_VERIFY_PEER_NAME' => $jab_verify_peer_name, - 'JAB_ALLOW_SELF_SIGNED' => $jab_allow_self_signed, - 'S_CAN_USE_SSL' => $jabber::can_use_ssl(), - 'S_GTALK_NOTE' => (!@function_exists('dns_get_record')) ? true : false, - )); - } -} diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index a954ec09f03..e01eace64e5 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1426,7 +1426,6 @@ function main($id, $mode) $user_row['iso_lang_id'] = $row['lang_id']; $data = array( - 'jabber' => $request->variable('jabber', $user_row['user_jabber'], true), 'bday_day' => 0, 'bday_month' => 0, 'bday_year' => 0, @@ -1458,9 +1457,6 @@ function main($id, $mode) if ($submit) { $error = validate_data($data, array( - 'jabber' => array( - array('string', true, 5, 255), - array('jabber')), 'bday_day' => array('num', true, 1, 31), 'bday_month' => array('num', true, 1, 12), 'bday_year' => array('num', true, 1901, gmdate('Y', time())), @@ -1496,7 +1492,6 @@ function main($id, $mode) if (!count($error)) { $sql_ary = array( - 'user_jabber' => $data['jabber'], 'user_birthday' => $data['user_birthday'], ); @@ -1553,7 +1548,6 @@ function main($id, $mode) unset($now); $template->assign_vars(array( - 'JABBER' => $data['jabber'], 'S_BIRTHDAY_DAY_OPTIONS' => $s_birthday_day_options, 'S_BIRTHDAY_MONTH_OPTIONS' => $s_birthday_month_options, 'S_BIRTHDAY_YEAR_OPTIONS' => $s_birthday_year_options, @@ -1787,7 +1781,6 @@ function main($id, $mode) $user_prefs_data = array( 'S_PREFS' => true, - 'S_JABBER_DISABLED' => ($config['jab_enable'] && $user_row['user_jabber'] && @extension_loaded('xml')) ? false : true, 'VIEW_EMAIL' => $data['viewemail'], 'MASS_EMAIL' => $data['massemail'], diff --git a/phpBB/includes/acp/info/acp_jabber.php b/phpBB/includes/acp/info/acp_jabber.php deleted file mode 100644 index 660299a12d8..00000000000 --- a/phpBB/includes/acp/info/acp_jabber.php +++ /dev/null @@ -1,34 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -class acp_jabber_info -{ - function module() - { - return array( - 'filename' => 'acp_jabber', - 'title' => 'ACP_JABBER_SETTINGS', - 'modes' => array( - 'settings' => array('title' => 'ACP_JABBER_SETTINGS', 'auth' => 'acl_a_jabber', 'cat' => array('ACP_CLIENT_COMMUNICATION')), - ), - ); - } - - function install() - { - } - - function uninstall() - { - } -} diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index e46091c9545..4bdaa127968 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -586,31 +586,3 @@ 'WEBPUSH_DROPDOWN_SUBSCRIBE' => 'Show “Subscribe” button in notification dropdown', 'WEBPUSH_DROPDOWN_SUBSCRIBE_EXPLAIN' => 'Display a “Subscribe” button in the Notification dropdown, allowing users to easily subscribe to push notifications from anywhere in the forum.', ]); - -// Jabber settings -$lang = array_merge($lang, array( - 'ACP_JABBER_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Jabber for instant messaging and board notifications. Jabber is an open source protocol and therefore available for use by anyone. Some Jabber servers include gateways or transports which allow you to contact users on other networks. Not all servers offer all transports and changes in protocols can prevent transports from operating. Please be sure to enter already registered account details - phpBB will use the details you enter here as is.', - - 'JAB_ALLOW_SELF_SIGNED' => 'Allow self-signed SSL certificates', - 'JAB_ALLOW_SELF_SIGNED_EXPLAIN' => 'Allow connections to Jabber server with self-signed SSL certificate.
Warning: Allowing self-signed SSL certificates may cause security implications.', - 'JAB_ENABLE' => 'Enable Jabber', - 'JAB_ENABLE_EXPLAIN' => 'Enables use of Jabber messaging and notifications.', - 'JAB_GTALK_NOTE' => 'Please note that GTalk will not work because the dns_get_record function could not be found. This function is not available in PHP4, and is not implemented on Windows platforms. It currently does not work on BSD-based systems, including Mac OS.', - 'JAB_PACKAGE_SIZE' => 'Jabber package size', - 'JAB_PACKAGE_SIZE_EXPLAIN' => 'This is the number of messages sent in one package. If set to 0 the message is sent immediately and will not be queued for later sending.', - 'JAB_PASSWORD' => 'Jabber password', - 'JAB_PASSWORD_EXPLAIN' => 'Warning: This password will be stored as plain text in the database, visible to everybody who can access your database or who can view this configuration page.', - 'JAB_PORT' => 'Jabber port', - 'JAB_PORT_EXPLAIN' => 'Leave blank unless you know it is not port 5222.', - 'JAB_SERVER' => 'Jabber server', - 'JAB_SERVER_EXPLAIN' => 'See %sjabber.org%s for a list of servers.', - 'JAB_SETTINGS_CHANGED' => 'Jabber settings changed successfully.', - 'JAB_USE_SSL' => 'Use SSL to connect', - 'JAB_USE_SSL_EXPLAIN' => 'If enabled a secure connection is tried to be established. The Jabber port will be modified to 5223 if port 5222 is specified.', - 'JAB_USERNAME' => 'Jabber username or JID', - 'JAB_USERNAME_EXPLAIN' => 'Specify a registered username or a valid JID. The username will not be checked for validity. If you only specify a username, then your JID will be the username and the server you specified above. Else, specify a valid JID, for example user@jabber.org.', - 'JAB_VERIFY_PEER' => 'Verify SSL certificate', - 'JAB_VERIFY_PEER_EXPLAIN' => 'Require verification of SSL certificate used by Jabber server.
Warning: Connecting peers with unverified SSL certificates may cause security implications.', - 'JAB_VERIFY_PEER_NAME' => 'Verify Jabber peer name', - 'JAB_VERIFY_PEER_NAME_EXPLAIN' => 'Require verification of peer name for Jabber servers using SSL / TLS connections.
Warning: Connecting to unverified peers may cause security implications.', -)); diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index 62c08f02262..e2bbef019e9 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -120,8 +120,6 @@ 'ACP_INACTIVE_USERS' => 'Inactive users', 'ACP_INDEX' => 'ACP index', - 'ACP_JABBER_SETTINGS' => 'Jabber settings', - 'ACP_LANGUAGE' => 'Language management', 'ACP_LANGUAGE_PACKS' => 'Language packs', 'ACP_LOAD_SETTINGS' => 'Load settings', @@ -642,7 +640,6 @@ 'LOG_DOWNLOAD_IP' => 'Added IP/hostname to download list
» %s', 'LOG_DOWNLOAD_REMOVE_IP' => 'Removed IP/hostname from download list
» %s', - 'LOG_ERROR_JABBER' => 'Jabber error
» %s', 'LOG_ERROR_EMAIL' => 'Email error
» %s', 'LOG_ERROR_CAPTCHA' => 'CAPTCHA error
» %s', @@ -686,11 +683,6 @@ 'LOG_IP_BROWSER_FORWARDED_CHECK' => 'Session IP/browser/X_FORWARDED_FOR check failed
»User IP “%1$s” checked against session IP “%2$s”, user browser string “%3$s” checked against session browser string “%4$s” and user X_FORWARDED_FOR string “%5$s” checked against session X_FORWARDED_FOR string “%6$s”.', - 'LOG_JAB_CHANGED' => 'Jabber account changed', - 'LOG_JAB_PASSCHG' => 'Jabber password changed', - 'LOG_JAB_REGISTER' => 'Jabber account registered', - 'LOG_JAB_SETTINGS_CHANGED' => 'Jabber settings changed', - 'LOG_LANGUAGE_PACK_DELETED' => 'Deleted language pack
» %s', 'LOG_LANGUAGE_PACK_INSTALLED' => 'Installed language pack
» %s', 'LOG_LANGUAGE_PACK_UPDATED' => 'Updated language pack details
» %s', diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index 590ccaaf9f5..516e8117684 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -168,7 +168,6 @@ $lang = array_merge($lang, array( 'ACL_A_BOARD' => 'Can alter board settings/check for updates', 'ACL_A_SERVER' => 'Can alter server/communication settings', - 'ACL_A_JABBER' => 'Can alter Jabber settings', 'ACL_A_PHPINFO' => 'Can view php settings', 'ACL_A_FORUM' => 'Can manage forums', From e32ebda982756be1c228b363398e9cdba78df966 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 13 Apr 2025 16:17:25 +0200 Subject: [PATCH 0692/1214] [ticket/17493] Remove memberlist IM page PHPBB-17493 --- phpBB/memberlist.php | 118 +----------------- .../prosilver/template/memberlist_im.html | 46 ------- 2 files changed, 2 insertions(+), 162 deletions(-) delete mode 100644 phpBB/styles/prosilver/template/memberlist_im.html diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 7b502e91c8b..c031b017886 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -373,120 +373,6 @@ ); break; - case 'contact': - - $page_title = $user->lang['IM_USER']; - $template_html = 'memberlist_im.html'; - - if (!$auth->acl_get('u_sendim')) - { - send_status_line(403, 'Forbidden'); - trigger_error('NOT_AUTHORISED'); - } - - $presence_img = ''; - switch ($action) - { - case 'jabber': - $lang = 'JABBER'; - $sql_field = 'user_jabber'; - $s_select = (@extension_loaded('xml') && $config['jab_enable']) ? 'S_SEND_JABBER' : 'S_NO_SEND_JABBER'; - $s_action = append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&action=$action&u=$user_id"); - break; - - default: - trigger_error('NO_MODE', E_USER_ERROR); - break; - } - - // Grab relevant data - $sql = "SELECT user_id, username, user_email, user_lang, $sql_field - FROM " . USERS_TABLE . " - WHERE user_id = $user_id - AND user_type IN (" . USER_NORMAL . ', ' . USER_FOUNDER . ')'; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (!$row) - { - trigger_error('NO_USER'); - } - else if (empty($row[$sql_field])) - { - trigger_error('IM_NO_DATA'); - } - - // Post data grab actions - switch ($action) - { - case 'jabber': - add_form_key('memberlist_messaging'); - - if ($submit && @extension_loaded('xml') && $config['jab_enable']) - { - if (check_form_key('memberlist_messaging')) - { - - $subject = sprintf($user->lang['IM_JABBER_SUBJECT'], $user->data['username'], $config['server_name']); - $message = $request->variable('message', '', true); - - if (empty($message)) - { - trigger_error('EMPTY_MESSAGE_IM'); - } - - $jabber = $phpbb_container->get('messenger.method.jabber'); - $jabber->set_use_queue(false); - - $jabber->template('profile_send_im', $row['user_lang']); - $jabber->subject(html_entity_decode($subject, ENT_COMPAT)); - $jabber->set_addresses($row); - - $jabber->assign_vars([ - 'BOARD_CONTACT' => phpbb_get_board_contact($config, $phpEx), - 'FROM_USERNAME' => html_entity_decode($user->data['username'], ENT_COMPAT), - 'TO_USERNAME' => html_entity_decode($row['username'], ENT_COMPAT), - 'MESSAGE' => html_entity_decode($message, ENT_COMPAT), - ]); - - $jabber->send(); - - $s_select = 'S_SENT_JABBER'; - } - else - { - trigger_error('FORM_INVALID'); - } - } - break; - } - - $template->assign_block_vars('navlinks', array( - 'BREADCRUMB_NAME' => $page_title, - 'U_BREADCRUMB' => append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&action=$action&u=$user_id"), - )); - - // Send vars to the template - $template->assign_vars(array( - 'IM_CONTACT' => $row[$sql_field], - 'A_IM_CONTACT' => addslashes($row[$sql_field]), - - 'USERNAME' => $row['username'], - 'CONTACT_NAME' => $row[$sql_field], - 'SITENAME' => $config['sitename'], - - 'PRESENCE_IMG' => $presence_img, - - 'L_SEND_IM_EXPLAIN' => $user->lang['IM_' . $lang], - 'L_IM_SENT_JABBER' => sprintf($user->lang['IM_SENT_JABBER'], $row['username']), - - $s_select => true, - 'S_IM_ACTION' => $s_action) - ); - - break; - case 'viewprofile': // Display a profile if ($user_id == ANONYMOUS && !$username) @@ -950,8 +836,8 @@ { // Generate the navlinks based on the selected topic $navlinks_sql_array = [ - 'SELECT' => 'f.parent_id, f.forum_parents, f.left_id, f.right_id, f.forum_type, f.forum_name, - f.forum_id, f.forum_desc, f.forum_desc_uid, f.forum_desc_bitfield, f.forum_desc_options, + 'SELECT' => 'f.parent_id, f.forum_parents, f.left_id, f.right_id, f.forum_type, f.forum_name, + f.forum_id, f.forum_desc, f.forum_desc_uid, f.forum_desc_bitfield, f.forum_desc_options, f.forum_options, t.topic_title', 'FROM' => [ FORUMS_TABLE => 'f', diff --git a/phpBB/styles/prosilver/template/memberlist_im.html b/phpBB/styles/prosilver/template/memberlist_im.html deleted file mode 100644 index f91454aacb7..00000000000 --- a/phpBB/styles/prosilver/template/memberlist_im.html +++ /dev/null @@ -1,46 +0,0 @@ - - -

{L_SEND_IM}

- -
- -
-
- -

{L_SEND_IM_EXPLAIN}

- - -

{L_IM_SENT_JABBER}

- - -
-
-
-
{USERNAME} [ {IM_CONTACT} ] {PRESENCE_IMG}
-
- - -
-
-
-
-
-
 
-
-
- -
-
 
-
{L_IM_NO_JABBER}
-
- - {S_FORM_TOKEN} -
- -
-
-
- -{L_CLOSE_WINDOW} - - From a7c7c38bfbfed5b069480c819218359eb1355d14 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 13 Apr 2025 20:48:15 +0200 Subject: [PATCH 0693/1214] [ticket/17493] Remove settings, values, and lang vars for jabber PHPBB-17493 --- phpBB/includes/functions_display.php | 5 ---- phpBB/includes/functions_user.php | 2 +- phpBB/includes/mcp/mcp_reports.php | 2 +- phpBB/includes/ucp/ucp_pm_viewmessage.php | 13 +--------- phpBB/includes/ucp/ucp_prefs.php | 7 ------ phpBB/includes/ucp/ucp_profile.php | 24 +++---------------- phpBB/includes/ucp/ucp_resend.php | 2 +- phpBB/install/convertors/convert_phpbb20.php | 1 - phpBB/install/schemas/schema_data.sql | 3 +-- phpBB/language/en/common.php | 6 ----- phpBB/language/en/memberlist.php | 5 ---- phpBB/language/en/ucp.php | 3 --- .../db/migration/data/v400/remove_jabber.php | 1 + phpBB/phpbb/permissions.php | 1 - phpBB/phpbb/ucp/controller/reset_password.php | 4 ++-- .../prosilver/template/memberlist_search.html | 6 ----- .../prosilver/template/memberlist_view.html | 1 - .../template/ucp_prefs_personal.html | 10 -------- .../template/ucp_profile_profile_info.html | 6 ----- phpBB/viewtopic.php | 9 ------- tests/auth/provider_apache_test.php | 1 - 21 files changed, 11 insertions(+), 101 deletions(-) diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index e8f49f03292..860bb8d89ab 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -1681,7 +1681,6 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl 'S_ONLINE' => ($config['load_onlinetrack'] && $online) ? true : false, 'RANK_IMG' => $user_rank_data['img'], 'RANK_IMG_SRC' => $user_rank_data['img_src'], - 'S_JABBER_ENABLED' => ($config['jab_enable']) ? true : false, 'S_WARNINGS' => ($auth->acl_getf_global('m_') || $auth->acl_get('m_warn')) ? true : false, @@ -1690,10 +1689,6 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl 'U_WARN' => ($warn_user_enabled && $auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $user_id) : '', 'U_PM' => ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && $can_receive_pm) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $user_id) : '', 'U_EMAIL' => $email, - 'U_JABBER' => ($data['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=jabber&u=' . $user_id) : '', - - 'USER_JABBER' => ($config['jab_enable'] && $auth->acl_get('u_sendim')) ? $data['user_jabber'] : '', - 'USER_JABBER_IMG' => ($config['jab_enable'] && $auth->acl_get('u_sendim') && $data['user_jabber']) ? $user->img('icon_contact_jabber', $data['user_jabber']) : '', 'L_SEND_EMAIL_USER' => $user->lang('SEND_EMAIL_USER', $username), 'L_CONTACT_USER' => $user->lang('CONTACT_USER', $username), diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index e495198a684..563564cf67e 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -2730,7 +2730,7 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna case 'approve': // Make sure we only approve those which are pending ;) - $sql = 'SELECT u.user_id, u.user_email, u.username, u.username_clean, u.user_notify_type, u.user_jabber, u.user_lang + $sql = 'SELECT u.user_id, u.user_email, u.username, u.username_clean, u.user_notify_type, u.user_lang FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug WHERE ug.group_id = ' . $group_id . ' AND ug.user_pending = 1 diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 7843bc1efe5..3325fd6b8da 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -661,7 +661,7 @@ function close_report($report_id_list, $mode, $action, $pm = false) { $post_info = ($pm) ? phpbb_get_pm_data($post_id_list) : phpbb_get_post_data($post_id_list, 'm_report'); - $sql = "SELECT r.report_id, r.$id_column, r.report_closed, r.user_id, r.user_notify, u.username, u.username_clean, u.user_email, u.user_jabber, u.user_lang, u.user_notify_type + $sql = "SELECT r.report_id, r.$id_column, r.report_closed, r.user_id, r.user_notify, u.username, u.username_clean, u.user_email, u.user_lang, u.user_notify_type FROM " . REPORTS_TABLE . ' r, ' . USERS_TABLE . ' u WHERE ' . $db->sql_in_set('r.report_id', $report_id_list) . ' ' . (($action == 'close') ? 'AND r.report_closed = 0' : '') . ' diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index 060a9941b39..d418e498871 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -201,18 +201,13 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) } } - $u_pm = $u_jabber = ''; + $u_pm = ''; if ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && ($user_info['user_allow_pm'] || $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))) { $u_pm = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $author_id); } - if ($config['jab_enable'] && $user_info['user_jabber'] && $auth->acl_get('u_sendim')) - { - $u_jabber = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=jabber&u=' . $author_id); - } - $can_edit_pm = ($message_row['message_time'] > time() - ($config['pm_edit_time'] * 60) || !$config['pm_edit_time']) && $folder_id == PRIVMSGS_OUTBOX && $auth->acl_get('u_pm_edit'); $msg_data = array( @@ -248,7 +243,6 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) 'MESSAGE_ID' => $message_row['msg_id'], 'U_PM' => $u_pm, - 'U_JABBER' => $u_jabber, 'U_DELETE' => ($auth->acl_get('u_pm_delete')) ? "$url&mode=compose&action=delete&f=$folder_id&p=" . $message_row['msg_id'] : '', 'U_EMAIL' => $user_info['email'], @@ -358,11 +352,6 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) 'NAME' => $user->lang['SEND_EMAIL'], 'U_CONTACT' => $user_info['email'], ), - array( - 'ID' => 'jabber', - 'NAME' => $user->lang['JABBER'], - 'U_CONTACT' => $u_jabber, - ), ); foreach ($contact_fields as $field) diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index d08a81b8545..480b4a3d12f 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -54,12 +54,6 @@ function main($id, $mode) 'allowpm' => $request->variable('allowpm', (bool) $user->data['user_allow_pm']), ); - if ($data['notifymethod'] == messenger_interface::NOTIFY_IM && (!$config['jab_enable'] || !$user->data['user_jabber'] || !@extension_loaded('xml'))) - { - // Jabber isnt enabled, or no jabber field filled in. Update the users table to be sure its correct. - $data['notifymethod'] = messenger_interface::NOTIFY_BOTH; - } - /** * Add UCP edit global settings data before they are assigned to the template or submitted * @@ -218,7 +212,6 @@ function main($id, $mode) 'options' => $timezone_select, ], 'S_CAN_HIDE_ONLINE' => (bool) $auth->acl_get('u_hideonline'), - 'S_SELECT_NOTIFY' => (bool) ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')), ]); break; diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 52b007a1fe6..77c1a56e9d3 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -284,9 +284,7 @@ function main($id, $mode) $cp_data = $cp_error = array(); - $data = array( - 'jabber' => $request->variable('jabber', $user->data['user_jabber'], true), - ); + $data = []; if ($config['allow_birthdays']) { @@ -318,11 +316,7 @@ function main($id, $mode) if ($submit) { - $validate_array = array( - 'jabber' => array( - array('string', true, 5, 255), - array('jabber')), - ); + $validate_array = []; if ($config['allow_birthdays']) { @@ -363,18 +357,8 @@ function main($id, $mode) if (!count($error)) { - $data['notify'] = $user->data['user_notify_type']; - - if ($data['notify'] == messenger_interface::NOTIFY_IM && (!$config['jab_enable'] || !$data['jabber'] || !@extension_loaded('xml'))) - { - // User has not filled in a jabber address (Or one of the modules is disabled or jabber is disabled) - // Disable notify by Jabber now for this user. - $data['notify'] = messenger_interface::NOTIFY_EMAIL; - } - $sql_ary = array( - 'user_jabber' => $data['jabber'], - 'user_notify_type' => $data['notify'], + 'user_notify_type' => messenger_interface::NOTIFY_EMAIL, ); if ($config['allow_birthdays']) @@ -446,8 +430,6 @@ function main($id, $mode) $template->assign_vars(array( 'ERROR' => (count($error)) ? implode('
', $error) : '', - 'S_JABBER_ENABLED' => $config['jab_enable'], - 'JABBER' => $data['jabber'], )); // Get additional profile fields and assign them to the template block var 'profile_fields' diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 43e26567e5e..d3512865dbf 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -128,7 +128,7 @@ function main($id, $mode) // Grab an array of user_id's with a_user permissions ... these users can activate a user $admin_ary = $auth->acl_get_list(false, 'a_user', false); - $sql = 'SELECT user_id, username, user_email, user_lang, user_jabber, user_notify_type + $sql = 'SELECT user_id, username, user_email, user_lang, user_notify_type FROM ' . USERS_TABLE . ' WHERE ' . $db->sql_in_set('user_id', $admin_ary[0]['a_user']); $result = $db->sql_query($sql); diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 8750c24dcc7..cff03b712e8 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -923,7 +923,6 @@ array('user_inactive_reason', '', 'phpbb_inactive_reason'), array('user_inactive_time', '', 'phpbb_inactive_time'), - array('user_jabber', '', ''), array('user_rank', 'users.user_rank', 'intval'), array('user_permissions', '', ''), diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index ad9d5e3e5ab..fdae5771909 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -429,7 +429,6 @@ INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_group', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_groupadd', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_groupdel', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_icons', 1); -INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_jabber', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_language', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_mauth', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_modules', 1); @@ -559,7 +558,7 @@ INSERT INTO phpbb_ranks (rank_title, rank_min, rank_special, rank_image) VALUES # -- Roles data # Standard Admin (a_) -INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 1, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%' AND auth_option NOT IN ('a_switchperm', 'a_jabber', 'a_phpinfo', 'a_server', 'a_backup', 'a_styles', 'a_clearlogs', 'a_modules', 'a_language', 'a_email', 'a_bots', 'a_search', 'a_aauth', 'a_roles'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 1, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%' AND auth_option NOT IN ('a_switchperm', 'a_phpinfo', 'a_server', 'a_backup', 'a_styles', 'a_clearlogs', 'a_modules', 'a_language', 'a_email', 'a_bots', 'a_search', 'a_aauth', 'a_roles'); # Forum admin (a_) INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 2, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%' AND auth_option IN ('a_', 'a_authgroups', 'a_authusers', 'a_fauth', 'a_forum', 'a_forumadd', 'a_forumdel', 'a_mauth', 'a_prune', 'a_uauth', 'a_viewauth', 'a_viewlogs'); diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 7b6623e19a1..e9225c62742 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -232,8 +232,6 @@ 'ENTER_USERNAME' => 'Enter username', 'ERR_CHANGING_DIRECTORY' => 'Unable to change directory.', 'ERR_CONNECTING_SERVER' => 'Error connecting to the server.', - 'ERR_JAB_AUTH' => 'Could not authorise on Jabber server.', - 'ERR_JAB_CONNECT' => 'Could not connect to Jabber server.', 'ERR_UNABLE_TO_LOGIN' => 'The specified username or password is incorrect.', 'ERR_UNWATCHING' => 'An error occurred while trying to unsubscribe.', 'ERR_WATCHING' => 'An error occurred while trying to subscribe.', @@ -372,7 +370,6 @@ 'IP' => 'IP', 'IP_BLACKLISTED' => 'Your IP %1$s has been blocked because it is blacklisted. For details please see %2$s.', - 'JABBER' => 'Jabber', 'JOINED' => 'Joined', 'JUMP_PAGE' => 'Enter the page number you wish to go to', 'JUMP_TO' => 'Jump to', @@ -801,7 +798,6 @@ 'TOO_LONG_CONFIRM_CODE' => 'The confirm code you entered is too long.', 'TOO_LONG_DATEFORMAT' => 'The date format you entered is too long.', - 'TOO_LONG_JABBER' => 'The Jabber account name you entered is too long.', 'TOO_LONG_NEW_PASSWORD' => 'The password you entered is too long.', 'TOO_LONG_PASSWORD_CONFIRM' => 'The password confirmation you entered is too long.', 'TOO_LONG_USER_PASSWORD' => 'The password you entered is too long.', @@ -814,7 +810,6 @@ 'TOO_SHORT_CONFIRM_CODE' => 'The confirm code you entered is too short.', 'TOO_SHORT_DATEFORMAT' => 'The date format you entered is too short.', - 'TOO_SHORT_JABBER' => 'The Jabber account name you entered is too short.', 'TOO_SHORT_NEW_PASSWORD' => 'The password you entered is too short.', 'TOO_SHORT_PASSWORD_CONFIRM' => 'The password confirmation you entered is too short.', 'TOO_SHORT_USER_PASSWORD' => 'The password you entered is too short.', @@ -943,7 +938,6 @@ 'WRONG_PASSWORD' => 'You entered an incorrect password.', 'WRONG_DATA_COLOUR' => 'The colour value you entered is invalid.', - 'WRONG_DATA_JABBER' => 'The name you entered is not a valid Jabber account name.', 'WRONG_DATA_LANG' => 'The language you specified is not valid.', 'WRONG_DATA_POST_SD' => 'The post sort direction you specified is not valid.', 'WRONG_DATA_POST_SK' => 'The post sort option you specified is not valid.', diff --git a/phpBB/language/en/memberlist.php b/phpBB/language/en/memberlist.php index 2e757043bb9..09675d80958 100644 --- a/phpBB/language/en/memberlist.php +++ b/phpBB/language/en/memberlist.php @@ -78,16 +78,12 @@ 'IM_ADD_CONTACT' => 'Add Contact', 'IM_DOWNLOAD_APP' => 'Download application', - 'IM_JABBER' => 'Please note that users may have selected to not receive unsolicited instant messages.', - 'IM_JABBER_SUBJECT' => 'This is an automated message please do not reply! Message from user %1$s at %2$s.', 'IM_MESSAGE' => 'Your message', 'IM_NAME' => 'Your Name', 'IM_NO_DATA' => 'There is no suitable contact information for this user.', - 'IM_NO_JABBER' => 'Sorry, direct messaging of Jabber users is not supported on this board. You will need a Jabber client installed on your system to contact the recipient above.', 'IM_RECIPIENT' => 'Recipient', 'IM_SEND' => 'Send message', 'IM_SEND_MESSAGE' => 'Send message', - 'IM_SENT_JABBER' => 'Your message to %1$s has been sent successfully.', 'IM_USER' => 'Send an instant message', 'LAST_ACTIVE' => 'Last active', @@ -125,7 +121,6 @@ 'SENDER_NAME' => 'Your name', 'SEND_ICQ_MESSAGE' => 'Send ICQ message', 'SEND_IM' => 'Instant messaging', - 'SEND_JABBER_MESSAGE' => 'Send Jabber message', 'SEND_MESSAGE' => 'Message', 'SEND_YIM_MESSAGE' => 'Send YIM message', 'SORT_EMAIL' => 'Email', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 3d90f15742d..bf5299e7aa1 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -331,7 +331,6 @@ 'NOTIFICATION_GROUP_POSTING' => 'Posting Notifications', 'NOTIFICATION_METHOD_BOARD' => 'Notifications', 'NOTIFICATION_METHOD_EMAIL' => 'Email', - 'NOTIFICATION_METHOD_JABBER' => 'Jabber', 'NOTIFICATION_METHOD_WEBPUSH' => 'Web Push', 'NOTIFICATION_TYPE' => 'Notification type', 'NOTIFICATION_TYPE_BOOKMARK' => 'Someone replies to a topic you have bookmarked', @@ -354,7 +353,6 @@ 'NOTIFY_METHOD_BOTH' => 'Both', 'NOTIFY_METHOD_EMAIL' => 'Email only', 'NOTIFY_METHOD_EXPLAIN' => 'Method for sending messages sent via this board.', - 'NOTIFY_METHOD_IM' => 'Jabber only', 'NOTIFY_ON_PM' => 'Notify me on new private messages', 'NOTIFY_WEBPUSH_ENABLE' => 'Enable receiving Web Push notifications', 'NOTIFY_WEBPUSH_ENABLE_EXPLAIN' => 'Enable receiving browser-based push notifications.
The notifications can be turned off at any time in your browser settings, by unsubscribing, or by disabling the push notifications below.', @@ -534,7 +532,6 @@ 'UCP_COPPA_BEFORE' => 'Before %s', 'UCP_COPPA_ON_AFTER' => 'On or after %s', 'UCP_EMAIL_ACTIVATE' => 'Please note that you will need to enter a valid email address before your account is activated. You will receive an email at the address you provide that contains an account activation link.', - 'UCP_JABBER' => 'Jabber address', 'UCP_LOGIN_LINK' => 'Set up an external account association', 'UCP_MAIN' => 'Overview', diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index 1669e7e0c17..2a6ed89c21f 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -20,6 +20,7 @@ class remove_jabber extends migration public static function depends_on(): array { return [ + '\phpbb\db\migration\data\v31x\add_jabber_ssl_context_config_options', '\phpbb\db\migration\data\v400\dev', '\phpbb\db\migration\data\v400\add_webpush', ]; diff --git a/phpBB/phpbb/permissions.php b/phpBB/phpbb/permissions.php index 79102f96a80..afbd7109713 100644 --- a/phpBB/phpbb/permissions.php +++ b/phpBB/phpbb/permissions.php @@ -317,7 +317,6 @@ public function get_permission_lang($permission) // Admin Permissions 'a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), 'a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), - 'a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), 'a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), 'a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), diff --git a/phpBB/phpbb/ucp/controller/reset_password.php b/phpBB/phpbb/ucp/controller/reset_password.php index 95a2a82b121..d9ec05316c1 100644 --- a/phpBB/phpbb/ucp/controller/reset_password.php +++ b/phpBB/phpbb/ucp/controller/reset_password.php @@ -176,7 +176,7 @@ public function request() } $sql_array = [ - 'SELECT' => 'user_id, username, user_permissions, user_email, user_jabber, user_notify_type, user_type,' + 'SELECT' => 'user_id, username, user_permissions, user_email, user_notify_type, user_type,' . ' user_lang, user_inactive_reason, reset_token, reset_token_expiration', 'FROM' => [$this->users_table => 'u'], 'WHERE' => "user_email = '" . $this->db->sql_escape($email) . "'" . @@ -308,7 +308,7 @@ public function reset() add_form_key('ucp_reset_password'); $sql_array = [ - 'SELECT' => 'user_id, username, user_permissions, user_email, user_jabber, user_notify_type, user_type,' + 'SELECT' => 'user_id, username, user_permissions, user_email, user_notify_type, user_type,' . ' user_lang, user_inactive_reason, reset_token, reset_token_expiration', 'FROM' => [$this->users_table => 'u'], 'WHERE' => 'user_id = ' . $user_id, diff --git a/phpBB/styles/prosilver/template/memberlist_search.html b/phpBB/styles/prosilver/template/memberlist_search.html index 3d9e0dec2b7..a5644cc0c06 100644 --- a/phpBB/styles/prosilver/template/memberlist_search.html +++ b/phpBB/styles/prosilver/template/memberlist_search.html @@ -29,12 +29,6 @@

{L_FIND_USERNAME}

- - -
-
-
-
diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index 2a69cb08bef..2a6d13847af 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -75,7 +75,6 @@

{L_CONTACT_USER}

{L_EMAIL_ADDRESS}{L_COLON}
{L_SEND_EMAIL_USER}
{L_PM}{L_COLON}
{L_SEND_PRIVATE_MESSAGE}
-
{L_JABBER}{L_COLON}
{L_SEND_JABBER_MESSAGE}
{L_JABBER}{L_COLON}
{USER_JABBER}
diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index fe1ea17c746..82d2274c2f5 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -40,16 +40,6 @@

{L_TITLE}

- -
-
-
- - - -
-
-
diff --git a/phpBB/styles/prosilver/template/ucp_profile_profile_info.html b/phpBB/styles/prosilver/template/ucp_profile_profile_info.html index 0f0acc6f99a..0dd54ee85fa 100644 --- a/phpBB/styles/prosilver/template/ucp_profile_profile_info.html +++ b/phpBB/styles/prosilver/template/ucp_profile_profile_info.html @@ -21,12 +21,6 @@

{L_TITLE} [ {L_UCP_JABBER}{L_COLON} -
-

-
for="{profile_fields.FIELD_ID}">{profile_fields.LANG_NAME}{L_COLON} * diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 152e7ee52fa..31812042922 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -802,7 +802,6 @@ 'SEARCH_IMG' => $user->img('icon_user_search', 'SEARCH_USER_POSTS'), 'PM_IMG' => $user->img('icon_contact_pm', 'SEND_PRIVATE_MESSAGE'), 'EMAIL_IMG' => $user->img('icon_contact_email', 'SEND_EMAIL'), - 'JABBER_IMG' => $user->img('icon_contact_jabber', 'JABBER') , 'REPORT_IMG' => $user->img('icon_post_report', 'REPORT_POST'), 'REPORTED_IMG' => $user->img('icon_topic_reported', 'POST_REPORTED'), 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'POST_UNAPPROVED'), @@ -1423,7 +1422,6 @@ 'rank_image_src' => '', 'pm' => '', 'email' => '', - 'jabber' => '', 'search' => '', 'age' => '', @@ -1493,7 +1491,6 @@ 'contact_user' => $user->lang('CONTACT_USER', get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['username'])), 'online' => false, - 'jabber' => ($config['jab_enable'] && $row['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&action=jabber&u=$poster_id") : '', 'search' => ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$poster_id&sr=posts") : '', 'author_full' => get_username_string('full', $poster_id, $row['username'], $row['user_colour']), @@ -2068,7 +2065,6 @@ 'U_SEARCH' => $user_cache[$poster_id]['search'], 'U_PM' => $u_pm, 'U_EMAIL' => $user_cache[$poster_id]['email'], - 'U_JABBER' => $user_cache[$poster_id]['jabber'], 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p={$row['post_id']}&f={$row['forum_id']}&redirect=" . urlencode(str_replace('&', '&', $viewtopic_url . '&p=' . $row['post_id'] . '#p' . $row['post_id']))), 'U_REPORT' => ($auth->acl_get('f_report', $forum_id)) ? $phpbb_container->get('controller.helper')->route('phpbb_report_post_controller', array('id' => $row['post_id'])) : '', @@ -2179,11 +2175,6 @@ 'NAME' => $user->lang['SEND_EMAIL'], 'U_CONTACT' => $user_cache[$poster_id]['email'], ), - array( - 'ID' => 'jabber', - 'NAME' => $user->lang['JABBER'], - 'U_CONTACT' => $user_cache[$poster_id]['jabber'], - ), ); foreach ($contact_fields as $field) diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index bc211b4ed54..3604bbf9cd2 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -165,7 +165,6 @@ public function test_autologin() 'user_sig' => '', 'user_sig_bbcode_uid' => '', 'user_sig_bbcode_bitfield' => '', - 'user_jabber' => '', 'user_actkey' => '', 'user_actkey_expiration' => 0, 'user_newpasswd' => '', From 3128b3fa9b3ceb7d05855bc6624d31aff71b6169 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 13 Apr 2025 21:00:19 +0200 Subject: [PATCH 0694/1214] [ticket/17493] Remove more jabber permissions and elements PHPBB-17493 --- phpBB/develop/add_permissions.php | 13 +- phpBB/includes/functions_user.php | 202 ------------------ phpBB/install/schemas/schema_data.sql | 9 +- phpBB/language/en/acp/permissions_phpbb.php | 1 - phpBB/memberlist.php | 10 - phpBB/phpbb/cron/task/core/queue.php | 2 +- .../db/migration/data/v400/remove_jabber.php | 2 + phpBB/phpbb/message/message.php | 26 +-- phpBB/phpbb/permissions.php | 1 - tests/functions/validate_jabber_test.php | 83 ------- 10 files changed, 26 insertions(+), 323 deletions(-) delete mode 100644 tests/functions/validate_jabber_test.php diff --git a/phpBB/develop/add_permissions.php b/phpBB/develop/add_permissions.php index 6b8913de8c8..1531e67de64 100644 --- a/phpBB/develop/add_permissions.php +++ b/phpBB/develop/add_permissions.php @@ -140,7 +140,6 @@ 'u_sendemail' => array(0, 1), 'u_readpm' => array(0, 1), 'u_sendpm' => array(0, 1), - 'u_sendim' => array(0, 1), 'u_hideonline' => array(0, 1), 'u_viewonline' => array(0, 1), 'u_viewprofile' => array(0, 1), @@ -247,7 +246,7 @@ function mass_auth($ug_type, $forum_id, $ug_id, $acl_list, $setting) { if (!isset($group_ids[$ug_id])) { - $sql = 'SELECT group_id FROM ' . GROUPS_TABLE . " + $sql = 'SELECT group_id FROM ' . GROUPS_TABLE . " WHERE group_name = '" . strtoupper($ug_id) . "'"; $result = $db->sql_query_limit($sql, 1); $id = (int) $db->sql_fetchfield('group_id', 0, $result); @@ -340,7 +339,7 @@ function mass_auth($ug_type, $forum_id, $ug_id, $acl_list, $setting) case ACL_NO: if (isset($cur_auth[$forum][$auth_option_id])) { - $sql_ary['delete'][] = "DELETE FROM $table + $sql_ary['delete'][] = "DELETE FROM $table WHERE forum_id = $forum AND auth_option_id = $auth_option_id AND $id_field = $ug_id"; @@ -354,10 +353,10 @@ function mass_auth($ug_type, $forum_id, $ug_id, $acl_list, $setting) } else if ($cur_auth[$forum][$auth_option_id] != $setting) { - $sql_ary['update'][] = "UPDATE " . $table . " - SET auth_setting = $setting - WHERE $id_field = $ug_id - AND forum_id = $forum + $sql_ary['update'][] = "UPDATE " . $table . " + SET auth_setting = $setting + WHERE $id_field = $ug_id + AND forum_id = $forum AND auth_option_id = $auth_option_id"; } } diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 563564cf67e..42c9e7fd5a7 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -1514,208 +1514,6 @@ function validate_user_email($email, $allowed_email = false) return false; } -/** -* Validate jabber address -* Taken from the jabber class within flyspray (see author notes) -* -* @author flyspray.org -*/ -function validate_jabber($jid) -{ - if (!$jid) - { - return false; - } - - $separator_pos = strpos($jid, '@'); - - if ($separator_pos === false) - { - return 'WRONG_DATA'; - } - - $username = substr($jid, 0, $separator_pos); - $realm = substr($jid, $separator_pos + 1); - - if (strlen($username) == 0 || strlen($realm) < 3) - { - return 'WRONG_DATA'; - } - - $arr = explode('.', $realm); - - if (count($arr) == 0) - { - return 'WRONG_DATA'; - } - - foreach ($arr as $part) - { - if (substr($part, 0, 1) == '-' || substr($part, -1, 1) == '-') - { - return 'WRONG_DATA'; - } - - if (!preg_match("@^[a-zA-Z0-9-.]+$@", $part)) - { - return 'WRONG_DATA'; - } - } - - $boundary = array(array(0, 127), array(192, 223), array(224, 239), array(240, 247), array(248, 251), array(252, 253)); - - // Prohibited Characters RFC3454 + RFC3920 - $prohibited = array( - // Table C.1.1 - array(0x0020, 0x0020), // SPACE - // Table C.1.2 - array(0x00A0, 0x00A0), // NO-BREAK SPACE - array(0x1680, 0x1680), // OGHAM SPACE MARK - array(0x2000, 0x2001), // EN QUAD - array(0x2001, 0x2001), // EM QUAD - array(0x2002, 0x2002), // EN SPACE - array(0x2003, 0x2003), // EM SPACE - array(0x2004, 0x2004), // THREE-PER-EM SPACE - array(0x2005, 0x2005), // FOUR-PER-EM SPACE - array(0x2006, 0x2006), // SIX-PER-EM SPACE - array(0x2007, 0x2007), // FIGURE SPACE - array(0x2008, 0x2008), // PUNCTUATION SPACE - array(0x2009, 0x2009), // THIN SPACE - array(0x200A, 0x200A), // HAIR SPACE - array(0x200B, 0x200B), // ZERO WIDTH SPACE - array(0x202F, 0x202F), // NARROW NO-BREAK SPACE - array(0x205F, 0x205F), // MEDIUM MATHEMATICAL SPACE - array(0x3000, 0x3000), // IDEOGRAPHIC SPACE - // Table C.2.1 - array(0x0000, 0x001F), // [CONTROL CHARACTERS] - array(0x007F, 0x007F), // DELETE - // Table C.2.2 - array(0x0080, 0x009F), // [CONTROL CHARACTERS] - array(0x06DD, 0x06DD), // ARABIC END OF AYAH - array(0x070F, 0x070F), // SYRIAC ABBREVIATION MARK - array(0x180E, 0x180E), // MONGOLIAN VOWEL SEPARATOR - array(0x200C, 0x200C), // ZERO WIDTH NON-JOINER - array(0x200D, 0x200D), // ZERO WIDTH JOINER - array(0x2028, 0x2028), // LINE SEPARATOR - array(0x2029, 0x2029), // PARAGRAPH SEPARATOR - array(0x2060, 0x2060), // WORD JOINER - array(0x2061, 0x2061), // FUNCTION APPLICATION - array(0x2062, 0x2062), // INVISIBLE TIMES - array(0x2063, 0x2063), // INVISIBLE SEPARATOR - array(0x206A, 0x206F), // [CONTROL CHARACTERS] - array(0xFEFF, 0xFEFF), // ZERO WIDTH NO-BREAK SPACE - array(0xFFF9, 0xFFFC), // [CONTROL CHARACTERS] - array(0x1D173, 0x1D17A), // [MUSICAL CONTROL CHARACTERS] - // Table C.3 - array(0xE000, 0xF8FF), // [PRIVATE USE, PLANE 0] - array(0xF0000, 0xFFFFD), // [PRIVATE USE, PLANE 15] - array(0x100000, 0x10FFFD), // [PRIVATE USE, PLANE 16] - // Table C.4 - array(0xFDD0, 0xFDEF), // [NONCHARACTER CODE POINTS] - array(0xFFFE, 0xFFFF), // [NONCHARACTER CODE POINTS] - array(0x1FFFE, 0x1FFFF), // [NONCHARACTER CODE POINTS] - array(0x2FFFE, 0x2FFFF), // [NONCHARACTER CODE POINTS] - array(0x3FFFE, 0x3FFFF), // [NONCHARACTER CODE POINTS] - array(0x4FFFE, 0x4FFFF), // [NONCHARACTER CODE POINTS] - array(0x5FFFE, 0x5FFFF), // [NONCHARACTER CODE POINTS] - array(0x6FFFE, 0x6FFFF), // [NONCHARACTER CODE POINTS] - array(0x7FFFE, 0x7FFFF), // [NONCHARACTER CODE POINTS] - array(0x8FFFE, 0x8FFFF), // [NONCHARACTER CODE POINTS] - array(0x9FFFE, 0x9FFFF), // [NONCHARACTER CODE POINTS] - array(0xAFFFE, 0xAFFFF), // [NONCHARACTER CODE POINTS] - array(0xBFFFE, 0xBFFFF), // [NONCHARACTER CODE POINTS] - array(0xCFFFE, 0xCFFFF), // [NONCHARACTER CODE POINTS] - array(0xDFFFE, 0xDFFFF), // [NONCHARACTER CODE POINTS] - array(0xEFFFE, 0xEFFFF), // [NONCHARACTER CODE POINTS] - array(0xFFFFE, 0xFFFFF), // [NONCHARACTER CODE POINTS] - array(0x10FFFE, 0x10FFFF), // [NONCHARACTER CODE POINTS] - // Table C.5 - array(0xD800, 0xDFFF), // [SURROGATE CODES] - // Table C.6 - array(0xFFF9, 0xFFF9), // INTERLINEAR ANNOTATION ANCHOR - array(0xFFFA, 0xFFFA), // INTERLINEAR ANNOTATION SEPARATOR - array(0xFFFB, 0xFFFB), // INTERLINEAR ANNOTATION TERMINATOR - array(0xFFFC, 0xFFFC), // OBJECT REPLACEMENT CHARACTER - array(0xFFFD, 0xFFFD), // REPLACEMENT CHARACTER - // Table C.7 - array(0x2FF0, 0x2FFB), // [IDEOGRAPHIC DESCRIPTION CHARACTERS] - // Table C.8 - array(0x0340, 0x0340), // COMBINING GRAVE TONE MARK - array(0x0341, 0x0341), // COMBINING ACUTE TONE MARK - array(0x200E, 0x200E), // LEFT-TO-RIGHT MARK - array(0x200F, 0x200F), // RIGHT-TO-LEFT MARK - array(0x202A, 0x202A), // LEFT-TO-RIGHT EMBEDDING - array(0x202B, 0x202B), // RIGHT-TO-LEFT EMBEDDING - array(0x202C, 0x202C), // POP DIRECTIONAL FORMATTING - array(0x202D, 0x202D), // LEFT-TO-RIGHT OVERRIDE - array(0x202E, 0x202E), // RIGHT-TO-LEFT OVERRIDE - array(0x206A, 0x206A), // INHIBIT SYMMETRIC SWAPPING - array(0x206B, 0x206B), // ACTIVATE SYMMETRIC SWAPPING - array(0x206C, 0x206C), // INHIBIT ARABIC FORM SHAPING - array(0x206D, 0x206D), // ACTIVATE ARABIC FORM SHAPING - array(0x206E, 0x206E), // NATIONAL DIGIT SHAPES - array(0x206F, 0x206F), // NOMINAL DIGIT SHAPES - // Table C.9 - array(0xE0001, 0xE0001), // LANGUAGE TAG - array(0xE0020, 0xE007F), // [TAGGING CHARACTERS] - // RFC3920 - array(0x22, 0x22), // " - array(0x26, 0x26), // & - array(0x27, 0x27), // ' - array(0x2F, 0x2F), // / - array(0x3A, 0x3A), // : - array(0x3C, 0x3C), // < - array(0x3E, 0x3E), // > - array(0x40, 0x40) // @ - ); - - $pos = 0; - $result = true; - - while ($pos < strlen($username)) - { - $len = $uni = 0; - for ($i = 0; $i <= 5; $i++) - { - if (ord($username[$pos]) >= $boundary[$i][0] && ord($username[$pos]) <= $boundary[$i][1]) - { - $len = $i + 1; - $uni = (ord($username[$pos]) - $boundary[$i][0]) * pow(2, $i * 6); - - for ($k = 1; $k < $len; $k++) - { - $uni += (ord($username[$pos + $k]) - 128) * pow(2, ($i - $k) * 6); - } - - break; - } - } - - if ($len == 0) - { - return 'WRONG_DATA'; - } - - foreach ($prohibited as $pval) - { - if ($uni >= $pval[0] && $uni <= $pval[1]) - { - $result = false; - break 2; - } - } - - $pos = $pos + $len; - } - - if (!$result) - { - return 'WRONG_DATA'; - } - - return false; -} - /** * Validate hex colour value * diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index fdae5771909..7f53a3adc42 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -482,7 +482,6 @@ INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_readpm', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_savedrafts', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_search', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sendemail', 1); -INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sendim', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sendpm', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sig', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_viewonline', 1); @@ -528,10 +527,10 @@ INSERT INTO phpbb_forums (forum_name, forum_desc, left_id, right_id, parent_id, INSERT INTO phpbb_forums (forum_name, forum_desc, left_id, right_id, parent_id, forum_type, forum_posts_approved, forum_posts_unapproved, forum_posts_softdeleted, forum_topics_approved, forum_topics_unapproved, forum_topics_softdeleted, forum_last_post_id, forum_last_poster_id, forum_last_poster_name, forum_last_poster_colour, forum_last_post_subject, forum_last_post_time, forum_link, forum_password, forum_image, forum_rules, forum_rules_link, forum_rules_uid, forum_desc_uid, prune_freq, prune_days, prune_viewed, forum_parents, forum_flags) VALUES ('{L_FORUMS_TEST_FORUM_TITLE}', '{L_FORUMS_TEST_FORUM_DESC}', 2, 3, 1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 'Admin', 'AA0000', '{L_TOPICS_TOPIC_TITLE}', 972086460, '', '', '', '', '', '', '', 1, 7, 7, '', 48); # -- Users / Anonymous user -INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_actkey_expiration, user_newpasswd, user_allow_massemail) VALUES (2, 1, 'Anonymous', 'anonymous', 0, '', '', 'en', 1, 0, '', 0, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', 0, '', 0); +INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_actkey, user_actkey_expiration, user_newpasswd, user_allow_massemail) VALUES (2, 1, 'Anonymous', 'anonymous', 0, '', '', 'en', 1, 0, '', 0, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', 0, '', 0); # -- username: Admin password: admin (change this or remove it once everything is working!) -INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_actkey_expiration, user_newpasswd) VALUES (3, 5, 'Admin', 'admin', 0, '21232f297a57a5a743894a0e4a801fc3', 'admin@yourdomain.com', 'en', 1, 1, 'AA0000', 1, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', 0, ''); +INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_actkey, user_actkey_expiration, user_newpasswd) VALUES (3, 5, 'Admin', 'admin', 0, '21232f297a57a5a743894a0e4a801fc3', 'admin@yourdomain.com', 'en', 1, 1, 'AA0000', 1, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', 0, ''); # -- Groups INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('GUESTS', 3, 0, '', 0, '', '', '', 5); @@ -576,14 +575,14 @@ INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 6, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_forward'); # Limited Features (u_) -INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 7, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_sendim', 'u_masspm', 'u_masspm_group'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 7, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_masspm', 'u_masspm_group'); # No Private Messages (u_) INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_', 'u_chgavatar', 'u_chgcensors', 'u_chgemail', 'u_chgpasswd', 'u_download', 'u_hideonline', 'u_mention', 'u_sig', 'u_viewprofile'); INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_readpm', 'u_sendpm', 'u_masspm', 'u_masspm_group'); # No Avatar (u_) -INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 9, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_chgavatar', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_sendim', 'u_masspm', 'u_masspm_group'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 9, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_chgavatar', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_masspm', 'u_masspm_group'); INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 9, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_chgavatar'); # Full Moderator (m_) diff --git a/phpBB/language/en/acp/permissions_phpbb.php b/phpBB/language/en/acp/permissions_phpbb.php index 516e8117684..17361e1a3b4 100644 --- a/phpBB/language/en/acp/permissions_phpbb.php +++ b/phpBB/language/en/acp/permissions_phpbb.php @@ -98,7 +98,6 @@ 'ACL_U_PM_IMG' => 'Can use [img] BBCode tag in private messages', 'ACL_U_SENDEMAIL' => 'Can send emails', - 'ACL_U_SENDIM' => 'Can send instant messages', 'ACL_U_IGNOREFLOOD' => 'Can ignore flood limit', 'ACL_U_HIDEONLINE' => 'Can hide online status', 'ACL_U_VIEWONLINE' => 'Can view hidden online users', diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index c031b017886..3164ad6a84b 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -669,7 +669,6 @@ 'PM_IMG' => $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE']), 'L_SEND_EMAIL_USER' => $user->lang('SEND_EMAIL_USER', $member['username']), 'EMAIL_IMG' => $user->img('icon_contact_email', $user->lang['EMAIL']), - 'JABBER_IMG' => $user->img('icon_contact_jabber', $user->lang['JABBER']), 'SEARCH_IMG' => $user->img('icon_user_search', $user->lang['SEARCH']), 'S_PROFILE_ACTION' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group'), @@ -923,12 +922,6 @@ $sort_key_text = array('a' => $user->lang['SORT_USERNAME'], 'c' => $user->lang['SORT_JOINED'], 'd' => $user->lang['SORT_POST_COUNT']); $sort_key_sql = array('a' => 'u.username_clean', 'c' => 'u.user_regdate', 'd' => 'u.user_posts'); - if ($config['jab_enable'] && $auth->acl_get('u_sendim')) - { - $sort_key_text['k'] = $user->lang['JABBER']; - $sort_key_sql['k'] = 'u.user_jabber'; - } - if ($auth->acl_get('a_user')) { $sort_key_text['e'] = $user->lang['SORT_EMAIL']; @@ -979,7 +972,6 @@ { $username = $request->variable('username', '', true); $email = strtolower($request->variable('email', '')); - $jabber = $request->variable('jabber', ''); $search_group_id = $request->variable('search_group_id', 0); // when using these, make sure that we actually have values defined in $find_key_match @@ -1019,7 +1011,6 @@ $sql_where .= ($username) ? ' AND u.username_clean ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), utf8_clean_string($username))) : ''; $sql_where .= ($auth->acl_get('a_user') && $email) ? ' AND u.user_email ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), $email)) . ' ' : ''; - $sql_where .= ($jabber) ? ' AND u.user_jabber ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), $jabber)) . ' ' : ''; $sql_where .= (is_numeric($count) && isset($find_key_match[$count_select])) ? ' AND u.user_posts ' . $find_key_match[$count_select] . ' ' . (int) $count . ' ' : ''; if (isset($find_key_match[$joined_select]) && count($joined) == 3) @@ -1480,7 +1471,6 @@ $template->assign_vars(array( 'USERNAME' => $username, 'EMAIL' => $email, - 'JABBER' => $jabber, 'JOINED' => implode('-', $joined), 'ACTIVE' => implode('-', $active), 'COUNT' => $count, diff --git a/phpBB/phpbb/cron/task/core/queue.php b/phpBB/phpbb/cron/task/core/queue.php index 9395a538de3..7e902f53044 100644 --- a/phpBB/phpbb/cron/task/core/queue.php +++ b/phpBB/phpbb/cron/task/core/queue.php @@ -16,7 +16,7 @@ use phpbb\config\config; /** -* Queue cron task. Sends email and jabber messages queued by other scripts. +* Queue cron task. Sends email queued by other scripts. */ class queue extends \phpbb\cron\task\base { diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index 2a6ed89c21f..48d933d6267 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -67,6 +67,7 @@ public function update_data(): array 'ACP_JABBER_SETTINGS', ]], ['permission.remove', ['a_jabber']], + ['permission.remove', ['u_sendim']], ]; } @@ -94,6 +95,7 @@ public function revert_data(): array ], ]], ['permission.add', ['a_jabber', true]], + ['permission.add', ['u_sendim', true]], ]; } } diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index 900ef103c58..344c2f1cf29 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -26,27 +26,34 @@ class message /** @var string */ protected $subject = ''; + /** @var string */ protected $body = ''; + /** @var string */ protected $template = ''; + /** @var array */ protected $template_vars = array(); /** @var string */ protected $sender_ip = ''; + /** @var string */ protected $sender_name = ''; + /** @var string */ protected $sender_address = ''; + /** @var string */ protected $sender_lang = ''; + /** @var string|int */ protected $sender_id = ''; + /** @var string */ protected $sender_username = ''; - /** @var string */ - protected $sender_jabber = ''; + /** @var int */ protected $sender_notify_type = messenger_interface::NOTIFY_EMAIL; @@ -120,8 +127,7 @@ public function add_recipient_from_user_row(array $user) $user['user_email'], $user['user_lang'], $user['user_notify_type'], - $user['username'], - $user['user_jabber'] + $user['username'] ); } @@ -133,17 +139,15 @@ public function add_recipient_from_user_row(array $user) * @param string $recipient_lang * @param int $recipient_notify_type Used notification methods (Jabber, Email, ...) * @param string $recipient_username User Name (used for AntiAbuse header) - * @param string $recipient_jabber * @return void */ - public function add_recipient($recipient_name, $recipient_address, $recipient_lang, $recipient_notify_type = messenger_interface::NOTIFY_EMAIL, $recipient_username = '', $recipient_jabber = '') + public function add_recipient($recipient_name, $recipient_address, $recipient_lang, $recipient_notify_type = messenger_interface::NOTIFY_EMAIL, $recipient_username = '') { $this->recipients[] = array( 'name' => $recipient_name, 'user_email' => $recipient_address, 'lang' => $recipient_lang, 'username' => $recipient_username, - 'user_jabber' => $recipient_jabber, 'notify_type' => $recipient_notify_type, 'to_name' => $recipient_name, ); @@ -163,8 +167,7 @@ public function set_sender_from_user($user) $user->data['user_email'], $user->lang_name, $user->data['user_id'], - $user->data['username'], - $user->data['user_jabber'] + $user->data['username'] ); $this->set_sender_notify_type($user->data['user_notify_type']); @@ -179,10 +182,9 @@ public function set_sender_from_user($user) * @param string $sender_lang * @param int $sender_id User ID * @param string $sender_username User Name (used for AntiAbuse header) - * @param string $sender_jabber * @return void */ - public function set_sender($sender_ip, $sender_name, $sender_address, $sender_lang = '', $sender_id = 0, $sender_username = '', $sender_jabber = '') + public function set_sender($sender_ip, $sender_name, $sender_address, $sender_lang = '', $sender_id = 0, $sender_username = '') { $this->sender_ip = $sender_ip; $this->sender_name = $sender_name; @@ -190,7 +192,6 @@ public function set_sender($sender_ip, $sender_name, $sender_address, $sender_la $this->sender_lang = $sender_lang; $this->sender_id = $sender_id; $this->sender_username = $sender_username; - $this->sender_jabber = $sender_jabber; } /** @@ -225,7 +226,6 @@ public function cc_sender() 'user_email' => $this->sender_address, 'name' => $this->sender_name, 'username' => $this->sender_username, - 'user_jabber' => $this->sender_jabber, 'notify_type' => $this->sender_notify_type, 'to_name' => $this->recipients[0]['to_name'], ); diff --git a/phpBB/phpbb/permissions.php b/phpBB/phpbb/permissions.php index afbd7109713..87fa05c5ff1 100644 --- a/phpBB/phpbb/permissions.php +++ b/phpBB/phpbb/permissions.php @@ -253,7 +253,6 @@ public function get_permission_lang($permission) 'u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), 'u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), - 'u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), 'u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), 'u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), 'u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), diff --git a/tests/functions/validate_jabber_test.php b/tests/functions/validate_jabber_test.php deleted file mode 100644 index 92cf5062d5c..00000000000 --- a/tests/functions/validate_jabber_test.php +++ /dev/null @@ -1,83 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -require_once __DIR__ . '/../../phpBB/includes/functions_user.php'; -require_once __DIR__ . '/validate_data_helper.php'; - -class phpbb_functions_validate_jabber_test extends phpbb_test_case -{ - protected $helper; - - protected function setUp(): void - { - parent::setUp(); - - $this->helper = new phpbb_functions_validate_data_helper($this); - } - - public function test_validate_jabber() - { - $this->helper->assert_valid_data(array( - 'empty' => array( - array(), - '', - array('jabber'), - ), - 'no_seperator' => array( - array('WRONG_DATA'), - 'testjabber.ccc', - array('jabber'), - ), - 'no_user' => array( - array('WRONG_DATA'), - '@jabber.ccc', - array('jabber'), - ), - 'no_realm' => array( - array('WRONG_DATA'), - 'user@', - array('jabber'), - ), - 'dot_realm' => array( - array('WRONG_DATA'), - 'user@.....', - array('jabber'), - ), - '-realm' => array( - array('WRONG_DATA'), - 'user@-jabber.ccc', - array('jabber'), - ), - 'realm-' => array( - array('WRONG_DATA'), - 'user@jabber.ccc-', - array('jabber'), - ), - 'correct' => array( - array(), - 'user@jabber.09A-z.org', - array('jabber'), - ), - 'prohibited' => array( - array('WRONG_DATA'), - 'u@ser@jabber.ccc.org', - array('jabber'), - ), - 'prohibited_char' => array( - array('WRONG_DATA'), - 'uer@jabber.ccc.org', - array('jabber'), - ), - )); - } -} From ef47c691554f0592dfb65dca2844ad251cb1fbc5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 14 Apr 2025 20:27:03 +0200 Subject: [PATCH 0695/1214] [ticket/17493] Remove notification method jabber PHPBB-17493 --- .../container/services_notification.yml | 13 --- phpBB/includes/acp/acp_email.php | 54 +++------ phpBB/memberlist.php | 5 +- .../db/migration/data/v400/remove_jabber.php | 9 ++ phpBB/phpbb/notification/method/jabber.php | 104 ------------------ 5 files changed, 28 insertions(+), 157 deletions(-) delete mode 100644 phpBB/phpbb/notification/method/jabber.php diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index 0ac9dc6a885..ccf16223cf9 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -233,19 +233,6 @@ services: tags: - { name: notification.method } - notification.method.jabber: - class: phpbb\notification\method\jabber - shared: false - arguments: - - '@user_loader' - - '@user' - - '@config' - - '%core.root_path%' - - '%core.php_ext%' - - '@messenger.method_collection' - tags: - - { name: notification.method } - notification.method.webpush: class: phpbb\notification\method\webpush shared: false diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index dc62e385cd9..8dbdbf409dc 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -76,13 +76,13 @@ function main($id, $mode) { // If giving usernames the admin is able to email inactive users too... $sql_ary = array( - 'SELECT' => 'user_id, username, user_email, user_notify_type, user_lang', + 'SELECT' => 'user_id, username, user_email, user_lang', 'FROM' => array( USERS_TABLE => '', ), 'WHERE' => $db->sql_in_set('username_clean', array_map('utf8_clean_string', $usernames)) . ' AND user_allow_massemail = 1', - 'ORDER_BY' => 'user_lang, user_notify_type', + 'ORDER_BY' => 'user_lang', ); } else @@ -90,7 +90,7 @@ function main($id, $mode) if ($group_id) { $sql_ary = array( - 'SELECT' => 'u.user_id, u.user_email, u.username, u.username_clean, u.user_lang, u.user_notify_type', + 'SELECT' => 'u.user_id, u.user_email, u.username, u.username_clean, u.user_lang', 'FROM' => array( USERS_TABLE => 'u', USER_GROUP_TABLE => 'ug', @@ -100,19 +100,19 @@ function main($id, $mode) AND u.user_id = ug.user_id AND u.user_allow_massemail = 1 AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')', - 'ORDER_BY' => 'u.user_lang, u.user_notify_type', + 'ORDER_BY' => 'u.user_lang', ); } else { $sql_ary = array( - 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_email, u.user_lang, u.user_notify_type', + 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_email, u.user_lang', 'FROM' => array( USERS_TABLE => 'u', ), 'WHERE' => 'u.user_allow_massemail = 1 AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')', - 'ORDER_BY' => 'u.user_lang, u.user_notify_type', + 'ORDER_BY' => 'u.user_lang', ); } } @@ -154,15 +154,12 @@ function main($id, $mode) $max_chunk_size = (int) $config['email_max_chunk_size']; $email_list = array(); $old_lang = $rows[0]['user_lang']; - $old_notify_type = $rows[0]['user_notify_type']; foreach ($rows as $row) { - if (($row['user_notify_type'] == messenger_interface::NOTIFY_EMAIL && $row['user_email']) || - ($row['user_notify_type'] == messenger_interface::NOTIFY_IM && $row['user_jabber']) || - ($row['user_notify_type'] == messenger_interface::NOTIFY_BOTH && ($row['user_email'] || $row['user_jabber']))) + if ($row['user_email']) { - if ($i == $max_chunk_size || $row['user_lang'] != $old_lang || $row['user_notify_type'] != $old_notify_type) + if ($i == $max_chunk_size || $row['user_lang'] != $old_lang) { $i = 0; @@ -172,14 +169,11 @@ function main($id, $mode) } $old_lang = $row['user_lang']; - $old_notify_type = $row['user_notify_type']; } $email_list[$j][$i]['lang'] = $row['user_lang']; - $email_list[$j][$i]['method'] = $row['user_notify_type']; $email_list[$j][$i]['email'] = $row['user_email']; $email_list[$j][$i]['name'] = $row['username']; - $email_list[$j][$i]['jabber'] = $row['user_jabber']; $i++; } } @@ -225,7 +219,6 @@ function main($id, $mode) for ($i = 0, $size = count($email_list); $i < $size; $i++) { $used_lang = $email_list[$i][0]['lang']; - $used_method = $email_list[$i][0]['method']; /** * @var \phpbb\messenger\method\messenger_interface $messenger_method @@ -234,40 +227,29 @@ function main($id, $mode) foreach ($messenger_collection_iterator as $messenger_method) { $notify_method = $messenger_method->get_id(); - if ($notify_method == $used_method || $used_method == messenger_interface::NOTIFY_BOTH) + if ($notify_method == messenger_interface::NOTIFY_EMAIL) { $messenger_method->set_use_queue($use_queue); $messenger_method->template($email_template, $used_lang); $messenger_method->subject(html_entity_decode($subject, ENT_COMPAT)); $messenger_method->assign_vars($template_data); - if ($notify_method == messenger_interface::NOTIFY_EMAIL) + for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) { - for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + $email_row = $email_list[$i][$j]; + if (count($email_list[$i]) == 1) { - $email_row = $email_list[$i][$j]; - if (count($email_list[$i]) == 1) - { - $messenger_method->to($email_row['email'], $email_row['name']); - } - else - { - $messenger_method->bcc($email_row['email'], $email_row['name']); - } + $messenger_method->to($email_row['email'], $email_row['name']); } - - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->set_mail_priority($priority); - } - else if ($notify_method == messenger_interface::NOTIFY_IM) - { - for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + else { - $email_row = $email_list[$i][$j]; - $messenger_method->to($email_row['jabber'], $email_row['name']); + $messenger_method->bcc($email_row['email'], $email_row['name']); } } + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->set_mail_priority($priority); + $errored = !$messenger_method->send() || $errored; $messenger_method->save_queue(); } diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 3164ad6a84b..e392463b4f7 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -963,7 +963,7 @@ $select_single = $request->variable('select_single', false); // Search URL parameters, if any of these are in the URL we do a search - $search_params = array('username', 'email', 'jabber', 'search_group_id', 'joined_select', 'active_select', 'count_select', 'joined', 'active', 'count', 'ip'); + $search_params = array('username', 'email', 'search_group_id', 'joined_select', 'active_select', 'count_select', 'joined', 'active', 'count', 'ip'); // We validate form and field here, only id/class allowed $form = (!preg_match('/^[a-z0-9_-]+$/i', $form)) ? '' : $form; @@ -1316,7 +1316,6 @@ 'select_single' => array('select_single', $select_single), 'username' => array('username', '', true), 'email' => array('email', ''), - 'jabber' => array('jabber', ''), 'search_group_id' => array('search_group_id', 0), 'joined_select' => array('joined_select', 'lt'), 'active_select' => array('active_select', 'lt'), @@ -1478,7 +1477,6 @@ 'S_IP_SEARCH_ALLOWED' => ($auth->acl_getf_global('m_info')) ? true : false, 'S_EMAIL_SEARCH_ALLOWED'=> ($auth->acl_get('a_user')) ? true : false, - 'S_JABBER_ENABLED' => $config['jab_enable'], 'S_IN_SEARCH_POPUP' => ($form && $field) ? true : false, 'S_SEARCH_USER' => ($mode == 'searchuser' || ($mode == '' && $submit)), 'S_FORM_NAME' => $form, @@ -1706,7 +1704,6 @@ 'PROFILE_IMG' => $user->img('icon_user_profile', $user->lang['PROFILE']), 'PM_IMG' => $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE']), 'EMAIL_IMG' => $user->img('icon_contact_email', $user->lang['EMAIL']), - 'JABBER_IMG' => $user->img('icon_contact_jabber', $user->lang['JABBER']), 'SEARCH_IMG' => $user->img('icon_user_search', $user->lang['SEARCH']), 'U_FIND_MEMBER' => ($config['load_search'] || $auth->acl_get('a_')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser' . (($start) ? "&start=$start" : '') . (!empty($params) ? '&' . implode('&', $params) : '')) : '', diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index 48d933d6267..994f8c0167c 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -20,6 +20,7 @@ class remove_jabber extends migration public static function depends_on(): array { return [ + '\phpbb\db\migration\data\v310\notifications_use_full_name', '\phpbb\db\migration\data\v31x\add_jabber_ssl_context_config_options', '\phpbb\db\migration\data\v400\dev', '\phpbb\db\migration\data\v400\add_webpush', @@ -68,6 +69,7 @@ public function update_data(): array ]], ['permission.remove', ['a_jabber']], ['permission.remove', ['u_sendim']], + ['custom', [[$this, 'remove_from_user_notifcations']]], ]; } @@ -98,4 +100,11 @@ public function revert_data(): array ['permission.add', ['u_sendim', true]], ]; } + + public function remove_from_user_notifcations() + { + $sql = 'DELETE FROM ' . $this->table_prefix . 'user_notifications + WHERE notification_method = ' . $this->db->sql_escape('notification.method.jabber'); + $this->db->sql_query($sql); + } } diff --git a/phpBB/phpbb/notification/method/jabber.php b/phpBB/phpbb/notification/method/jabber.php deleted file mode 100644 index e3482f98be0..00000000000 --- a/phpBB/phpbb/notification/method/jabber.php +++ /dev/null @@ -1,104 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\notification\method; - -use phpbb\notification\type\type_interface; -use phpbb\user; -use phpbb\user_loader; -use phpbb\config\config; -use phpbb\di\service_collection; -use phpbb\messenger\method\messenger_interface; - -/** -* Jabber notification method class -* This class handles sending Jabber messages for notifications -*/ - -class jabber extends \phpbb\notification\method\messenger_base -{ - /** @var user */ - protected $user; - - /** @var config */ - protected $config; - - /** @var service_collection */ - protected $messenger; - - /** - * Notification Method jabber Constructor - * - * @param user_loader $user_loader - * @param user $user - * @param config $config - * @param string $phpbb_root_path - * @param string $php_ext - * @param service_collection $messenger - */ - public function __construct(user_loader $user_loader, user $user, config $config, $phpbb_root_path, $php_ext, service_collection $messenger) - { - parent::__construct($messenger, $user_loader, $phpbb_root_path, $php_ext); - - $this->user = $user; - $this->config = $config; - } - - /** - * Get notification method name - * - * @return string - */ - public function get_type() - { - return 'notification.method.jabber'; - } - - /** - * Is this method available for the user? - * This is checked on the notifications options - * - * @param type_interface|null $notification_type An optional instance of a notification type. If provided, this - * method additionally checks if the type provides an email template. - * @return bool - */ - public function is_available(type_interface $notification_type = null) - { - return parent::is_available($notification_type) && $this->global_available() && !empty($this->user->data['user_jabber']); - } - - /** - * Is this method available at all? - * This is checked before notifications are sent - */ - public function global_available() - { - return !( - empty($this->config['jab_enable']) || - empty($this->config['jab_host']) || - empty($this->config['jab_username']) || - empty($this->config['jab_password']) || - !@extension_loaded('xml') - ); - } - - public function notify() - { - if (!$this->global_available()) - { - return; - } - - $this->notify_using_messenger(messenger_interface::NOTIFY_IM, 'short/'); - } -} From 610fa67928a0ada7afbff728bddd1fc5b3fb7cae Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 14 Apr 2025 20:33:13 +0200 Subject: [PATCH 0696/1214] [ticket/17493] Remove jabber as messenger method PHPBB-17493 --- .../default/container/services_messenger.yml | 9 - phpBB/phpbb/message/user_form.php | 2 +- phpBB/phpbb/messenger/method/jabber.php | 1236 ----------------- tests/messenger/method_jabber_test.php | 311 ----- tests/messenger/queue_test.php | 17 - tests/notification/base.php | 1 - .../notification_method_email_test.php | 1 - tests/notification/submit_post_base.php | 1 - 8 files changed, 1 insertion(+), 1577 deletions(-) delete mode 100644 phpBB/phpbb/messenger/method/jabber.php delete mode 100644 tests/messenger/method_jabber_test.php diff --git a/phpBB/config/default/container/services_messenger.yml b/phpBB/config/default/container/services_messenger.yml index 8645d2f17cc..14bdf18df25 100644 --- a/phpBB/config/default/container/services_messenger.yml +++ b/phpBB/config/default/container/services_messenger.yml @@ -38,15 +38,6 @@ services: tags: - { name: messenger.method } - messenger.method.jabber: - class: phpbb\messenger\method\jabber - shared: false - parent: messenger.method.base - calls: - - [init, []] - tags: - - { name: messenger.method } - messenger.queue: class: phpbb\messenger\queue shared: false diff --git a/phpBB/phpbb/message/user_form.php b/phpBB/phpbb/message/user_form.php index 46a012297df..f52eabc7e9a 100644 --- a/phpBB/phpbb/message/user_form.php +++ b/phpBB/phpbb/message/user_form.php @@ -34,7 +34,7 @@ class user_form extends form */ protected function get_user_row($user_id) { - $sql = 'SELECT user_id, username, user_colour, user_email, user_allow_viewemail, user_lang, user_jabber, user_notify_type + $sql = 'SELECT user_id, username, user_colour, user_email, user_allow_viewemail, user_lang, user_notify_type FROM ' . USERS_TABLE . ' WHERE user_id = ' . (int) $user_id . ' AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; diff --git a/phpBB/phpbb/messenger/method/jabber.php b/phpBB/phpbb/messenger/method/jabber.php deleted file mode 100644 index a4a2c07f616..00000000000 --- a/phpBB/phpbb/messenger/method/jabber.php +++ /dev/null @@ -1,1236 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\messenger\method; - -/** - * - * Based on Jabber class from Flyspray project - * - * @version class.jabber2.php 1595 2008-09-19 (0.9.9) - * @copyright 2006 Flyspray.org - * @author Florian Schmitz (floele) - * - * Slightly modified by Acyd Burn (2006) - * Refactored to a service (2024) - */ -class jabber extends base -{ - /** @var string */ - protected $connect_server; - - /** @var resource|null */ - protected $connection = null; - - /** @var bool */ - protected $enable_logging = true; - - /** @var array */ - protected $features = []; - - /** @var array */ - protected $jid = []; - - /** @var array */ - protected $log_array = []; - - /** @var string */ - protected $password; - - /** @var int */ - protected $port; - - /** @var string */ - protected $resource = 'functions_jabber.phpbb.php'; - - /** @var string */ - protected $server; - - /** @var array */ - protected $session = []; - - /** @var array */ - protected $stream_options = []; - - /** @var int */ - protected $timeout = 10; - - /** @var array */ - protected $to = []; - - /** @var bool */ - protected $use_ssl = false; - - /** @var string */ - protected $username; - - /** @var string Stream close handshake */ - private const STREAM_CLOSE_HANDSHAKE = ''; - - /** - * Set initial parameter values - * To init correctly, username() call should go before server() - * and ssl() call should go before port() and stream_options() calls. - * - * Example: - * $this->username($username) - * ->password($password) - * ->ssl($use_ssl) - * ->server($server) - * ->port($port) - * ->stream_options( - * 'verify_peer' => true, - * 'verify_peer_name' => true, - * 'allow_self_signed' => false, - * ); - * - * @return void - */ - public function init(): void - { - $this->username($this->config['jab_username']) - ->password($this->config['jab_password']) - ->ssl((bool) $this->config['jab_use_ssl']) - ->server($this->config['jab_host']) - ->port($this->config['jab_port']) - ->stream_options['ssl'] = [ - 'verify_peer' => $this->config['jab_verify_peer'], - 'verify_peer_name' => $this->config['jab_verify_peer_name'], - 'allow_self_signed' => $this->config['jab_allow_self_signed'], - ]; - } - - /** - * {@inheritDoc} - */ - public function get_id(): int - { - return self::NOTIFY_IM; - } - - /** - * {@inheritDoc} - */ - public function get_queue_object_name(): string - { - return 'jabber'; - } - - /** - * {@inheritDoc} - */ - public function is_enabled(): bool - { - return - !empty($this->config['jab_enable']) && - !empty($this->config['jab_host']) && - !empty($this->config['jab_username']) && - !empty($this->config['jab_password']); - } - - /** - * Set ssl context options - * See http://php.net/manual/en/context.ssl.php - * - * @param array $options SSL context options array - * @return self - */ - public function stream_options(array $options = []): self - { - if ($this->use_ssl) - { - // Change default stream options if needed - $this->stream_options['ssl'] = array_merge($this->stream_options['ssl'], $options); - } - - return $this; - } - - /** - * Set password to connect to server - * - * @param string $password Password to connect to server - * @return self - */ - public function password(string $password = ''): self - { - $this->password = html_entity_decode($password, ENT_COMPAT); - - return $this; - } - - /** - * Set use of ssl to connect to server - * - * @param bool $use_ssl Flag indicating use of ssl to connect to server - * @return self - */ - public function ssl(bool $use_ssl = false): self - { - $this->use_ssl = $use_ssl && self::can_use_ssl(); - - return $this; - } - - /** - * Set port to connect to server - * use_ssl flag should be set first - * - * @param int $port Port to connect to server - * @return self - */ - public function port(int $port = 5222): self - { - $this->port = ($port > 0) ? $port : 5222; - - // Change port if we use SSL - if ($this->port == 5222 && $this->use_ssl) - { - $this->port = 5223; - } - - return $this; - } - - /** - * Set username to connect to server - * - * @param string $username Username to connect to server - * @return self - */ - public function username(string $username = ''): self - { - if (str_contains($username, '@')) - { - $this->jid = explode('@', $username, 2); - $this->username = $this->jid[0]; - } - else - { - $this->username = $username; - } - - return $this; - } - - /** - * Set server to connect - * Username should be set first - * - * @param string $server Server to connect - * @return self - */ - public function server(string $server = ''): self - { - $this->connect_server = $server ?: 'localhost'; - $this->server = $this->jid[1] ?? $this->connect_server; - - return $this; - } - - /** - * Check if it's possible to use the SSL functionality - * - * @return bool - */ - public static function can_use_ssl(): bool - { - return @extension_loaded('openssl'); - } - - /** - * Check if it's possible to use TLS functionality - * - * @return bool - */ - public static function can_use_tls(): bool - { - if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('stream_set_blocking') || !function_exists('stream_get_wrappers')) - { - return false; - } - - /** - * Make sure the encryption stream is supported - * Also seem to work without the crypto stream if correctly compiled - - $streams = stream_get_wrappers(); - - if (!in_array('streams.crypto', $streams)) - { - return false; - } - */ - - return true; - } - - /** - * Sets the resource which is used. No validation is done here, only escaping - * - * @param string $name - * @return void - */ - public function set_resource(string $name): void - { - $this->resource = $name; - } - - /** - * Connect to the server - * - * @return bool - */ - public function connect(): bool - { -/* if (!$this->check_jid($this->username . '@' . $this->server)) - { - $this->add_to_log('Error: Jabber ID is not valid: ' . $this->username . '@' . $this->server); - return false; - }*/ - - $this->session['ssl'] = $this->use_ssl; - - if ($this->open_socket($this->connect_server, $this->port)) - { - $this->send_xml("\n"); - $this->send_xml("\n"); - } - else - { - $this->add_to_log('Error: connect() #2'); - return false; - } - - // Now we listen what the server has to say...and give appropriate responses - $this->response($this->listen()); - return true; - } - - /** - * Disconnect from the server - * - * @return bool - */ - public function disconnect(): bool - { - if ($this->connected()) - { - // disconnect gracefully - if (isset($this->session['sent_presence'])) - { - $this->send_presence('offline', '', true); - } - - $this->send(self::STREAM_CLOSE_HANDSHAKE); - // Check stream close handshake reply - $stream_close_reply = $this->listen(); - - if ($stream_close_reply != self::STREAM_CLOSE_HANDSHAKE) - { - $this->add_to_log("Error: Unexpected stream close handshake reply ”{$stream_close_reply}”"); - } - - $this->session = []; - /** @psalm-suppress InvalidPropertyAssignmentValue */ - return fclose($this->connection); - } - - return false; - } - - /** - * Check if it's still connected to the server - * - * @return bool - */ - public function connected(): bool - { - return is_resource($this->connection) && !feof($this->connection); - } - - /** - * Initiates login (using data from contructor, after calling connect()) - * - * @return bool - */ - public function login(): bool - { - if (empty($this->features)) - { - $this->add_to_log('Error: No feature information from server available.'); - return false; - } - - return $this->response($this->features); - } - - /** - * {@inheritDoc} - */ - public function set_addresses(array $user_row): void - { - if (isset($user_row['user_jabber']) && $user_row['user_jabber']) - { - $this->to($user_row['user_jabber'], (isset($user_row['username']) ? $user_row['username'] : '')); - } - } - - /** - * Sets jabber contact to send message to - * - * @param string $address Jabber "To" recipient address - * @param string $realname Jabber "To" recipient name - * @return void - */ - public function to(string $address, string $realname = ''): void - { - // IM-Addresses could be empty - if (!trim($address)) - { - return; - } - - $pos = !empty($this->to) ? count($this->to) : 0; - $this->to[$pos]['uid'] = trim($address); - $this->to[$pos]['name'] = trim($realname); - } - - /** - * Resets all the data (address, template file, etc) to default - */ - public function reset(): void - { - $this->subject = $this->msg = ''; - $this->additional_headers = $this->to = []; - $this->use_queue = true; - unset($this->template); - } - - /** - * {@inheritDoc} - */ - public function set_use_queue(bool $use_queue = true): void - { - $this->use_queue = !$this->config['jab_package_size'] ? false : $use_queue; - } - - /** - * {@inheritDoc} - */ - public function process_queue(array &$queue_data): void - { - $queue_object_name = $this->get_queue_object_name(); - $messages_count = count($queue_data[$queue_object_name]['data']); - - if (!$this->is_enabled() || !$messages_count) - { - unset($queue_data[$queue_object_name]); - return; - } - - @set_time_limit(0); - - $package_size = $queue_data[$queue_object_name]['package_size'] ?? 0; - $num_items = (!$package_size || $messages_count < $package_size) ? $messages_count : $package_size; - - for ($i = 0; $i < $num_items; $i++) - { - // Make variables available... - extract(array_shift($queue_data[$queue_object_name]['data'])); - - if (!$this->connect()) - { - $this->error($this->user->lang['ERR_JAB_CONNECT'] . '
' . $this->get_log()); - return; - } - - if (!$this->login()) - { - $this->error($this->user->lang['ERR_JAB_AUTH'] . '
' . $this->get_log()); - return; - } - - foreach ($addresses as $address) - { - if ($this->send_message($address, $msg, $subject) === false) - { - $this->error($this->get_log()); - continue; - } - } - } - - // No more data for this object? Unset it - if (!count($queue_data[$queue_object_name]['data'])) - { - unset($queue_data[$queue_object_name]); - } - - $this->disconnect(); - } - - /** - * {@inheritDoc} - */ - public function send(): bool - { - $this->prepare_message(); - - if (empty($this->to)) - { - $this->add_to_log('Error: Could not send, recepient addresses undefined.'); - return false; - } - - $addresses = []; - foreach ($this->to as $uid_ary) - { - $addresses[] = $uid_ary['uid']; - } - $addresses = array_unique($addresses); - - if (!$this->use_queue) - { - if (!$this->connect()) - { - $this->error($this->user->lang['ERR_JAB_CONNECT'] . '
' . $this->get_log()); - return false; - } - - if (!$this->login()) - { - $this->error($this->user->lang['ERR_JAB_AUTH'] . '
' . $this->get_log()); - return false; - } - - foreach ($addresses as $address) - { - if ($this->send_message($address, $this->msg, $this->subject) === false) - { - $this->error($this->get_log()); - continue; - } - } - - $this->disconnect(); - } - else - { - $this->queue->init('jabber', $this->config['jab_package_size']); - $this->queue->put('jabber', [ - 'addresses' => $addresses, - 'subject' => $this->subject, - 'msg' => $this->msg, - ]); - } - unset($addresses); - - $this->reset(); - - return true; - } - - /** - * Send data to the Jabber server - * - * @param string $xml - * @return int|bool - */ - public function send_xml(string $xml): int|bool - { - if ($this->connected()) - { - $xml = trim($xml); - return fwrite($this->connection, $xml); - } - else - { - $this->add_to_log('Error: Could not send, connection lost (flood?).'); - return false; - } - } - - /** - * Open socket - * - * @param string $server Host to connect to - * @param int $port Port number - * - * @return bool - */ - public function open_socket(string $server, int $port): bool - { - if (@function_exists('dns_get_record')) - { - $record = @dns_get_record("_xmpp-client._tcp.$server", DNS_SRV); - if (!empty($record) && !empty($record[0]['target'])) - { - $server = $record[0]['target']; - } - } - - $remote_socket = $this->use_ssl ? 'ssl://' . $server . ':' . $port : $server . ':' . $port; - $socket_context = stream_context_create($this->stream_options); - - if ($this->connection = @stream_socket_client($remote_socket, $errorno, $errorstr, $this->timeout, STREAM_CLIENT_CONNECT, $socket_context)) - { - stream_set_blocking($this->connection, 0); - stream_set_timeout($this->connection, 60); - - return true; - } - - // Apparently an error occurred... - $this->add_to_log('Error: open_socket() - ' . $errorstr); - return false; - } - - /** - * Get connection log - * - * @return string - */ - public function get_log(): string - { - if ($this->enable_logging && count($this->log_array)) - { - return implode("

", $this->log_array); - } - - return ''; - } - - /** - * Add information to log - * - * @param string $string Log entry - * @return void - */ - protected function add_to_log(string $string): void - { - if ($this->enable_logging) - { - $this->log_array[] = utf8_htmlspecialchars($string); - } - } - - /** - * Listens to the connection until it gets data or the timeout is reached. - * Thus, it should only be called if data is expected to be received. - * - * @param int $timeout Connection timeout - * @param bool $wait Flag indicating if it should wait for the responce until timeout - * @return bool|array Either false for timeout or an array with the received data - */ - public function listen(int $timeout = 10, bool $wait = false): bool|array - { - if (!$this->connected()) - { - return false; - } - - // Wait for a response until timeout is reached - $start = time(); - $data = ''; - - do - { - $read = trim(fread($this->connection, 4096)); - $data .= $read; - } - while (time() <= $start + $timeout && !feof($this->connection) && ($wait || $data == '' || $read != '' || (substr(rtrim($data), -1) != '>'))); - - if ($data != '') - { - return $this->xmlize($data); - } - else - { - $this->add_to_log('Timeout, no response from server.'); - return false; - } - } - - /** - * Initiates account registration (based on data used for constructor) - * - * @return bool|null - */ - public function register(): bool|null - { - if (!isset($this->session['id']) || isset($this->session['jid'])) - { - $this->add_to_log('Error: Cannot initiate registration.'); - return false; - } - - $this->send_xml(""); - return $this->response($this->listen()); - } - - /** - * Sets account presence. No additional info required (default is "online" status) - * - * @param string $message Account status (online, offline) - * @param string $type Status type (dnd, away, chat, xa or nothing) - * @param bool $unavailable Set to true to make unavailable status - * @return int|bool - */ - function send_presence(string $message = '', string $type = '', bool $unavailable = false): int|bool - { - if (!isset($this->session['jid'])) - { - $this->add_to_log('ERROR: send_presence() - Cannot set presence at this point, no jid given.'); - return false; - } - - $type = strtolower($type); - $type = (in_array($type, array('dnd', 'away', 'chat', 'xa'))) ? ''. $type .'' : ''; - - $unavailable = ($unavailable) ? " type='unavailable'" : ''; - $message = ($message) ? '' . utf8_htmlspecialchars($message) .'' : ''; - - $this->session['sent_presence'] = !$unavailable; - - return $this->send_xml("" . $type . $message . ''); - } - - /** - * This handles all the different XML elements - * - * @param array $xml - * @return bool - */ - function response(array $xml): bool - { - if (!count($xml)) - { - return false; - } - - // did we get multiple elements? do one after another - // array('message' => ..., 'presence' => ...) - if (count($xml) > 1) - { - foreach ($xml as $key => $value) - { - $this->response(array($key => $value)); - } - return true; - } - else if (is_array(reset($xml)) && count(reset($xml)) > 1) - { - // or even multiple elements of the same type? - // array('message' => array(0 => ..., 1 => ...)) - foreach (reset($xml) as $value) - { - $this->response(array(key($xml) => array(0 => $value))); - } - return true; - } - - switch (key($xml)) - { - case 'stream:stream': - // Connection initialised (or after authentication). Not much to do here... - - if (isset($xml['stream:stream'][0]['#']['stream:features'])) - { - // we already got all info we need - $this->features = $xml['stream:stream'][0]['#']; - } - else - { - $this->features = $this->listen(); - } - - $second_time = isset($this->session['id']); - $this->session['id'] = isset($xml['stream:stream'][0]['@']['id']) ? $xml['stream:stream'][0]['@']['id'] : ''; - - if ($second_time) - { - // If we are here for the second time after TLS, we need to continue logging in - return $this->login(); - } - - // go on with authentication? - if (isset($this->features['stream:features'][0]['#']['bind']) || !empty($this->session['tls'])) - { - return $this->response($this->features); - } - return false; - break; - - case 'stream:features': - // Resource binding after successful authentication - if (isset($this->session['authenticated'])) - { - // session required? - $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']); - - $this->send_xml(" - - " . utf8_htmlspecialchars($this->resource) . ' - - '); - return $this->response($this->listen()); - } - - // Let's use TLS if SSL is not enabled and we can actually use it - if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls'])) - { - $this->add_to_log('Switching to TLS.'); - $this->send_xml("\n"); - return $this->response($this->listen()); - } - - // Does the server support SASL authentication? - - // I hope so, because we do (and no other method). - if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl') - { - // Now decide on method - $methods = array(); - - foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value) - { - $methods[] = $value['#']; - } - - // we prefer DIGEST-MD5 - // we don't want to use plain authentication (neither does the server usually) if no encryption is in place - - // http://www.xmpp.org/extensions/attic/jep-0078-1.7.html - // The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS) - // and the client has verified that the server certificate is signed by a trusted certificate authority. - - if (in_array('DIGEST-MD5', $methods)) - { - $this->send_xml(""); - } - else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || !empty($this->session['tls']))) - { - // http://www.ietf.org/rfc/rfc4616.txt (PLAIN SASL Mechanism) - $this->send_xml("" - . base64_encode($this->username . '@' . $this->server . chr(0) . $this->username . chr(0) . $this->password) . - ''); - } - else if (in_array('ANONYMOUS', $methods)) - { - $this->send_xml(""); - } - else - { - // not good... - $this->add_to_log('Error: No authentication method supported.'); - $this->disconnect(); - return false; - } - - return $this->response($this->listen()); - } - else - { - // ok, this is it. bye. - $this->add_to_log('Error: Server does not offer SASL authentication.'); - $this->disconnect(); - return false; - } - break; - - case 'challenge': - // continue with authentication...a challenge literally -_- - $decoded = base64_decode($xml['challenge'][0]['#']); - $decoded = $this->parse_data($decoded); - - if (!isset($decoded['digest-uri'])) - { - $decoded['digest-uri'] = 'xmpp/'. $this->server; - } - - // better generate a cnonce, maybe it's needed - $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true))); - - // second challenge? - if (isset($decoded['rspauth'])) - { - $this->send_xml(""); - } - else - { - // Make sure we only use 'auth' for qop (relevant for $this->encrypt_password()) - // If the is choking up on the changed parameter we may need to adjust encrypt_password() directly - if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false) - { - $decoded['qop'] = 'auth'; - } - - $response = array( - 'username' => $this->username, - 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))), - 'charset' => 'utf-8', - 'nc' => '00000001', - 'qop' => 'auth', // only auth being supported - ); - - foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key) - { - if (isset($decoded[$key])) - { - $response[$key] = $decoded[$key]; - } - } - - $this->send_xml("" . base64_encode($this->implode_data($response)) . ''); - } - - return $this->response($this->listen()); - break; - - case 'failure': - $this->add_to_log('Error: Server sent "failure".'); - $this->disconnect(); - return false; - break; - - case 'proceed': - // continue switching to TLS - $meta = stream_get_meta_data($this->connection); - stream_set_blocking($this->connection, 1); - - if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) - { - $this->add_to_log('Error: TLS mode change failed.'); - return false; - } - - stream_set_blocking($this->connection, $meta['blocked']); - $this->session['tls'] = true; - - // new stream - $this->send_xml("\n"); - $this->send_xml("\n"); - - return $this->response($this->listen()); - break; - - case 'success': - // Yay, authentication successful. - $this->send_xml("\n"); - $this->session['authenticated'] = true; - - // we have to wait for another response - return $this->response($this->listen()); - break; - - case 'iq': - // we are not interested in IQs we did not expect - if (!isset($xml['iq'][0]['@']['id'])) - { - return false; - } - - // multiple possibilities here - switch ($xml['iq'][0]['@']['id']) - { - case 'bind_1': - $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#']; - - // and (maybe) yet another request to be able to send messages *finally* - if ($this->session['sess_required']) - { - $this->send_xml(" - - "); - return $this->response($this->listen()); - } - - return true; - break; - - case 'sess_1': - return true; - break; - - case 'reg_1': - $this->send_xml(" - - " . utf8_htmlspecialchars($this->username) . " - " . utf8_htmlspecialchars($this->password) . " - - "); - return $this->response($this->listen()); - break; - - case 'reg_2': - // registration end - if (isset($xml['iq'][0]['#']['error'])) - { - $this->add_to_log('Warning: Registration failed.'); - return false; - } - return true; - break; - - case 'unreg_1': - return true; - break; - - default: - $this->add_to_log('Notice: Received unexpected IQ.'); - return false; - break; - } - break; - - case 'message': - // we are only interested in content... - if (!isset($xml['message'][0]['#']['body'])) - { - return false; - } - - $message['body'] = $xml['message'][0]['#']['body'][0]['#']; - $message['from'] = $xml['message'][0]['@']['from']; - - if (isset($xml['message'][0]['#']['subject'])) - { - $message['subject'] = $xml['message'][0]['#']['subject'][0]['#']; - } - $this->session['messages'][] = $message; - return true; - break; - - default: - // hm...don't know this response - $this->add_to_log('Notice: Unknown server response'); - return false; - break; - } - } - - /** - * Send Jabber message - * - * @param string $to Recepient usermane - * @param string $text Message text - * @param string $subject Message subject - * @param string $type Message type - * - * @return int|bool - */ - public function send_message(string $to, string $text, string $subject = '', string $type = 'normal'): int|bool - { - if (!isset($this->session['jid'])) - { - return false; - } - - if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline'))) - { - $type = 'normal'; - } - - return $this->send_xml(" - " . utf8_htmlspecialchars($subject) . " - " . utf8_htmlspecialchars($text) . " - " - ); - } - - /** - * Encrypts a password as in RFC 2831 - * - * @param array $data Needs data from the client-server connection - * @return string - */ - public function encrypt_password(array $data): string - { - // let's me think about again... - foreach (array('realm', 'cnonce', 'digest-uri') as $key) - { - if (!isset($data[$key])) - { - $data[$key] = ''; - } - } - - $pack = md5($this->username . ':' . $data['realm'] . ':' . $this->password); - - if (isset($data['authzid'])) - { - $a1 = pack('H32', $pack) . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']); - } - else - { - $a1 = pack('H32', $pack) . sprintf(':%s:%s', $data['nonce'], $data['cnonce']); - } - - // should be: qop = auth - $a2 = 'AUTHENTICATE:'. $data['digest-uri']; - - return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2))); - } - - /** - * Parse data string like a="b",c="d",... or like a="a, b", c, d="e", f=g,... - * - * @param string $data - * @return array a => b ... - */ - public function parse_data(string $data): array - { - $data = explode(',', $data); - $pairs = array(); - $key = false; - - foreach ($data as $pair) - { - $dd = strpos($pair, '='); - - if ($dd) - { - $key = trim(substr($pair, 0, $dd)); - $pairs[$key] = trim(trim(substr($pair, $dd + 1)), '"'); - } - else if (strpos(strrev(trim($pair)), '"') === 0 && $key) - { - // We are actually having something left from "a, b" values, add it to the last one we handled. - $pairs[$key] .= ',' . trim(trim($pair), '"'); - continue; - } - } - - return $pairs; - } - - /** - * The opposite of jabber::parse_data() - * - * @param array $data Data array - * @return string - */ - public function implode_data(array $data): string - { - $return = array(); - foreach ($data as $key => $value) - { - $return[] = $key . '="' . $value . '"'; - } - return implode(',', $return); - } - - /** - * xmlize() - * @author Hans Anderson - * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/ - * - * @param string $data Data string - * @param string|int|bool $skip_white New XML parser option value - * @param string $encoding Encoding value - * @return array - */ - function xmlize(string $data, string|int|bool $skip_white = 1, string $encoding = 'UTF-8'): array - { - $data = trim($data); - - if (substr($data, 0, 5) != ''. $data . ''; - } - - $vals = $index = $array = array(); - $parser = xml_parser_create($encoding); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); - xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $skip_white); - xml_parse_into_struct($parser, $data, $vals, $index); - xml_parser_free($parser); - - $i = 0; - $tagname = $vals[$i]['tag']; - - $array[$tagname][0]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array(); - $array[$tagname][0]['#'] = $this->_xml_depth($vals, $i); - - if (substr($data, 0, 5) != '_xml_depth($vals, $i); - - break; - - case 'cdata': - array_push($children, $vals[$i]['value']); - break; - - case 'complete': - - $tagname = $vals[$i]['tag']; - $size = (isset($children[$tagname])) ? count($children[$tagname]) : 0; - $children[$tagname][$size]['#'] = (isset($vals[$i]['value'])) ? $vals[$i]['value'] : array(); - - if (isset($vals[$i]['attributes'])) - { - $children[$tagname][$size]['@'] = $vals[$i]['attributes']; - } - - break; - - case 'close': - return $children; - break; - } - } - - return $children; - } -} diff --git a/tests/messenger/method_jabber_test.php b/tests/messenger/method_jabber_test.php deleted file mode 100644 index d61cd9b1b15..00000000000 --- a/tests/messenger/method_jabber_test.php +++ /dev/null @@ -1,311 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -class phpbb_messenger_method_jabber_test extends \phpbb_test_case -{ - protected $assets_bag; - protected $cache_path; - protected config $config; - protected $dispatcher; - protected $extension_manager; - protected jabber $method_jabber; - protected $method_base; - protected $language; - protected $log; - protected $path_helper; - protected queue $queue; - protected $request; - protected $twig_extensions_collection; - protected $twig_lexer; - protected $user; - - public function setUp(): void - { - global $config, $request, $symfony_request, $user, $phpbb_root_path, $phpEx; - - $this->assets_bag = new assets_bag(); - $this->cache_path = $phpbb_root_path . 'cache/' . PHPBB_ENVIRONMENT . '/twig'; - $this->config = new config([ - 'force_server_vars' => false, - 'jab_username' => 'test', - 'jab_password' => 'password', - 'jab_use_ssl' => false, - 'jab_host' => 'localhost', - 'jab_port' => 5222, - 'jab_verify_peer' => true, - 'jab_verify_peer_name' => true, - 'jab_allow_self_signed' => false, - ]); - $config = $this->config; - $this->dispatcher = $this->getMockBuilder('\phpbb\event\dispatcher') - ->disableOriginalConstructor() - ->getMock(); - $this->filesystem = new \phpbb\filesystem\filesystem(); - $this->language = new language(new language_file_loader($phpbb_root_path, $phpEx)); - $this->queue = $this->createMock(queue::class); - $this->request = new phpbb_mock_request(); - $request = $this->request; - $this->symfony_request = new symfony_request(new phpbb_mock_request()); - $symfony_request = $this->symfony_request; - $this->user = $this->getMockBuilder('\phpbb\user') - ->setConstructorArgs([$this->language, '\phpbb\datetime']) - ->getMock(); - $user = $this->user; - $user->page['root_script_path'] = 'phpbb/'; - $this->user->host = 'yourdomain.com'; - $this->path_helper = new path_helper( - $this->symfony_request, - $this->request, - $phpbb_root_path, - $phpEx - ); - $phpbb_container = new phpbb_mock_container_builder; - $this->twig_extensions_collection = new \phpbb\di\service_collection($phpbb_container); - $twig = new \phpbb\template\twig\environment( - $this->assets_bag, - $this->config, - $this->filesystem, - $this->path_helper, - $this->cache_path, - null, - new \phpbb\template\twig\loader(''), - $this->dispatcher, - array( - 'cache' => false, - 'debug' => false, - 'auto_reload' => true, - 'autoescape' => false, - ) - ); - $this->twig_lexer = new \phpbb\template\twig\lexer($twig); - $this->extension_manager = new phpbb_mock_extension_manager( - __DIR__ . '/', - array( - 'vendor2/foo' => array( - 'ext_name' => 'vendor2/foo', - 'ext_active' => '1', - 'ext_path' => 'ext/vendor2/foo/', - ), - ) - ); - $this->log = $this->createMock(\phpbb\log\log_interface::class); - - $this->method_jabber = new jabber( - $this->assets_bag, - $this->config, - $this->dispatcher, - $this->language, - $this->queue, - $this->path_helper, - $this->request, - $this->twig_extensions_collection, - $this->twig_lexer, - $this->user, - $phpbb_root_path, - $this->cache_path, - $this->extension_manager, - $this->log - ); - } - - public function test_miscellaneous() - { - $this->method_jabber->init(); - $this->assertEquals(messenger_interface::NOTIFY_IM, $this->method_jabber->get_id()); - $this->assertEquals('jabber', $this->method_jabber->get_queue_object_name()); - $this->assertFalse($this->method_jabber->is_enabled()); - $this->config->set('jab_enable', true); - $this->assertTrue($this->method_jabber->is_enabled()); - $this->assertEquals(@extension_loaded('openssl'), $this->method_jabber->can_use_ssl()); - } - - public function test_stream_options() - { - $this->method_jabber->init(); - $this->assertEquals($this->method_jabber, $this->method_jabber->stream_options([ - 'allow_self_signed' => true, - ])); - - $stream_options_reflection = new \ReflectionProperty($this->method_jabber, 'stream_options'); - $stream_options = $stream_options_reflection->getValue($this->method_jabber); - $this->assertEquals([ - 'ssl' => [ - 'allow_self_signed' => false, - 'verify_peer' => true, - 'verify_peer_name' => true, - ], - ], $stream_options); - - $this->method_jabber->ssl(true); - - $this->assertEquals($this->method_jabber, $this->method_jabber->stream_options([ - 'allow_self_signed' => true, - ])); - $stream_options = $stream_options_reflection->getValue($this->method_jabber); - $this->assertEquals([ - 'ssl' => [ - 'allow_self_signed' => true, - 'verify_peer' => true, - 'verify_peer_name' => true, - ], - ], $stream_options); - } - - public function test_port_ssl_switch() - { - $port_reflection = new \ReflectionProperty($this->method_jabber, 'port'); - - $this->method_jabber->port(); - $this->assertEquals(5222, $port_reflection->getValue($this->method_jabber)); - - $this->method_jabber->ssl(true) - ->port(); - $this->assertEquals(5223, $port_reflection->getValue($this->method_jabber)); - } - - public function test_username() - { - $jabber_reflection = new \ReflectionClass($this->method_jabber); - $username_reflection = $jabber_reflection->getProperty('username'); - $jid_reflection = $jabber_reflection->getProperty('jid'); - - $this->method_jabber->username('foo@bar'); - $this->assertEquals(['foo', 'bar'], $jid_reflection->getValue($this->method_jabber)); - $this->assertEquals('foo', $username_reflection->getValue($this->method_jabber)); - - $this->method_jabber->username('bar@baz@qux'); - $this->assertEquals(['bar', 'baz@qux'], $jid_reflection->getValue($this->method_jabber)); - $this->assertEquals('bar', $username_reflection->getValue($this->method_jabber)); - } - - public function test_server() - { - $jabber_reflection = new \ReflectionClass($this->method_jabber); - $connect_server_reflection = $jabber_reflection->getProperty('connect_server'); - $server_reflection = $jabber_reflection->getProperty('server'); - - $this->method_jabber->server(); - $this->assertEquals('localhost', $connect_server_reflection->getValue($this->method_jabber)); - $this->assertEquals('localhost', $server_reflection->getValue($this->method_jabber)); - - $this->method_jabber->server('foobar.com'); - $this->assertEquals('foobar.com', $connect_server_reflection->getValue($this->method_jabber)); - $this->assertEquals('foobar.com', $server_reflection->getValue($this->method_jabber)); - - $this->method_jabber->username('foo@bar.com'); - $this->method_jabber->server('foobar.com'); - $this->assertEquals('foobar.com', $connect_server_reflection->getValue($this->method_jabber)); - $this->assertEquals('bar.com', $server_reflection->getValue($this->method_jabber)); - } - - public function test_encrypt_password() - { - $this->method_jabber->init(); - $this->method_jabber->password('password'); - $data = [ - 'realm' => 'example.com', - 'nonce' => '12345', - 'cnonce' => 'abcde', - 'digest-uri' => 'xmpp/example.com', - 'nc' => '00000001', - 'qop' => 'auth', - ]; - - $expected = md5(sprintf( - '%s:%s:%s:%s:%s:%s', - md5(pack('H32', md5('test:example.com:password')) . ':12345:abcde'), - $data['nonce'], - $data['nc'], - $data['cnonce'], - $data['qop'], - md5('AUTHENTICATE:xmpp/example.com') - )); - $this->assertEquals($expected, $this->method_jabber->encrypt_password($data)); - } - - public function test_parse_data() - { - $data = 'key1="value1",key2="value2",key3="value3"'; - $expected = [ - 'key1' => 'value1', - 'key2' => 'value2', - 'key3' => 'value3', - ]; - - $this->assertEquals($expected, $this->method_jabber->parse_data($data)); - } - - public function test_implode_data() - { - $data = [ - 'key1' => 'value1', - 'key2' => 'value2', - 'key3' => 'value3', - ]; - $expected = 'key1="value1",key2="value2",key3="value3"'; - - $this->assertEquals($expected, $this->method_jabber->implode_data($data)); - } - - public function test_xmlize() - { - $xml = 'content'; - $result = $this->method_jabber->xmlize($xml); - - $this->assertArrayHasKey('root', $result); - $this->assertArrayHasKey('child', $result['root'][0]['#']); - $this->assertEquals('content', $result['root'][0]['#']['child'][0]['#']); - $this->assertEquals(['key' => 'value'], $result['root'][0]['#']['child'][0]['@']); - } - - public function test_send_xml() - { - $jabber_mock = $this->getMockBuilder(jabber::class) - ->setConstructorArgs([ - $this->assets_bag, - $this->config, - $this->dispatcher, - $this->language, - $this->queue, - $this->path_helper, - $this->request, - $this->twig_extensions_collection, - $this->twig_lexer, - $this->user, - '', - '', - $this->extension_manager, - $this->log, - ]) - ->onlyMethods(['send_xml']) - ->getMock(); - - $jabber_mock->expects($this->once()) - ->method('send_xml') - ->with('Test') - ->willReturn(true); - - $this->assertTrue($jabber_mock->send_xml('Test')); - } -} diff --git a/tests/messenger/queue_test.php b/tests/messenger/queue_test.php index e99893d5778..cb058cdc751 100644 --- a/tests/messenger/queue_test.php +++ b/tests/messenger/queue_test.php @@ -239,27 +239,10 @@ public function test_process_complete() ], $queue_data['email']); unset($queue_data['email']); }); - $jabber_method = $this->getMockBuilder('phpbb\messenger\method\jabber') - ->disableOriginalConstructor() - ->onlyMethods(['get_queue_object_name', 'process_queue']) - ->getMock(); - $jabber_method->method('get_queue_object_name') - ->willReturn('jabber'); - $jabber_method->method('process_queue') - ->willReturnCallback(function(array &$queue_data) { - $this->assertEquals([ - 'package_size' => 10, - 'data' => [ - ['data2'], - ], - ], $queue_data['jabber']); - unset($queue_data['jabber']); - }); $this->service_collection->method('getIterator') ->willReturn(new \ArrayIterator([ 'email' => $email_method, - 'jabber' => $jabber_method, ])); // Process the queue diff --git a/tests/notification/base.php b/tests/notification/base.php index 07fef3a1c5e..ec6319c420d 100644 --- a/tests/notification/base.php +++ b/tests/notification/base.php @@ -148,7 +148,6 @@ protected function setUp(): void $messenger_method_collection = new \phpbb\di\service_collection($phpbb_container); $messenger_method_collection->add('messenger.method.email'); - $messenger_method_collection->add('messenger.method.jabber'); $phpbb_container->set('messenger.method_collection', $messenger_method_collection); $phpbb_container->compile(); diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php index 15811cc9680..6400c8bb1d0 100644 --- a/tests/notification/notification_method_email_test.php +++ b/tests/notification/notification_method_email_test.php @@ -107,7 +107,6 @@ protected function setUp(): void $messenger_method_collection = new \phpbb\di\service_collection($phpbb_container); $messenger_method_collection->add('messenger.method.email'); - $messenger_method_collection->add('messenger.method.jabber'); $phpbb_container->set('messenger.method_collection', $messenger_method_collection); $this->notification_method_email = $this->getMockBuilder('\phpbb\notification\method\email') diff --git a/tests/notification/submit_post_base.php b/tests/notification/submit_post_base.php index 57b12425091..08505667d7a 100644 --- a/tests/notification/submit_post_base.php +++ b/tests/notification/submit_post_base.php @@ -161,7 +161,6 @@ protected function setUp(): void $messenger_method_collection = new \phpbb\di\service_collection($phpbb_container); $messenger_method_collection->add('messenger.method.email'); - $messenger_method_collection->add('messenger.method.jabber'); $phpbb_container->set('messenger.method_collection', $messenger_method_collection); $phpbb_container->addCompilerPass(new phpbb\di\pass\markpublic_pass()); From bb26658a00c0de83f26a1a7e443f6919c862ac34 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 14 Apr 2025 21:39:06 +0200 Subject: [PATCH 0697/1214] [ticket/17493] Remove jabber data in queue test PHPBB-17493 --- tests/messenger/queue_test.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/messenger/queue_test.php b/tests/messenger/queue_test.php index cb058cdc751..3f87de91fc2 100644 --- a/tests/messenger/queue_test.php +++ b/tests/messenger/queue_test.php @@ -202,20 +202,14 @@ public function test_process_complete() // First save queue data $this->assertFileDoesNotExist($this->cache_file); $this->messenger_queue->init('email', 5); - $this->messenger_queue->init('jabber', 10); $this->assertEquals([ 'email' => [ 'package_size' => 5, 'data' => [], ], - 'jabber' => [ - 'package_size' => 10, - 'data' => [], - ] ], $this->messenger_queue->get_data()); $this->messenger_queue->put('email', ['data1']); - $this->messenger_queue->put('jabber', ['data2']); $this->messenger_queue->save(); $this->assertFileExists($this->cache_file); $this->assertEquals([], $this->messenger_queue->get_data()); From dd53db16259b17aaeb5fe9bd59e49299fbfb274e Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 15 Apr 2025 20:40:21 +0700 Subject: [PATCH 0698/1214] [ticket/17498] Move to Ubuntu 22.04 runner images for SQLite and MSSQL tests PHPBB-17498 --- .github/workflows/tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 32d84776717..e3d8ea86430 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -351,24 +351,24 @@ jobs: # Other database types, namely sqlite3 and mssql other-tests: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: include: - php: '7.2' db: "sqlite3" - - php: '7.2' - db: "mcr.microsoft.com/mssql/server:2017-latest" - db_alias: 'MSSQL 2017' - php: '7.2' db: "mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04" db_alias: 'MSSQL 2019' + - php: '7.2' + db: "mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04" + db_alias: 'MSSQL 2022' name: PHP ${{ matrix.php }} - ${{ matrix.db_alias != '' && matrix.db_alias || matrix.db }} services: mssql: - image: ${{ matrix.db != 'mcr.microsoft.com/mssql/server:2017-latest' && matrix.db != 'mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04' && 'mcr.microsoft.com/mssql/server:2017-latest' || matrix.db }} + image: ${{ matrix.db != 'mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04' && matrix.db != 'mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04' && 'mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04' || matrix.db }} env: SA_PASSWORD: "Pssw0rd_12" ACCEPT_EULA: "y" @@ -400,7 +400,7 @@ jobs: env: MATRIX_DB: ${{ matrix.db }} run: | - if [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2017-latest' ] || [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04' ] + if [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04' ] || [ $MATRIX_DB == 'mcr.microsoft.com/mssql/server:2019-CU27-ubuntu-20.04' ] then db='mssql' else From da32d51a8161245ce892ca3f45456b1449b246b4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 18 Apr 2025 17:07:42 +0200 Subject: [PATCH 0699/1214] [ticket/17493] Remove remnants of user_notify_type PHPBB-17493 --- phpBB/includes/acp/acp_email.php | 40 ++++----- phpBB/includes/acp/acp_inactive.php | 27 +++--- phpBB/includes/acp/acp_users.php | 2 - phpBB/includes/functions_user.php | 3 +- phpBB/includes/mcp/mcp_reports.php | 2 +- phpBB/includes/ucp/ucp_activate.php | 23 +++-- phpBB/includes/ucp/ucp_prefs.php | 2 - phpBB/includes/ucp/ucp_profile.php | 20 +++-- phpBB/includes/ucp/ucp_resend.php | 25 +++--- .../db/migration/data/v400/remove_jabber.php | 15 ++-- .../data/v400/remove_notify_type.php | 64 ++++++++++++++ phpBB/phpbb/message/admin_form.php | 1 - phpBB/phpbb/message/message.php | 83 +++++++------------ phpBB/phpbb/message/topic_form.php | 1 - phpBB/phpbb/message/user_form.php | 2 +- phpBB/phpbb/ucp/controller/reset_password.php | 4 +- tests/auth/provider_apache_test.php | 1 - tests/functional/user_password_reset_test.php | 2 +- tests/notification/convert_test.php | 22 ++++- tests/notification/fixtures/convert.xml | 7 -- 20 files changed, 192 insertions(+), 154 deletions(-) create mode 100644 phpBB/phpbb/db/migration/data/v400/remove_notify_type.php diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index 8dbdbf409dc..5156676881a 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -226,33 +226,29 @@ function main($id, $mode) */ foreach ($messenger_collection_iterator as $messenger_method) { - $notify_method = $messenger_method->get_id(); - if ($notify_method == messenger_interface::NOTIFY_EMAIL) - { - $messenger_method->set_use_queue($use_queue); - $messenger_method->template($email_template, $used_lang); - $messenger_method->subject(html_entity_decode($subject, ENT_COMPAT)); - $messenger_method->assign_vars($template_data); + $messenger_method->set_use_queue($use_queue); + $messenger_method->template($email_template, $used_lang); + $messenger_method->subject(html_entity_decode($subject, ENT_COMPAT)); + $messenger_method->assign_vars($template_data); - for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + { + $email_row = $email_list[$i][$j]; + if (count($email_list[$i]) == 1) + { + $messenger_method->to($email_row['email'], $email_row['name']); + } + else { - $email_row = $email_list[$i][$j]; - if (count($email_list[$i]) == 1) - { - $messenger_method->to($email_row['email'], $email_row['name']); - } - else - { - $messenger_method->bcc($email_row['email'], $email_row['name']); - } + $messenger_method->bcc($email_row['email'], $email_row['name']); } + } - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->set_mail_priority($priority); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->set_mail_priority($priority); - $errored = !$messenger_method->send() || $errored; - $messenger_method->save_queue(); - } + $errored = !$messenger_method->send() || $errored; + $messenger_method->save_queue(); } } unset($email_list); diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 138e06e5258..b4b39cbdf9b 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -185,7 +185,7 @@ function main($id, $mode) trigger_error($user->lang['EMAIL_DISABLED'] . adm_back_link($this->u_action), E_USER_WARNING); } - $sql = 'SELECT user_id, username, user_email, user_lang, user_notify_type, user_regdate, user_actkey + $sql = 'SELECT user_id, username, user_email, user_lang, user_regdate, user_actkey FROM ' . USERS_TABLE . ' WHERE ' . $db->sql_in_set('user_id', $mark) . ' AND user_inactive_reason'; @@ -211,20 +211,17 @@ function main($id, $mode) */ foreach ($messenger_collection_iterator as $messenger_method) { - if ($messenger_method->get_id() == $user_row['user_notify_type'] || $user_row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) - { - $messenger_method->template('user_remind_inactive', $row['user_lang']); - $messenger_method->set_addresses($row); - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->assign_vars([ - 'USERNAME' => html_entity_decode($row['username'], ENT_COMPAT), - 'REGISTER_DATE' => $user->format_date($row['user_regdate'], false, true), - 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u=" . $row['user_id'] . '&k=' . $row['user_actkey'], - ]); - - $messenger_method->send(); - $messenger_method->save_queue(); - } + $messenger_method->template('user_remind_inactive', $row['user_lang']); + $messenger_method->set_addresses($row); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->assign_vars([ + 'USERNAME' => html_entity_decode($row['username'], ENT_COMPAT), + 'REGISTER_DATE' => $user->format_date($row['user_regdate'], false, true), + 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u=" . $row['user_id'] . '&k=' . $row['user_actkey'], + ]); + + $messenger_method->send(); + $messenger_method->save_queue(); } $usernames[] = $row['username']; diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index e01eace64e5..42d0338ba25 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1577,7 +1577,6 @@ function main($id, $mode) 'viewemail' => $request->variable('viewemail', $user_row['user_allow_viewemail']), 'massemail' => $request->variable('massemail', $user_row['user_allow_massemail']), 'hideonline' => $request->variable('hideonline', !$user_row['user_allow_viewonline']), - 'notifymethod' => $request->variable('notifymethod', $user_row['user_notify_type']), 'notifypm' => $request->variable('notifypm', $user_row['user_notify_pm']), 'allowpm' => $request->variable('allowpm', $user_row['user_allow_pm']), @@ -1648,7 +1647,6 @@ function main($id, $mode) 'user_allow_viewemail' => $data['viewemail'], 'user_allow_massemail' => $data['massemail'], 'user_allow_viewonline' => !$data['hideonline'], - 'user_notify_type' => $data['notifymethod'], 'user_notify_pm' => $data['notifypm'], 'user_dateformat' => $data['dateformat'], diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 42c9e7fd5a7..a408f695aa2 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -246,7 +246,6 @@ function user_add($user_row, $cp_data = false, $notifications_data = null) 'user_notify' => 0, 'user_notify_pm' => 1, - 'user_notify_type' => messenger_interface::NOTIFY_EMAIL, 'user_allow_pm' => 1, 'user_allow_viewonline' => 1, 'user_allow_viewemail' => 1, @@ -2528,7 +2527,7 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna case 'approve': // Make sure we only approve those which are pending ;) - $sql = 'SELECT u.user_id, u.user_email, u.username, u.username_clean, u.user_notify_type, u.user_lang + $sql = 'SELECT u.user_id FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug WHERE ug.group_id = ' . $group_id . ' AND ug.user_pending = 1 diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 3325fd6b8da..ee9be079aeb 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -661,7 +661,7 @@ function close_report($report_id_list, $mode, $action, $pm = false) { $post_info = ($pm) ? phpbb_get_pm_data($post_id_list) : phpbb_get_post_data($post_id_list, 'm_report'); - $sql = "SELECT r.report_id, r.$id_column, r.report_closed, r.user_id, r.user_notify, u.username, u.username_clean, u.user_email, u.user_lang, u.user_notify_type + $sql = "SELECT r.report_id, r.$id_column, r.report_closed, r.user_id, r.user_notify, u.username, u.username_clean, u.user_email, u.user_lang FROM " . REPORTS_TABLE . ' r, ' . USERS_TABLE . ' u WHERE ' . $db->sql_in_set('r.report_id', $report_id_list) . ' ' . (($action == 'close') ? 'AND r.report_closed = 0' : '') . ' diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php index 81036c19568..50ccdfa7e6a 100644 --- a/phpBB/includes/ucp/ucp_activate.php +++ b/phpBB/includes/ucp/ucp_activate.php @@ -35,7 +35,7 @@ function main($id, $mode) $user_id = $request->variable('u', 0); $key = $request->variable('k', ''); - $sql = 'SELECT user_id, username, user_type, user_email, user_newpasswd, user_lang, user_notify_type, user_actkey, user_inactive_reason + $sql = 'SELECT user_id, username, user_type, user_email, user_newpasswd, user_lang, user_actkey, user_inactive_reason FROM ' . USERS_TABLE . " WHERE user_id = $user_id"; $result = $db->sql_query($sql); @@ -140,18 +140,15 @@ function main($id, $mode) */ foreach ($messenger_collection_iterator as $messenger_method) { - if ($messenger_method->get_id() == $user_row['user_notify_type'] || $user_row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) - { - $messenger_method->set_use_queue(false); - $messenger_method->template('admin_welcome_activated', $user_row['user_lang']); - $messenger_method->set_addresses($user_row); - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->assign_vars([ - 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), - ]); - - $messenger_method->send(); - } + $messenger_method->set_use_queue(false); + $messenger_method->template('admin_welcome_activated', $user_row['user_lang']); + $messenger_method->set_addresses($user_row); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->assign_vars([ + 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), + ]); + + $messenger_method->send(); } $message = 'ACCOUNT_ACTIVE_ADMIN'; diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 480b4a3d12f..86440cd7144 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -42,7 +42,6 @@ function main($id, $mode) case 'personal': add_form_key('ucp_prefs_personal'); $data = array( - 'notifymethod' => $request->variable('notifymethod', $user->data['user_notify_type']), 'dateformat' => $request->variable('dateformat', $user->data['user_dateformat'], true), 'lang' => basename($request->variable('lang', $user->data['user_lang'])), 'user_style' => $request->variable('user_style', (int) $user->data['user_style']), @@ -99,7 +98,6 @@ function main($id, $mode) 'user_allow_viewemail' => $data['viewemail'], 'user_allow_massemail' => $data['massemail'], 'user_allow_viewonline' => ($auth->acl_get('u_hideonline')) ? !$data['hideonline'] : $user->data['user_allow_viewonline'], - 'user_notify_type' => $data['notifymethod'], 'user_options' => $user->data['user_options'], 'user_dateformat' => $data['dateformat'], diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 77c1a56e9d3..a5a1fe3d709 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -357,9 +357,7 @@ function main($id, $mode) if (!count($error)) { - $sql_ary = array( - 'user_notify_type' => messenger_interface::NOTIFY_EMAIL, - ); + $sql_ary = []; if ($config['allow_birthdays']) { @@ -378,13 +376,17 @@ function main($id, $mode) $vars = array('cp_data', 'data', 'sql_ary'); extract($phpbb_dispatcher->trigger_event('core.ucp_profile_info_modify_sql_ary', compact($vars))); - $sql = 'UPDATE ' . USERS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' - WHERE user_id = ' . $user->data['user_id']; - $db->sql_query($sql); + // Skip query if no data to update + if (count($sql_ary)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); - // Update Custom Fields - $cp->update_profile_field_data($user->data['user_id'], $cp_data); + // Update Custom Fields + $cp->update_profile_field_data($user->data['user_id'], $cp_data); + } meta_refresh(3, $this->u_action); $message = $user->lang['PROFILE_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '
', ''); diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index d3512865dbf..023ea80852d 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -128,7 +128,7 @@ function main($id, $mode) // Grab an array of user_id's with a_user permissions ... these users can activate a user $admin_ary = $auth->acl_get_list(false, 'a_user', false); - $sql = 'SELECT user_id, username, user_email, user_lang, user_notify_type + $sql = 'SELECT user_id, username, user_email, user_lang FROM ' . USERS_TABLE . ' WHERE ' . $db->sql_in_set('user_id', $admin_ary[0]['a_user']); $result = $db->sql_query($sql); @@ -145,19 +145,16 @@ function main($id, $mode) foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->set_use_queue(false); - if ($messenger_method->get_id() == $row['user_notify_type'] || $row['user_notify_type'] == $messenger_method::NOTIFY_BOTH) - { - $messenger_method->template('admin_activate', $row['user_lang']); - $messenger_method->set_addresses($row); - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->assign_vars([ - 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), - 'U_USER_DETAILS' => $board_url . "/memberlist.$phpEx?mode=viewprofile&u={$user_row['user_id']}", - 'U_ACTIVATE' => $board_url . "/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k={$user_row['user_actkey']}", - ]); - - $messenger_method->send(); - } + $messenger_method->template('admin_activate', $row['user_lang']); + $messenger_method->set_addresses($row); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->assign_vars([ + 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), + 'U_USER_DETAILS' => $board_url . "/memberlist.$phpEx?mode=viewprofile&u={$user_row['user_id']}", + 'U_ACTIVATE' => $board_url . "/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k={$user_row['user_actkey']}", + ]); + + $messenger_method->send(); } } $db->sql_freeresult($result); diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index 994f8c0167c..c297ef0a8a4 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -69,7 +69,7 @@ public function update_data(): array ]], ['permission.remove', ['a_jabber']], ['permission.remove', ['u_sendim']], - ['custom', [[$this, 'remove_from_user_notifcations']]], + ['custom', [[$this, 'move_jabber_to_email_notifications']]], ]; } @@ -101,10 +101,15 @@ public function revert_data(): array ]; } - public function remove_from_user_notifcations() + public function move_jabber_to_email_notifications(int $start = 0) { - $sql = 'DELETE FROM ' . $this->table_prefix . 'user_notifications - WHERE notification_method = ' . $this->db->sql_escape('notification.method.jabber'); - $this->db->sql_query($sql); + $limit = 1000; + + $sql = 'UPDATE ' . $this->tables['user_notifications'] . ' + SET ' . $this->db->sql_build_array('UPDATE', ['method' => 'notification.method.email']) . " + WHERE method = 'notification.method.jabber'"; + $this->db->sql_query_limit($sql, $limit, $start); + + return $this->db->sql_affectedrows() < $limit ? true : $start + $limit; } } diff --git a/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php b/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php new file mode 100644 index 00000000000..a4b3cce8edb --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php @@ -0,0 +1,64 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class remove_notify_type extends migration +{ + public static function depends_on(): array + { + return [ + '\phpbb\db\migration\data\v400\remove_jabber', + '\phpbb\db\migration\data\v400\dev', + '\phpbb\db\migration\data\v30x\release_3_0_0', + ]; + } + + public function update_schema(): array + { + return [ + 'drop_columns' => [ + $this->table_prefix . 'users' => [ + 'user_notify_type', + ], + ] + ]; + } + + public function revert_schema(): array + { + return [ + 'add_columns' => [ + $this->table_prefix . 'users' => [ + 'user_notify_type' => ['TINT:4', 0], + ], + ] + ]; + } + + public function update_data() + { + return [ + ['custom', [[$this, 'remove_jabber_user_notifications']]], + ]; + } + + protected function remove_jabber_user_notifications(): void + { + $sql = 'DELETE FROM ' . $this->tables['user_notifications'] . " + WHERE method = 'notification.method.jabber'"; + $this->db->sql_query($sql); + } +} diff --git a/phpBB/phpbb/message/admin_form.php b/phpBB/phpbb/message/admin_form.php index 72beaa38a68..e6e64210d86 100644 --- a/phpBB/phpbb/message/admin_form.php +++ b/phpBB/phpbb/message/admin_form.php @@ -157,7 +157,6 @@ public function submit(\phpbb\di\service_collection $messenger) } $this->message->set_sender($this->user->ip, $this->sender_name, $this->sender_address, $this->user->lang_name); - $this->message->set_sender_notify_type(messenger_interface::NOTIFY_EMAIL); } $this->message->set_template('contact_admin'); diff --git a/phpBB/phpbb/message/message.php b/phpBB/phpbb/message/message.php index 344c2f1cf29..9eae8719a47 100644 --- a/phpBB/phpbb/message/message.php +++ b/phpBB/phpbb/message/message.php @@ -54,9 +54,6 @@ class message /** @var string */ protected $sender_username = ''; - /** @var int */ - protected $sender_notify_type = messenger_interface::NOTIFY_EMAIL; - /** @var array */ protected $recipients; @@ -126,7 +123,6 @@ public function add_recipient_from_user_row(array $user) $user['username'], $user['user_email'], $user['user_lang'], - $user['user_notify_type'], $user['username'] ); } @@ -137,18 +133,16 @@ public function add_recipient_from_user_row(array $user) * @param string $recipient_name Displayed sender name * @param string $recipient_address Email address * @param string $recipient_lang - * @param int $recipient_notify_type Used notification methods (Jabber, Email, ...) * @param string $recipient_username User Name (used for AntiAbuse header) * @return void */ - public function add_recipient($recipient_name, $recipient_address, $recipient_lang, $recipient_notify_type = messenger_interface::NOTIFY_EMAIL, $recipient_username = '') + public function add_recipient($recipient_name, $recipient_address, $recipient_lang, $recipient_username = '') { $this->recipients[] = array( 'name' => $recipient_name, 'user_email' => $recipient_address, 'lang' => $recipient_lang, 'username' => $recipient_username, - 'notify_type' => $recipient_notify_type, 'to_name' => $recipient_name, ); } @@ -169,8 +163,6 @@ public function set_sender_from_user($user) $user->data['user_id'], $user->data['username'] ); - - $this->set_sender_notify_type($user->data['user_notify_type']); } /** @@ -194,17 +186,6 @@ public function set_sender($sender_ip, $sender_name, $sender_address, $sender_la $this->sender_username = $sender_username; } - /** - * Which notification type should be used? Jabber, Email, ...? - * - * @param int $sender_notify_type - * @return void - */ - public function set_sender_notify_type($sender_notify_type) - { - $this->sender_notify_type = $sender_notify_type; - } - /** * Ok, now the same email if CC specified, but without exposing the user's email address * @@ -226,7 +207,6 @@ public function cc_sender() 'user_email' => $this->sender_address, 'name' => $this->sender_name, 'username' => $this->sender_username, - 'notify_type' => $this->sender_notify_type, 'to_name' => $this->recipients[0]['to_name'], ); } @@ -257,40 +237,37 @@ public function send(\phpbb\di\service_collection $messenger, $contact) foreach ($messenger_collection_iterator as $messenger_method) { $messenger_method->set_use_queue(false); - if ($messenger_method->get_id() == $recipient['notify_type'] || $recipient['notify_type'] == $messenger_method::NOTIFY_BOTH) + $messenger_method->template($this->template, $recipient['lang']); + $messenger_method->set_addresses($recipient); + $messenger_method->reply_to($this->sender_address); + + $messenger_method->header('X-AntiAbuse', 'Board servername - ' . $this->server_name); + $messenger_method->header('X-AntiAbuse', 'User IP - ' . $this->sender_ip); + if ($this->sender_id) { - $messenger_method->template($this->template, $recipient['lang']); - $messenger_method->set_addresses($recipient); - $messenger_method->reply_to($this->sender_address); - - $messenger_method->header('X-AntiAbuse', 'Board servername - ' . $this->server_name); - $messenger_method->header('X-AntiAbuse', 'User IP - ' . $this->sender_ip); - if ($this->sender_id) - { - $messenger_method->header('X-AntiAbuse', 'User_id - ' . $this->sender_id); - } - - if ($this->sender_username) - { - $messenger_method->header('X-AntiAbuse', 'Username - ' . $this->sender_username); - } - - $messenger_method->subject(html_entity_decode($this->subject, ENT_COMPAT)); - - $messenger_method->assign_vars([ - 'BOARD_CONTACT' => $contact, - 'TO_USERNAME' => html_entity_decode($recipient['to_name'], ENT_COMPAT), - 'FROM_USERNAME' => html_entity_decode($this->sender_name, ENT_COMPAT), - 'MESSAGE' => html_entity_decode($this->body, ENT_COMPAT), - ]); - - if (count($this->template_vars)) - { - $messenger_method->assign_vars($this->template_vars); - } - - $messenger_method->send(); + $messenger_method->header('X-AntiAbuse', 'User_id - ' . $this->sender_id); } + + if ($this->sender_username) + { + $messenger_method->header('X-AntiAbuse', 'Username - ' . $this->sender_username); + } + + $messenger_method->subject(html_entity_decode($this->subject, ENT_COMPAT)); + + $messenger_method->assign_vars([ + 'BOARD_CONTACT' => $contact, + 'TO_USERNAME' => html_entity_decode($recipient['to_name'], ENT_COMPAT), + 'FROM_USERNAME' => html_entity_decode($this->sender_name, ENT_COMPAT), + 'MESSAGE' => html_entity_decode($this->body, ENT_COMPAT), + ]); + + if (count($this->template_vars)) + { + $messenger_method->assign_vars($this->template_vars); + } + + $messenger_method->send(); } } } diff --git a/phpBB/phpbb/message/topic_form.php b/phpBB/phpbb/message/topic_form.php index dd73a67ab2c..8141a66b621 100644 --- a/phpBB/phpbb/message/topic_form.php +++ b/phpBB/phpbb/message/topic_form.php @@ -134,7 +134,6 @@ public function submit(\phpbb\di\service_collection $messenger) $this->recipient_lang, messenger_interface::NOTIFY_EMAIL ); - $this->message->set_sender_notify_type(messenger_interface::NOTIFY_EMAIL); parent::submit($messenger); } diff --git a/phpBB/phpbb/message/user_form.php b/phpBB/phpbb/message/user_form.php index f52eabc7e9a..69189a81f11 100644 --- a/phpBB/phpbb/message/user_form.php +++ b/phpBB/phpbb/message/user_form.php @@ -34,7 +34,7 @@ class user_form extends form */ protected function get_user_row($user_id) { - $sql = 'SELECT user_id, username, user_colour, user_email, user_allow_viewemail, user_lang, user_notify_type + $sql = 'SELECT user_id, username, user_colour, user_email, user_allow_viewemail, user_lang FROM ' . USERS_TABLE . ' WHERE user_id = ' . (int) $user_id . ' AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; diff --git a/phpBB/phpbb/ucp/controller/reset_password.php b/phpBB/phpbb/ucp/controller/reset_password.php index d9ec05316c1..790e00138c1 100644 --- a/phpBB/phpbb/ucp/controller/reset_password.php +++ b/phpBB/phpbb/ucp/controller/reset_password.php @@ -176,7 +176,7 @@ public function request() } $sql_array = [ - 'SELECT' => 'user_id, username, user_permissions, user_email, user_notify_type, user_type,' + 'SELECT' => 'user_id, username, user_permissions, user_email, user_type,' . ' user_lang, user_inactive_reason, reset_token, reset_token_expiration', 'FROM' => [$this->users_table => 'u'], 'WHERE' => "user_email = '" . $this->db->sql_escape($email) . "'" . @@ -308,7 +308,7 @@ public function reset() add_form_key('ucp_reset_password'); $sql_array = [ - 'SELECT' => 'user_id, username, user_permissions, user_email, user_notify_type, user_type,' + 'SELECT' => 'user_id, username, user_permissions, user_email, user_type,' . ' user_lang, user_inactive_reason, reset_token, reset_token_expiration', 'FROM' => [$this->users_table => 'u'], 'WHERE' => 'user_id = ' . $user_id, diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index 3604bbf9cd2..2fcb5b84845 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -152,7 +152,6 @@ public function test_autologin() 'user_post_sortby_dir' => 'a', 'user_notify' => 0, 'user_notify_pm' => 1, - 'user_notify_type' => 0, 'user_allow_pm' => 1, 'user_allow_viewonline' => 1, 'user_allow_viewemail' => 1, diff --git a/tests/functional/user_password_reset_test.php b/tests/functional/user_password_reset_test.php index 7e15aca56f9..cf0f9038179 100644 --- a/tests/functional/user_password_reset_test.php +++ b/tests/functional/user_password_reset_test.php @@ -263,7 +263,7 @@ public function test_resendActivation() protected function get_user_data($username) { $db = $this->get_db(); - $sql = 'SELECT user_id, username, user_type, user_email, user_newpasswd, user_lang, user_notify_type, user_actkey, user_inactive_reason, reset_token, reset_token_expiration + $sql = 'SELECT user_id, username, user_type, user_email, user_newpasswd, user_lang, user_actkey, user_inactive_reason, reset_token, reset_token_expiration FROM ' . USERS_TABLE . " WHERE username = '" . $db->sql_escape($username) . "'"; $result = $db->sql_query($sql); diff --git a/tests/notification/convert_test.php b/tests/notification/convert_test.php index 0def0be5edd..6c1f7b496e9 100644 --- a/tests/notification/convert_test.php +++ b/tests/notification/convert_test.php @@ -30,15 +30,33 @@ protected function setUp(): void $this->db = $this->new_dbal(); $this->doctrine_db = $this->new_doctrine_dbal(); $factory = new \phpbb\db\tools\factory(); + $db_tools = $factory->get($this->doctrine_db); + $core_tables = self::get_core_tables(); + + // Add user_notify_type column for testing this migration and set type + $db_tools->sql_column_add($core_tables['users'], 'user_notify_type', ['TINT:4', 0]); + $user_notify_type_map = [ + 1 => 0, + 2 => 0, + 3 => 1, + 4 => 1, + 5 => 2, + 6 => 2, + ]; + + foreach ($user_notify_type_map as $user_id => $notify_type) + { + $this->db->sql_query('UPDATE ' . $core_tables['users'] . ' SET user_notify_type = ' . (int) $notify_type . ' WHERE user_id = ' . (int) $user_id); + } $this->migration = new \phpbb\db\migration\data\v310\notification_options_reconvert( new \phpbb\config\config(array()), $this->db, - $factory->get($this->doctrine_db), + $db_tools, $phpbb_root_path, $phpEx, 'phpbb_', - self::get_core_tables() + $core_tables ); } diff --git a/tests/notification/fixtures/convert.xml b/tests/notification/fixtures/convert.xml index c9d8fafa973..9b357504d16 100644 --- a/tests/notification/fixtures/convert.xml +++ b/tests/notification/fixtures/convert.xml @@ -4,7 +4,6 @@ user_id username username_clean - user_notify_type user_notify_pm user_permissions user_sig @@ -13,7 +12,6 @@ 1 1 0 - 0 @@ -21,7 +19,6 @@ 2 2 2 - 0 1 @@ -30,7 +27,6 @@ 3 3 3 - 1 0 @@ -40,7 +36,6 @@ 4 4 1 - 1 @@ -48,7 +43,6 @@ 5 5 5 - 2 0 @@ -57,7 +51,6 @@ 6 6 6 - 2 1 From d985c8be60031435fb804157ff6a720d688e59b5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 18 Apr 2025 17:20:25 +0200 Subject: [PATCH 0700/1214] [ticket/17493] Remove unused notify type in add_recipient PHPBB-17493 --- phpBB/phpbb/message/admin_form.php | 3 +-- phpBB/phpbb/message/topic_form.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/phpBB/phpbb/message/admin_form.php b/phpBB/phpbb/message/admin_form.php index e6e64210d86..ac03558f74d 100644 --- a/phpBB/phpbb/message/admin_form.php +++ b/phpBB/phpbb/message/admin_form.php @@ -165,8 +165,7 @@ public function submit(\phpbb\di\service_collection $messenger) $this->message->add_recipient( $this->user->lang['ADMINISTRATOR'], $this->config['board_contact'], - $this->config['default_lang'], - messenger_interface::NOTIFY_EMAIL + $this->config['default_lang'] ); $this->message->set_template_vars(array( diff --git a/phpBB/phpbb/message/topic_form.php b/phpBB/phpbb/message/topic_form.php index 8141a66b621..8386eff89ae 100644 --- a/phpBB/phpbb/message/topic_form.php +++ b/phpBB/phpbb/message/topic_form.php @@ -131,8 +131,7 @@ public function submit(\phpbb\di\service_collection $messenger) $this->message->add_recipient( $this->recipient_name, $this->recipient_address, - $this->recipient_lang, - messenger_interface::NOTIFY_EMAIL + $this->recipient_lang ); parent::submit($messenger); From 5ab0446eb188be36b9bc0796d8a3a7252d3b1565 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 18 Apr 2025 19:49:57 +0200 Subject: [PATCH 0701/1214] [ticket/17493] Remove unused use statements PHPBB-17493 --- phpBB/phpbb/message/admin_form.php | 2 -- phpBB/phpbb/message/topic_form.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/phpBB/phpbb/message/admin_form.php b/phpBB/phpbb/message/admin_form.php index ac03558f74d..30c863bb7ff 100644 --- a/phpBB/phpbb/message/admin_form.php +++ b/phpBB/phpbb/message/admin_form.php @@ -13,8 +13,6 @@ namespace phpbb\message; -use phpbb\messenger\method\messenger_interface; - /** * Class admin_form * Displays a message to the user and allows him to send an email diff --git a/phpBB/phpbb/message/topic_form.php b/phpBB/phpbb/message/topic_form.php index 8386eff89ae..15a4f30ac20 100644 --- a/phpBB/phpbb/message/topic_form.php +++ b/phpBB/phpbb/message/topic_form.php @@ -13,8 +13,6 @@ namespace phpbb\message; -use phpbb\messenger\method\messenger_interface; - /** * Class topic_form * Form used to send topics as notification emails From 27550ce59b8a66a6f1a988b8471711b630eaf745 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 20 Apr 2025 07:55:28 +0200 Subject: [PATCH 0702/1214] [ticket/17493] Remove not needed function in migration and fix type hinting PHPBB-17493 --- .../phpbb/db/migration/data/v400/remove_jabber.php | 2 +- .../db/migration/data/v400/remove_notify_type.php | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index c297ef0a8a4..5ff6b1e9b54 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -101,7 +101,7 @@ public function revert_data(): array ]; } - public function move_jabber_to_email_notifications(int $start = 0) + public function move_jabber_to_email_notifications(?int $start = 0) { $limit = 1000; diff --git a/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php b/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php index a4b3cce8edb..7ed606cd804 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_notify_type.php @@ -47,18 +47,4 @@ public function revert_schema(): array ] ]; } - - public function update_data() - { - return [ - ['custom', [[$this, 'remove_jabber_user_notifications']]], - ]; - } - - protected function remove_jabber_user_notifications(): void - { - $sql = 'DELETE FROM ' . $this->tables['user_notifications'] . " - WHERE method = 'notification.method.jabber'"; - $this->db->sql_query($sql); - } } From 54d8a49e7000dc57f28ee99cd37d35a0271b4a45 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 20 Apr 2025 07:58:53 +0200 Subject: [PATCH 0703/1214] [ticket/17493] Improve handling of nullable start parameter PHPBB-17493 --- phpBB/phpbb/db/migration/data/v400/remove_jabber.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index 5ff6b1e9b54..b82fdfc8dbf 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -101,14 +101,14 @@ public function revert_data(): array ]; } - public function move_jabber_to_email_notifications(?int $start = 0) + public function move_jabber_to_email_notifications(?int $start) { $limit = 1000; $sql = 'UPDATE ' . $this->tables['user_notifications'] . ' SET ' . $this->db->sql_build_array('UPDATE', ['method' => 'notification.method.email']) . " WHERE method = 'notification.method.jabber'"; - $this->db->sql_query_limit($sql, $limit, $start); + $this->db->sql_query_limit($sql, $limit, $start ?: 0); return $this->db->sql_affectedrows() < $limit ? true : $start + $limit; } From 52f04a3c2c4ae255633e23edb95f97b42d7eb5a8 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 20 Apr 2025 08:53:14 +0200 Subject: [PATCH 0704/1214] [ticket/17493] Remove "short" email templates as they were jabber only PHPBB-17493 --- phpBB/language/en/email/short/bookmark.txt | 13 ------------- phpBB/language/en/email/short/newtopic_notify.txt | 11 ----------- phpBB/language/en/email/short/post_approved.txt | 13 ------------- phpBB/language/en/email/short/post_disapproved.txt | 11 ----------- phpBB/language/en/email/short/post_in_queue.txt | 13 ------------- phpBB/language/en/email/short/privmsg_notify.txt | 13 ------------- phpBB/language/en/email/short/quote.txt | 13 ------------- phpBB/language/en/email/short/report_pm.txt | 10 ---------- phpBB/language/en/email/short/report_post.txt | 13 ------------- phpBB/language/en/email/short/topic_approved.txt | 10 ---------- phpBB/language/en/email/short/topic_disapproved.txt | 11 ----------- phpBB/language/en/email/short/topic_in_queue.txt | 13 ------------- phpBB/language/en/email/short/topic_notify.txt | 13 ------------- 13 files changed, 157 deletions(-) delete mode 100644 phpBB/language/en/email/short/bookmark.txt delete mode 100644 phpBB/language/en/email/short/newtopic_notify.txt delete mode 100644 phpBB/language/en/email/short/post_approved.txt delete mode 100644 phpBB/language/en/email/short/post_disapproved.txt delete mode 100644 phpBB/language/en/email/short/post_in_queue.txt delete mode 100644 phpBB/language/en/email/short/privmsg_notify.txt delete mode 100644 phpBB/language/en/email/short/quote.txt delete mode 100644 phpBB/language/en/email/short/report_pm.txt delete mode 100644 phpBB/language/en/email/short/report_post.txt delete mode 100644 phpBB/language/en/email/short/topic_approved.txt delete mode 100644 phpBB/language/en/email/short/topic_disapproved.txt delete mode 100644 phpBB/language/en/email/short/topic_in_queue.txt delete mode 100644 phpBB/language/en/email/short/topic_notify.txt diff --git a/phpBB/language/en/email/short/bookmark.txt b/phpBB/language/en/email/short/bookmark.txt deleted file mode 100644 index 963b644415d..00000000000 --- a/phpBB/language/en/email/short/bookmark.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Reply in "{TOPIC_TITLE}" - -Hello {USERNAME}, - -Your bookmarked topic "{TOPIC_TITLE}" received a new reply since your last visit to "{SITENAME}". No more notifications will be sent until you visit the topic. - -If you want to view the newest post made since your last visit, click the following link: -{U_NEWEST_POST} - -If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: -{U_NOTIFICATION_SETTINGS} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/newtopic_notify.txt b/phpBB/language/en/email/short/newtopic_notify.txt deleted file mode 100644 index e8f8c25b9e1..00000000000 --- a/phpBB/language/en/email/short/newtopic_notify.txt +++ /dev/null @@ -1,11 +0,0 @@ -Subject: New topic in "{FORUM_NAME}" - -Hello {USERNAME}, - -The forum "{FORUM_NAME}" received a new topic "{TOPIC_TITLE}" by {AUTHOR_NAME} since your last visit to "{SITENAME}". No more notifications will be sent until you visit the forum. -{U_FORUM} - -If you no longer wish to watch this forum click the following link: -{U_STOP_WATCHING_FORUM} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/post_approved.txt b/phpBB/language/en/email/short/post_approved.txt deleted file mode 100644 index 87e8acebb62..00000000000 --- a/phpBB/language/en/email/short/post_approved.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Post approved - "{POST_SUBJECT}" - -Hello {USERNAME}, - -Your post "{POST_SUBJECT}" at "{SITENAME}" was approved by a moderator or administrator. - -If you want to view the post, click the following link: -{U_VIEW_POST} - -If you want to view the topic, click the following link: -{U_VIEW_TOPIC} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/post_disapproved.txt b/phpBB/language/en/email/short/post_disapproved.txt deleted file mode 100644 index 11a0564cb83..00000000000 --- a/phpBB/language/en/email/short/post_disapproved.txt +++ /dev/null @@ -1,11 +0,0 @@ -Subject: Post disapproved - "{POST_SUBJECT}" - -Hello {USERNAME}, - -Your post "{POST_SUBJECT}" at "{SITENAME}" was disapproved by a moderator or administrator. - -The following reason was given for the disapproval: - -{REASON} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/post_in_queue.txt b/phpBB/language/en/email/short/post_in_queue.txt deleted file mode 100644 index 8ccbc8e9bbc..00000000000 --- a/phpBB/language/en/email/short/post_in_queue.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Post needs approval - "{TOPIC_TITLE}" - -Hello {USERNAME}, - -The post "{POST_SUBJECT}" at "{SITENAME}" needs approval. - -If you want to view the post, click the following link: -{U_VIEW_POST} - -If you want to view the topic, click the following link: -{U_TOPIC} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/privmsg_notify.txt b/phpBB/language/en/email/short/privmsg_notify.txt deleted file mode 100644 index 0c579fa51cb..00000000000 --- a/phpBB/language/en/email/short/privmsg_notify.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: New private message - -Hello {USERNAME}, - -A new private message from "{AUTHOR_NAME}" to your account on "{SITENAME}" with the following subject has arrived: -{SUBJECT} - -You can view your new message by clicking on the following link: -{U_VIEW_MESSAGE} - -You have requested that you be notified on this event, remember that you can always choose not to be notified of new messages by changing the appropriate setting in your profile. - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/quote.txt b/phpBB/language/en/email/short/quote.txt deleted file mode 100644 index 871f4710371..00000000000 --- a/phpBB/language/en/email/short/quote.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Quoted in "{TOPIC_TITLE}" - -Hello {USERNAME}, - -"{AUTHOR_NAME}" quoted you in the topic "{TOPIC_TITLE}" at "{SITENAME}". - -If you want to view the quoted post, click the following link: -{U_VIEW_POST} - -If you no longer wish to receive updates about replies quoting you, please update your notification settings here: -{U_NOTIFICATION_SETTINGS} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/report_pm.txt b/phpBB/language/en/email/short/report_pm.txt deleted file mode 100644 index 771646046b5..00000000000 --- a/phpBB/language/en/email/short/report_pm.txt +++ /dev/null @@ -1,10 +0,0 @@ -Subject: Private Message report - "{SUBJECT}" - -Hello {USERNAME}, - -A Private Message titled "{SUBJECT}" by "{AUTHOR_NAME}" at "{SITENAME}" was reported. - -If you want to view the report, click the following link: -{U_VIEW_REPORT} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/report_post.txt b/phpBB/language/en/email/short/report_post.txt deleted file mode 100644 index 1e50af16151..00000000000 --- a/phpBB/language/en/email/short/report_post.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Post report - "{TOPIC_TITLE}" - -Hello {USERNAME}, - -The post "{POST_SUBJECT}" at "{SITENAME}" was reported. - -If you want to view the report, click the following link: -{U_VIEW_REPORT} - -If you want to view the post, click the following link: -{U_VIEW_POST} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_approved.txt b/phpBB/language/en/email/short/topic_approved.txt deleted file mode 100644 index 132a15ea246..00000000000 --- a/phpBB/language/en/email/short/topic_approved.txt +++ /dev/null @@ -1,10 +0,0 @@ -Subject: Topic approved - "{TOPIC_TITLE}" - -Hello {USERNAME}, - -Your topic "{TOPIC_TITLE}" at "{SITENAME}" was approved by a moderator or administrator. - -If you want to view the topic, click the following link: -{U_VIEW_TOPIC} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_disapproved.txt b/phpBB/language/en/email/short/topic_disapproved.txt deleted file mode 100644 index dfb0f292283..00000000000 --- a/phpBB/language/en/email/short/topic_disapproved.txt +++ /dev/null @@ -1,11 +0,0 @@ -Subject: Topic disapproved - "{TOPIC_TITLE}" - -Hello {USERNAME}, - -Your topic "{TOPIC_TITLE}" at "{SITENAME}" was disapproved by a moderator or administrator. - -The following reason was given for the disapproval: - -{REASON} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_in_queue.txt b/phpBB/language/en/email/short/topic_in_queue.txt deleted file mode 100644 index f4ed7b2ace0..00000000000 --- a/phpBB/language/en/email/short/topic_in_queue.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Topic needs approval - "{TOPIC_TITLE}" - -Hello {USERNAME}, - -The topic "{TOPIC_TITLE}" at "{SITENAME}" needs approval. - -If you want to view the topic, click the following link: -{U_VIEW_TOPIC} - -If you want to view the forum, click the following link: -{U_FORUM} - -{EMAIL_SIG} diff --git a/phpBB/language/en/email/short/topic_notify.txt b/phpBB/language/en/email/short/topic_notify.txt deleted file mode 100644 index d92b342f33c..00000000000 --- a/phpBB/language/en/email/short/topic_notify.txt +++ /dev/null @@ -1,13 +0,0 @@ -Subject: Reply in "{TOPIC_TITLE}" - -Hello {USERNAME}, - -The topic "{TOPIC_TITLE}" received a new reply by {AUTHOR_NAME} since your last visit to "{SITENAME}". No more emails will be sent until you visit the topic. - -If you want to view the newest post made since your last visit, click the following link: -{U_NEWEST_POST} - -If you no longer wish to watch this topic click the following link: -{U_STOP_WATCHING_TOPIC} - -{EMAIL_SIG} From bde52e28f8765a73265b1ff76bce1c117a73c442 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 11:44:49 +0200 Subject: [PATCH 0705/1214] [ticket/17493] Move cpf update outside if PHPBB-17493 --- phpBB/includes/ucp/ucp_profile.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index a5a1fe3d709..93f98d613f2 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -383,11 +383,11 @@ function main($id, $mode) SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $user->data['user_id']; $db->sql_query($sql); - - // Update Custom Fields - $cp->update_profile_field_data($user->data['user_id'], $cp_data); } + // Always update custom fields + $cp->update_profile_field_data($user->data['user_id'], $cp_data); + meta_refresh(3, $this->u_action); $message = $user->lang['PROFILE_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); trigger_error($message); From 3caab55e819772bfad43960901a3898f10405c4b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 15:16:41 +0200 Subject: [PATCH 0706/1214] [ticket/17493] Remove remnants of notify type PHPBB-17493 --- phpBB/includes/acp/acp_users.php | 3 --- phpBB/includes/constants.php | 8 -------- phpBB/includes/ucp/ucp_prefs.php | 3 --- phpBB/install/convertors/convert_phpbb20.php | 1 - .../data/v310/notification_options_reconvert.php | 8 ++++++-- phpBB/phpbb/messenger/method/email.php | 8 -------- .../messenger/method/messenger_interface.php | 16 ---------------- phpBB/phpbb/notification/method/email.php | 2 +- .../phpbb/notification/method/messenger_base.php | 6 +++--- tests/messenger/method_email_test.php | 1 - 10 files changed, 10 insertions(+), 46 deletions(-) diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 42d0338ba25..6abf55ebc8a 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1784,9 +1784,6 @@ function main($id, $mode) 'MASS_EMAIL' => $data['massemail'], 'ALLOW_PM' => $data['allowpm'], 'HIDE_ONLINE' => $data['hideonline'], - 'NOTIFY_EMAIL' => ($data['notifymethod'] == messenger_interface::NOTIFY_EMAIL) ? true : false, - 'NOTIFY_IM' => ($data['notifymethod'] == messenger_interface::NOTIFY_IM) ? true : false, - 'NOTIFY_BOTH' => ($data['notifymethod'] == messenger_interface::NOTIFY_BOTH) ? true : false, 'NOTIFY_PM' => $data['notifypm'], 'BBCODE' => $data['bbcode'], 'SMILIES' => $data['smilies'], diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index e9890d27e1d..e8b77ab2ce9 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -119,14 +119,6 @@ define('POST_ANNOUNCE', 2); define('POST_GLOBAL', 3); -// Notify methods -/** @deprecated 4.0.0-a1 Replaced by \phpbb\messenger\method\messenger_interface::NOTIFY_EMAIL, to be removed in 5.0.0-a1 */ -define('NOTIFY_EMAIL', 0); -/** @deprecated 4.0.0-a1 Replaced by \phpbb\messenger\method\messenger_interface::NOTIFY_IM, to be removed in 5.0.0-a1 */ -define('NOTIFY_IM', 1); -/** @deprecated 4.0.0-a1 Replaced by \phpbb\messenger\method\messenger_interface::NOTIFY_BOTH, to be removed in 5.0.0-a1 */ -define('NOTIFY_BOTH', 2); - // Notify status define('NOTIFY_YES', 0); define('NOTIFY_NO', 1); diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 86440cd7144..a5794870b72 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -176,9 +176,6 @@ function main($id, $mode) $template->assign_vars([ 'ERROR' => (count($error)) ? implode('
', $error) : '', - 'S_NOTIFY_EMAIL' => ($data['notifymethod'] == messenger_interface::NOTIFY_EMAIL) ? true : false, - 'S_NOTIFY_IM' => ($data['notifymethod'] == messenger_interface::NOTIFY_IM) ? true : false, - 'S_NOTIFY_BOTH' => ($data['notifymethod'] == messenger_interface::NOTIFY_BOTH) ? true : false, 'S_VIEW_EMAIL' => $data['viewemail'], 'S_MASS_EMAIL' => $data['massemail'], 'S_ALLOW_PM' => $data['allowpm'], diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index cff03b712e8..db5a38db1f9 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -937,7 +937,6 @@ array('user_emailtime', 'users.user_emailtime', 'null_to_zero'), array('user_notify', 'users.user_notify', 'intval'), array('user_notify_pm', 'users.user_notify_pm', 'intval'), - array('user_notify_type', $messenger_method::NOTIFY_EMAIL, ''), array('user_allow_pm', 'users.user_allow_pm', 'intval'), array('user_allow_viewonline', 'users.user_allow_viewonline', 'intval'), array('user_allow_viewemail', 'users.user_viewemail', 'intval'), diff --git a/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php b/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php index 7f58076a8d0..f2879f4d719 100644 --- a/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php +++ b/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php @@ -17,6 +17,10 @@ class notification_options_reconvert extends \phpbb\db\migration\migration { + protected const NOTIFY_EMAIL = 0; + protected const NOTIFY_IM = 1; + protected const NOTIFY_BOTH = 2; + public static function depends_on() { return array('\phpbb\db\migration\data\v310\notifications_schema_fix'); @@ -69,12 +73,12 @@ public function perform_conversion(\phpbb\db\sql_insert_buffer $insert_buffer, $ // In-board notification $notification_methods[] = ''; - if ($row['user_notify_type'] == messenger_interface::NOTIFY_EMAIL || $row['user_notify_type'] == messenger_interface::NOTIFY_BOTH) + if ($row['user_notify_type'] == self::NOTIFY_EMAIL || $row['user_notify_type'] == self::NOTIFY_BOTH) { $notification_methods[] = 'email'; } - if ($row['user_notify_type'] == messenger_interface::NOTIFY_IM || $row['user_notify_type'] == messenger_interface::NOTIFY_BOTH) + if ($row['user_notify_type'] == self::NOTIFY_IM || $row['user_notify_type'] == self::NOTIFY_BOTH) { $notification_methods[] = 'jabber'; } diff --git a/phpBB/phpbb/messenger/method/email.php b/phpBB/phpbb/messenger/method/email.php index 7bd92538c2d..b601fca232e 100644 --- a/phpBB/phpbb/messenger/method/email.php +++ b/phpBB/phpbb/messenger/method/email.php @@ -74,14 +74,6 @@ class email extends base /** @var AbstractTransport */ protected AbstractTransport $transport; - /** - * {@inheritDoc} - */ - public function get_id(): int - { - return self::NOTIFY_EMAIL; - } - /** * {@inheritDoc} */ diff --git a/phpBB/phpbb/messenger/method/messenger_interface.php b/phpBB/phpbb/messenger/method/messenger_interface.php index 6d82f89b6f3..d9eb247f770 100644 --- a/phpBB/phpbb/messenger/method/messenger_interface.php +++ b/phpBB/phpbb/messenger/method/messenger_interface.php @@ -18,22 +18,6 @@ */ interface messenger_interface { - /** @var int Email notify method used */ - public const NOTIFY_EMAIL = 0; - - /** @var int Instant messaging (Jabber) notify method used */ - public const NOTIFY_IM = 1; - - /** @var int Both notify methods used */ - public const NOTIFY_BOTH = 2; - - /** - * Get messenger method id - * - * @return int - */ - public function get_id(): int; - /** * Check if the messenger method is enabled * diff --git a/phpBB/phpbb/notification/method/email.php b/phpBB/phpbb/notification/method/email.php index 8a2fff1bb95..8ef86f4599e 100644 --- a/phpBB/phpbb/notification/method/email.php +++ b/phpBB/phpbb/notification/method/email.php @@ -136,7 +136,7 @@ public function notify() $insert_buffer->flush(); - $this->notify_using_messenger(messenger_interface::NOTIFY_EMAIL); + $this->notify_using_messenger('messenger.method.email'); } /** diff --git a/phpBB/phpbb/notification/method/messenger_base.php b/phpBB/phpbb/notification/method/messenger_base.php index 165735e56c4..616199519c6 100644 --- a/phpBB/phpbb/notification/method/messenger_base.php +++ b/phpBB/phpbb/notification/method/messenger_base.php @@ -67,12 +67,12 @@ public function is_available(type_interface $notification_type = null) /** * Notify using phpBB messenger * - * @param int $notify_method Notify method for messenger (e.g. \phpbb\messenger\method\messenger_interface::NOTIFY_IM) + * @param string $notify_method Notify method service for messenger (e.g. 'messenger.method.email'), empty string for all available methods * @param string $template_dir_prefix Base directory to prepend to the email template name * * @return void */ - protected function notify_using_messenger($notify_method, $template_dir_prefix = '') + protected function notify_using_messenger(string $notify_method, string $template_dir_prefix = ''): void { if (empty($this->queue)) { @@ -120,7 +120,7 @@ protected function notify_using_messenger($notify_method, $template_dir_prefix = */ foreach ($messenger_collection_iterator as $messenger_method) { - if ($messenger_method->get_id() == $notify_method || $notify_method == $messenger_method::NOTIFY_BOTH) + if (empty($notify_method) || $messenger_collection_iterator->key() == $notify_method) { $messenger_method->template($notification->get_email_template(), $user['user_lang'], '', $template_dir_prefix); $messenger_method->set_addresses($user); diff --git a/tests/messenger/method_email_test.php b/tests/messenger/method_email_test.php index c21a883f74f..fe5c24808c5 100644 --- a/tests/messenger/method_email_test.php +++ b/tests/messenger/method_email_test.php @@ -119,7 +119,6 @@ public function setUp(): void public function test_miscellaneous(): void { - $this->assertEquals(email::NOTIFY_EMAIL, $this->method_email->get_id()); $this->assertEquals('email', $this->method_email->get_queue_object_name()); $this->assertFalse($this->method_email->is_enabled()); $this->config->offsetSet('email_enable', true); From 3125f3e1d368a424ea6931aea352364d8e819a5c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 15:24:07 +0200 Subject: [PATCH 0707/1214] [ticket/17493] Remove unused methods and use statements PHPBB-17493 --- .../migration/data/v310/notification_options_reconvert.php | 2 -- phpBB/phpbb/messenger/method/base.php | 5 ----- phpBB/phpbb/notification/method/email.php | 3 +-- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php b/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php index f2879f4d719..c0b02bc496d 100644 --- a/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php +++ b/phpBB/phpbb/db/migration/data/v310/notification_options_reconvert.php @@ -13,8 +13,6 @@ namespace phpbb\db\migration\data\v310; -use phpbb\messenger\method\messenger_interface; - class notification_options_reconvert extends \phpbb\db\migration\migration { protected const NOTIFY_EMAIL = 0; diff --git a/phpBB/phpbb/messenger/method/base.php b/phpBB/phpbb/messenger/method/base.php index f3db4155cad..17213c6b3de 100644 --- a/phpBB/phpbb/messenger/method/base.php +++ b/phpBB/phpbb/messenger/method/base.php @@ -141,11 +141,6 @@ public function __construct( $this->set_use_queue(); } - /** - * {@inheritdoc} - */ - abstract public function get_id(): int; - /** * {@inheritdoc} */ diff --git a/phpBB/phpbb/notification/method/email.php b/phpBB/phpbb/notification/method/email.php index 8ef86f4599e..07ce0b4f063 100644 --- a/phpBB/phpbb/notification/method/email.php +++ b/phpBB/phpbb/notification/method/email.php @@ -19,14 +19,13 @@ use phpbb\config\config; use phpbb\db\driver\driver_interface; use phpbb\di\service_collection; -use phpbb\messenger\method\messenger_interface; /** * Email notification method class * This class handles sending emails for notifications */ -class email extends \phpbb\notification\method\messenger_base +class email extends messenger_base { /** @var user */ protected $user; From 75a24ae4845ce3d60460afc7082dd1bbb12626f0 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 15:42:59 +0200 Subject: [PATCH 0708/1214] [ticket/17493] Explicitly use email in acp/ucp modules PHPBB-17493 --- phpBB/includes/acp/acp_email.php | 51 +++++++++++++---------------- phpBB/includes/acp/acp_inactive.php | 36 +++++++++----------- phpBB/includes/ucp/ucp_activate.php | 33 ++++++++----------- phpBB/includes/ucp/ucp_resend.php | 37 +++++++++------------ 4 files changed, 68 insertions(+), 89 deletions(-) diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index 5156676881a..2d1b4b72dd2 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -213,43 +213,38 @@ function main($id, $mode) ); extract($phpbb_dispatcher->trigger_event('core.acp_email_send_before', compact($vars))); - /** @var \phpbb\di\service_collection */ - $messenger = $phpbb_container->get('messenger.method_collection'); - $messenger_collection_iterator = $messenger->getIterator(); + /** @var \phpbb\di\service_collection $messenger_collection */ + $messenger_collection = $phpbb_container->get('messenger.method_collection'); + /** @var \phpbb\messenger\method\messenger_interface $messenger_method */ + $messenger_method = $messenger_collection->offsetGet('messenger.method.email'); + for ($i = 0, $size = count($email_list); $i < $size; $i++) { $used_lang = $email_list[$i][0]['lang']; - /** - * @var \phpbb\messenger\method\messenger_interface $messenger_method - * @psalm-suppress UndefinedMethod - */ - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->set_use_queue($use_queue); - $messenger_method->template($email_template, $used_lang); - $messenger_method->subject(html_entity_decode($subject, ENT_COMPAT)); - $messenger_method->assign_vars($template_data); + $messenger_method->set_use_queue($use_queue); + $messenger_method->template($email_template, $used_lang); + $messenger_method->subject(html_entity_decode($subject, ENT_COMPAT)); + $messenger_method->assign_vars($template_data); - for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + { + $email_row = $email_list[$i][$j]; + if (count($email_list[$i]) == 1) { - $email_row = $email_list[$i][$j]; - if (count($email_list[$i]) == 1) - { - $messenger_method->to($email_row['email'], $email_row['name']); - } - else - { - $messenger_method->bcc($email_row['email'], $email_row['name']); - } + $messenger_method->to($email_row['email'], $email_row['name']); + } + else + { + $messenger_method->bcc($email_row['email'], $email_row['name']); } + } - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->set_mail_priority($priority); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->set_mail_priority($priority); - $errored = !$messenger_method->send() || $errored; - $messenger_method->save_queue(); - } + $errored = !$messenger_method->send() || $errored; + $messenger_method->save_queue(); } unset($email_list); diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index b4b39cbdf9b..35bbdf4e852 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -194,9 +194,10 @@ function main($id, $mode) $result = $db->sql_query($sql); - /** @var \phpbb\di\service_collection */ - $messenger = $phpbb_container->get('messenger.method_collection'); - $messenger_collection_iterator = $messenger->getIterator(); + /** @var \phpbb\di\service_collection $messenger_collection */ + $messenger_collection = $phpbb_container->get('messenger.method_collection'); + /** @var \phpbb\messenger\method\messenger_interface $messenger_method */ + $messenger_method = $messenger_collection->offsetGet('messenger.method.email'); if ($row = $db->sql_fetchrow($result)) { @@ -205,24 +206,17 @@ function main($id, $mode) do { - /** - * @var \phpbb\messenger\method\messenger_interface $messenger_method - * @psalm-suppress UndefinedMethod - */ - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->template('user_remind_inactive', $row['user_lang']); - $messenger_method->set_addresses($row); - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->assign_vars([ - 'USERNAME' => html_entity_decode($row['username'], ENT_COMPAT), - 'REGISTER_DATE' => $user->format_date($row['user_regdate'], false, true), - 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u=" . $row['user_id'] . '&k=' . $row['user_actkey'], - ]); - - $messenger_method->send(); - $messenger_method->save_queue(); - } + $messenger_method->template('user_remind_inactive', $row['user_lang']); + $messenger_method->set_addresses($row); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->assign_vars([ + 'USERNAME' => html_entity_decode($row['username'], ENT_COMPAT), + 'REGISTER_DATE' => $user->format_date($row['user_regdate'], false, true), + 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u=" . $row['user_id'] . '&k=' . $row['user_actkey'], + ]); + + $messenger_method->send(); + $messenger_method->save_queue(); $usernames[] = $row['username']; $user_ids[] = (int) $row['user_id']; diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php index 50ccdfa7e6a..5351e7c5182 100644 --- a/phpBB/includes/ucp/ucp_activate.php +++ b/phpBB/includes/ucp/ucp_activate.php @@ -131,25 +131,20 @@ function main($id, $mode) $phpbb_notifications = $phpbb_container->get('notification_manager'); $phpbb_notifications->delete_notifications('notification.type.admin_activate_user', $user_row['user_id']); - $messenger = $phpbb_container->get('messenger.method_collection'); - $messenger_collection_iterator = $messenger->getIterator(); - - /** - * @var \phpbb\messenger\method\messenger_interface $messenger_method - * @psalm-suppress UndefinedMethod - */ - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->set_use_queue(false); - $messenger_method->template('admin_welcome_activated', $user_row['user_lang']); - $messenger_method->set_addresses($user_row); - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->assign_vars([ - 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), - ]); - - $messenger_method->send(); - } + /** @var \phpbb\di\service_collection $messenger_collection */ + $messenger_collection = $phpbb_container->get('messenger.method_collection'); + /** @var \phpbb\messenger\method\messenger_interface $messenger_method */ + $messenger_method = $messenger_collection->offsetGet('messenger.method.email'); + + $messenger_method->set_use_queue(false); + $messenger_method->template('admin_welcome_activated', $user_row['user_lang']); + $messenger_method->set_addresses($user_row); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->assign_vars([ + 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), + ]); + + $messenger_method->send(); $message = 'ACCOUNT_ACTIVE_ADMIN'; } diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 023ea80852d..c2debbbe27b 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -133,29 +133,24 @@ function main($id, $mode) WHERE ' . $db->sql_in_set('user_id', $admin_ary[0]['a_user']); $result = $db->sql_query($sql); - /** @var \phpbb\di\service_collection */ - $messenger = $phpbb_container->get('messenger.method_collection'); - $messenger_collection_iterator = $messenger->getIterator(); + /** @var \phpbb\di\service_collection $messenger_collection */ + $messenger_collection = $phpbb_container->get('messenger.method_collection'); + /** @var \phpbb\messenger\method\messenger_interface $messenger_method */ + $messenger_method = $messenger_collection->offsetGet('messenger.method.email'); + while ($row = $db->sql_fetchrow($result)) { - /** - * @var \phpbb\messenger\method\messenger_interface $messenger_method - * @psalm-suppress UndefinedMethod - */ - foreach ($messenger_collection_iterator as $messenger_method) - { - $messenger_method->set_use_queue(false); - $messenger_method->template('admin_activate', $row['user_lang']); - $messenger_method->set_addresses($row); - $messenger_method->anti_abuse_headers($config, $user); - $messenger_method->assign_vars([ - 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), - 'U_USER_DETAILS' => $board_url . "/memberlist.$phpEx?mode=viewprofile&u={$user_row['user_id']}", - 'U_ACTIVATE' => $board_url . "/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k={$user_row['user_actkey']}", - ]); - - $messenger_method->send(); - } + $messenger_method->set_use_queue(false); + $messenger_method->template('admin_activate', $row['user_lang']); + $messenger_method->set_addresses($row); + $messenger_method->anti_abuse_headers($config, $user); + $messenger_method->assign_vars([ + 'USERNAME' => html_entity_decode($user_row['username'], ENT_COMPAT), + 'U_USER_DETAILS' => $board_url . "/memberlist.$phpEx?mode=viewprofile&u={$user_row['user_id']}", + 'U_ACTIVATE' => $board_url . "/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k={$user_row['user_actkey']}", + ]); + + $messenger_method->send(); } $db->sql_freeresult($result); } From c0504c2e01eb0dab322c1ed5f7ff0f78970f5539 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 3 Mar 2025 21:29:38 +0100 Subject: [PATCH 0709/1214] [ticket/17481] Make build package directory version independent PHPBB-17481 --- build/build.xml | 14 +++++++------- build/build_changelog.php | 2 +- build/build_helper.php | 2 +- build/compare.sh | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/build.xml b/build/build.xml index 40709ccfde9..79e43a08543 100644 --- a/build/build.xml +++ b/build/build.xml @@ -166,32 +166,32 @@ - + - + - + - - + + save/phpbb-${prevversion}_to_${newversion}_language.patch" /> save/phpbb-${prevversion}_to_${newversion}_prosilver.patch" /> package_infos = array( - 'package_name' => 'phpBB3', + 'package_name' => 'phpBB', 'name_prefix' => 'phpbb', 'simple_name' => 'release-' . $_latest, 'new_version_number' => $_latest, diff --git a/build/compare.sh b/build/compare.sh index df442fd4c79..9d01028aa96 100755 --- a/build/compare.sh +++ b/build/compare.sh @@ -19,9 +19,9 @@ do $command "$1.$ext" - for file in `find phpBB3 -name '.svn' -prune -o -type f -print` + for file in `find phpBB -name '.svn' -prune -o -type f -print` do - orig_file="${file/#phpBB3/$orig_dir}" + orig_file="${file/#phpBB/$orig_dir}" diff_result=`diff $orig_file $file` if [ -n "$diff_result" ] @@ -31,7 +31,7 @@ do fi done - rm -rf phpBB3 + rm -rf phpBB done cd .. From 2bbc013d8336587bbcdbb47c77509ab6396dd7a1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 15:47:24 +0200 Subject: [PATCH 0710/1214] [ticket/17481] Stop creating update packages for all except last 3.1 and 3.2 PHPBB-17481 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 79e43a08543..57868251bc0 100644 --- a/build/build.xml +++ b/build/build.xml @@ -4,7 +4,7 @@ - + From 7f3b37560e925f3d10bda67433bbcc8d36de628c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 14 Nov 2024 21:01:48 +0100 Subject: [PATCH 0711/1214] [ticket/17501] Move navbar above header and breadcrumbs below PHPBB-17501 --- .../prosilver/template/navbar_header.html | 63 ------------------- .../prosilver/template/overall_header.html | 8 ++- 2 files changed, 6 insertions(+), 65 deletions(-) diff --git a/phpBB/styles/prosilver/template/navbar_header.html b/phpBB/styles/prosilver/template/navbar_header.html index 702a0187653..7d6ee666eca 100644 --- a/phpBB/styles/prosilver/template/navbar_header.html +++ b/phpBB/styles/prosilver/template/navbar_header.html @@ -203,68 +203,5 @@ {% endif %} - - - diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index 949d3fb1af3..c35f0ab3789 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -79,7 +79,11 @@
+ {% include 'breadcrumbs.html' %} + From 6fd9a78872e9e4f5c8345c629f4a0a3d08ee3d2a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 14 Nov 2024 21:03:01 +0100 Subject: [PATCH 0712/1214] [ticket/17501] Adjust CSS for moved navbar & breadcrumbs PHPBB-17501 --- .../prosilver/template/breadcrumbs.html | 53 +++++++++++++++++++ phpBB/styles/prosilver/theme/colours.css | 25 +++++++-- phpBB/styles/prosilver/theme/common.css | 22 ++++++-- 3 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 phpBB/styles/prosilver/template/breadcrumbs.html diff --git a/phpBB/styles/prosilver/template/breadcrumbs.html b/phpBB/styles/prosilver/template/breadcrumbs.html new file mode 100644 index 00000000000..f71f6e73ff2 --- /dev/null +++ b/phpBB/styles/prosilver/template/breadcrumbs.html @@ -0,0 +1,53 @@ + diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index c642adb22ce..e14a133f5dc 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -99,18 +99,19 @@ th a:hover { } /* round cornered boxes and backgrounds */ -.headerbar { - color: #ffffff; +.headerbar, +.headerbar h1 { + color: #eaf8ff; } .headerbar, .forumbg { - background-color: #13a4ec; + background-color: #4688ce; background-repeat: repeat-x; } .forabg { - background-color: #13a4ec; + background-color: #4688ce; background-repeat: repeat-x; } @@ -118,6 +119,18 @@ th a:hover { background-color: #c9dee8; } +.headerbar .navbar a { + color: #eaf8ff; +} + +.headerbar .navbar .dropdown a { + color: #0f4d8a; +} + +.header-profile { + text-shadow: 0 0 var(--ps-font-tiny) #eaf8ff; +} + .panel { background-color: #f0f3f5; color: #29303d; @@ -814,6 +827,10 @@ dd.profile-warnings { /* icon images */ .site_logo { background-image: url("./images/site_logo.svg"); } +.c-hero-logo-img g { + fill: #eaf8ff; +} + /* colours and backgrounds for cp.css */ /* main cp box */ diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index e8f32465e25..46edea7c045 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -17,7 +17,7 @@ body { line-height: normal; word-wrap: break-word; margin: 0; - padding: 12px 0; + padding: 0 0 12px; -webkit-print-color-adjust: exact; } @@ -151,12 +151,13 @@ a:hover { /* Main blocks ---------------------------------------- */ .wrap { - border: 1px solid transparent; - border-radius: 8px; + border: solid transparent; + border-width: 0 1px 1px; + border-radius: 0 0 8px 8px; min-width: 625px; max-width: 1152px; margin: 0 auto; - padding: 15px; + padding: 0 15px 15px; } .page-body { @@ -200,7 +201,9 @@ a:hover { /* Round cornered boxes and backgrounds ---------------------------------------- */ .headerbar { - border-radius: 7px; + background-image: url('images/lighter2.png'); + border-radius: 0 0 7px 7px; + background-size: cover; display: flex; flex-direction: column; margin-bottom: 0.5rem; @@ -212,6 +215,11 @@ a:hover { padding: 3px 10px; } +.headerbar .navbar { + background: none; + padding: calc(var(--ps-line-height) * 0.25) 5px calc(var(--ps-line-height) * 0.5); +} + .forabg { border-radius: 7px; clear: both; @@ -340,6 +348,10 @@ ul.linklist .dropdown-up .dropdown { bottom: 18px; } +ul.nav-breadcrumbs { + margin: var(--ps-line-height) 0; +} + /* Bulletin icons for list items ---------------------------------------- */ ul.linklist.bulletin > li:before { From 81c49aa6a526844f074b46d2b42cea453796e5c3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 20:18:40 +0200 Subject: [PATCH 0713/1214] [ticket/17501] Remove max width from subforums PHPBB-17501 --- phpBB/styles/prosilver/theme/responsive.css | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index 80e605df625..ce5462455e0 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -328,7 +328,6 @@ vertical-align: bottom; text-overflow: ellipsis; overflow: hidden; - max-width: 100px; } /* Pagination From b666bc9e0a3b6848d3d8b7bf156b6495bd9efedf Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 20:33:36 +0200 Subject: [PATCH 0714/1214] [ticket/17501] Add to top button to viewtopic and add missing title in FAQ PHPBB-17501 --- phpBB/styles/prosilver/template/faq_body.html | 2 +- phpBB/styles/prosilver/template/viewtopic_body.html | 9 ++++++--- phpBB/styles/prosilver/theme/content.css | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/phpBB/styles/prosilver/template/faq_body.html b/phpBB/styles/prosilver/template/faq_body.html index 542e894c61e..2a1c2dfd925 100644 --- a/phpBB/styles/prosilver/template/faq_body.html +++ b/phpBB/styles/prosilver/template/faq_body.html @@ -44,7 +44,7 @@

{faq_block.BLOCK_TITLE}

diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 2031fa64dd6..70bc764e06f 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -423,9 +423,6 @@

@@ -496,6 +493,12 @@

+ + diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index 7101aeee462..07a4ddb325d 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -465,7 +465,7 @@ p.author { clear: left; overflow: hidden; width: 100%; - margin-top: calc(var(--ps-font-small) * 1.4); + margin: calc(var(--ps-font-small) * 1.4) 0; padding-top: 2px; } From 3a553f07bccc1805cadab30aed628bb65a91b739 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 20:36:04 +0200 Subject: [PATCH 0715/1214] [ticket/17501] Remove unused CSS properties PHPBB-17501 --- phpBB/styles/prosilver/theme/common.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 46edea7c045..909727132ec 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -201,9 +201,7 @@ a:hover { /* Round cornered boxes and backgrounds ---------------------------------------- */ .headerbar { - background-image: url('images/lighter2.png'); border-radius: 0 0 7px 7px; - background-size: cover; display: flex; flex-direction: column; margin-bottom: 0.5rem; From 3801eb094633f20a0b546765a47d3f65b081d270 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 21 Apr 2025 20:38:59 +0200 Subject: [PATCH 0716/1214] [ticket/17501] Remove not needed parentheses PHPBB-17501 --- phpBB/styles/prosilver/template/overall_header.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index c35f0ab3789..e12a1294bd1 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -68,7 +68,7 @@ {% if NOTIFICATIONS_WEBPUSH_ENABLE %} - {% include('ucp_notifications_webpush.html') %} + {% include 'ucp_notifications_webpush.html' %} {% endif %} @@ -83,7 +83,7 @@
diff --git a/phpBB/styles/prosilver/template/viewforum_body.html b/phpBB/styles/prosilver/template/viewforum_body.html index e9d04905bca..c02cc4879ac 100644 --- a/phpBB/styles/prosilver/template/viewforum_body.html +++ b/phpBB/styles/prosilver/template/viewforum_body.html @@ -158,14 +158,14 @@

{L_LOGIN_LOGOUT}
style="background-image: url('{T_ICONS_PATH}{topicrow.TOPIC_ICON_IMG}'); background-repeat: no-repeat;" title="{topicrow.TOPIC_FOLDER_IMG_ALT}"> - + {% if topicrow.U_NEWEST_POST and topicrow.S_UNREAD_TOPIC and not S_IS_BOT %}{% endif %}
- + {% if topicrow.U_NEWEST_POST and topicrow.S_UNREAD_TOPIC and not S_IS_BOT %} {NEW_POST} - + {% endif %} {topicrow.TOPIC_TITLE}{topicrow.TOPIC_TITLE} diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 300d57d0c72..01910394002 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -384,7 +384,12 @@ // Display active topics? $s_display_active = ($forum_data['forum_type'] == FORUM_CAT && ($forum_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS)) ? true : false; -$s_search_hidden_fields = array('fid' => array($forum_id)); +// Send the forum id and send a parameter to make it clear it's a quick search +$s_search_hidden_fields = [ + 'fid' => [$forum_id], + 'viewforum' => $forum_id, +]; + if ($_SID) { $s_search_hidden_fields['sid'] = $_SID; @@ -1017,7 +1022,7 @@ 'S_TOPIC_MOVED' => ($row['topic_status'] == ITEM_MOVED) ? true : false, 'U_NEWEST_POST' => $auth->acl_get('f_read', $forum_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params . '&view=unread') . '#unread' : false, - 'U_LAST_POST' => $auth->acl_get('f_read', $forum_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'] : false, + 'U_LAST_POST' => $auth->acl_get('f_read', $forum_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'] : false, 'U_LAST_POST_AUTHOR' => get_username_string('profile', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), 'U_TOPIC_AUTHOR' => get_username_string('profile', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), 'U_VIEW_TOPIC' => $view_topic_url, From 078cd300fc32f362b957e03b2aea33059ee61f3e Mon Sep 17 00:00:00 2001 From: battye Date: Thu, 17 Jul 2025 15:45:56 +0000 Subject: [PATCH 0853/1214] [ticket/17157] Optimise the quick search code PHPBB-17157 --- phpBB/search.php | 32 +++++--------------------------- phpBB/viewforum.php | 10 ++++++++-- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/phpBB/search.php b/phpBB/search.php index 3e052040434..59ed6a646b8 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -247,29 +247,12 @@ $ex_fid_ary = array_unique(array_merge(array_keys($auth->acl_getf('!f_read', true)), array_keys($auth->acl_getf('!f_search', true)))); } - // There are two exceptional scenarios we want to consider if there are any forums where an read forum = no, can read topics = yes - // In these cases, the user should see the topic title in the search results but not the link to the topic (or any posts) because - // they don't have the permissions for that. - $show_topic_title_only = false; - - // Firstly, is someone doing a quick search from the viewforum page? If so, force it to be a topic-only search for that one forum - // We know if this is the case due to the presence of this request var - $forum_quick_search = $request->variable('viewforum', 0); - if ($forum_quick_search && $auth->acl_get('f_list_topics', $forum_quick_search) && !$auth->acl_get('f_read', $forum_quick_search)) + // Consider if there are any forums where can read forum = no, can read topics = yes + // In these cases, the user should see the topic title in the search results but not the link to the topic (or any posts) because they don't have the permissions + if ($request->variable('sr', '') == 'topics' && $search_fields == 'titleonly') { - $show_topic_title_only = true; - } - - // Secondly, is someone doing a topic search from the main search page? If so, we will strip the topic links while still showing the name - else if ($request->variable('sr', '') == 'topics' && $search_fields == 'titleonly') - { - // We will allow the 'can read topics = yes' forums back in to the search - $show_topic_title_only = true; - } - - if ($show_topic_title_only) - { - // Remove from $ex_fid_ary any of the 'can read topics' forums (meaning they will not be excluded from the search) + // The user could get here from a quick search through the viewforum page, or by doing a main search displayed by topics and searching only the topic titles. + // Allow the 'can read topics = yes' forums back in to the search by removing from $ex_fid_ary any of the 'can read topics' forums $ex_fid_ary = array_diff($ex_fid_ary, array_keys($auth->acl_getf('f_list_topics', true))); } @@ -375,11 +358,6 @@ $show_results = ($show_results == 'posts') ? 'posts' : 'topics'; } - if ($show_topic_title_only) - { - $show_results = 'topics'; - } - // define some variables needed for retrieving post_id/topic_id information $sort_by_sql = [ 'a' => 'u.username_clean', diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 01910394002..24bb5a99bbb 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -384,12 +384,18 @@ // Display active topics? $s_display_active = ($forum_data['forum_type'] == FORUM_CAT && ($forum_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS)) ? true : false; -// Send the forum id and send a parameter to make it clear it's a quick search +// Send the forum id... and maybe some other fields, depending on permissions $s_search_hidden_fields = [ 'fid' => [$forum_id], - 'viewforum' => $forum_id, ]; +if ($auth->acl_get('f_list_topics', $forum_id) && !$auth->acl_get('f_read', $forum_id)) +{ + // If the user has list access but not read access, then force the search to only be a topic title search + $s_search_hidden_fields['sr'] = 'topics'; + $s_search_hidden_fields['sf'] = 'titleonly'; +} + if ($_SID) { $s_search_hidden_fields['sid'] = $_SID; From e0e457b03bc4db38436be6fe375b20ce8c3dbef7 Mon Sep 17 00:00:00 2001 From: battye Date: Fri, 18 Jul 2025 06:35:52 +0000 Subject: [PATCH 0854/1214] [ticket/17157] Whitespace and minor edits PHPBB-17157 --- phpBB/search.php | 2 +- phpBB/styles/prosilver/template/search_results.html | 4 ++-- phpBB/styles/prosilver/template/viewforum_body.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/search.php b/phpBB/search.php index 59ed6a646b8..e136096b601 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -247,7 +247,7 @@ $ex_fid_ary = array_unique(array_merge(array_keys($auth->acl_getf('!f_read', true)), array_keys($auth->acl_getf('!f_search', true)))); } - // Consider if there are any forums where can read forum = no, can read topics = yes + // Consider if there are any forums where can read forum = no, can read topics = yes // In these cases, the user should see the topic title in the search results but not the link to the topic (or any posts) because they don't have the permissions if ($request->variable('sr', '') == 'topics' && $search_fields == 'titleonly') { diff --git a/phpBB/styles/prosilver/template/search_results.html b/phpBB/styles/prosilver/template/search_results.html index 8f664201bbb..4c4c6aaa1cb 100644 --- a/phpBB/styles/prosilver/template/search_results.html +++ b/phpBB/styles/prosilver/template/search_results.html @@ -80,7 +80,7 @@

{SEARCH_TITLE} bg1 bg2">
style="background-image: url({T_ICONS_PATH}{searchresults.TOPIC_ICON_IMG}); background-repeat: no-repeat;" title="{searchresults.TOPIC_FOLDER_IMG_ALT}"> - {% if searchresults.U_NEWEST_POST and searchresults.S_UNREAD_TOPIC and not S_IS_BOT %}{% endif %} + {% if searchresults.U_NEWEST_POST and searchresults.S_UNREAD_TOPIC and not S_IS_BOT %}{% endif %}
{% if searchresults.U_NEWEST_POST and searchresults.S_UNREAD_TOPIC and not S_IS_BOT %} @@ -88,7 +88,7 @@

{SEARCH_TITLE} {L_TOPIC_UNAPPROVED} diff --git a/phpBB/styles/prosilver/template/viewforum_body.html b/phpBB/styles/prosilver/template/viewforum_body.html index c02cc4879ac..49aad65572e 100644 --- a/phpBB/styles/prosilver/template/viewforum_body.html +++ b/phpBB/styles/prosilver/template/viewforum_body.html @@ -158,7 +158,7 @@

{L_LOGIN_LOGOUT}
style="background-image: url('{T_ICONS_PATH}{topicrow.TOPIC_ICON_IMG}'); background-repeat: no-repeat;" title="{topicrow.TOPIC_FOLDER_IMG_ALT}"> - {% if topicrow.U_NEWEST_POST and topicrow.S_UNREAD_TOPIC and not S_IS_BOT %}{% endif %} + {% if topicrow.U_NEWEST_POST and topicrow.S_UNREAD_TOPIC and not S_IS_BOT %}{% endif %}
{% if topicrow.U_NEWEST_POST and topicrow.S_UNREAD_TOPIC and not S_IS_BOT %} From 69fa84b41b1cf209c7faccb4ba35096329e8cb3b Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Fri, 18 Jul 2025 08:55:06 -0700 Subject: [PATCH 0855/1214] [ticket/17535] Update test fix PHPBB-17535 Signed-off-by: Matt Friedman --- tests/help/manager_test.php | 58 +++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/tests/help/manager_test.php b/tests/help/manager_test.php index 36b3fa4143c..428e0e5201b 100644 --- a/tests/help/manager_test.php +++ b/tests/help/manager_test.php @@ -80,18 +80,22 @@ public function test_add_block($block_name, $switch, $questions, $switch_expecte ->method('lang') ->willReturnCallback(fn(string $arg) => strtoupper($arg)); - $matcher = $this->exactly(count($questions) + 1); - $this->template->expects($matcher) + $expected_calls = [ + ['faq_block', [ + 'BLOCK_TITLE' => strtoupper($block_name), + 'SWITCH_COLUMN' => $switch_expected, + ]], + ...$template_call_args + ]; + + $this->template->expects($this->exactly(count($questions) + 1)) ->method('assign_block_vars') - ->willReturnCallback(function ($arg1, $arg2) use ($matcher, $block_name, $switch_expected, $template_call_args) { - $callNr = $matcher->numberOfInvocations(); - match (true) { - $callNr == 1 => $this->assertEquals([$arg1, $arg2], ['faq_block', [ - 'BLOCK_TITLE' => strtoupper($block_name), - 'SWITCH_COLUMN' => $switch_expected, - ]]), - $callNr > 1 => $this->assertEquals([$arg1, $arg2], $template_call_args[$callNr - 2]), - };}); + ->willReturnCallback(function($blockName, $blockVars) use (&$expected_calls) { + static $call = 0; + $this->assertEquals($expected_calls[$call][0], $blockName); + $this->assertEquals($expected_calls[$call][1], $blockVars); + $call++; + }); $this->manager->add_block($block_name, $switch, $questions); @@ -137,21 +141,25 @@ public function test_add_block_double_switch() ->method('lang') ->willReturnCallback(fn(string $arg) => strtoupper($arg)); - $matcher = $this->exactly(2); - $this->template->expects($matcher) + $expected_calls = [ + ['faq_block', [ + 'BLOCK_TITLE' => strtoupper($block_name[0]), + 'SWITCH_COLUMN' => $switch_expected[0], + ]], + ['faq_block', [ + 'BLOCK_TITLE' => strtoupper($block_name[1]), + 'SWITCH_COLUMN' => $switch_expected[1], + ]] + ]; + + $this->template->expects($this->exactly(count($expected_calls))) ->method('assign_block_vars') - ->willReturnCallback(function ($arg1, $arg2) use ($matcher, $block_name, $switch_expected) { - $callNr = $matcher->numberOfInvocations(); - match (true) { - $callNr == 1 => $this->assertEquals([$arg1, $arg2], ['faq_block', [ - 'BLOCK_TITLE' => strtoupper($block_name[0]), - 'SWITCH_COLUMN' => $switch_expected[0], - ]]), - $callNr == 2 => $this->assertEquals([$arg1, $arg2], ['faq_block', [ - 'BLOCK_TITLE' => strtoupper($block_name[1]), - 'SWITCH_COLUMN' => $switch_expected[1], - ]]), - };}); + ->willReturnCallback(function($blockName, $blockVars) use (&$expected_calls) { + static $call = 0; + $this->assertEquals($expected_calls[$call][0], $blockName); + $this->assertEquals($expected_calls[$call][1], $blockVars); + $call++; + }); $this->manager->add_block($block_name[0], true); $this->assertTrue($this->manager->switched_column()); From 788e408ceb54428ed27f131e8ccb6558d3a76438 Mon Sep 17 00:00:00 2001 From: rxu Date: Fri, 18 Jul 2025 23:11:41 +0700 Subject: [PATCH 0856/1214] [ticket/17535] Fix global var names duplication PHPBB-17535 --- tests/bbcode/parser_test.php | 2 +- tests/bbcode/url_bbcode_test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bbcode/parser_test.php b/tests/bbcode/parser_test.php index 0b91742bd8b..1786fabc5fc 100644 --- a/tests/bbcode/parser_test.php +++ b/tests/bbcode/parser_test.php @@ -249,7 +249,7 @@ public function test_bbcode_firstpass($description, $message, $expected, $incomp $this->markTestIncomplete($incomplete); } - global $user, $request, $symfony_request, $phpbb_dispatcher, $config, $phpEx, $request, $symfony_request; + global $user, $request, $symfony_request, $phpbb_dispatcher, $config, $phpEx; $phpEx = 'php'; $config = new \phpbb\config\config([ 'max_post_font_size' => 0, diff --git a/tests/bbcode/url_bbcode_test.php b/tests/bbcode/url_bbcode_test.php index 727648fb063..5f42f1d4bb4 100644 --- a/tests/bbcode/url_bbcode_test.php +++ b/tests/bbcode/url_bbcode_test.php @@ -52,7 +52,7 @@ public static function url_bbcode_test_data() */ public function test_url($description, $message, $expected) { - global $user, $request, $symfony_request, $phpbb_dispatcher, $config, $phpEx, $request, $symfony_request; + global $user, $request, $symfony_request, $phpbb_dispatcher, $config, $phpEx; $phpEx = 'php'; $config = new \phpbb\config\config([ 'max_post_font_size' => 0, From 980b6e6f9e4e592a1396e0934363b9d3bfe02446 Mon Sep 17 00:00:00 2001 From: rxu Date: Mon, 14 Jul 2025 00:38:54 +0700 Subject: [PATCH 0857/1214] [ticket/17530] Use Doctrine driver middleware instead of 'platform' parameter PHPBB-17530 --- .../db/doctrine/case_insensitive_string.php | 6 +- .../phpbb/db/doctrine/connection_factory.php | 19 ++- .../doctrine/connection_parameter_factory.php | 31 +---- phpBB/phpbb/db/doctrine/oci8/connection.php | 99 ---------------- phpBB/phpbb/db/doctrine/oci8/driver.php | 65 ----------- phpBB/phpbb/db/doctrine/oci8/result.php | 109 ------------------ phpBB/phpbb/db/doctrine/oci8/statement.php | 58 ---------- .../middleware/mysql/phpbb_mysql_driver.php | 31 +++++ .../mysql/phpbb_mysql_middleware.php | 28 +++++ .../mysql/phpbb_mysql_platform.php} | 4 +- .../middleware/oracle/phpbb_oracle_driver.php | 41 +++++++ .../oracle/phpbb_oracle_middleware.php | 28 +++++ .../oracle/phpbb_oracle_platform.php} | 4 +- .../oracle/phpbb_oracle_schema_manager.php} | 4 +- .../postgresql/phpbb_postgresql_driver.php | 31 +++++ .../phpbb_postgresql_middleware.php | 28 +++++ .../postgresql/phpbb_postgresql_platform.php} | 4 +- .../middleware/sqlsrv/phpbb_sqlsrv_driver.php | 31 +++++ .../sqlsrv/phpbb_sqlsrv_middleware.php | 28 +++++ .../sqlsrv/phpbb_sqlsrv_platform.php} | 4 +- 20 files changed, 283 insertions(+), 370 deletions(-) delete mode 100644 phpBB/phpbb/db/doctrine/oci8/connection.php delete mode 100644 phpBB/phpbb/db/doctrine/oci8/driver.php delete mode 100644 phpBB/phpbb/db/doctrine/oci8/result.php delete mode 100644 phpBB/phpbb/db/doctrine/oci8/statement.php create mode 100644 phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php create mode 100644 phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php rename phpBB/phpbb/db/{doctrine/mysql_platform.php => middleware/mysql/phpbb_mysql_platform.php} (93%) create mode 100644 phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php create mode 100644 phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php rename phpBB/phpbb/db/{doctrine/oracle_platform.php => middleware/oracle/phpbb_oracle_platform.php} (98%) rename phpBB/phpbb/db/{doctrine/oci8/schema_manager.php => middleware/oracle/phpbb_oracle_schema_manager.php} (90%) create mode 100644 phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php create mode 100644 phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php rename phpBB/phpbb/db/{doctrine/postgresql_platform.php => middleware/postgresql/phpbb_postgresql_platform.php} (97%) create mode 100644 phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php create mode 100644 phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php rename phpBB/phpbb/db/{doctrine/sqlsrv_platform.php => middleware/sqlsrv/phpbb_sqlsrv_platform.php} (97%) diff --git a/phpBB/phpbb/db/doctrine/case_insensitive_string.php b/phpBB/phpbb/db/doctrine/case_insensitive_string.php index 484308fc063..a8ac6e76057 100644 --- a/phpBB/phpbb/db/doctrine/case_insensitive_string.php +++ b/phpBB/phpbb/db/doctrine/case_insensitive_string.php @@ -15,6 +15,8 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use phpbb\db\middleware\postgresql\phpbb_postgresql_platform; +use phpbb\db\middleware\oracle\phpbb_oracle_platform; /** * Case-insensitive string type (only supported by Postgres). @@ -28,7 +30,7 @@ class case_insensitive_string extends Type */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - if ($platform instanceof postgresql_platform) + if ($platform instanceof phpbb_postgresql_platform) { return 'varchar_ci'; } @@ -37,7 +39,7 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st // we used 3 times larger capacity for strings on oracle for unicode strings // as on other platforms. This is not the case with varchar_ci, which uses // the same length as other platforms. - if ($platform instanceof oracle_platform) + if ($platform instanceof phpbb_oracle_platform) { return $platform->getAsciiStringTypeDeclarationSQL($column); } diff --git a/phpBB/phpbb/db/doctrine/connection_factory.php b/phpBB/phpbb/db/doctrine/connection_factory.php index fb24994b3dd..5ae5107da4e 100644 --- a/phpBB/phpbb/db/doctrine/connection_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_factory.php @@ -13,12 +13,17 @@ namespace phpbb\db\doctrine; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Types\Type; use InvalidArgumentException; use phpbb\config_php_file; +use phpbb\db\middleware\mysql\phpbb_mysql_middleware; +use phpbb\db\middleware\oracle\phpbb_oracle_middleware; +use phpbb\db\middleware\postgresql\phpbb_postgresql_middleware; +use phpbb\db\middleware\sqlsrv\phpbb_sqlsrv_middleware; use phpbb\exception\runtime_exception; /** @@ -94,9 +99,21 @@ public static function get_connection_from_params( $port ); + $middleware = match($driver) + { + 'pdo_mysql', 'mysqli' => [new phpbb_mysql_middleware()], + 'pdo_oci', 'oci8' => [new phpbb_oracle_middleware()], + 'pdo_pgsql', 'pgsql' => [new phpbb_postgresql_middleware()], + 'pdo_sqlsrv', 'sqlsrv' => [new phpbb_sqlsrv_middleware()], + default => [], + }; + try { - $connection = DriverManager::getConnection($params); + $connection_config = new Configuration(); + $connection_config->setMiddlewares($middleware); + + $connection = DriverManager::getConnection($params, $connection_config); if (!Type::hasType(case_insensitive_string::CASE_INSENSITIVE_STRING)) { Type::addType(case_insensitive_string::CASE_INSENSITIVE_STRING, case_insensitive_string::class); diff --git a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php index 90e41061fc9..6e59c4a61e1 100644 --- a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php @@ -14,7 +14,6 @@ namespace phpbb\db\doctrine; use InvalidArgumentException; -use phpbb\db\doctrine\oci8\driver as oci8_driver; /** * Helper class to generate Doctrine DBAL configuration. @@ -146,37 +145,17 @@ private static function build_sqlite_parameters(array $params, string $path, str */ private static function enrich_parameters(array $params) : array { - $enrichment_tags = [ - 'pdo_mysql' => [ - 'charset' => 'UTF8', - 'platform' => new mysql_platform(), - ], - 'oci8' => [ - 'charset' => 'UTF8', - 'platform' => new oracle_platform(), - 'driverClass' => oci8_driver::class, - ], - 'pdo_pgsql' => [ - 'charset' => 'UTF8', - 'platform' => new postgresql_platform(), - ], - 'pdo_sqlsrv' => [ - 'platform' => new sqlsrv_platform(), - ], - ]; - - if ($params['driver'] === 'pdo_mysql' && extension_loaded('pdo_mysql')) + if (in_array($params['driver'], ['mysqli', 'pdo_mysql', 'pgsql', 'pdo_pgsql', 'oci8', 'pdo_oci'])) { - $enrichment_tags['pdo_mysql'][\PDO::MYSQL_ATTR_FOUND_ROWS] = true; + $params['charset'] = 'UTF8'; } - $driver = $params['driver']; - if (!array_key_exists($driver, $enrichment_tags)) + if ($params['driver'] === 'pdo_mysql' && extension_loaded('pdo_mysql')) { - return $params; + $params[\PDO::MYSQL_ATTR_FOUND_ROWS] = true; } - return array_merge($params, $enrichment_tags[$driver]); + return $params; } /* diff --git a/phpBB/phpbb/db/doctrine/oci8/connection.php b/phpBB/phpbb/db/doctrine/oci8/connection.php deleted file mode 100644 index 34ed744e9db..00000000000 --- a/phpBB/phpbb/db/doctrine/oci8/connection.php +++ /dev/null @@ -1,99 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\db\doctrine\oci8; - -use Doctrine\DBAL\Driver\Connection as DriverConnection; -use Doctrine\DBAL\Driver\Result as DriverResult; -use Doctrine\DBAL\Driver\Statement as DriverStatement; -use Doctrine\DBAL\ParameterType; - -class connection implements DriverConnection -{ - /** - * @var DriverConnection - */ - private $wrapped; - - /** - * @param DriverConnection $wrapped - */ - public function __construct(DriverConnection $wrapped) - { - $this->wrapped = $wrapped; - } - - /** - * {@inheritDoc} - */ - public function prepare(string $sql): DriverStatement - { - return new statement($this->wrapped->prepare($sql)); - } - - /** - * {@inheritDoc} - */ - public function query(string $sql): DriverResult - { - return new result($this->wrapped->query($sql)); - } - - /** - * {@inheritDoc} - */ - public function quote($value, $type = ParameterType::STRING) - { - return $this->wrapped->quote($value, $type); - } - - /** - * {@inheritDoc} - */ - public function exec(string $sql): int - { - return $this->wrapped->exec($sql); - } - - /** - * {@inheritDoc} - */ - public function lastInsertId($name = null) - { - return $this->wrapped->lastInsertId($name); - } - - /** - * {@inheritDoc} - */ - public function beginTransaction(): bool - { - return $this->wrapped->beginTransaction(); - } - - /** - * {@inheritDoc} - */ - public function commit(): bool - { - return $this->wrapped->commit(); - } - - /** - * {@inheritDoc} - */ - public function rollBack(): bool - { - return $this->wrapped->rollBack(); - } -} diff --git a/phpBB/phpbb/db/doctrine/oci8/driver.php b/phpBB/phpbb/db/doctrine/oci8/driver.php deleted file mode 100644 index 9a2c67aee05..00000000000 --- a/phpBB/phpbb/db/doctrine/oci8/driver.php +++ /dev/null @@ -1,65 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\db\doctrine\oci8; - -use Doctrine\DBAL\Connection as DoctrineConnection; -use Doctrine\DBAL\Driver\API\ExceptionConverter; -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Driver as DoctrineDriver; -use Doctrine\DBAL\Driver\OCI8\Driver as OCI8Driver; - -class driver implements DoctrineDriver -{ - /** - * @var DoctrineDriver - */ - private $wrapped; - - public function __construct() - { - $this->wrapped = new OCI8Driver(); - } - - /** - * {@inheritDoc} - */ - public function connect(array $params) - { - return new connection($this->wrapped->connect($params)); - } - - /** - * {@inheritDoc} - */ - public function getDatabasePlatform() - { - return $this->wrapped->getDatabasePlatform(); - } - - /** - * {@inheritDoc} - */ - public function getSchemaManager(DoctrineConnection $conn, AbstractPlatform $platform) - { - return new schema_manager($conn, $platform); - } - - /** - * {@inheritDoc} - */ - public function getExceptionConverter(): ExceptionConverter - { - return $this->wrapped->getExceptionConverter(); - } -} diff --git a/phpBB/phpbb/db/doctrine/oci8/result.php b/phpBB/phpbb/db/doctrine/oci8/result.php deleted file mode 100644 index 1ab10854279..00000000000 --- a/phpBB/phpbb/db/doctrine/oci8/result.php +++ /dev/null @@ -1,109 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\db\doctrine\oci8; - -use Doctrine\DBAL\Driver\Result as DriverResult; - -class result implements DriverResult -{ - /** - * @var DriverResult - */ - private $wrapped; - - /** - * @param DriverResult $wrapped - */ - public function __construct(DriverResult $wrapped) - { - $this->wrapped = $wrapped; - } - - /** - * {@inheritDoc} - */ - public function fetchNumeric() - { - return $this->wrapped->fetchNumeric(); - } - - /** - * {@inheritDoc} - */ - public function fetchAssociative() - { - return array_change_key_case($this->wrapped->fetchAssociative(), CASE_LOWER); - } - - /** - * {@inheritDoc} - */ - public function fetchOne() - { - return $this->wrapped->fetchOne(); - } - - /** - * {@inheritDoc} - */ - public function fetchAllNumeric(): array - { - return $this->wrapped->fetchAllNumeric(); - } - - /** - * {@inheritDoc} - */ - public function fetchAllAssociative(): array - { - $rows = []; - foreach ($this->wrapped->fetchAllAssociative() as $row) - { - $rows[] = array_change_key_case($row, CASE_LOWER); - } - return $rows; - } - - /** - * {@inheritDoc} - */ - public function fetchFirstColumn(): array - { - return $this->wrapped->fetchFirstColumn(); - } - - /** - * {@inheritDoc} - */ - public function rowCount(): int - { - return $this->wrapped->rowCount(); - } - - /** - * {@inheritDoc} - */ - public function columnCount(): int - { - return $this->wrapped->columnCount(); - } - - /** - * {@inheritDoc} - */ - public function free(): void - { - $this->wrapped->free(); - } -} diff --git a/phpBB/phpbb/db/doctrine/oci8/statement.php b/phpBB/phpbb/db/doctrine/oci8/statement.php deleted file mode 100644 index bca22473c4c..00000000000 --- a/phpBB/phpbb/db/doctrine/oci8/statement.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\db\doctrine\oci8; - -use Doctrine\DBAL\Driver\Result as DriverResult; -use Doctrine\DBAL\Driver\Statement as DriverStatement; -use Doctrine\DBAL\ParameterType; - -class statement implements DriverStatement -{ - /** - * @var DriverStatement - */ - private $wrapped; - - /** - * @param DriverStatement $wrapped - */ - public function __construct(DriverStatement $wrapped) - { - $this->wrapped = $wrapped; - } - - /** - * {@inheritDoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING): bool - { - return $this->wrapped->bindValue($param, $value, $type); - } - - /** - * {@inheritDoc} - */ - public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool - { - return $this->wrapped->bindParam($param, $variable, $type, $length); - } - - /** - * {@inheritDoc} - */ - public function execute($params = null): DriverResult - { - return new result($this->wrapped->execute($params)); - } -} diff --git a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php b/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php new file mode 100644 index 00000000000..651a9797652 --- /dev/null +++ b/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\mysql; + +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; + +/** + * MySQL Doctrine driver middleware. + * Makes use of phpBB's MySQL specific platform. + */ +class phpbb_mysql_driver extends AbstractDriverMiddleware +{ + /** + * {@inheritDoc} + */ + public function createDatabasePlatformForVersion($version) + { + return new phpbb_mysql_platform(); + } +} diff --git a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php b/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php new file mode 100644 index 00000000000..5e79142a9e4 --- /dev/null +++ b/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\mysql; + +use Doctrine\DBAL\Driver; + +/** + * MySQL Doctrine middleware. + * Makes use of phpBB's MySQL specific platform. + */ +class phpbb_mysql_middleware implements Driver\Middleware +{ + public function wrap(Driver $driver): Driver + { + return new phpbb_mysql_driver($driver); + } +} diff --git a/phpBB/phpbb/db/doctrine/mysql_platform.php b/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_platform.php similarity index 93% rename from phpBB/phpbb/db/doctrine/mysql_platform.php rename to phpBB/phpbb/db/middleware/mysql/phpbb_mysql_platform.php index 8a2090b6d90..a64430613d6 100644 --- a/phpBB/phpbb/db/doctrine/mysql_platform.php +++ b/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_platform.php @@ -11,7 +11,7 @@ * */ -namespace phpbb\db\doctrine; +namespace phpbb\db\middleware\mysql; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Schema\TableDiff; @@ -23,7 +23,7 @@ * If it's indexed as primary key, it should be declared as NOT NULL * because MySQL primary key columns cannot be NULL. */ -class mysql_platform extends AbstractMySQLPlatform +class phpbb_mysql_platform extends AbstractMySQLPlatform { /** * {@inheritDoc} diff --git a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php new file mode 100644 index 00000000000..fc341a2243f --- /dev/null +++ b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php @@ -0,0 +1,41 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\oracle; + +use Doctrine\DBAL\Connection as DoctrineConnection; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Oracle Doctrine driver middleware. + * Makes use of phpBB's Oracle specific platform. + */ +class phpbb_oracle_driver extends AbstractDriverMiddleware +{ + /** + * {@inheritDoc} + */ + public function getSchemaManager(DoctrineConnection $conn, AbstractPlatform $platform) + { + return new phpbb_oracle_schema_manager($conn, $platform); + } + + /** + * {@inheritDoc} + */ + public function createDatabasePlatformForVersion($version) + { + return new phpbb_oracle_platform(); + } +} diff --git a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php new file mode 100644 index 00000000000..be35ce963a8 --- /dev/null +++ b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\oracle; + +use Doctrine\DBAL\Driver; + +/** + * Oracle Doctrine middleware. + * Makes use of phpBB's Oracle specific platform. + */ +class phpbb_oracle_middleware implements Driver\Middleware +{ + public function wrap(Driver $driver): Driver + { + return new phpbb_oracle_driver($driver); + } +} diff --git a/phpBB/phpbb/db/doctrine/oracle_platform.php b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_platform.php similarity index 98% rename from phpBB/phpbb/db/doctrine/oracle_platform.php rename to phpBB/phpbb/db/middleware/oracle/phpbb_oracle_platform.php index 7c011e6e775..efafcd768d6 100644 --- a/phpBB/phpbb/db/doctrine/oracle_platform.php +++ b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_platform.php @@ -11,7 +11,7 @@ * */ -namespace phpbb\db\doctrine; +namespace phpbb\db\middleware\oracle; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\Identifier; @@ -21,7 +21,7 @@ /** * Oracle specific schema restrictions for BC. */ -class oracle_platform extends OraclePlatform +class phpbb_oracle_platform extends OraclePlatform { /** * {@inheritDoc} diff --git a/phpBB/phpbb/db/doctrine/oci8/schema_manager.php b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_schema_manager.php similarity index 90% rename from phpBB/phpbb/db/doctrine/oci8/schema_manager.php rename to phpBB/phpbb/db/middleware/oracle/phpbb_oracle_schema_manager.php index c0b2caea1d8..078630eb8ae 100644 --- a/phpBB/phpbb/db/doctrine/oci8/schema_manager.php +++ b/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_schema_manager.php @@ -11,14 +11,14 @@ * */ -namespace phpbb\db\doctrine\oci8; +namespace phpbb\db\middleware\oracle; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\OracleSchemaManager; use Doctrine\DBAL\Schema\Table; -class schema_manager extends OracleSchemaManager +class phpbb_oracle_schema_manager extends OracleSchemaManager { /** * {@inheritdoc} diff --git a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php b/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php new file mode 100644 index 00000000000..9464825c784 --- /dev/null +++ b/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\postgresql; + +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; + +/** + * PostgreSQL Doctrine driver middleware. + * Makes use of phpBB's PostgreSQL specific platform. + */ +class phpbb_postgresql_driver extends AbstractDriverMiddleware +{ + /** + * {@inheritDoc} + */ + public function createDatabasePlatformForVersion($version) + { + return new phpbb_postgresql_platform(); + } +} diff --git a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php b/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php new file mode 100644 index 00000000000..31d3fbcb209 --- /dev/null +++ b/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\postgresql; + +use Doctrine\DBAL\Driver; + +/** + * PostgreSQL Doctrine middleware. + * Makes use of phpBB's PostgreSQL specific platform. + */ +class phpbb_postgresql_middleware implements Driver\Middleware +{ + public function wrap(Driver $driver): Driver + { + return new phpbb_postgresql_driver($driver); + } +} diff --git a/phpBB/phpbb/db/doctrine/postgresql_platform.php b/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_platform.php similarity index 97% rename from phpBB/phpbb/db/doctrine/postgresql_platform.php rename to phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_platform.php index d67b2439b2e..7ed3508ba09 100644 --- a/phpBB/phpbb/db/doctrine/postgresql_platform.php +++ b/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_platform.php @@ -11,7 +11,7 @@ * */ -namespace phpbb\db\doctrine; +namespace phpbb\db\middleware\postgresql; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; @@ -31,7 +31,7 @@ * to stay compatible with the existing DB we have to change its * naming and not ours. */ -class postgresql_platform extends PostgreSQLPlatform +class phpbb_postgresql_platform extends PostgreSQLPlatform { /** * {@inheritdoc} diff --git a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php b/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php new file mode 100644 index 00000000000..ba7ec877f64 --- /dev/null +++ b/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\sqlsrv; + +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; + +/** + * Microsoft SQL server Doctrine driver middleware. + * Makes use of phpBB's SQL Server specific platform. + */ +class phpbb_sqlsrv_driver extends AbstractDriverMiddleware +{ + /** + * {@inheritDoc} + */ + public function createDatabasePlatformForVersion($version) + { + return new phpbb_sqlsrv_platform(); + } +} diff --git a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php b/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php new file mode 100644 index 00000000000..a5603fd422b --- /dev/null +++ b/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\middleware\sqlsrv; + +use Doctrine\DBAL\Driver; + +/** + * Microsoft SQL server Doctrine middleware. + * Makes use of phpBB's SQL Server specific platform. + */ +class phpbb_sqlsrv_middleware implements Driver\Middleware +{ + public function wrap(Driver $driver): Driver + { + return new phpbb_sqlsrv_driver($driver); + } +} diff --git a/phpBB/phpbb/db/doctrine/sqlsrv_platform.php b/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_platform.php similarity index 97% rename from phpBB/phpbb/db/doctrine/sqlsrv_platform.php rename to phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_platform.php index ca2a9dd7e64..896cb72643a 100644 --- a/phpBB/phpbb/db/doctrine/sqlsrv_platform.php +++ b/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_platform.php @@ -11,7 +11,7 @@ * */ -namespace phpbb\db\doctrine; +namespace phpbb\db\middleware\sqlsrv; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\Identifier; @@ -20,7 +20,7 @@ /** * SQLServer specific schema restrictions for BC. */ -class sqlsrv_platform extends SQLServerPlatform +class phpbb_sqlsrv_platform extends SQLServerPlatform { /** * {@inheritDoc} From 86195ac0f9d287b0f08843435f0d97aba25981b6 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 19 Jul 2025 15:17:18 +0700 Subject: [PATCH 0858/1214] [ticket/17530] Better classes naming PHPBB-17530 --- .../db/doctrine/case_insensitive_string.php | 8 ++++---- phpBB/phpbb/db/doctrine/connection_factory.php | 16 ++++++++-------- .../db/doctrine/connection_parameter_factory.php | 8 +++----- .../mysql/{phpbb_mysql_driver.php => driver.php} | 4 ++-- ...phpbb_mysql_middleware.php => middleware.php} | 5 +++-- .../{phpbb_mysql_platform.php => platform.php} | 2 +- .../{phpbb_oracle_driver.php => driver.php} | 6 +++--- ...hpbb_oracle_middleware.php => middleware.php} | 5 +++-- .../{phpbb_oracle_platform.php => platform.php} | 2 +- ...cle_schema_manager.php => schema_manager.php} | 2 +- .../{phpbb_postgresql_driver.php => driver.php} | 4 ++-- ..._postgresql_middleware.php => middleware.php} | 5 +++-- ...hpbb_postgresql_platform.php => platform.php} | 2 +- .../{phpbb_sqlsrv_driver.php => driver.php} | 4 ++-- ...hpbb_sqlsrv_middleware.php => middleware.php} | 5 +++-- .../{phpbb_sqlsrv_platform.php => platform.php} | 2 +- 16 files changed, 41 insertions(+), 39 deletions(-) rename phpBB/phpbb/db/middleware/mysql/{phpbb_mysql_driver.php => driver.php} (85%) rename phpBB/phpbb/db/middleware/mysql/{phpbb_mysql_middleware.php => middleware.php} (78%) rename phpBB/phpbb/db/middleware/mysql/{phpbb_mysql_platform.php => platform.php} (95%) rename phpBB/phpbb/db/middleware/oracle/{phpbb_oracle_driver.php => driver.php} (83%) rename phpBB/phpbb/db/middleware/oracle/{phpbb_oracle_middleware.php => middleware.php} (78%) rename phpBB/phpbb/db/middleware/oracle/{phpbb_oracle_platform.php => platform.php} (98%) rename phpBB/phpbb/db/middleware/oracle/{phpbb_oracle_schema_manager.php => schema_manager.php} (94%) rename phpBB/phpbb/db/middleware/postgresql/{phpbb_postgresql_driver.php => driver.php} (85%) rename phpBB/phpbb/db/middleware/postgresql/{phpbb_postgresql_middleware.php => middleware.php} (77%) rename phpBB/phpbb/db/middleware/postgresql/{phpbb_postgresql_platform.php => platform.php} (98%) rename phpBB/phpbb/db/middleware/sqlsrv/{phpbb_sqlsrv_driver.php => driver.php} (86%) rename phpBB/phpbb/db/middleware/sqlsrv/{phpbb_sqlsrv_middleware.php => middleware.php} (78%) rename phpBB/phpbb/db/middleware/sqlsrv/{phpbb_sqlsrv_platform.php => platform.php} (98%) diff --git a/phpBB/phpbb/db/doctrine/case_insensitive_string.php b/phpBB/phpbb/db/doctrine/case_insensitive_string.php index a8ac6e76057..e7b62f86491 100644 --- a/phpBB/phpbb/db/doctrine/case_insensitive_string.php +++ b/phpBB/phpbb/db/doctrine/case_insensitive_string.php @@ -15,8 +15,8 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; -use phpbb\db\middleware\postgresql\phpbb_postgresql_platform; -use phpbb\db\middleware\oracle\phpbb_oracle_platform; +use phpbb\db\middleware\oracle\platform as oracle_platform; +use phpbb\db\middleware\postgresql\platform as postgresql_platform; /** * Case-insensitive string type (only supported by Postgres). @@ -30,7 +30,7 @@ class case_insensitive_string extends Type */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - if ($platform instanceof phpbb_postgresql_platform) + if ($platform instanceof postgresql_platform) { return 'varchar_ci'; } @@ -39,7 +39,7 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st // we used 3 times larger capacity for strings on oracle for unicode strings // as on other platforms. This is not the case with varchar_ci, which uses // the same length as other platforms. - if ($platform instanceof phpbb_oracle_platform) + if ($platform instanceof oracle_platform) { return $platform->getAsciiStringTypeDeclarationSQL($column); } diff --git a/phpBB/phpbb/db/doctrine/connection_factory.php b/phpBB/phpbb/db/doctrine/connection_factory.php index 5ae5107da4e..15a0a6416e3 100644 --- a/phpBB/phpbb/db/doctrine/connection_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_factory.php @@ -20,10 +20,10 @@ use Doctrine\DBAL\Types\Type; use InvalidArgumentException; use phpbb\config_php_file; -use phpbb\db\middleware\mysql\phpbb_mysql_middleware; -use phpbb\db\middleware\oracle\phpbb_oracle_middleware; -use phpbb\db\middleware\postgresql\phpbb_postgresql_middleware; -use phpbb\db\middleware\sqlsrv\phpbb_sqlsrv_middleware; +use phpbb\db\middleware\mysql\middleware as mysql_middleware; +use phpbb\db\middleware\oracle\middleware as oracle_middleware; +use phpbb\db\middleware\postgresql\middleware as postgresql_middleware; +use phpbb\db\middleware\sqlsrv\middleware as sqlsrv_middleware; use phpbb\exception\runtime_exception; /** @@ -101,10 +101,10 @@ public static function get_connection_from_params( $middleware = match($driver) { - 'pdo_mysql', 'mysqli' => [new phpbb_mysql_middleware()], - 'pdo_oci', 'oci8' => [new phpbb_oracle_middleware()], - 'pdo_pgsql', 'pgsql' => [new phpbb_postgresql_middleware()], - 'pdo_sqlsrv', 'sqlsrv' => [new phpbb_sqlsrv_middleware()], + 'pdo_mysql', 'mysqli' => [new mysql_middleware()], + 'pdo_oci', 'oci8' => [new oracle_middleware()], + 'pdo_pgsql', 'pgsql' => [new postgresql_middleware()], + 'pdo_sqlsrv', 'sqlsrv' => [new sqlsrv_middleware()], default => [], }; diff --git a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php index 6e59c4a61e1..ea5a929ba75 100644 --- a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php @@ -13,8 +13,6 @@ namespace phpbb\db\doctrine; -use InvalidArgumentException; - /** * Helper class to generate Doctrine DBAL configuration. */ @@ -32,7 +30,7 @@ class connection_parameter_factory * * @return array Doctrine DBAL connection parameters. * - * @throws InvalidArgumentException If a required parameter is empty or null. + * @throws \InvalidArgumentException If a required parameter is empty or null. */ public static function get_configuration( string $driver, @@ -68,7 +66,7 @@ public static function get_configuration( * * @return array Doctrine's DBAL configuration for SQLite. * - * @throws InvalidArgumentException If a required parameter is empty or null. + * @throws \InvalidArgumentException If a required parameter is empty or null. */ private static function build_connection_parameters( array $params, @@ -87,7 +85,7 @@ private static function build_connection_parameters( if (empty($user) || empty($name)) { - throw new InvalidArgumentException('Required database parameter is not set.'); + throw new \InvalidArgumentException('Required database parameter is not set.'); } $params = array_merge($params, [ diff --git a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php b/phpBB/phpbb/db/middleware/mysql/driver.php similarity index 85% rename from phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php rename to phpBB/phpbb/db/middleware/mysql/driver.php index 651a9797652..306cadb72de 100644 --- a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_driver.php +++ b/phpBB/phpbb/db/middleware/mysql/driver.php @@ -19,13 +19,13 @@ * MySQL Doctrine driver middleware. * Makes use of phpBB's MySQL specific platform. */ -class phpbb_mysql_driver extends AbstractDriverMiddleware +class driver extends AbstractDriverMiddleware { /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { - return new phpbb_mysql_platform(); + return new platform(); } } diff --git a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php b/phpBB/phpbb/db/middleware/mysql/middleware.php similarity index 78% rename from phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php rename to phpBB/phpbb/db/middleware/mysql/middleware.php index 5e79142a9e4..793512cfffe 100644 --- a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_middleware.php +++ b/phpBB/phpbb/db/middleware/mysql/middleware.php @@ -14,15 +14,16 @@ namespace phpbb\db\middleware\mysql; use Doctrine\DBAL\Driver; +use phpbb\db\middleware\mysql\driver as mysql_driver; /** * MySQL Doctrine middleware. * Makes use of phpBB's MySQL specific platform. */ -class phpbb_mysql_middleware implements Driver\Middleware +class middleware implements Driver\Middleware { public function wrap(Driver $driver): Driver { - return new phpbb_mysql_driver($driver); + return new mysql_driver($driver); } } diff --git a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_platform.php b/phpBB/phpbb/db/middleware/mysql/platform.php similarity index 95% rename from phpBB/phpbb/db/middleware/mysql/phpbb_mysql_platform.php rename to phpBB/phpbb/db/middleware/mysql/platform.php index a64430613d6..ed4cbdb1bbf 100644 --- a/phpBB/phpbb/db/middleware/mysql/phpbb_mysql_platform.php +++ b/phpBB/phpbb/db/middleware/mysql/platform.php @@ -23,7 +23,7 @@ * If it's indexed as primary key, it should be declared as NOT NULL * because MySQL primary key columns cannot be NULL. */ -class phpbb_mysql_platform extends AbstractMySQLPlatform +class platform extends AbstractMySQLPlatform { /** * {@inheritDoc} diff --git a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php b/phpBB/phpbb/db/middleware/oracle/driver.php similarity index 83% rename from phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php rename to phpBB/phpbb/db/middleware/oracle/driver.php index fc341a2243f..bb4172bfe05 100644 --- a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_driver.php +++ b/phpBB/phpbb/db/middleware/oracle/driver.php @@ -21,14 +21,14 @@ * Oracle Doctrine driver middleware. * Makes use of phpBB's Oracle specific platform. */ -class phpbb_oracle_driver extends AbstractDriverMiddleware +class driver extends AbstractDriverMiddleware { /** * {@inheritDoc} */ public function getSchemaManager(DoctrineConnection $conn, AbstractPlatform $platform) { - return new phpbb_oracle_schema_manager($conn, $platform); + return new schema_manager($conn, $platform); } /** @@ -36,6 +36,6 @@ public function getSchemaManager(DoctrineConnection $conn, AbstractPlatform $pla */ public function createDatabasePlatformForVersion($version) { - return new phpbb_oracle_platform(); + return new platform(); } } diff --git a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php b/phpBB/phpbb/db/middleware/oracle/middleware.php similarity index 78% rename from phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php rename to phpBB/phpbb/db/middleware/oracle/middleware.php index be35ce963a8..c54ca4d81d7 100644 --- a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_middleware.php +++ b/phpBB/phpbb/db/middleware/oracle/middleware.php @@ -14,15 +14,16 @@ namespace phpbb\db\middleware\oracle; use Doctrine\DBAL\Driver; +use phpbb\db\middleware\oracle\driver as oracle_driver; /** * Oracle Doctrine middleware. * Makes use of phpBB's Oracle specific platform. */ -class phpbb_oracle_middleware implements Driver\Middleware +class middleware implements Driver\Middleware { public function wrap(Driver $driver): Driver { - return new phpbb_oracle_driver($driver); + return new oracle_driver($driver); } } diff --git a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_platform.php b/phpBB/phpbb/db/middleware/oracle/platform.php similarity index 98% rename from phpBB/phpbb/db/middleware/oracle/phpbb_oracle_platform.php rename to phpBB/phpbb/db/middleware/oracle/platform.php index efafcd768d6..8604f50fb19 100644 --- a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_platform.php +++ b/phpBB/phpbb/db/middleware/oracle/platform.php @@ -21,7 +21,7 @@ /** * Oracle specific schema restrictions for BC. */ -class phpbb_oracle_platform extends OraclePlatform +class platform extends OraclePlatform { /** * {@inheritDoc} diff --git a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_schema_manager.php b/phpBB/phpbb/db/middleware/oracle/schema_manager.php similarity index 94% rename from phpBB/phpbb/db/middleware/oracle/phpbb_oracle_schema_manager.php rename to phpBB/phpbb/db/middleware/oracle/schema_manager.php index 078630eb8ae..2ec1c08df0f 100644 --- a/phpBB/phpbb/db/middleware/oracle/phpbb_oracle_schema_manager.php +++ b/phpBB/phpbb/db/middleware/oracle/schema_manager.php @@ -18,7 +18,7 @@ use Doctrine\DBAL\Schema\OracleSchemaManager; use Doctrine\DBAL\Schema\Table; -class phpbb_oracle_schema_manager extends OracleSchemaManager +class schema_manager extends OracleSchemaManager { /** * {@inheritdoc} diff --git a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php b/phpBB/phpbb/db/middleware/postgresql/driver.php similarity index 85% rename from phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php rename to phpBB/phpbb/db/middleware/postgresql/driver.php index 9464825c784..35a2de28be5 100644 --- a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_driver.php +++ b/phpBB/phpbb/db/middleware/postgresql/driver.php @@ -19,13 +19,13 @@ * PostgreSQL Doctrine driver middleware. * Makes use of phpBB's PostgreSQL specific platform. */ -class phpbb_postgresql_driver extends AbstractDriverMiddleware +class driver extends AbstractDriverMiddleware { /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { - return new phpbb_postgresql_platform(); + return new platform(); } } diff --git a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php b/phpBB/phpbb/db/middleware/postgresql/middleware.php similarity index 77% rename from phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php rename to phpBB/phpbb/db/middleware/postgresql/middleware.php index 31d3fbcb209..defdcea6eb5 100644 --- a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_middleware.php +++ b/phpBB/phpbb/db/middleware/postgresql/middleware.php @@ -14,15 +14,16 @@ namespace phpbb\db\middleware\postgresql; use Doctrine\DBAL\Driver; +use phpbb\db\middleware\postgresql\driver as postgresql_driver; /** * PostgreSQL Doctrine middleware. * Makes use of phpBB's PostgreSQL specific platform. */ -class phpbb_postgresql_middleware implements Driver\Middleware +class middleware implements Driver\Middleware { public function wrap(Driver $driver): Driver { - return new phpbb_postgresql_driver($driver); + return new postgresql_driver($driver); } } diff --git a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_platform.php b/phpBB/phpbb/db/middleware/postgresql/platform.php similarity index 98% rename from phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_platform.php rename to phpBB/phpbb/db/middleware/postgresql/platform.php index 7ed3508ba09..e5e7a9a1c6f 100644 --- a/phpBB/phpbb/db/middleware/postgresql/phpbb_postgresql_platform.php +++ b/phpBB/phpbb/db/middleware/postgresql/platform.php @@ -31,7 +31,7 @@ * to stay compatible with the existing DB we have to change its * naming and not ours. */ -class phpbb_postgresql_platform extends PostgreSQLPlatform +class platform extends PostgreSQLPlatform { /** * {@inheritdoc} diff --git a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php b/phpBB/phpbb/db/middleware/sqlsrv/driver.php similarity index 86% rename from phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php rename to phpBB/phpbb/db/middleware/sqlsrv/driver.php index ba7ec877f64..4fbd9e33f0b 100644 --- a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_driver.php +++ b/phpBB/phpbb/db/middleware/sqlsrv/driver.php @@ -19,13 +19,13 @@ * Microsoft SQL server Doctrine driver middleware. * Makes use of phpBB's SQL Server specific platform. */ -class phpbb_sqlsrv_driver extends AbstractDriverMiddleware +class driver extends AbstractDriverMiddleware { /** * {@inheritDoc} */ public function createDatabasePlatformForVersion($version) { - return new phpbb_sqlsrv_platform(); + return new platform(); } } diff --git a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php b/phpBB/phpbb/db/middleware/sqlsrv/middleware.php similarity index 78% rename from phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php rename to phpBB/phpbb/db/middleware/sqlsrv/middleware.php index a5603fd422b..03814e8cfa0 100644 --- a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_middleware.php +++ b/phpBB/phpbb/db/middleware/sqlsrv/middleware.php @@ -14,15 +14,16 @@ namespace phpbb\db\middleware\sqlsrv; use Doctrine\DBAL\Driver; +use phpbb\db\middleware\sqlsrv\driver as sqlsrv_driver; /** * Microsoft SQL server Doctrine middleware. * Makes use of phpBB's SQL Server specific platform. */ -class phpbb_sqlsrv_middleware implements Driver\Middleware +class middleware implements Driver\Middleware { public function wrap(Driver $driver): Driver { - return new phpbb_sqlsrv_driver($driver); + return new sqlsrv_driver($driver); } } diff --git a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_platform.php b/phpBB/phpbb/db/middleware/sqlsrv/platform.php similarity index 98% rename from phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_platform.php rename to phpBB/phpbb/db/middleware/sqlsrv/platform.php index 896cb72643a..a7a58e3dda6 100644 --- a/phpBB/phpbb/db/middleware/sqlsrv/phpbb_sqlsrv_platform.php +++ b/phpBB/phpbb/db/middleware/sqlsrv/platform.php @@ -20,7 +20,7 @@ /** * SQLServer specific schema restrictions for BC. */ -class phpbb_sqlsrv_platform extends SQLServerPlatform +class platform extends SQLServerPlatform { /** * {@inheritDoc} From 1633288447de76c0429391dc03772b5112c199f9 Mon Sep 17 00:00:00 2001 From: battye Date: Sun, 20 Jul 2025 04:38:51 +0000 Subject: [PATCH 0859/1214] [ticket/17034] Fix min posts rank validation PHPBB-17034 --- phpBB/adm/style/acp_ranks.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/adm/style/acp_ranks.html b/phpBB/adm/style/acp_ranks.html index aac5de6fe7c..aa31db497aa 100644 --- a/phpBB/adm/style/acp_ranks.html +++ b/phpBB/adm/style/acp_ranks.html @@ -44,7 +44,7 @@

{L_ACP_MANAGE_RANKS}

style="display: none;">
-
+
From 7056bcba9130394306cf6bb6ab7bf4efa64a1345 Mon Sep 17 00:00:00 2001 From: rxu Date: Mon, 28 Jul 2025 22:51:14 +0700 Subject: [PATCH 0860/1214] [ticket/17535] Address code review comments PHPBB-17535 --- phpBB/phpbb/cache/driver/base.php | 46 +++++++++---------- phpBB/phpbb/cache/driver/memory.php | 13 +----- tests/auth/provider_apache_test.php | 8 ++-- tests/dbal/connect_test.php | 2 +- tests/functional/extension_acp_test.php | 1 - tests/text_processing/message_parser_test.php | 2 +- 6 files changed, 30 insertions(+), 42 deletions(-) diff --git a/phpBB/phpbb/cache/driver/base.php b/phpBB/phpbb/cache/driver/base.php index a93e6ce08c0..9dfc28b3f65 100644 --- a/phpBB/phpbb/cache/driver/base.php +++ b/phpBB/phpbb/cache/driver/base.php @@ -31,33 +31,33 @@ function purge() try { $iterator = new \DirectoryIterator($this->cache_dir); - } - catch (\Exception $e) - { - return; - } - foreach ($iterator as $fileInfo) - { - if ($fileInfo->isDot()) + foreach ($iterator as $fileInfo) { - continue; - } - $filename = $fileInfo->getFilename(); - if ($fileInfo->isDir()) - { - $this->remove_dir($fileInfo->getPathname()); - } - else if (strpos($filename, 'container_') === 0 || - strpos($filename, 'autoload_') === 0 || - strpos($filename, 'url_matcher') === 0 || - strpos($filename, 'url_generator') === 0 || - strpos($filename, 'sql_') === 0 || - strpos($filename, 'data_') === 0) - { - $this->remove_file($fileInfo->getPathname()); + if ($fileInfo->isDot()) + { + continue; + } + $filename = $fileInfo->getFilename(); + if ($fileInfo->isDir()) + { + $this->remove_dir($fileInfo->getPathname()); + } + else if (strpos($filename, 'container_') === 0 || + strpos($filename, 'autoload_') === 0 || + strpos($filename, 'url_matcher') === 0 || + strpos($filename, 'url_generator') === 0 || + strpos($filename, 'sql_') === 0 || + strpos($filename, 'data_') === 0) + { + $this->remove_file($fileInfo->getPathname()); + } } } + catch (\Exception $e) + { + // Do not return, to purge vars cached in memory + } unset($this->vars); unset($this->sql_rowset); diff --git a/phpBB/phpbb/cache/driver/memory.php b/phpBB/phpbb/cache/driver/memory.php index 4ccd0607cd6..feee5888608 100644 --- a/phpBB/phpbb/cache/driver/memory.php +++ b/phpBB/phpbb/cache/driver/memory.php @@ -50,18 +50,7 @@ function __construct() */ function purge() { - unset($this->vars); - unset($this->sql_rowset); - unset($this->sql_row_pointer); - - if (function_exists('opcache_reset')) - { - @opcache_reset(); - } - - $this->vars = []; - $this->sql_rowset = []; - $this->sql_row_pointer = []; + parent::purge(); $this->is_modified = true; diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index f291751575d..2094205bfe1 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -74,10 +74,10 @@ public function test_login() ->will($this->returnValue(true)); $this->request->expects($this->exactly(2)) ->method('server') - ->willReturnCallback(fn(string $arg) => match(true) { - $arg === 'PHP_AUTH_USER' => 'foobar', - $arg === 'PHP_AUTH_PW' => 'example', - }); + ->willReturnMap([ + ['PHP_AUTH_USER', 'foobar'], + ['PHP_AUTH_PW', 'example'] + ]); $expected = array( 'status' => LOGIN_SUCCESS, diff --git a/tests/dbal/connect_test.php b/tests/dbal/connect_test.php index d1ec75470a3..7220a60bf75 100644 --- a/tests/dbal/connect_test.php +++ b/tests/dbal/connect_test.php @@ -35,7 +35,7 @@ public function test_failing_connect() if ($db->get_sql_layer() === 'mysqli') { - $this->setExpectedTriggerError(E_WARNING); + $this->setExpectedTriggerError(E_WARNING); } else if ($db->get_sql_layer() !== 'sqlite3') { diff --git a/tests/functional/extension_acp_test.php b/tests/functional/extension_acp_test.php index bcc299fae51..30dcaa89c70 100644 --- a/tests/functional/extension_acp_test.php +++ b/tests/functional/extension_acp_test.php @@ -255,7 +255,6 @@ public function test_extensions_catalog() $form = $crawler->selectButton('Submit')->form(); $form['minimum_stability']->select('dev'); - $form['enable_packagist']->select('1'); $form['repositories'] = 'https://satis.phpbb.com/'; $crawler = self::submit($form); $this->assertContainsLang('CONFIG_UPDATED', $crawler->filter('div[class="successbox"] > p')->text()); diff --git a/tests/text_processing/message_parser_test.php b/tests/text_processing/message_parser_test.php index ca301b9b3e8..35dcb4b5666 100644 --- a/tests/text_processing/message_parser_test.php +++ b/tests/text_processing/message_parser_test.php @@ -61,7 +61,7 @@ protected function prepare_s9e_services($setup = null) $user->style = array('style_id' => 1); $user->expects($this->any()) - -> method('__get')->with('lang')->willReturn([ + ->method('__get')->with('lang')->willReturn([ 'NO_POLL_TITLE' => 'You have to enter a poll title.', 'POLL_TITLE_TOO_LONG' => 'The poll title must contain fewer than 100 characters.', 'POLL_TITLE_COMP_TOO_LONG' => 'The parsed size of your poll title is too large, consider removing BBCodes or smilies.', From e794dd4a87408ca08d6410105b2df868f95a0b40 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 30 Jul 2025 09:05:38 +0700 Subject: [PATCH 0861/1214] [ticket/17535] Remove redundant empty lines PHPBB-17535 --- tests/dbal/connect_test.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/dbal/connect_test.php b/tests/dbal/connect_test.php index 7220a60bf75..5216312d091 100644 --- a/tests/dbal/connect_test.php +++ b/tests/dbal/connect_test.php @@ -31,8 +31,6 @@ public function test_failing_connect() // Failure to connect results in a trigger_error call in dbal. // phpunit converts triggered errors to exceptions. // In particular there should be no fatals here. - - if ($db->get_sql_layer() === 'mysqli') { $this->setExpectedTriggerError(E_WARNING); From bfc60efeb519a66acfd788ee38a44867e577c857 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 31 Aug 2025 22:00:51 +0200 Subject: [PATCH 0862/1214] [ticket/17537] Cache extensions paths as metadata for ConfigCache This should help with checking whether the ConfigCache is still fresh. PHPBB-17537 --- phpBB/phpbb/di/container_builder.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/di/container_builder.php b/phpBB/phpbb/di/container_builder.php index 59595a9c603..087dbf9649e 100644 --- a/phpBB/phpbb/di/container_builder.php +++ b/phpBB/phpbb/di/container_builder.php @@ -458,6 +458,7 @@ protected function load_extensions() } $extensions = $ext_container->get('ext.manager')->all_enabled(); + $resources = []; // Load each extension found $autoloaders = 'container_extensions[] = new $extension_class($ext_name, $path); + if (is_dir($path)) + { + $resources[] = new \Symfony\Component\Config\Resource\DirectoryResource($path); + } + // Load extension autoloader $filename = $path . 'vendor/autoload.php'; if (file_exists($filename)) @@ -489,7 +495,7 @@ protected function load_extensions() } $configCache = new ConfigCache($this->get_autoload_filename(), false); - $configCache->write($autoloaders); + $configCache->write($autoloaders, $resources); require($this->get_autoload_filename()); } From 7ce82d6b4cd1b3bb9a233826ae17596d7fbb2f2f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 7 Sep 2025 13:05:25 -0700 Subject: [PATCH 0863/1214] [ticket/17538] Proof-read and improve language of TOS and Privacy Policy Proof-read and improve language of TOS and Privacy Policy. PHPBB-17538 --- phpBB/language/en/ucp.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 1f52db2901b..368f1f2b594 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -38,22 +38,26 @@ // Privacy policy and T&C $lang = array_merge($lang, array( - 'TERMS_OF_USE_CONTENT' => 'By accessing “%1$s” (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”), you agree to be legally bound by the following terms. If you do not agree to be legally bound by all of the following terms then please do not access and/or use “%1$s”. We may change these at any time and we’ll do our utmost in informing you, though it would be prudent to review this regularly yourself as your continued usage of “%1$s” after changes mean you agree to be legally bound by these terms as they are updated and/or amended. + 'TERMS_OF_USE_CONTENT' => 'By accessing “%1$s” (hereinafter “we,” “us,” “our,” “%1$s,” “%2$s”), you agree to be legally bound by the following terms. If you do not agree to be legally bound by all the following terms, please do not access or use “%1$s.” We may change these terms at any time and will make every effort to inform you of such changes. However, it is your responsibility to review this document regularly, as your continued use of “%1$s” after changes are made constitutes your agreement to be legally bound by the updated and/or amended terms.

- Our forums are powered by phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) which is a bulletin board solution released under the “GNU General Public License v2” (hereinafter “GPL”) and can be downloaded from www.phpbb.com. The phpBB software only facilitates internet based discussions; phpBB Limited is not responsible for what we allow and/or disallow as permissible content and/or conduct. For further information about phpBB, please see: https://www.phpbb.com/. + Our forums are powered by phpBB (hereinafter “they,” “them,” “their,” “phpBB software,” “www.phpbb.com,” “phpBB Limited,” “phpBB Teams”), a bulletin board solution released under the “GNU General Public License v2” (hereinafter “GPL”), which can be downloaded from www.phpbb.com. The phpBB software only facilitates internet-based discussions; phpBB Limited is not responsible for the content or conduct permitted or disallowed on this site. For further information about phpBB, please see: https://www.phpbb.com/.

- You agree not to post any abusive, obscene, vulgar, libellous, hateful, threatening, sexually-orientated or any other material that may violate any laws be it of your country, the country where “%1$s” is hosted or International Law. Doing so may lead to you being immediately and permanently banned, with notification of your Internet Service Provider if deemed required by us. The IP address of all posts are recorded to aid in enforcing these conditions. You agree that “%1$s” have the right to remove, edit, move or close any topic at any time should we see fit. As a user you agree to any information you have entered to being stored in a database. While this information will not be disclosed to any third party without your consent, neither “%1$s” nor phpBB shall be held responsible for any hacking attempt that may lead to the data being compromised. + You agree not to post any abusive, obscene, vulgar, libellous, hateful, threatening, sexually oriented, or otherwise unlawful material, whether under the laws of your country, the country in which “%1$s” is hosted, or under international law. Doing so may result in your immediate and permanent ban, with notification of your Internet Service Provider if deemed necessary by us. The IP address of all posts is recorded to aid in enforcing these conditions. +

+ You agree that “%1$s” reserves the right to remove, edit, move, or close any topic at any time, at our sole discretion. As a user, you agree that any information you enter may be stored in a database. While this information will not be disclosed to any third party without your consent, neither “%1$s” nor phpBB shall be held responsible for any hacking attempt that may lead to data being compromised. ', - 'PRIVACY_POLICY' => 'This policy explains in detail how “%1$s” along with its affiliated companies (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”) and phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) use any information collected during any session of usage by you (hereinafter “your information”). + 'PRIVACY_POLICY' => 'This policy explains in detail how “%1$s” and its affiliated companies (hereinafter “we,” “us,” “our,” “%1$s,” “%2$s”) and phpBB (hereinafter “they,” “them,” “their,” “phpBB software,” “www.phpbb.com,” “phpBB Limited,” “phpBB Teams”) use information collected during your use of this site (hereinafter “your information”). +

+ Your information is collected in two ways. When you browse “%1$s,” the phpBB software will create several cookies. Cookies are small text files stored in your web browser’s temporary files. The first two cookies contain a user identifier (hereinafter “user-id”) and an anonymous session identifier (hereinafter “session-id”), both automatically assigned by the phpBB software. A third cookie will be created once you have browsed topics within “%1$s.” It stores information about which topics you have read, thereby improving your user experience.

- Your information is collected via two ways. Firstly, by browsing “%1$s” will cause the phpBB software to create a number of cookies, which are small text files that are downloaded on to your computer’s web browser temporary files. The first two cookies just contain a user identifier (hereinafter “user-id”) and an anonymous session identifier (hereinafter “session-id”), automatically assigned to you by the phpBB software. A third cookie will be created once you have browsed topics within “%1$s” and is used to store which topics have been read, thereby improving your user experience. + We may also create cookies external to the phpBB software while you are browsing “%1$s.” These fall outside the scope of this document, which only covers cookies created by the phpBB software.

- We may also create cookies external to the phpBB software whilst browsing “%1$s”, though these are outside the scope of this document which is intended to only cover the pages created by the phpBB software. The second way in which we collect your information is by what you submit to us. This can be, and is not limited to: posting as an anonymous user (hereinafter “anonymous posts”), registering on “%1$s” (hereinafter “your account”) and posts submitted by you after registration and whilst logged in (hereinafter “your posts”). + The second way we collect information is through what you submit to us. This includes but is not limited to: posting as an anonymous user (hereinafter “anonymous posts”), registering on “%1$s” (hereinafter “your account”), posts you submit after registering and while logged in (hereinafter “your posts”).

- Your account will at a bare minimum contain a uniquely identifiable name (hereinafter “your user name”), a personal password used for logging into your account (hereinafter “your password”) and a personal, valid email address (hereinafter “your email”). Your information for your account at “%1$s” is protected by data-protection laws applicable in the country that hosts us. Any information beyond your user name, your password, and your email address required by “%1$s” during the registration process is either mandatory or optional, at the discretion of “%1$s”. In all cases, you have the option of what information in your account is publicly displayed. Furthermore, within your account, you have the option to opt-in or opt-out of automatically generated emails from the phpBB software. + Your account will contain at a minimum: a unique username (hereinafter “your username”), a personal password used to log in (hereinafter “your password”), a valid email address (hereinafter “your email”). Your account information on “%1$s” is protected by the data-protection laws applicable in the country that hosts us. Any information beyond your username, password, and email address that is requested during registration may be mandatory or optional, at the discretion of “%1$s.” In all cases, you may choose what information in your account is publicly displayed. You may also opt in or out of automatically generated emails from the phpBB software.

- Your password is ciphered (a one-way hash) so that it is secure. However, it is recommended that you do not reuse the same password across a number of different websites. Your password is the means of accessing your account at “%1$s”, so please guard it carefully and under no circumstance will anyone affiliated with “%1$s”, phpBB or another 3rd party, legitimately ask you for your password. Should you forget your password for your account, you can use the “I forgot my password” feature provided by the phpBB software. This process will ask you to submit your user name and your email, then the phpBB software will generate a new password to reclaim your account. + Your password is stored as a one-way hash to ensure security. However, we recommend that you do not reuse the same password across multiple websites. Your password is the key to your account on “%1$s,” so please keep it secure. Under no circumstances will anyone affiliated with “%1$s,” phpBB, or any third party legitimately ask for your password. If you forget your password, you can use the “I forgot my password” feature provided by the phpBB software. This process requires you to submit your username and email address, after which the phpBB software will generate a new password for you to regain access to your account. ', )); From 8a84f2c543736eec3482037cebe5e961c0b2df6a Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 9 Sep 2025 08:31:11 -0700 Subject: [PATCH 0864/1214] [ticket/17538] Use British English comma placements PHPBB-17538 --- phpBB/language/en/ucp.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 368f1f2b594..61f29136276 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -38,26 +38,26 @@ // Privacy policy and T&C $lang = array_merge($lang, array( - 'TERMS_OF_USE_CONTENT' => 'By accessing “%1$s” (hereinafter “we,” “us,” “our,” “%1$s,” “%2$s”), you agree to be legally bound by the following terms. If you do not agree to be legally bound by all the following terms, please do not access or use “%1$s.” We may change these terms at any time and will make every effort to inform you of such changes. However, it is your responsibility to review this document regularly, as your continued use of “%1$s” after changes are made constitutes your agreement to be legally bound by the updated and/or amended terms. + 'TERMS_OF_USE_CONTENT' => 'By accessing “%1$s” (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”), you agree to be legally bound by the following terms. If you do not agree to be legally bound by all the following terms, please do not access or use “%1$s”. We may change these terms at any time and will make every effort to inform you of such changes. However, it is your responsibility to review this document regularly, as your continued use of “%1$s” after changes are made constitutes your agreement to be legally bound by the updated and/or amended terms.

- Our forums are powered by phpBB (hereinafter “they,” “them,” “their,” “phpBB software,” “www.phpbb.com,” “phpBB Limited,” “phpBB Teams”), a bulletin board solution released under the “GNU General Public License v2” (hereinafter “GPL”), which can be downloaded from www.phpbb.com. The phpBB software only facilitates internet-based discussions; phpBB Limited is not responsible for the content or conduct permitted or disallowed on this site. For further information about phpBB, please see: https://www.phpbb.com/. + Our forums are powered by phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”), a bulletin board solution released under the “GNU General Public License v2” (hereinafter “GPL”), which can be downloaded from www.phpbb.com. The phpBB software only facilitates internet-based discussions; phpBB Limited is not responsible for the content or conduct permitted or disallowed on this site. For further information about phpBB, please see: https://www.phpbb.com/.

You agree not to post any abusive, obscene, vulgar, libellous, hateful, threatening, sexually oriented, or otherwise unlawful material, whether under the laws of your country, the country in which “%1$s” is hosted, or under international law. Doing so may result in your immediate and permanent ban, with notification of your Internet Service Provider if deemed necessary by us. The IP address of all posts is recorded to aid in enforcing these conditions.

You agree that “%1$s” reserves the right to remove, edit, move, or close any topic at any time, at our sole discretion. As a user, you agree that any information you enter may be stored in a database. While this information will not be disclosed to any third party without your consent, neither “%1$s” nor phpBB shall be held responsible for any hacking attempt that may lead to data being compromised. ', - 'PRIVACY_POLICY' => 'This policy explains in detail how “%1$s” and its affiliated companies (hereinafter “we,” “us,” “our,” “%1$s,” “%2$s”) and phpBB (hereinafter “they,” “them,” “their,” “phpBB software,” “www.phpbb.com,” “phpBB Limited,” “phpBB Teams”) use information collected during your use of this site (hereinafter “your information”). + 'PRIVACY_POLICY' => 'This policy explains in detail how “%1$s” and its affiliated companies (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”) and phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) use information collected during your use of this site (hereinafter “your information”).

- Your information is collected in two ways. When you browse “%1$s,” the phpBB software will create several cookies. Cookies are small text files stored in your web browser’s temporary files. The first two cookies contain a user identifier (hereinafter “user-id”) and an anonymous session identifier (hereinafter “session-id”), both automatically assigned by the phpBB software. A third cookie will be created once you have browsed topics within “%1$s.” It stores information about which topics you have read, thereby improving your user experience. + Your information is collected in two ways. When you browse “%1$s”, the phpBB software will create several cookies. Cookies are small text files stored in your web browser’s temporary files. The first two cookies contain a user identifier (hereinafter “user-id”) and an anonymous session identifier (hereinafter “session-id”), both automatically assigned by the phpBB software. A third cookie will be created once you have browsed topics within “%1$s”. It stores information about which topics you have read, thereby improving your user experience.

- We may also create cookies external to the phpBB software while you are browsing “%1$s.” These fall outside the scope of this document, which only covers cookies created by the phpBB software. + We may also create cookies external to the phpBB software while you are browsing “%1$s”. These fall outside the scope of this document, which only covers cookies created by the phpBB software.

The second way we collect information is through what you submit to us. This includes but is not limited to: posting as an anonymous user (hereinafter “anonymous posts”), registering on “%1$s” (hereinafter “your account”), posts you submit after registering and while logged in (hereinafter “your posts”).

- Your account will contain at a minimum: a unique username (hereinafter “your username”), a personal password used to log in (hereinafter “your password”), a valid email address (hereinafter “your email”). Your account information on “%1$s” is protected by the data-protection laws applicable in the country that hosts us. Any information beyond your username, password, and email address that is requested during registration may be mandatory or optional, at the discretion of “%1$s.” In all cases, you may choose what information in your account is publicly displayed. You may also opt in or out of automatically generated emails from the phpBB software. + Your account will contain at a minimum: a unique username (hereinafter “your username”), a personal password used to log in (hereinafter “your password”), a valid email address (hereinafter “your email”). Your account information on “%1$s” is protected by the data-protection laws applicable in the country that hosts us. Any information beyond your username, password, and email address that is requested during registration may be mandatory or optional, at the discretion of “%1$s”. In all cases, you may choose what information in your account is publicly displayed. You may also opt in or out of automatically generated emails from the phpBB software.

- Your password is stored as a one-way hash to ensure security. However, we recommend that you do not reuse the same password across multiple websites. Your password is the key to your account on “%1$s,” so please keep it secure. Under no circumstances will anyone affiliated with “%1$s,” phpBB, or any third party legitimately ask for your password. If you forget your password, you can use the “I forgot my password” feature provided by the phpBB software. This process requires you to submit your username and email address, after which the phpBB software will generate a new password for you to regain access to your account. + Your password is stored as a one-way hash to ensure security. However, we recommend that you do not reuse the same password across multiple websites. Your password is the key to your account on “%1$s”, so please keep it secure. Under no circumstances will anyone affiliated with “%1$s”, phpBB, or any third party legitimately ask for your password. If you forget your password, you can use the “I forgot my password” feature provided by the phpBB software. This process requires you to submit your username and email address, after which the phpBB software will generate a new password for you to regain access to your account. ', )); From d6d0f755ff65c5377402b12b6032486c170ce155 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 11 Sep 2025 20:34:29 +0200 Subject: [PATCH 0865/1214] [ticket/17540] Use windows 2025 runners and limit to single PHP version PHPBB-17540 --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 72bd7d6d42b..79dcc272770 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -495,11 +495,11 @@ jobs: # Test with IIS & PostgreSQL on Windows windows-tests: - runs-on: windows-latest + runs-on: windows-2025 strategy: matrix: type: ['unit', 'functional'] - php: ['8.1', '8.2', '8.3'] + php: ['8.4'] db: ['postgres'] name: Windows - ${{ matrix.type }} - PHP ${{ matrix.php }} - ${{ matrix.db }} From c5758e7ac4875065a8cbe4022e7d08a853904431 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 11 Sep 2025 11:29:41 -0700 Subject: [PATCH 0866/1214] [ticket/17451] Prevent web push service worker from updating user activity PHPBB-17451 --- phpBB/phpbb/session.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index 5a1c2e872f3..579040d703e 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -1715,7 +1715,7 @@ public function update_user_lastvisit() { global $db; - if (isset($this->data['session_time'], $this->data['user_id'])) + if (isset($this->data['session_time'], $this->data['user_id']) && !$this->is_push_notification_request()) { $sql = 'UPDATE ' . USERS_TABLE . ' SET user_lastvisit = ' . (int) $this->data['session_time'] . ', @@ -1734,7 +1734,7 @@ public function update_last_active_time() { global $db; - if (isset($this->time_now, $this->data['user_id'])) + if (isset($this->time_now, $this->data['user_id']) && !$this->is_push_notification_request()) { $sql = 'UPDATE ' . USERS_TABLE . ' SET user_last_active = ' . $this->time_now . ' @@ -1742,4 +1742,16 @@ public function update_last_active_time() $db->sql_query($sql); } } + + /** + * Determine if the request is an Ajax request from the web push service worker + * + * @return bool True if the request is an Ajax request and the page URL contains '/push/notification', otherwise false + */ + protected function is_push_notification_request(): bool + { + global $request; + + return $request->is_ajax() && str_contains($this->page['page'], '/push/notification'); + } } From e482a6e74b14628cb1c4a7ee9adc324dd2fc0a33 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 13 Sep 2025 08:59:19 +0200 Subject: [PATCH 0867/1214] [ticket/17540] Default initialize rename_index array PHPBB-17540 --- .../db/migration/data/v400/rename_duplicated_index_names.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php b/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php index 805d68ff757..f2d8107a9b3 100644 --- a/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php +++ b/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php @@ -27,7 +27,7 @@ class rename_duplicated_index_names extends migration /** * @var array */ - protected static $rename_index; + protected static $rename_index = []; public static function depends_on() { From 949423abd99b4b522ee4d0454cf4f6688107114b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 13 Sep 2025 09:57:45 +0200 Subject: [PATCH 0868/1214] [ticket/17540] Update composer installers to version 2.3.0 PHPBB-17540 --- phpBB/composer.json | 2 +- phpBB/composer.lock | 45 ++++++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index 7dfe52b3220..8ff7e38d25d 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -34,7 +34,7 @@ "bantu/ini-get-wrapper": "~1.0", "carlos-mg89/oauth": "^0.8.15", "composer/composer": "^2.0", - "composer/installers": "^1.9", + "composer/installers": "^2.3", "composer/package-versions-deprecated": "^1.11", "doctrine/dbal": "^3.9", "google/recaptcha": "~1.1", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index eed819db811..f9090155f53 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9f600f681e6bb2efaa5a7c6a445e09bf", + "content-hash": "43fe1ef1a33684f75d832b93f7ac764c", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -436,39 +436,37 @@ }, { "name": "composer/installers", - "version": "v1.12.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19" + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19", - "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0" - }, - "replace": { - "roundcube/plugin-installer": "*", - "shama/baton": "*" + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" }, "require-dev": { - "composer/composer": "1.6.* || ^2.0", - "composer/semver": "^1 || ^3", - "phpstan/phpstan": "^0.12.55", - "phpstan/phpstan-phpunit": "^0.12.16", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.3" + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" }, "type": "composer-plugin", "extra": { "class": "Composer\\Installers\\Plugin", "branch-alias": { - "dev-main": "1.x-dev" - } + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true }, "autoload": { "psr-4": { @@ -489,7 +487,6 @@ "description": "A multi-framework Composer library installer", "homepage": "https://composer.github.io/installers/", "keywords": [ - "Craft", "Dolibarr", "Eliasis", "Hurad", @@ -510,7 +507,6 @@ "Whmcs", "WolfCMS", "agl", - "aimeos", "annotatecms", "attogram", "bitrix", @@ -519,6 +515,7 @@ "cockpit", "codeigniter", "concrete5", + "concreteCMS", "croogo", "dokuwiki", "drupal", @@ -529,7 +526,6 @@ "grav", "installer", "itop", - "joomla", "known", "kohana", "laravel", @@ -538,6 +534,7 @@ "magento", "majima", "mako", + "matomo", "mediawiki", "miaoxing", "modulework", @@ -557,9 +554,7 @@ "silverstripe", "sydes", "sylius", - "symfony", "tastyigniter", - "typo3", "wordpress", "yawik", "zend", @@ -567,7 +562,7 @@ ], "support": { "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v1.12.0" + "source": "https://github.com/composer/installers/tree/v2.3.0" }, "funding": [ { @@ -583,7 +578,7 @@ "type": "tidelift" } ], - "time": "2021-09-13T08:19:44+00:00" + "time": "2024-06-24T20:46:46+00:00" }, { "name": "composer/metadata-minifier", From 07772c910545a3945a3a7179b71aaa73b1141f46 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 13 Sep 2025 20:21:28 +0200 Subject: [PATCH 0869/1214] [ticket/17540] Use VigLink in extensions ACP test PHPBB-17540 --- tests/functional/extension_acp_test.php | 62 ++++++++----------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/tests/functional/extension_acp_test.php b/tests/functional/extension_acp_test.php index 30dcaa89c70..8f76a91b957 100644 --- a/tests/functional/extension_acp_test.php +++ b/tests/functional/extension_acp_test.php @@ -300,41 +300,33 @@ function ($node, $i) use ($extension_name) $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&start=' . $i * 20 . '&mode=catalog&sid=' . $this->sid); } - $extension_filter($crawler, 'Scroll Page', $scrollpage_install_link); - $extension_filter($crawler, 'Scroll To Top', $scrolltotop_install_link); + $extension_filter($crawler, 'VigLink', $viglink_install_link); } - if (!isset($scrolltotop_install_link) || !isset($scrollpage_install_link)) + if (!isset($viglink_install_link)) { $this->fail('Failed acquiring install links for test extensions'); } - // Attempt to install vse/scrollpage extension - $crawler = self::$client->click($scrollpage_install_link); + // Attempt to install phpbb/viglink extension + $crawler = self::$client->click($viglink_install_link); $this->assertContainsLang('EXTENSIONS_INSTALLED', $crawler->filter('.successbox > p')->text()); // Assert there's console log output - $this->assertStringContainsString('Locking vse/scrollpage', $crawler->filter('.console-output > pre')->text()); - - // Attempt to install vse/scrolltotop extension - $crawler = self::$client->click($scrolltotop_install_link); - $this->assertContainsLang('EXTENSIONS_INSTALLED', $crawler->filter('.successbox > p')->text()); - // Assert there's console log output - $this->assertStringContainsString('Locking vse/scrolltotop', $crawler->filter('.console-output > pre')->text()); + $this->assertStringContainsString('Locking phpbb/viglink', $crawler->filter('.console-output > pre')->text()); // Ensure installed extension appears in available extensions list $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); - $this->assertStringContainsString('Scroll To Top', $crawler->filter('strong[title="vse/scrolltotop"]')->text()); - $this->assertStringContainsString('Scroll Page', $crawler->filter('strong[title="vse/scrollpage"]')->text()); + $this->assertStringContainsString('VigLink', $crawler->filter('strong[title="phpbb/viglink"]')->text()); } public function test_extensions_catalog_updating_extension() { - // Enable 'Scroll Page' extension installed earlier + // Enable 'VigLink' extension installed earlier $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); $extension_enable_link = $crawler->filter('tr')->reduce( function ($node, $i) { - return (bool) (strpos($node->text(), 'Scroll Page') !== false); + return (bool) (strpos($node->text(), 'VigLink') !== false); } )->selectLink($this->lang('EXTENSION_ENABLE'))->link(); $crawler = self::$client->click($extension_enable_link); @@ -342,22 +334,22 @@ function ($node, $i) $crawler = self::submit($form); $this->assertContainsLang('EXTENSION_ENABLE_SUCCESS', $crawler->filter('.successbox')->text()); - // Update 'Scroll Page' enabled extension + // Update 'VigLink' enabled extension $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); - $scrollpage_update_link = $crawler->filter('tr')->reduce( + $viglink_update_link = $crawler->filter('tr')->reduce( function ($node, $i) { - return (bool) (strpos($node->text(), 'Scroll Page') !== false); + return (bool) (strpos($node->text(), 'VigLink') !== false); } )->selectLink($this->lang('EXTENSION_UPDATE'))->link(); - $crawler = self::$client->click($scrollpage_update_link); + $crawler = self::$client->click($viglink_update_link); $this->assertContainsLang('EXTENSIONS_UPDATED', $crawler->filter('.successbox > p')->text()); // Assert there's console log output $this->assertStringContainsString('Updating packages', $crawler->filter('.console-output > pre')->text()); // Ensure installed extension still appears in available extensions list $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); - $this->assertStringContainsString('Scroll Page', $crawler->filter('strong[title="vse/scrollpage"]')->text()); + $this->assertStringContainsString('VigLink', $crawler->filter('strong[title="phpbb/viglink"]')->text()); } public function test_extensions_catalog_removing_extension() @@ -365,36 +357,22 @@ public function test_extensions_catalog_removing_extension() $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); // Check if both enabled and disabled extensions have 'Remove' action available - $scrollpage_remove_link = $crawler->filter('tr')->reduce( - function ($node, $i) - { - return (bool) (strpos($node->text(), 'Scroll Page') !== false); - } - )->selectLink($this->lang('EXTENSION_REMOVE'))->link(); - - $scrolltotop_remove_link = $crawler->filter('tr')->reduce( + $viglink_remove_link = $crawler->filter('tr')->reduce( function ($node, $i) { - return (bool) (strpos($node->text(), 'Scroll To Top') !== false); + return (bool) (strpos($node->text(), 'VigLink') !== false); } )->selectLink($this->lang('EXTENSION_REMOVE'))->link(); // Test extensions removal - // Remove 'Scroll Page' enabled extension - $crawler = self::$client->click($scrollpage_remove_link); - $this->assertContainsLang('EXTENSIONS_REMOVED', $crawler->filter('.successbox > p')->text()); - // Assert there's console log output - $this->assertStringContainsString('Removing vse/scrollpage', $crawler->filter('.console-output > pre')->text()); - - // Remove 'Scroll To Top' disabled extension - $crawler = self::$client->click($scrolltotop_remove_link); + // Remove 'VigLink' enabled extension + $crawler = self::$client->click($viglink_remove_link); $this->assertContainsLang('EXTENSIONS_REMOVED', $crawler->filter('.successbox > p')->text()); // Assert there's console log output - $this->assertStringContainsString('Removing vse/scrolltotop', $crawler->filter('.console-output > pre')->text()); + $this->assertStringContainsString('Removing phpbb/viglink', $crawler->filter('.console-output > pre')->text()); // Ensure removed extensions do not appear in available extensions list - $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); - $this->assertStringNotContainsString('Scroll Page', $this->get_content()); - $this->assertStringNotContainsString('Scroll To Top', $this->get_content()); + self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); + $this->assertStringNotContainsString('VigLink', $this->get_content()); } } From 1af2f922b514c7a62f11ec25ccafe7d7a7fcb685 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 13 Sep 2025 14:03:32 -0700 Subject: [PATCH 0870/1214] [ticket/17541] Avoid variable naming conflicts in loop PHPBB-17541 --- phpBB/phpbb/composer/installer.php | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 2e7ab9a6145..d848c8b9afc 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -390,22 +390,22 @@ protected function do_get_available_packages($type) } } - foreach ($compatible_packages as $name => $versions) + foreach ($compatible_packages as $package_name => $package_versions) { // Determine the highest version of the package /** @var CompletePackage|CompleteAliasPackage $highest_version */ $highest_version = null; // Sort the versions array in descending order - usort($versions, function ($a, $b) + usort($package_versions, function ($a, $b) { return version_compare($b->getVersion(), $a->getVersion()); }); // The first element in the sorted array is the highest version - if (!empty($versions)) + if (!empty($package_versions)) { - $highest_version = $versions[0]; + $highest_version = $package_versions[0]; // If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage, // so we need to get the package being aliased in order to show the true non-numeric version. @@ -416,23 +416,23 @@ protected function do_get_available_packages($type) } // Generates the entry - $available[$name] = []; - $available[$name]['name'] = $highest_version->getPrettyName(); - $available[$name]['display_name'] = $highest_version->getExtra()['display-name']; - $available[$name]['composer_name'] = $highest_version->getName(); - $available[$name]['version'] = $highest_version->getPrettyVersion(); + $available[$package_name] = []; + $available[$package_name]['name'] = $highest_version->getPrettyName(); + $available[$package_name]['display_name'] = $highest_version->getExtra()['display-name']; + $available[$package_name]['composer_name'] = $highest_version->getName(); + $available[$package_name]['version'] = $highest_version->getPrettyVersion(); if ($highest_version instanceof CompletePackage) { - $available[$name]['description'] = $highest_version->getDescription(); - $available[$name]['url'] = $highest_version->getHomepage(); - $available[$name]['authors'] = $highest_version->getAuthors(); + $available[$package_name]['description'] = $highest_version->getDescription(); + $available[$package_name]['url'] = $highest_version->getHomepage(); + $available[$package_name]['authors'] = $highest_version->getAuthors(); } else { - $available[$name]['description'] = ''; - $available[$name]['url'] = ''; - $available[$name]['authors'] = []; + $available[$package_name]['description'] = ''; + $available[$package_name]['url'] = ''; + $available[$package_name]['authors'] = []; } } From 30b69b6403476a4b7731d5947a2a95357d5288fe Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 13 Sep 2025 21:39:27 -0700 Subject: [PATCH 0871/1214] [ticket/17541] Ensure highest compat. ver. ext is installed PHPBB-17541 --- phpBB/phpbb/composer/installer.php | 141 +++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index d848c8b9afc..993519dd5e2 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -587,8 +587,149 @@ protected function generate_ext_json_file(array $packages) $lockFile->write([]); } + // First pass write: base file with requested packages as provided $json_file->write($ext_json_data); $this->ext_json_file_backup = $ext_json_file_backup; + + // Second pass: resolve and pin the highest compatible versions for unconstrained requested packages + try + { + // Build a list of requested packages without explicit constraints + $unconstrained = []; + foreach ($packages as $name => $constraint) + { + // The $packages array can be either ['vendor/package' => '^1.2'] or ['vendor/package'] (numeric keys). + if (is_int($name)) + { + // Numeric key means just a name + $pkgName = $constraint; + $unconstrained[$pkgName] = true; + } + else + { + // If constraint is empty or '*' treat as unconstrained + if ($constraint === '' || $constraint === '*' || $constraint === null) + { + $unconstrained[$name] = true; + } + } + } + + if (!empty($unconstrained)) + { + // Load composer on the just-written file so repositories and core constraints are available + $extComposer = $this->get_composer($this->get_composer_ext_json_filename()); + + /** @var ConstraintInterface $core_constraint */ + $core_constraint = $extComposer->getPackage()->getRequires()['phpbb/phpbb']->getConstraint(); + $core_stability = $extComposer->getPackage()->getMinimumStability(); + + // Resolve highest compatible versions for each unconstrained package + $pins = $this->resolve_highest_versions(array_keys($unconstrained), $extComposer, $core_constraint, $core_stability); + + if (!empty($pins)) + { + // Merge pins into require section, overwriting unconstrained entries + foreach ($pins as $pkg => $version) + { + $ext_json_data['require'][$pkg] = $version; + } + + // Rewrite composer-ext.json with pinned versions + $json_file->write($ext_json_data); + } + } + } + catch (\Exception $e) + { + // If resolution fails for any reason, keep the first-pass file intact (Composer will still resolve). + // Intentionally swallow to avoid breaking installation flow. + } + } + + /** + * Resolve the highest compatible versions for the given package names + * based on repositories and phpBB/PHP constraints from the provided Composer instance. + * + * @param array $packageNames list of package names to resolve + * @param Composer|PartialComposer $composer Composer instance configured with repositories + * @param ConstraintInterface $core_constraint phpBB version constraint + * @param string $core_stability minimum stability + * @return array [packageName => prettyVersion] + */ + private function resolve_highest_versions(array $packageNames, $composer, ConstraintInterface $core_constraint, $core_stability): array + { + $io = new NullIO(); + + $compatible_packages = []; + $repositories = $composer->getRepositoryManager()->getRepositories(); + + foreach ($repositories as $repository) + { + try + { + if ($repository instanceof ComposerRepository) + { + foreach ($packageNames as $name) + { + $versions = $repository->findPackages($name); + if (!empty($versions)) + { + $compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $name, $versions); + } + } + } + else + { + // Preload and filter by name for non-composer repositories + $byName = []; + foreach ($repository->getPackages() as $pkg) + { + $n = $pkg->getName(); + if (in_array($n, $packageNames, true)) + { + $byName[$n][] = $pkg; + } + } + + foreach ($byName as $name => $versions) + { + $compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $name, $versions); + } + } + } + catch (\Exception $e) + { + continue; + } + } + + $pins = []; + foreach ($packageNames as $name) + { + if (empty($compatible_packages[$name])) + { + continue; + } + + $package_versions = $compatible_packages[$name]; + + // Sort descending by normalized version + usort($package_versions, function ($a, $b) { + return version_compare($b->getVersion(), $a->getVersion()); + }); + + $highest = $package_versions[0]; + if ($highest instanceof CompleteAliasPackage) + { + $highest = $highest->getAliasOf(); + } + + // Pin to the resolved highest compatible version using its pretty version + $pins[$name] = $highest->getPrettyVersion(); + } + + return $pins; } /** From 17670ed48f84e66650212233d6925e45e06ebc8f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Sep 2025 14:12:26 +0200 Subject: [PATCH 0872/1214] [ticket/17540] Change handling of rename_index array PHPBB-17540 --- .../db/migration/data/v400/rename_duplicated_index_names.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php b/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php index f2d8107a9b3..3df6feee19e 100644 --- a/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php +++ b/phpBB/phpbb/db/migration/data/v400/rename_duplicated_index_names.php @@ -27,7 +27,7 @@ class rename_duplicated_index_names extends migration /** * @var array */ - protected static $rename_index = []; + protected static $rename_index; public static function depends_on() { @@ -72,7 +72,7 @@ public function update_schema() } return [ - 'rename_index' => self::$rename_index, + 'rename_index' => self::$rename_index ?? [], ]; } From 5b4afdfedc409a38d6dc072b1a8c1f134089799c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Sep 2025 14:13:28 +0200 Subject: [PATCH 0873/1214] [ticket/17540] Use standard John Doe token PHPBB-17540 --- tests/functions/fixtures/user_delete.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functions/fixtures/user_delete.xml b/tests/functions/fixtures/user_delete.xml index 4c4479d29bd..65df5616be5 100644 --- a/tests/functions/fixtures/user_delete.xml +++ b/tests/functions/fixtures/user_delete.xml @@ -40,7 +40,7 @@ 2 897a897b797c8789997d7979879 auth.provider.oauth.service.google - {"accessToken":"ya29.YPHwCWVkrvwu1kgbYKiDNYaQ451ZuHy9OEQAGVME8if-WBzR-v7a9ftxbx41kaL)5VLEXB-6qJEvri","endOfLife":1429959670,"extraParams":{"token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsImupZCI6IjE0YuRjNzc2MDQwYjUyNDZmNTI5OWFkZDVlMmQ1NWNOPTdjMDdlZTAifQ.eyJpc3MiOiJhY2NvdW90cy5nb78nbGUuY29tIiwic3ViIjoiMTExMDMwNwerNjM4MTM5NTQwMTM1IiwiYXpwIjoiOTk3MzUwMTY0NzE0LWhwOXJrYjZpcjM4MW80YjV1NjRpaGtmM29zMnRvbWxhLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJtYXJjLmFsZXhhbmRlci4zN0BnbWFpbC5jb20iLCJhdF9oYXNoIjoiWHk2b1JabnVZUWRfRTZDeDV0RkItdyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdWQiOiI5OTczNTAxNjQ3MTQtaHA5cmtiNmlyMzgxbzRiNXU2NGloa2Yzb3MydG9tbGEuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0Mjk5NTYwNzEsImV4cCI6MTQyOTk1OTY3MX0.C5gfSzjqwlRRvVMuTP6jfWIuEHMXn55oYHsSA3eh97n2BZL0TZHhUm4K206Fgucd6ufAphan4l0J7y6tMAHLZPr-kk6KDINxWnPG-up99reblGutay0lRYjMCcrhJAOql8EI1bi84GyliZFYHL67pE0ZtSf-CMb1CeH18TFe-Fk"},"refreshToken":null,"token_class":"OAuth\\\\OAuth2\\\\Token\\\\StdOAuth2Token"} + {"accessToken":"ya29.YPHwCWVkrvwu1kgbYKiDNYaQ451ZuHy9OEQAGVME8if-WBzR-v7a9ftxbx41kaL)5VLEXB-6qJEvri","endOfLife":1429959670,"extraParams":{"token_type":"Bearer","id_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30"},"refreshToken":null,"token_class":"OAuth\\\\OAuth2\\\\Token\\\\StdOAuth2Token"} From cd40a0594e9fcb009f7dd6dd4873be06b805f58d Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Sep 2025 15:29:12 +0200 Subject: [PATCH 0874/1214] [ticket/17540] Fix some small warnings PHPBB-17540 --- tests/console/thumbnail_test.php | 20 +++++++++++++++----- tests/lint_test.php | 8 ++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/console/thumbnail_test.php b/tests/console/thumbnail_test.php index aa5e72bb824..68d46517572 100644 --- a/tests/console/thumbnail_test.php +++ b/tests/console/thumbnail_test.php @@ -97,11 +97,21 @@ protected function tearDown(): void { parent::tearDown(); - unlink($this->phpbb_root_path . 'files/test_png_1'); - unlink($this->phpbb_root_path . 'files/test_png_2'); - unlink($this->phpbb_root_path . 'files/test_txt'); - unlink($this->phpbb_root_path . 'files/thumb_test_png_1'); - unlink($this->phpbb_root_path . 'files/thumb_test_png_2'); + $delete_files = [ + $this->phpbb_root_path . 'files/test_png_1', + $this->phpbb_root_path . 'files/test_png_2', + $this->phpbb_root_path . 'files/test_txt', + $this->phpbb_root_path . 'files/thumb_test_png_1', + $this->phpbb_root_path . 'files/thumb_test_png_2' + ]; + + foreach ($delete_files as $file) + { + if (file_exists($file)) + { + unlink($file); + } + } } public function test_thumbnails() diff --git a/tests/lint_test.php b/tests/lint_test.php index 55fc3941704..e7904f9c837 100644 --- a/tests/lint_test.php +++ b/tests/lint_test.php @@ -52,12 +52,12 @@ public function test_lint($path) $this->assertEquals(0, $status, "PHP lint failed for $path:\n$output"); } - public function lint_data() + public static function lint_data(): array { - return $this->check(__DIR__ . '/..'); + return self::check(__DIR__ . '/..'); } - protected function check($root) + protected static function check($root): array { $files = array(); $dh = opendir($root); @@ -92,7 +92,7 @@ protected function check($root) __DIR__ . '/../node_modules', ))) { - $files = array_merge($files, $this->check($path)); + $files = array_merge($files, self::check($path)); } else if (substr($filename, strlen($filename)-4) == '.php') { From 1590efe52ef35e4ad71190315208f23b186244cc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Sep 2025 18:49:17 +0200 Subject: [PATCH 0875/1214] [ticket/17540] Try tests against PHP 8.5 PHPBB-17540 --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 79dcc272770..4a021cd1b44 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,6 +145,10 @@ jobs: db: "mysql:8.0" - php: '8.4' db: "mariadb:10.3" + - php: '8.5' + db: "mysql:8.0" + - php: '8.5' + db: "mariadb:10.3" name: PHP ${{ matrix.php }} - ${{ matrix.db_alias != '' && matrix.db_alias || matrix.db }} @@ -286,6 +290,8 @@ jobs: db: "postgres:9.5" - php: '8.4' db: "postgres:9.5" + - php: '8.5' + db: "postgres:9.5" name: PHP ${{ matrix.php }} - ${{ matrix.db }} From 20aa99937b880c183732016f4790dfec4f5e7b89 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 10 Jun 2025 10:16:02 +0700 Subject: [PATCH 0876/1214] [ticket/17524] Add possibility to use index key length in migrations PHPBB-17524 --- phpBB/phpbb/db/tools/doctrine.php | 50 ++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/phpBB/phpbb/db/tools/doctrine.php b/phpBB/phpbb/db/tools/doctrine.php index 87628d440ea..ae734c41a5e 100644 --- a/phpBB/phpbb/db/tools/doctrine.php +++ b/phpBB/phpbb/db/tools/doctrine.php @@ -655,24 +655,16 @@ protected function schema_create_table(Schema $schema, string $table_name, array $columns = (is_array($key_data[1])) ? $key_data[1] : [$key_data[1]]; $key_name = !str_starts_with($key_name, $short_table_name) ? self::add_prefix($key_name, $short_table_name) : $key_name; - // Supports key columns defined with there length - $columns = array_map(function (string $column) - { - if (strpos($column, ':') !== false) - { - $parts = explode(':', $column, 2); - return $parts[0]; - } - return $column; - }, $columns); + $options = []; + $this->schema_get_index_key_data($columns, $options); if ($key_data[0] === 'UNIQUE') { - $table->addUniqueIndex($columns, $key_name); + $table->addUniqueIndex($columns, $key_name, $options); } else { - $table->addIndex($columns, $key_name); + $table->addIndex($columns, $key_name, [], $options); } } } @@ -869,7 +861,10 @@ protected function schema_create_index(Schema $schema, string $table_name, strin return; } - $table->addIndex($columns, $index_name); + $options = []; + $this->schema_get_index_key_data($columns, $options); + + $table->addIndex($columns, $index_name, [], $options); } /** @@ -925,7 +920,10 @@ protected function schema_create_unique_index(Schema $schema, string $table_name return; } - $table->addUniqueIndex($columns, $index_name); + $options = []; + $this->schema_get_index_key_data($columns, $options); + + $table->addUniqueIndex($columns, $index_name, $options); } /** @@ -974,6 +972,30 @@ protected function schema_create_primary_key(Schema $schema, string $table_name, $table->setPrimaryKey($columns); } + /** + * Checks if index data contains key length + * and put it into $options['lengths'] array. + * Handles key length in formats of 'keyname:123' or 'keyname(123)' + * + * @param array $columns + * @param array $options + */ + protected function schema_get_index_key_data(array &$columns, array &$options): void + { + if (!empty($columns)) + { + $columns = array_map(function (string $column) use (&$options) + { + if (preg_match('/^([a-zA-Z0-9_]+)(?:(?:\:|\()([0-9]{1,3})\)?)?$/', $column, $parts)) + { + $options['lengths'][] = $parts[2] ?? null; + return $parts[1]; + } + return $column; + }, $columns); + } + } + /** * Recreate an index of a table * From 73cb930dcd3b92e1fd6ce586777e9024491a8a72 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 10 Jun 2025 11:31:48 +0700 Subject: [PATCH 0877/1214] [ticket/17524] Add index data getter to db tools PHPBB-17524 --- phpBB/phpbb/db/tools/doctrine.php | 40 +++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/db/tools/doctrine.php b/phpBB/phpbb/db/tools/doctrine.php index ae734c41a5e..f26bcf92089 100644 --- a/phpBB/phpbb/db/tools/doctrine.php +++ b/phpBB/phpbb/db/tools/doctrine.php @@ -405,6 +405,42 @@ public static function remove_prefix(string $name, string $prefix = ''): string return $prefix && str_starts_with($name, $prefix) ? substr($name, strlen($prefix)) : $name; } + /** + * Returns an array of the table index names and relevant data in format + * [ + * [$index_name] = [ + * 'columns' => (array) $index_columns, + * 'flags' => (array) $index_flags, + * 'options' => (array) $index_options, + * 'is_primary'=> (bool) $isPrimary, + * 'is_unique' => (bool) $isUnique, + * 'is_simple' => (bool) $isSimple, + * ] + * + * @param string $table_name + * + * @return array + */ + public function sql_get_table_index_data(string $table_name): array + { + $schema = $this->get_schema(); + $table = $schema->getTable($table_name); + $indexes = []; + foreach ($table->getIndexes() as $index) + { + $indexes[$index->getName()] = [ + 'columns' => array_map('strtolower', $index->getUnquotedColumns()), + 'flags' => $index->getFlags(), + 'options' => $index->getOptions(), + 'is_primary'=> $index->isPrimary(), + 'is_unique' => $index->isUnique(), + 'is_simple' => $index->isSimpleIndex(), + ]; + } + + return $indexes; + } + /** * Returns an array of indices for either unique and primary keys, or simple indices. * @@ -992,8 +1028,8 @@ protected function schema_get_index_key_data(array &$columns, array &$options): return $parts[1]; } return $column; - }, $columns); - } + }, $columns); + } } /** From 1adb04179611ec85f8eb13db3ba0dffdadc8015c Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 10 Jun 2025 13:55:48 +0700 Subject: [PATCH 0878/1214] [ticket/17524] Add index migrator tests PHPBB-17524 --- tests/dbal/migration/schema.php | 20 ++++++++++---- tests/dbal/migrator_test.php | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/tests/dbal/migration/schema.php b/tests/dbal/migration/schema.php index 38663bcc16b..d91ed6a3160 100644 --- a/tests/dbal/migration/schema.php +++ b/tests/dbal/migration/schema.php @@ -22,12 +22,22 @@ function update_schema() ), ), 'add_tables' => array( - $this->table_prefix . 'foobar' => array( - 'COLUMNS' => array( - 'module_id' => array('UINT:3', NULL, 'auto_increment'), - ), + $this->table_prefix . 'foobar' => [ + 'COLUMNS' => [ + 'module_id' => ['UINT:3', NULL, 'auto_increment'], + 'user_id' => ['ULINT', 0], + 'endpoint' => ['TEXT', ''], + 'expiration_time' => ['TIMESTAMP', 0], + 'p256dh' => ['VCHAR', ''], + 'auth' => ['VCHAR:100', ''], + ], 'PRIMARY_KEY' => 'module_id', - ), + 'KEYS' => [ + 'i_simple' => ['INDEX', ['user_id', 'endpoint:191']], + 'i_uniq' => ['UNIQUE', ['expiration_time', 'p256dh(100)']], + 'i_auth' => ['INDEX', 'auth'], + ], + ], ), ); } diff --git a/tests/dbal/migrator_test.php b/tests/dbal/migrator_test.php index 284c52a07cc..89517f5dbea 100644 --- a/tests/dbal/migrator_test.php +++ b/tests/dbal/migrator_test.php @@ -413,6 +413,54 @@ public function test_schema() $this->assertTrue($this->db_tools->sql_column_exists('phpbb_config', 'test_column1')); $this->assertTrue($this->db_tools->sql_table_exists('phpbb_foobar')); + $index_data_row = $this->db_tools->sql_get_table_index_data('phpbb_foobar'); + $is_mysql = $this->db->get_sql_layer() === 'mysqli'; // Index length only applies to MySQL indexes + $is_mssql = in_array($this->db->get_sql_layer(), ['mssqlnative', 'mssql_odbc']); // MSSQL primary index key has 'clustered' flag + foreach ($index_data_row as $index_name => $index_data) + { + switch ($index_name) + { + case 'i_simple': + $this->assertEquals(['user_id', 'endpoint'], $index_data['columns']); + $this->assertEmpty($index_data['flags']); + $this->assertFalse($index_data['is_primary']); + $this->assertFalse($index_data['is_unique']); + $this->assertTrue($index_data['is_simple']); + $this->assertEquals(2, count($index_data['options']['lengths'])); + $this->assertEmpty($index_data['options']['lengths'][0]); + $this->assertEquals($is_mysql ? 191 : null, $index_data['options']['lengths'][1]); + break; + case 'i_uniq': + $this->assertEquals(['expiration_time', 'p256dh'], $index_data['columns']); + $this->assertEmpty($index_data['flags']); + $this->assertFalse($index_data['is_primary']); + $this->assertTrue($index_data['is_unique']); + $this->assertFalse($index_data['is_simple']); + $this->assertEquals(2, count($index_data['options']['lengths'])); + $this->assertEmpty($index_data['options']['lengths'][0]); + $this->assertEquals($is_mysql ? 100 : null, $index_data['options']['lengths'][1]); + break; + case 'i_auth': + $this->assertEquals(['auth'], $index_data['columns']); + $this->assertEmpty($index_data['flags']); + $this->assertFalse($index_data['is_primary']); + $this->assertFalse($index_data['is_unique']); + $this->assertTrue($index_data['is_simple']); + $this->assertEquals(1, count($index_data['options']['lengths'])); + $this->assertEmpty($index_data['options']['lengths'][0]); + break; + default: // Primary key + $this->assertEquals(['module_id'], $index_data['columns']); + $this->assertEquals($is_mssql ? ['clustered'] : [], $index_data['flags']); + $this->assertTrue($index_data['is_primary']); + $this->assertTrue($index_data['is_unique']); + $this->assertFalse($index_data['is_simple']); + $this->assertEquals(1, count($index_data['options']['lengths'])); + $this->assertEmpty($index_data['options']['lengths'][0]); + break; + } + } + while ($this->migrator->migration_state('phpbb_dbal_migration_schema')) { $this->migrator->revert('phpbb_dbal_migration_schema'); From e6ffd0c7246aea0b6630f2b153355ccc1214ad09 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 11 Jun 2025 21:04:56 +0700 Subject: [PATCH 0879/1214] [ticket/17524] Add test assertions PHPBB-17524 --- tests/dbal/migrator_test.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/dbal/migrator_test.php b/tests/dbal/migrator_test.php index 89517f5dbea..49620c703ab 100644 --- a/tests/dbal/migrator_test.php +++ b/tests/dbal/migrator_test.php @@ -414,7 +414,12 @@ public function test_schema() $this->assertTrue($this->db_tools->sql_table_exists('phpbb_foobar')); $index_data_row = $this->db_tools->sql_get_table_index_data('phpbb_foobar'); - $is_mysql = $this->db->get_sql_layer() === 'mysqli'; // Index length only applies to MySQL indexes + $this->assertEquals(4, count($index_data_row)); + $this->assertTrue(isset($index_data_row['i_simple'])); + $this->assertTrue(isset($index_data_row['i_uniq'])); + $this->assertTrue(isset($index_data_row['i_auth'])); + + $is_mysql = $this->db->get_sql_layer() === 'mysqli'; // Key 'lengths' option only applies to MySQL indexes $is_mssql = in_array($this->db->get_sql_layer(), ['mssqlnative', 'mssql_odbc']); // MSSQL primary index key has 'clustered' flag foreach ($index_data_row as $index_name => $index_data) { From c3022e718a6259b8cd11aca2a2fe4b7e60424d3b Mon Sep 17 00:00:00 2001 From: rxu Date: Thu, 12 Jun 2025 01:16:25 +0700 Subject: [PATCH 0880/1214] [ticket/17524] Try not to hit MSSQL index key length limitations 900 bytes for a clustered index. 1,700 bytes for a nonclustered index. For SQL Server 2014 (12.x) and earlier, all versions supported 900 bytes for all index types. PHPBB-17524 --- tests/dbal/migration/schema.php | 4 ++-- tests/dbal/migrator_test.php | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/dbal/migration/schema.php b/tests/dbal/migration/schema.php index d91ed6a3160..04c20c46cb5 100644 --- a/tests/dbal/migration/schema.php +++ b/tests/dbal/migration/schema.php @@ -26,9 +26,9 @@ function update_schema() 'COLUMNS' => [ 'module_id' => ['UINT:3', NULL, 'auto_increment'], 'user_id' => ['ULINT', 0], - 'endpoint' => ['TEXT', ''], + 'endpoint' => ['VCHAR:220', ''], 'expiration_time' => ['TIMESTAMP', 0], - 'p256dh' => ['VCHAR', ''], + 'p256dh' => ['VCHAR:200', ''], 'auth' => ['VCHAR:100', ''], ], 'PRIMARY_KEY' => 'module_id', diff --git a/tests/dbal/migrator_test.php b/tests/dbal/migrator_test.php index 49620c703ab..d72abd6b9f3 100644 --- a/tests/dbal/migrator_test.php +++ b/tests/dbal/migrator_test.php @@ -420,14 +420,16 @@ public function test_schema() $this->assertTrue(isset($index_data_row['i_auth'])); $is_mysql = $this->db->get_sql_layer() === 'mysqli'; // Key 'lengths' option only applies to MySQL indexes - $is_mssql = in_array($this->db->get_sql_layer(), ['mssqlnative', 'mssql_odbc']); // MSSQL primary index key has 'clustered' flag + // MSSQL primary index key has 'clustered' flag, 'nonclustered' otherwise + // See https://learn.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described?view=sql-server-ver17#indexes-and-constraints + $is_mssql = in_array($this->db->get_sql_layer(), ['mssqlnative', 'mssql_odbc']); foreach ($index_data_row as $index_name => $index_data) { switch ($index_name) { case 'i_simple': $this->assertEquals(['user_id', 'endpoint'], $index_data['columns']); - $this->assertEmpty($index_data['flags']); + $this->assertEquals($is_mssql ? ['nonclustered'] : [], $index_data['flags']); $this->assertFalse($index_data['is_primary']); $this->assertFalse($index_data['is_unique']); $this->assertTrue($index_data['is_simple']); @@ -437,7 +439,7 @@ public function test_schema() break; case 'i_uniq': $this->assertEquals(['expiration_time', 'p256dh'], $index_data['columns']); - $this->assertEmpty($index_data['flags']); + $this->assertEquals($is_mssql ? ['nonclustered'] : [], $index_data['flags']); $this->assertFalse($index_data['is_primary']); $this->assertTrue($index_data['is_unique']); $this->assertFalse($index_data['is_simple']); @@ -447,7 +449,7 @@ public function test_schema() break; case 'i_auth': $this->assertEquals(['auth'], $index_data['columns']); - $this->assertEmpty($index_data['flags']); + $this->assertEquals($is_mssql ? ['nonclustered'] : [], $index_data['flags']); $this->assertFalse($index_data['is_primary']); $this->assertFalse($index_data['is_unique']); $this->assertTrue($index_data['is_simple']); From 29c3a71019591438dfb6f37add3422aa61bddbec Mon Sep 17 00:00:00 2001 From: rxu Date: Sun, 6 Jul 2025 09:00:39 +0700 Subject: [PATCH 0881/1214] [ticket/17524] Fix tests PHPBB-17524 --- tests/dbal/migrator_test.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/dbal/migrator_test.php b/tests/dbal/migrator_test.php index d72abd6b9f3..89e158541a9 100644 --- a/tests/dbal/migrator_test.php +++ b/tests/dbal/migrator_test.php @@ -47,6 +47,9 @@ class phpbb_dbal_migrator_test extends phpbb_database_test_case /** @var \phpbb\extension\manager */ protected $extension_manager; + /** @var string */ + protected $table_prefix; + public function getDataSet() { return $this->createXMLDataSet(__DIR__.'/fixtures/migrator.xml'); @@ -58,11 +61,12 @@ protected function setUp(): void parent::setUp(); + $this->table_prefix = $table_prefix; $this->db = $this->new_dbal(); $this->doctrine_db = $this->new_doctrine_dbal(); $factory = new \phpbb\db\tools\factory(); $this->db_tools = $factory->get($this->doctrine_db); - $this->db_tools->set_table_prefix($table_prefix); + $this->db_tools->set_table_prefix($this->table_prefix); $this->config = new \phpbb\config\db($this->db, new phpbb_mock_cache, 'phpbb_config'); @@ -413,21 +417,24 @@ public function test_schema() $this->assertTrue($this->db_tools->sql_column_exists('phpbb_config', 'test_column1')); $this->assertTrue($this->db_tools->sql_table_exists('phpbb_foobar')); + $short_table_name = \phpbb\db\doctrine\table_helper::generate_shortname('foobar'); $index_data_row = $this->db_tools->sql_get_table_index_data('phpbb_foobar'); $this->assertEquals(4, count($index_data_row)); - $this->assertTrue(isset($index_data_row['i_simple'])); - $this->assertTrue(isset($index_data_row['i_uniq'])); - $this->assertTrue(isset($index_data_row['i_auth'])); + $this->assertTrue(isset($index_data_row[$short_table_name . '_i_simple'])); + $this->assertTrue(isset($index_data_row[$short_table_name . '_i_uniq'])); + $this->assertTrue(isset($index_data_row[$short_table_name . '_i_auth'])); $is_mysql = $this->db->get_sql_layer() === 'mysqli'; // Key 'lengths' option only applies to MySQL indexes + // MSSQL primary index key has 'clustered' flag, 'nonclustered' otherwise // See https://learn.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described?view=sql-server-ver17#indexes-and-constraints $is_mssql = in_array($this->db->get_sql_layer(), ['mssqlnative', 'mssql_odbc']); + foreach ($index_data_row as $index_name => $index_data) { switch ($index_name) { - case 'i_simple': + case $short_table_name . '_i_simple': $this->assertEquals(['user_id', 'endpoint'], $index_data['columns']); $this->assertEquals($is_mssql ? ['nonclustered'] : [], $index_data['flags']); $this->assertFalse($index_data['is_primary']); @@ -437,7 +444,7 @@ public function test_schema() $this->assertEmpty($index_data['options']['lengths'][0]); $this->assertEquals($is_mysql ? 191 : null, $index_data['options']['lengths'][1]); break; - case 'i_uniq': + case $short_table_name . '_i_uniq': $this->assertEquals(['expiration_time', 'p256dh'], $index_data['columns']); $this->assertEquals($is_mssql ? ['nonclustered'] : [], $index_data['flags']); $this->assertFalse($index_data['is_primary']); @@ -447,7 +454,7 @@ public function test_schema() $this->assertEmpty($index_data['options']['lengths'][0]); $this->assertEquals($is_mysql ? 100 : null, $index_data['options']['lengths'][1]); break; - case 'i_auth': + case $short_table_name . '_i_auth': $this->assertEquals(['auth'], $index_data['columns']); $this->assertEquals($is_mssql ? ['nonclustered'] : [], $index_data['flags']); $this->assertFalse($index_data['is_primary']); From 07d75791be0b5440e83ad4bf9f6b00beac505a77 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 15 Sep 2025 17:49:32 -0700 Subject: [PATCH 0882/1214] [ticket/17542] Mock event dispatcher override early exits PHPBB-17542 --- tests/mock/event_dispatcher.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/mock/event_dispatcher.php b/tests/mock/event_dispatcher.php index fa8b4a10363..654a08d7f16 100644 --- a/tests/mock/event_dispatcher.php +++ b/tests/mock/event_dispatcher.php @@ -22,8 +22,20 @@ public function __construct() { } - public function trigger_event($eventName, $data = array()) + public function trigger_event($eventName, $data = array()): array { - return array(); + // Ensure tests never hard-exit when phpBB calls exit_handler() + if ($eventName === 'core.exit_handler') + { + // Set the override flag so exit_handler() returns instead of exit; + if (is_array($data)) + { + $data['exit_handler_override'] = true; + } + return (array) $data; + } + + // Default behaviour of the mock: return the input data unchanged + return (array) $data; } } From 5336d51f54077e480fa55712d2beef353e1779cf Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 16 Sep 2025 22:12:22 +0700 Subject: [PATCH 0883/1214] [ticket/17543] Update composer and dependencies Update composer.phar and dependencies for better support of upcoming PHP 8.5. PHPBB-17543 --- composer.phar | Bin 3114082 -> 3124368 bytes phpBB/composer.lock | 1155 ++++++++++++++++++++++--------------------- 2 files changed, 603 insertions(+), 552 deletions(-) diff --git a/composer.phar b/composer.phar index 7a3bef9dffd8d0fe6e29b47fd521e51891cc0312..ee830d113df2a40cd7fcbd49966711d4aa4276bb 100755 GIT binary patch delta 28500 zcmb`w2Vh&p`2cQt#J0TT*p8>HC&!8H*s``IIaAY;Ey=Pad1h%@dsxG=L=q>A1_FVQ z2RTM28w!E2#0nwoy-G`=Y$yfF3X~QoE&qG(ot?zc{?h;d56E}=?z`{4`__GT@1FSJ z{D*0~-guaHM{Ztm&R=fueB6?ib;+f_kK^xaYVpr=3vodni)^suWM!2$J@nOF!%SX1 z%1m>bU`hJF}0wR zs@$|-B~|%!!D_0~k&ht3{1p`BwtNlA%{Pm)vKHSIb>i>KYVr4q3vjHUf^68g-?N~f5rD9+02|NIbduHhBbaJ)1t zOT6*v9(ZF6%gDx)U-#zR?IuW`=#k(^s!Srv($fg<`;amB;L0ib#8SzB!QBtBO%6CjZeLvT|f>BNJ)yd#6 z_UBVrM7ey^-bJ{d(Lxe0?#{|OS~UJBexjpxcX>YkFoQ|;dY3^-@zJ`htczEtv&l)Q z%BvYm*HWa25-ZU>0j-9OUsR4P=H_x0)&In7MYd2uL1q@J$ss>2hdeZ|DI)Fg;KC~W z-wP{>2#>h48XBmf7vECN!F3fCRIiN{CbHL2T2|J(S81=p%el4q!G#t0ClzRZIh6(q z)lxY`RSs9$s7ghZgQ}#fMySdks;pFny9hxpTx2B&H~>ZSmgM8>$N_jYm3VFSBC=sW z9Qk|Ni|>Pl*`vKl3canHJ} ztfKdyN|DAxOkzh(8HKU023ha>H988?S*xTfiCR6`Sy`Wz#a%bHe@+5F)KUiW9W>*; z#iyMT~oY z&N@0_BKTHj5$agR@R++SG)0<>N@;oCLLeHtfz==VD?d!!%WIK zJff_u=JgBjK&Yj_3i8T|3Au71*Q8umH>${qo1g=n`*rtwq?-~9+|`J}!Sjt2vAs|N zjl9c$CRIdWtYRU_53x+txVNy-M89TfC`dPkAX_oYwVg0j_q0^sF*nW$42?r)%c(y7 zY}ENm_5cO>l1+7%GqbXu6t2H}j)e_Pl_dA)XJ=)pTW&l)NA=pKa>ma~sW}qm+RyI& z9rW#5{E@{BoVz%W6v7{2FH#8qZc;IJ=%_D5+0D0Hj2~jpVdGmDBM~21JU|Y3X;xO& z$kZnsnMb0&J(!jC+b3o|#CI<3#&ee}qtFCPf>h<&B@wDp zz0^ll&RZHFJAMzxxZs{w-T1pL4R~!!Db?EEA|hKaDagut>WOR1@RxgP@pD^{D)zQW zD74pG1XQJ@RY2muc1c#&^PI7Fh&6I|2OST!BG2reQ*If0PfhUUy} zqoltqH!Djs_R$-&6!dMS_<^=UYM5KwsKB!YB4xdB+3)d#?Tc}284C3)mi16*+m|61 zd~O+v`K9e^$iVbZXp!Iib_QRwu#Omgb30=1igx6p@3w0hM_^bZG)9y=vq!wR-dKy9 zI*@=?^T=Of>; z@ln2T3m-}MOFjx3%LP=>xUVEDYtNgPCrE1{M*OXynBwa}gwfiWm391_WC~|>vhgxe zJ}wYanb*0{OFl9DbUC4RQaVKAdBTNM_p60e03!l;wMKCrmH5B`%0w82Aru)%K}nz= zcy^0#$7wo>&TS%O(#J$n3iY2Nx{y}t_v!djdmsm9;xLTkcTT`1VdB&gqc62#4M$tsfZ zayZt`Bj0|4zig<*o2AI4*GOd))0?G~jYXj0U$xk=oV2lstN{N?$|n2Y0UIT5m zaiU?R3{AXWhBB>7WQdD5W+AoR$UoM0%NUPC(Gk-i%I90;Uy-xE15$djyMY|y7-aFi zHLETlbwo6jEk}mfv7CzU2O-AYlRE-)82e^19$2oYCPH6<*7)%il+lP_TJpfoSAS0? zcVDk8!e3m0(y-50Agk_PsbF9kiaVnGvEZ8>q>#T}Spx2IXP1+K_B^Bp{H2vP|AYxH)wq8ZlKBIxW?5a0R)!6$QDC`cHCluI zy&A2trZuSP>NS*tieYG4%U$+wQUt`H3^`hG?}s`)+f%TeOr1!b_D})j4H!TIOYdHcA5b*ohk6!M*7kIdn5xjU z$O+_HMqOgea!?*AL96te97pk!k-_*yT zX1}Ws&Hm-S)g-SEKw3BIblf>~nZA~6_zhg8Mpdhs_#{jb2|Anf)TZVg=-O|cS9I;1 zP#)A%Liwr<^4{YAI}ALH_&@dK)QAiNl@R<3E-VNBS$Zu_>u=5RDlEn81OE=k#_j9t`tTV^Zw8D)&~k&hE) zUcqWP99+P7S5EaI%DeiZc|?B`RX zecg|YsKbOHHWNyT_L?}1Lnsmv<<*zm=fvMIp=4meKpQzfIvnGb$2m`v$$)7=X`Mc7 zb5d;lxSHaVCF-I1Km3~O7TjPFk`vxjla*D# zFa8tGur%N|Sx_!@+_HkvpUxqP5M|@fOWwt|t!=>DtaXf4FrX0BiE{TxE1=6?U4dUX zT!@FQl*>25L010o@OSu$Ybx;DtQe{pTH~de_6X2Zn*IwND<{n&Gbi5 z4>q10LLPGAkdd*7L17W))E09L*Tc!)KE$ds*`hH@IqQ*TiE_pMLvs9;{T2Av>k7f) zhpXw8zF0W)gMda3MU+?O9Xf{dj}TlhV(e#A)QR%5?mzCuj|?NBeK|ZxnU!ipL{&D8 zpmg-Hk$zHqZBTh0-GTFP`N0bO>U9h8uSW{-zEPwu&nU{sZXHES*H5Eplh!w8Aqji{ zL+($1{PK1D{c9`mhsSu-INywIpeok!byVfvaVJ%2TsurvQftw&b!;u-_oubpR8#l5 z*?reKYQwS+vU%{!N4`N~L8*P&dMfNQpuW~`P~-TeXI0?g^&J$E1MATW`q_HKeD?-w zAAbl^yI6d83g7lz1%7w~8vU;utW-zU#sR8wd zny$EN34Un8L5)=4o}emGH$~_{n6g)1NQk6G-aT5PpsM7o7CDk~?t;MhLqp1Io z-(9P3rWhv5yy~01vtrlaho*#hIIyU;4eeEk@-SXNo5he47z}kN5hNz=o{dTG2haoz z!4Sp7v0#9za6_naAe5jWHQ_K-xiIXfDnHIbf)OOI*CSI@)4-&UsvMj2Qk9OVgPh(f zm>_&^xv~}K-v%u4KVVqj8umLcx5vwHNdlqvC(!JUC6ISFO(BMN%~t+Bg)|gR zA_IISxt1D$KfRHvTt021DnCwB{&*A`_3i(7`mb=UsK7(1CaU$a6t#hAf=TS->OwAz zYZdqf>3Y04T|{Bjr%}opN~1*Zt~APk|CL4&Pkfe%LcZWE6b@cH3-xN*)JZjsZkkP0 zH=*eE#U``@c5J3Lh?}7Hn=7t8pIj^6*t`h8X*0^f9^Q;%RqhswUq6gS*KWRmSotdO z`!_Kfjfp@&A9lN`NPhs?5m9a%`SupvwrMeb=N4qK&u>8-)XyNYwiz^&D`!ybd2oc$7y=S9%b~ z*~iZwp=67lgSKUp=b%t_^c-pz)e4Ps)t{y|!ys9S^UkGm(N$0?KTjSw00Vs`zV%#` z=J9A<7JQ4wL+!UKeyp0gu0C4WYrEN zw@Y@Q!`6>?jFR|n=%Cv_|6zFdp2c|RLMjW=Kv}*f2pOOl2w)NZrwfsJp16=Yr2jqa zwauMehd+EX#s9{ON~w{e7p*1fPQX*C#jjz_a8;x*{&f*bE|={@s+-!0EbqCUda85% z#Yj%!ixK45#mIt7FF}y?mms0udI@EGPeNXv2>~^{=lvbO9;2W0%r#=A{_LmHbl5A5zeo-jIzi zMHoo5UtCHV;0>^~;?_1*NqSF5EXnc7H^Q zD{>{JwMQ00V|>o_9cg%xEA#OuuSAjTy(=jl)I!I3Z=dsLeDZN(gi3P$clT2BCrb0{ z$8UpSm5bkYRWUWCqgNq&E4&(|tEQ_FWbf6~QNm6bdWLHrC?MVU=c|$XF58RnC-+hj z>N=Q$y!lr62VJa%*mLjSZo+xjEym0Cqg1$K zKT^fE{V4N$W`7rHhMmyW|M5V!A3wBjG2VFqZRv~$5ZS8^P-HKKa#;KJS9a1;7zdH1 z$POayU3d_Y{oO&NtLj74$;20spRX@|IPYZSIhc>{IfNq5;X^1+{Co(h-*63b$wSv5 zlAm6K<|@0EicT-VobT$ImMF=~9oHgW-nbTdZtHcZW8^wy0}ozD*?_nOp2Yvtc{$0N5Q*VuY1N?v|x1sf6>uo5L zd;2zO(?mR0T7PfTKZ$_8`*0=hJ&dNm<1k|2-NT5gnuv9~4>Rz4 zZ?7Zy`39~p?#iYOgeVDk{v9YuCK7lQ8bN+4uO3E>2Aua`0eIwRW?mgv8BayX{A4>M zNyJ{F+;Xq;J~GkTe53$>br_4D>B~I!%(6FBchzzaCI>h95upA z^u`iE=hf2j_wHK2Sb*|Ke^RHO}}w0jl!c zJx;13yf;Eh>dH^Dvci|S+Ga<^H{8nrwT-nU_}BO5QmEhGiwveOgW}3189C#-^^{SRlsbN7{GqA!c2OWB1qNZ`?=e^h21D|2?(ga_B9q@#XiI zQzMz~M>)_{_p2CNmrw%`WmPyEo6}L`!QwiJ@INYuQh5U)4%g~y@q8EWo) z3#qUO|Jws70v11rl0(CTXuH1a!Bvb!NU20QefA^ILHmP-`49`Qc?j{#d5ALIosjaI zm*u@p<~;<)U(%%o{#oDu(?jbhMphi9LIspd)}?<)0>nru{^U_a@J~k(BMlEDMm9W* zvfu|FMoMKKqt@NOL5Dj3cQ3EqooT=iKAMkTa;%vYJCU(W4vqAZG3&si1+ak*gAsBY z#v>?}Ymv_3#zp6@+D^n7 zoER?nD6+>*k5YN>&k*CY!%ti^r@#Y`7FEE_g2NSy5t+>Q$i?vd$B_C59z#K8?_<=X z_pQ)39=>d7iu9mkkCoyTk0aH!KaQfM_i-e#TOUW}`t##RbmLDTyFKs(n*5hfP~rGP zxS}>)BL0+|eB`Nx_{&e`<6E9Y3-;AdQigLElqmSSvzRmh3acDn@l*+=Rl`%1R_}yZ z!5?mUja&dCPqi_WHfl4p7dbdidzwn5mx;2n?&vM~C+WiPKA(?=pGIMI*VD6y;ZIZN zQr|&8Q9OD{4$NHZaRPPqGnAHNgqF2KmqL?kfVmAkHZ;Xpfh`8`G%*u^Om?I~lm0LIe=CE&fC)r-I#GugR!o%cg7 zZ9zPm!p@?D+xJ(u;zxdL0Cw}jwo*C;ZMIEJxEyi7-LZh#uzq~i$`$LktXnp|*4eqP zy{&WE=9UhurI`=^l069$3sD!BbI28qdBdT$SPH6>1Ov<+L0HNz6?2A#jjua+v4&epcJ&K7P}Z;z#H2&tVvFV#OVZTbUQgL$dHVpRr&m){fJZ-EtdZt%GD`*3Ps_F)?aU;#R{qc=7_{q*Z zzA@)9c2mG>Pa{o25qM)USG=_uX=}WNXa&a@jA~#$C#SrSkV{JoR0-%iC#P1_jkUmo ziI_L!Zh@k-N5k=e_dnC3+DU4z@fPaahFQB~8A@mTb9k|gYwFJV~La_;A4D#w?Nh{l!2r5`PG?oD)TQn zi$({pJeXg>gXU^;I?=Vk7Sbi+9&e1Z>Lj$a*+JbZY{R-S!2DuyU8ZAKeq~u{%n|iQ z;_`sk7K8c)y$AAF=PajFg!|5j@F^4s;>~kIw>F>Bl)2zQ{)-F1@nxL4TzI(=1DrpX zi?&~QLq7Zzz)xZ3!W-xXkI9<7p;!cl9C;uB`I)c z1ssP*#DJ4XE3tSqK``WnZ5;luadrw<;1yrd?wpBm6qFri)PsY*A_=(4U-Xv(48pLr zO=khxQ@Is6o7XmFwgrlEX(v&)ZY&C9P6UgJmw_NCs#Tp-Bn(S1S~uGwk$LJ3I$^xp zjJcq|Ai_xusClX>Q*uSo6S>(?x3BIkTAD{E;tcrHl|`Jg5Ip5EvlWxjfE3Y zhYRWj#xt_Ry~j%q9xF~j#!MbtRAr0XGW)icew_naKwc^EpIur6vd<~40k@r3T2hn< zks&4mG4WTvs4QrKQ54$rS>P2(ZWRlDal7Ij7!hBiu87MPZ*5x3YwE!GJb{SU1~`An zsq0I7BSa4<1XtKWhI^aC-%4*8nu7lf4NbLl(0S9mE}mFq7enWv|HltT2M5RVs!L`^ zCFzl42qCD`oo>NkXl{nu4BFzP010k8?P&oyzIOtK?b#`JcY`O-Ej_n}0REx}qy*l* zIlrpNorZo2SEpF41+e}3wad_aPr``>>({1E&AniDe2ScX9yvZDcM|>|$r08B{{@V2 zX(f2&>{5Q&yy;}%bXMl)kx;Py?9v+L{~z*Ry`#CRqWLs|lbmeNzNj>leNmZViayTg z$kw%Q;;rq>^k*|pTnHL}%V-7<94L8tyXO){#&b#e{&JcZY`dhqHe)?h{zv8MQRlwX zzjkkDeO#VleY|ksq5PGYfJd0xmxn59z{S^8*b1YVDPRkQT+!`cTvL(x;+o15FT)uQ z!bfUw84W@;+1xJh<+{qIOlNV`j#_dBB%BCFm|Zy)ndg>NT~`3MEU99F$=eoIGKrjm zGyu0ZKD#y%i{{BL0Cr&%2|Ia260&M#OB*=;WMu_3LwZr|28(*opfk$#8%%@!29pIE z8M)%}z`o9^`lk7E)SOB}m>Gj}(&MFnKajWVwf9|Z>RL~Sj*aKb%<{%yx zVm&&G)~eWG>F-k;Hw>zK)FUv`fn)M&RW?j-PNa&MAXHLa2pF`=qRh*ERWq~(fAdIH4Up`wDg!dSs+_2m6R-<1`|zsq9I*Jxq8bvrHW3YM za0J3J7mQV~u7&GEz(p{=odK#c46vxTqOQjq*Cy=U-M{27bKWhBoCP5MYJL@9%w$)A zT@ximV1KmgQo#Fj)l#rZl~bDe!(EGd%7FI|`K)q>EfBE7fY9TLx2$Vh2Q$w1cUG;) zL?2sp8MXjhy&4-|ms#-9qWjnVw=r`R)khx99iJLn2kyzOX_^_2nb(cSE$hZ@@^#~O z#k%o;e%-iBy>1iv1y*JoNx-J@=-|3>hY~gu_ylgmv~Jv^CHs@WO(cY9C8$DFqLl(d zbh6Ucy1EEc2TW zYA)B%XTQ?V0`g>jMMm0IE2hmM0fU#u1iP2jUVRcIZm&J>-7udQS5{Z~ElG*r9ncXfC@|_~S$SD!p`4WLZ zDuW1J4*KH7^o(JB?cED9On+^te7oV|TKK7hpZbj9;)V_-cxZbAJ>Q}?V``-m(3o{i zV9oJbY`xN8Q7Ywjms_6HDdcXQ)flpPEF;qawKN!W^R0ofN^G9+$_8u(M^fn?)%AtP zbZH+?El(Pf!z#7jAousk`BpVu;V~!&c%tcEi`=Jh8;2C~umwz>-_Sav^U2*5#9-+e z8cflhBSzl9h%qv1R#+_>gL}|wROvLmQ+DBidw{1-YCR64!8edJTGV`l%E~iXtb$<( zGpbI5@rQkiQMzFumQ)Ums)hyzdeq5YmBptH7?cK*DW%8Y7#h;{@*ToKg+mbV=rn_Y zQ2|t$V0vmy5C}L@Nw;}e?9=twK|6q*5^!EFu7rkqNJYQO?s zQPnR3FW+2WIHUJ)>5-urS8fr@q&iPL>J5&H#^MgEIAoV4eO&%<)a{GK)sZQc!l&n( z_y&p9t1*Q~0uv*as3>OQ8qATTD&kHUCWq6#Qo30jv-XWiMEcQwewRozZMN`&qb|P_ zraZlI_oO7GGVoQhXiPX6=!5qG{ppwu9QdiO4y<~=uDnjK8?q!M8dch&pA1d*Yq^ts zqmz9$OV=QvyNMPyQRbSo{27A*cGydD~D|Yxy>qQcY4C3X0OoM-{UoauZ!zg zCoOf*ET6Mfw!*D~hAQyR8LC#{8%?(lSxhM%e=uOQ_Nu01gEEZ+N;0V$80{qt)idQV z$_La2#ef_dE0Aw(Xr%p$QPK&koNtuYfKl;(7^U!6j8Z@+tn&XfO6wUKC7rO!|6-J8 zD^GpWC;|6pD8SDzt1p`|3?~%v$%uL|Eq0Fe%k&}BaB@n)8=F+fhWnE~o=K5NdTc$R zs7E0j4QfTQ$WT<$ZVajhR63K99_WvTrp<=d z=Z&Rg)^_orFW7Ggn!~1^fPJ)gfUh#DMue`JLAlti_RH)>yMRj%T0HTI;j~Q@^rW?1 z|8!)~Kj27OCzEO2n8y|7O~(03txst*iM#mPpmxYE;mIALzPQ@gJ{j=`($etgP+%ZA zFv8dP>FFt%FkuM9qx~a8K~v9|KA85UtQK+HY6vOSLcv5_tai=V6Dgk}ZR7FcF@4;w za_OB0zRltn#8Z7fw}h*b_6J9mqhWeFZR55F9DNc^jN4_A54)rm`?PV;A6BU(;t8J9 zF{XF+jmi|mMv**fiONhPN!z4eJt9?$crJ}Qzzp`86_GKH`(PDD%H9{UT<OMitI-2T@ z$Ax;ODkg0A%~(PnLr^&!m4-wKou*4k*VydC0$qDd?BJz5F`rm$oa`0(V(osZ)nc3C zYGu6m0>sz*6(o#d%3cC|1*?eGfw)iVEd@1Vibr7}h*M|yfwLwtI#K-eRQr21p> z;W571JveRFS`((UHZU3Q8`A_lHtDoUYL=$V5qG=aYM1s8_o~L+VS_jlXiv8XyUZ~u zFRcm>ho$C_g+9p-#`*Cn7hjoBfSI#6x4UsH`ib=$BZeJV+Bf0uDYVf0!A zmSjXQtsAiQYt>%6yGPgWu)CtEX}_Sy)?<|q#)l_0LrLv4Jst?hdqXpZP+%%3Hu0wh zyuCty*d*w(M6A7Tp-d>$s5OCJp4%~Eo0ga+d*frtglD2hJe(S|#Rh^=K`d-jNCiDY zSv*1)NCtI1a(R5JPp|e`t-i>B#534qZTH4BlLm#*J8E)wO@_m@a+ED zq8hmVVMN9@7!JoVr5x@61p@cBTW9or zL7$hdlp05-R71WgtuoNhh5J{vcq9;%z*N%am$~(Q+R4d8!q^p#PV)tVXqeCI)g%?N z_OxQEuTSa~4_QYN61~!*pAhSUba!7rFJQ7M!!}E}J!EwbPZ+eepvEtX*$e?&yDuRU zPYuOn(`ikYVrF!JAD2$Xl=?~Yh?S{zSD)jX@YR~P371)+AGGwK={@l1J?)01J3 ze0az*l9pNqf;@A&zbD}L>9m77Vb>(R-;$K5#Iin@NZ)4@aQk#V(KJu58tC(+%-%3} zv}bHuD^`gN35VV|AxGL_oQdeT0-GwS@29JKqW!5Uuei@< z5O_yz38hqF8kP8ahOGgYDyE6VlF_tNC`yDzQ|Vr@&m0`(1tQb!J+ZDat3&0JP7Ddd z7Lmcg8#4vy>cpT`;FF}_g6%T-rZrNf+oXtwJOZ&(60{DxgcDsu!vh0TGfoTF0`Dk} zc_x$Ac%N6@9`iW+r{kl3sd^-!8PO@*`;~OpsChz~gwOu_B@Tmg*d(2bm;(M}&t%9a z5eCg(os>6WiVYj4R8FfZ8n-zDBU33&g728}h=gf}+oEv|I>f!{sKO(p4~TSbn=2iO zDg!-Y^+3G8UFA%B$83Gt9)V0Ek&MFipf73SYo&c6{jgG{Nhx@OXvAf->I}hNWk?vZ z#7v3-ok~6+SJ1-(Lvj@CiqzGBmBn?7z(ZSM>Nhy$3-;;#y#6FzWK{_wQj<(N&@NI< zTSXCz+(~$ic0i=oKtHmJ#9pu1sEYM#L6?Z~yO30-{{e9N?Nwl60vuG9c9EH3u z8SArZWbJ`8zb9fHol2!5-bufINISw$sFgF(RF~UtHOEFxg8oP};u-Es4ykmL{X#y^ z=1MBVX47cbFg@T`O{i4;dRy8x*dG~|^qT|mq`WugjST1&$zEfRN+M|&PVt>Sfn64W z5jfyZYbPTjzH7{?P)(-XZkQB#M$^MqYnU#z4f_H~u}a6~>1T90t&*QIYlcm6YiPJn%o~f)Q+&;kG8Hk% zCVKsru7HCpaHM%P^XnCj3G`hCdVj*B(R+HjqJd~AVQ-&K>$vW)sa+J4 z^=Mqe06cjJ&hYyM_R#^o#1t2}^k##9taoTcIyEXZjl?7Uk*VHDdWz4rXeS2fN$V&- zs7d;Q$zIKXc+e)aPiv!QeS0Dxn(~|d5l<-Q5eCKnxHM+;rJeS0(lgn`wK(Nr8Qf9z zO(#^&a3G+HCNx|ty-zn8(7986Gai#-Odq!jdBfVkK*%`S7uE`$@hPv!&ohp2ZNe${ zFfZyC8HJuvnOxM9?hT0b33W`KhDy^&BC$~=FGTmG9e!~r;?_$>EbgRv(!w+H!?7M! z%Prre2F7^NBt0HV zbcx2&V}Rzbtp|3{xHxn78x4`{W`~UfpPR(uT-P*w9l;TIIk^#6kQ)!gIIzi{AkO>1 zD{nW{XE<*)+*DK0joIPPFu;S@2xDPQ=BvLom>R%WAD0(`ca{|816?^2fX%Pdi!&dU zGe2KI8wY==VO~UAmvPiG;ZwBR4>R(>jK4fT6Js*FX|+*TJP{3H%?@}L6v8^;u`Tg9 zc$~K`^Kv88mwgf+6g4sH!A3T784zL2&P;~Q4AH<*$%p-tz6=xyX-O4;Kv#5<}rDcz=1G`L1tHIVbW(nA{jJXP6 zPgTN+EMv|9^%boRVC$RN1^BWzi@?5iX1rm19r*--V$TI1wVXvqtiuCISqC#Go1m!d zWa@L<+nc?B$%l_!Z|P*Js>fR}cv{@FHV&VIG_8%2e?v{{0G-P`9|&$Ms{(+_TmrCH zbKnCwE>jFUaERmO?v`~O*m%nZqP}56%Q{faV?GRC<1uaEa#m{%F!7nrMa`tnEbszX zOdgHeQt&uQ?1!()AaunPyWZncW@H6*=Zei&MuKl(gn{Oc_Pt-h|hO8oHmz( z$CGrCRH->%HPKiBisj70Gw&=9GTCR2edG$}D0ulNW^U#~IkPT%Zo*g!Y+}+HQ|yv zL_#r-?-KA`QYjxUUp5CHp2=4bqDvr=+C&b%lSc?}6S#95 zvzo`9o#UByJ#J$nv;H4?j8PUna}M((T1gj&CnPLd04FE{16MK^fgkT=GBekn$9zAJ z)Gb(Zj9HPn_d;f4Elmgx-p*{z5q1jqT+iI!DiDgGv5JK@!e@m7S(k$^?2_8;@Pu6= z6FOW{r&Kz@6WQiexemy0VAi&rVLz}Y$ZR_EL7?qA0ry7cNUoq$AO#oR$ebWlDCO{F z#0^PDmlRD9`H~E~uJI`vU@;mOfMvp3I#B+| zJdruv(zt{MHWDGDBfs$i82F%U&rDcQCeyzrEO;fhEc36TMpeO?6C?Tn-=aq6uMn&S zo2wg_w01e+Bd`hhaHq@e6xoF$q08nFbUC~1Vz|Oc#ZH0UA?^~%gr`_5DSAUqV_nM` zb_UySU^e|P$W`FI157pPk$KRnLQ`Pp#Z36zwWM(|7_V)-uaqy~2!s|MTyvla%Lp9| z=KT+1!R{{r)yrzCz*a>g1`aH0ECAug+C1<&yOH%PqADj_GuJdVUVY|fAO1B$_#;R2 zfR_X1&T}|qPA6}IFPZ4#J7K8cyClwuF1u7@hg(jOR3@8n3A%XH>Pf6~Cr!Q(Qduw; z70-|m;QwRR^8be(=k75wiwK+Nb9e$m-h5s+pHJC*W+}gML+%+%y%=00X>|OWP{7u+ z8}l+pr2mU_i!)wD<3JN}DeQfBV--=p;%uyH<-tdoHla|~<#O4&Bs`%IKFD;4Y$6$a zqA3tcx+X+on*%<{1ag0)1o%CTO~CDHTnoPSH+Hn1VY($II!i(SMB_Oyjk$D7V+CC3 z1l0IvoW77G*1H?e{{J?G44lIGIWVyx*&q^&14B>7%tK=Ab*rqQzu957;1<2fKhwq=LcDL({lNp0ui|PD65gGU4E2xO-_$o z4~m~)VL8ekIoSCWiw(LSW_^@xtITXCfFUSw8etHQsmz#yfYY91)qs~CWff%3dzAGlEg#;)v%?F0Bv*y#%Pi5* z<1B53F64w4@!+3OoD&Q?6Nrr(@Qe{FW*A5arl%A#JEeOcXANiP1gC@G{byOVL}kx& ztlCV;Q>hPRDP=Cc*Rtc3g2gSk@kX?B0HQO&2EdPr2B-r;A zt7s`_SU4Px`eUc7!<>Tg3mSL~qna}hf5l4VEVKpS&ji^xp)d@0E?dwJX@2?*i&=3} z^t13b!2f~8skgLc-l@WZ)D$2>0Tfr;eu|&D>tqB{^QIL`So%PQ} zxg3+q0N%^T>KmbF5Vy|Z#X-zOT_RMGcOx-_E$YX>`Vy?0rUY;2V@p9<0jAD~O0eG- zN(@$x!RGKlhMi%@JSGfQ!rO>uD63Ey{uhUK9)s_Ra6nZLCI=g2qQ02{?}!BH{R2$=U{^?L*` z-ZQxnew2%?$h_Byt*NSa$Nlh>+JE}YC?MFD!Nd(ci8%Zn=Xq|1fTWrLro!&2g%6ob z^OgiH38Pf`nhM*K1C~FCF~JrsCMVoUStt>f{`qLEb~) zb(cZ^gfaOf5XLWAo(|s243Knki7L3G2kS2Bb4|fe%+V4oRZPOwss?EJ~WnAHq(Dxv{{4n>4@P_D-~nHThsC@T~;!>0_9(``W)f&q+#Q<{9!gq3D5)tQx? zAs2CTHR_y)Z&qTnE!3*;$sl%Rr7t|?Ivue$49eTFuFN(wwxg_G#c_rmFocEX1rfbT z1&)icMWE;_R!64Lf%WDV!occx!Eg`*Cq7_R7tMXegwz_mD~L4>%;Io_Xi?;xW^?m~ zIfcgT8p^>=??6q)?8fws@aB9V67_~ppU^B6uszTV54tfkL<|W$@nB#+A_XN8Gk?uwpo;rO<0ty%Y6!b-S5uD&0lz+mi09*HBP2lsZu#rOf2oxVj(8&WQu7Y{Tp)!n@ zQC^KbNCT$5SU!V8%o!%V#4Ad~`UVc~#j3%#CaePVzt7@>jr*{IOx`|hV{V;Xr{};u z-RTUN4G;`$D`2O0e-Hi2aR>|3s^EkDd1w#<9$JbukZEmGEIbi6C!!Pc6YP_4YU;0C zgB{M6=v}tdDcYMy*-SK_lK!8r)6C}UF+Q!9V=|fM^-F{>2qeehnG+!b13t#$&D-R_ z76NUKek*|j^MbuF*4=so_E>gyj|m*=#wwRsU{(?H#^KLKV?FSpE?j}(DjRmrW`gAY z!~wSck;P|PJl?2NothWM5R}Rl;-09>1-Ex`xC8{|A)eMopm$}$H)BuG*u*nsmvg{8 zPe+zHI3-o#Z?SV|OWcVwy0}or^68_uPto-)Zo|9Mu5lG(X@$ zxYun9amh9eE<8z?s#Ey}?=V&X4}f@J&0(xDdkP-9y~=6_5L%Xb@HXr_nm9!4E#bBe z?po|{#SG2Q{H(JG&{^kDsQ9_@cC1i9^h-i%1|o1nd%A+>b)Cf`mSS<{_S>ze z4*2RmY(eI#`>;JJuS__26Lt;09yAQ4MTQvg^U^cd%`M zx2mZs^QnNnCcBV${SB_=Z5ZH+*&|?WJ#8V_bz^q^p1ZSiGyBBs9ogV_?<`oD5lGo= z8d(MFWo%{%dNSWi+*4=+n`P`~U<=V3GB3#3t=Zu3%h}&9=pf%-1(y!N6QjbFY(H4~ zZ(0S|u@VmRv>>+~b0iuVZsF^{d&dvmNC7mhc%ndhZgx9iJdxg(6-U zZEc>04ZP(TELXt$48)t6&`kIQpuL^AFF;`tkLV#vJK2(&LxpH55)B;gV>eWhl*gN= z;T3>pR8Oq~uf*9)flR@!DYnDCq#qNIua9L+3O0j2Ct((Noz`rks1B7?58Q?&D%>wOQ>+Eata7lqjBylg?=g)Q|_An`ocr!NO zbvae830oo%Cz{9C(c8MQsjwHmbv$Wd3p1+*+11&(gz7RD3;S;(YGRD5;B+3mfNcea zvx*l2YzKQm17s7vLPX?N{}-b*-Sq#$+kfXO^T-bN=<+!(R)GW8vgu&clk8hT=Tq#b zC{F+O6kC~1qi3YgvR_>aoUax#GLtbFzC&@cu-5$T+JBJ3JaL2vRF%tF9cdJ`Y0i<+iSf{qt8 zsb`ualm=5qV)F(M-ACl!bkL= z!!L1fhWL6@uKYXk1U>$s`IZ@mc%17|bhs8}} zdEkjqUe${KBKcV=v%DkorBjk&H^cN@+fuPw@>$7l za81LLMrcOA#DzK|eh7+sh5i49k{#faErrG9^pig^Kurx?RMgb?A9#aiJSRn>{>vFt z7DN4N!LR7#mpx7;Nrp#gaw}GYAGB;1*toE1HTe4PWemW}Z=%=DD)JQHMDYrAK2q95 z+6?_960mi*)B;9DQ&I6O5WdaW1};q()xo=PP4yXTMN{*a;04Cw4Vl+zi~muYfz?OZ zwC?S@1dHLP8Ge?)&r(M-cFAD~7?d#t;TqFL)XYvxRv06k?s!+sWXa%iC)k;iWRdb;;Ms z$)9H-ujy@oSAAMrxUFEPr;W9+jhldX)ktp1R`|ftMXj#|LBwsnA` z$+lXs-rBYh^rzYi3+F^e_L%L>|GuLE)a~gk2K&!zYbEGZ=e8}!HiO7>^g8efLE^=q zI_TSPpJ{`iWtrP&+8?mz5;6g_*E?9?%lF!=wjcbUJ#+AbjsuG_5C5*imjhmYts^&c z;HM6K4R|6^%Gz^9W8U`SDh~W~W{Ru0rC}}a#Idc@XMg@h%Lz&8Yblo9-F9D&xc49L zUq4Y`d*{IXVLdYnwqv>^4AjbwwjK7Jf{`m%^koy9J8C6+J_$g>{!S2++JGC+@>aM z7FO>3>fObNY!NZ*`MFEyU>(T&bJx&l=M$r_a*?d52{}4<2=UIN6C*0~CW*>T^G+cu zAIw`rRK}ahmUQ#UM9b~XYMh%dmNhjkyQvUD-j>plx8}DX%$82PdNlh;~Lrs-pZ z7+Cq2j+0g*2Nn{PbktsaiSOYOx?A2q>ljm)Pzk0w=QlM?eY}rztm98)$7AhHO~Osj zY7py!L1bZDH?iu9Hd5NeOy!m~l8;Z?NM5<~*WzpL=xl0Q8=SKZv2ZX&v?6=v_YqVM z&6nXL{snFGoU-s8VjU(_HL-xSjcx%c?*$7;@_$@F%vrPuDl2km4zv_8=j8Stf~v2b zoU^-Kj*|}6z&1)uc@rK#!7E3{W23)ysJ@1joR{Z3N8*c8v_d-v=1G&0HgAY8cZ))1H_B$Oe7)IgSC0)c~9ZMDXh6wUqSUx0mXOmPxvTsFdkOe6pgqsfm56@9JY3_?}KU$k)(~ zch9ePBA1L3s?qikoSz0cKP&d7@>s{Np2Y;sJA24ge(oWyqv$mgT|0a2MCIdNLM&|E z2WFp2#askAgVBZr`$%J-(MM8wp^uR7mVu_GyLPXMBGXHHke3)#WG$nYAhwZVAS(MA zL~z6eO-;ia7Trk>ZAXUtNqes8SK{(opx>W;bmd!kbYkn?-A|&x^Zf*k-B9p;&dy(O z%U~l`50DCX4OocfZW$ouJ362yT2?a2mNS?{M2F@zHEBkc+<9!B&za;p)Ik|BXKIj~ zeEFc2X!&%Im@H#7H9gJWaL+LoHV$>++@CwEsYx|*<73CDUOTjqwsAQbJFv9r)W5)x zrX!Coqao~NaF2mL^>;W6_o@F3DQTZ+i7!|g@UNeP>>E5b8aZPbso6cttoVYLG&MDO zD(}CDk4g+vk%8gm1h2YbQjCj+Irz}mZB0$LJ~jO=vTgZFWX?$|h+({w(nRIjlX665 z$?^nIIcIqapZFbIn z#IV1O@QBKSQ64`24jJFKZdXTtOoI$tyI(u&pb| z$UD4(g!*>YS{#plgyHkWt<%WbMLpQ+TUaD}m$Asme3PZ6T@TR=vlv!>bLrGah|x?( zEaRjH#m83>tK2j`vxAQlTmeY@stwR_u%NY1j1j}mn6TlJpAJ=d!@qCSB6rS%n}MJO z`DKDs=aR`OVrXKf^2nr?XlZ2|@X2;a`j?N_d(TL*mcoU9Ox#Vg?g=A7q|W3lov$?l)wme7OHc+JQhE(zAX zTr#%&Try3#g-dGpQ!a@Wt9S%f+`piyY1gYe%ec2-E8fbRPw@2+Ibw7YuJgNU9T}J$ zL^=e`2#-%BTxY{5dEfNYPR#7McCgNK_=||?SMmwm!f<COR@KAzgZ{L`pEdMMC&k4m$o7%Yv(LAInKwkk2H8`20Izhx(@@ z*WrR=9j8c1!Kb8Tg0)>ra&cs)g}#!EkM%31wC|wmFfp)FhW3AqOMNG#^z_O;e2GJl z#kbb3J`cAM*3q$wbj0yh1itTuBkswcpE@?;s+kcFt}+sWP){be_H`!{P9uQX$b;Md z{8v1uJ9K z4Y{LQ){$+|4>ePFIz$A?{c{E*t%`6!>9ju3ea&VLM zr(%MV5IO%apNry@4R zZ7?OTzyIws$IN%NiZtKDDslmenuxOnFh2gc{68yk-^FN5s23p9>V-JR2cXD~^UwYc z@heA=C)8x%A5rV@-Upyf&x*Bd#S0! zr0>hLWV~&eX?bL(<$J9dU&9O?G0~Rj#?xYpelFtE5jj^M1jo783JxQWn){I}bmW$C zuWlti?Tpr@rUtd^CS)6QH*6YRPcqNePtZ2X3H@Q^idSxIMN;}v+L|t+5i9pSbyo&9 z4t5y*0SuAfgV_ge`j_>@&VCN0fB%vl#EAx@KWSLfp|lXAuyO-DY8`0n!PlNNv?7}g zG(z}`4W#fd8P?#eJ_t$OsME2L&*2t?k&+tec*pPI-sDxTVIX;!4`Lmgjl>D&4H(d` zozr&hF`YbQBy{rGSX0xNBgtNnc<#k0VdY{$WFB(FLZ$%otYf%*RJg{Uo}73V zPXR3R+Ix&ye~4h?hc(1xtmLijDDM1w$s}ZjZJb7V zo0yB0b2-1Hkce$rH^xZ{t``muTdt$P@PYpgAZNNekpnhjcVolLgUkN<&JJXuy$|`= zMxtn|oy=d$4aVw;3DLJo7Cv1ZX4lvN{@i3Z1#|8>Jy$RT(@J;vN7GBa}e3y zPUOl{7a>PIEr`KO+A`)PGqu~id=8wzI}nq9`unGUL*Bl&6FKPP5bJ#5 z+elQL>rN#q_pJ*NmHzc^qEcT^?rVqEll=a)ekIYh^3<90*r~)pW)Wm_@28J`L8^sJ z`8zfc`vMKxYr{qrf?RT1Cz9PTP7v9%f!tz0+(0tFawBo9-v_B(B)q4NoO!qt*}suo z{iBUeVxn>rxqn~2iOgYzKpyG!%F{qx=&_CNU#w!Ri>QA1dq-D*4rsk08=_cFmWiIA}Z_*S=o~*6D>=# zS)y`5Hc3=|m}yDpNPYb+S0TEr`2R{q)jujioET=ZV34a`VbX$=1L=!`@vT;@AUbCGX~v1l?kII2;xajOs!U3Es7yxpP=#dp;+e{SDx`%9Rnh_fR9#Ojz^!c}Dm!a_ zqVhwHh{s2uQ{VCJvme2Yq!Y>1hlt)w>%_rk2xhrYs#@6)*E*5&8okK;MjJ7rw?XE# znFg6D-rXQm=#32gl9O z`oo>qo{R4lN46|RZrVa7X$QBEST*N#g5M-WqieUEhi!|U$OD^c{pNBiWz0sS1k&#z z{Rk`1^n7^>;@`Xsx$AV&W1l~r#GY5DNql0TL7pbgJcB^<0u0?V zYa0*XX#Uw5Y+4m+7IOrw{KfU{9mwRF#L?kvEWz5^`2!+@5ZHUxqFzfOn~oRp{c$_F zKVs!w;amQUV+|aG{PRpAhIgDrNcs#+KTrPj@lA;SEE2CSJd0%Z$+J9!ZkfL$k8Ang zkx=%??}%g8D0I%%|EO$%AlZd*&L)!8)le(HK;MO&aW;7pxcY2T^f%8YEj4@& zkz5QzWnVFFx)G6G+l8Ed&N#8?{&Prvj-EsM!^+>2rkW55ZY$J_W*+%-{;cZ5O;ogf# zjb$$)TMk`B`f~ervSq_|QoXlrC*1I9$kkKXe;k3?1H6a3n29g1fsGga*xLrZ7;9XB zY`&N{Gn@`PVueRuAv@+GPhZ@R&lrJ;{n2aoorkON&?QuaaS4;)N_Ghm8FlDaN2KeP zlOsqae{u=oiZ{UC&clDx;%sDhkaLk8Wcu{M4#H9wz+q>7nYkIsJ%;IJ5%N&3i?$0k zV%o#XBcF_mkWE{cA&(*_5$k_|5D+&Xvi=-r>))ZXW4%;_xwE4S+g7H#iRoDR+I!b8 zf(%Yzoxsw%ycK!#(iVcwN0*ZNUc1vrv|PQ@MO41qxq+zoc9C#!+b&YgKkOpYfz^P# zX}bWBEicZr^j$_=1Lz<`oU-*DC35JlUgWJSI*^^0kvsN*%Shz>=`zA<9yUB<XWnYdc6BwKc0N!&VYgCOKy`d|wl$iG}k2J(vCf|04Y@2i^-&UMR>j;qO} zc>HS86lY#d=74{=T7SSW0=TEp z_L80=-Ah{Ug1sc!zw9M#wPYW0iSY^K=jcTT=l+U8dz+D6`$!DhzmG(WU-psaH(f)9 z<0Z3ITJuczTkNW%lc*6S@5+&MDW6V7oV)J*???nmVGZ4jTXZ$_THo;Yf~3PpPE z%2jvbD#JSHH+17iY@$SZC;l<%G>Hx`E*0HfUAei199Hp#+i&bfez=iA4DG#%lr(V@ah}1TAaSH>!q>%yetf8} z4^0$(~eE3!}w`;y_6~5BlaHH-kwhG{e*t=tYGm^WF42?5xBh#3_+(yFG*zM#_ zamMXrPWSrl#PJDx@U-E+p&v0o-?6_7iSH-HKYu^Tz?=I?CRf}+0M<4L2iKhOw{vLqall zkvFC2U258^@X7(3f|c7AT)7ygK)sOc5@gNYZG_{P@7B;>gtoy(Vdc-#M>j)V;e-M0 z8^!|UmAh#)FS)g1C_VeQFVu^M4rijYndqYHp ze_syQ%jNGkHDxaij~!d^(J?v$IeOn5V%T^0k*;AtNd(!B%4lD0Ah^ZKXFOlsioAuA zjw8C?gOmN<(bP11=K9J7HX`K~_DmkXXs`0Gan( z@qm(6IEh#QD?i(~#CpsgxrgTWi23aQojC~Owjr(uX#~~8gT#Kj4n~=2>0Pgp25SQs zoJ^;+d=DKA(>gN$Au=m8Jw%?t&=n;R+_GD(m{8Fe)#YJ_|G!>f|h5X`{I)) z9wyv%8(ibnOXt3h=Q?m0^6y4_OL8XO|MBpt1WPABLcjymP}3!^Q~}9SJD6Arss2EE z81j!tNS68zk}PdJNG8G$9VG3`I7Do?UqM#R{mV=1cc6X9-p8Af?T3bOD`2Tg-sREb zV72G*7T7_BC`85!?NJg$Ha<$^E;Se_Y=fZ`7>aGcJNYH1&^acPqZVQPm;S9>q!zO<4=+ryX{HRdw+S7RNcC# zNH^Z|6e;|tPZ2o$E{v<8?ZOXm;d9R{LSA~h89DuFa;LuXX~K{0h8hLmgy!R}K#W?5 zoczoJ!nCGm2-DsLN2R~NUkW&qj%Ki>i?#q9 zVl0|>_p{&6p@3yH`Y7_l4}Ev*ewsB7R*X^>q3%)2=^fxn3FUfl-`08U;9V(Y5!f!J z%pK$~2TwVL3GbA0+2ZJMC|)r6OTpMUb2#TO!JGQb7%E;#X&IYeh+}8wjp0HeTL9i5 z#j<#u$|V0&ZFJgIwv8&AZOXRw<{$-rW}!C+XFo9r-uV6OC&2VMv+1*$Yu13J=g#f} z2e-~%L>UEZem}c+)=b-HTW33#j}F7<*u{7@vu>nVOl>5;jGPK*GDqNfxERkwN5;0A z&z%iFbKqw#YCd<~8zEHx$-EC&P$WRKwAs8sFu@TpxuTUq(Mo{`y<}dH_89Xt zux~GADOkI`Z2`LK+P0VGw+zF;mKCDKtpj(q!Owhj;LZgg%+5{T)?@lbIaEIZ|g+wzpbk+4iXEyyU|y=yKkn0qZd(^p|`TT zFKq!lY4-*&{1cT1UXparNB2v+Yc0SgXj6ed{?NG){g=G^8z1=os%|FOf4KW4^rNf0 z*UerUEEiH6gQ;v0@`Kg2@Xfqbcx=_y>1(>t>1!72I{|actU186v#SL-Zzzi)WuYwhDAy-hDFq)gKpM%0w^v zZ1Dr99#2Yw`b|p?JWi>A#k8eE+G5EcNQE~C!X^Laz-F6bb0N6dpMu8O?B5LIEg0LJ zOKr}LjjmY<|6eybaq61&>qD%u6|Au}V{2xbLg5Pf3T^4e*INX9u~;JFh|p6$UAj~K z>(^HWbUo$RS1>N3-~a2kk45xze*IP#)9c5+p+AV}_y0N(eNajdEIHN-hOKnVuirLX z>6&BT(8E^xHFG-D;Q*5-WO4*6xxAGeKDyaOS5PFe=wv$Eh^JEi$!sCYE`(EI2If{4&B^>adezeFtN`h^mq*dG)~c+_Rfs8p&Zl@0h)_INY{&U~Y{2he`* z8DuI$4uwLN;zeauom>{x8RatkKe^gj@>hjc@ENzKo9=3;j3!x9BjY+%@|a0sO*(5- zjYH-E{kwaa;NU4eOC6eq%qNeUU2>Vpp-P5|)BsN(3#iRGA5Rc0levck&GpU21ab>RrOz!IIAE3IdRyaYg4QQlsue;{( zyF+E2T3-q9tx+onoVu4jG3}=+%YI)XC`t%8EJLJHsl^4Rl%JK;Y1yiL(U^9rtahnH zW3dU+p>(6x2sLUYoq;7X$Gswdp`Moc{FPj-&au};#URz8m1(mMj=^N&xDrmbfFH7( zoN{MS%vF|a3YbDprRj6wu#Jj>JRnag?a{DWC^bu@adlSA zD%71hDLW}tTENuzbS7vY?`r|!$(}`E^;h%-)3#`s=k?pHJWoBFvKsjuRl{M=ve^x( zy&*Hpqse?M8P`aw9J$QuR8wWLDTU0cl5sPZOhhH+adbSk*qD!35)QRNY2ZdIwPHq5 zEmW-Ouv088c=B1B-55wI3{#DIf*W&$%o;_&#Hxyv396UtoDN6|g-XGZiCM)-L6+sM z#N%GESR}W)DjAKU!VxrrR%1}Dai)Y(6<=bGq|8oN-DZ<#Otw(SZFMOlQkQ9}uAwH_ z60^8o7kW7XUcSF{an~^o3|DqDYJDX^SkNa+R#rP%a|O88yjdcv>!MX7vPBVENfrM!f!wnz| zivr9pz{2}_yLKI>OBbsQ35WwV!we3nrZko?3$T6kCDSFP%4w48@aR{>Oh%hq$O&d# zjgUfErusbAnpG)d5usmnn)xAqs4h$eGPZiiW9Ass!c<5hRXWw?2#j{lltP{hq|KFJ zMpo8o^g^9hpYp{8QG=$IqXyh5POvU7`rYcdF`lgH9kPVnfP+fWp;g2Z2D_{_P|)p`b}b&6=+q_heQGic;c z)lCU&jcRT%@Ns-j*c#3F;LT2db;q*5(2DFuX< zYF0iK>VXdt=Quu%9JAF4(Hmc0r%9E)JZU|s5JbJ1WHgv|rHye}HlRtB{2p6F2UX&A z>&ga)Dn&IZOeASj(4aBpbV`*FY9jhuV?bL(9mM36PYI3NvveL|BCrqbWwu5QN?p~g zjOKvf17Z8cxxMYvPOdR;GiZwCd_pG+y3}cF-oUBYD%Fy~;T4ynT7SwX4hgI#dBa+a zWvk|r!yx5`%UZwJ?3*%DH94+C)yPh1xn;h#$WH0y)^O3Q$t65IK|@nA%GfG}(p)d( zoU)`$E9ImNnvg<~Zp393Zj2wE4!MH-WR)-W_|x_RHDYd^Wx5=j~!HGE8cX%S7&Q7;#y7PRtSvYWU=m(^L&}IQ;?SmxM`Qe<7l|48Kco+ zuv&yhksJlv+nX1q{Xz&q%7OLa=s_9E+S3*B=8)xBo{}!7NoxcRCtI8K zg~FMvs}h#T5;13->XHX#Nrm50kqPyFy@t(FMI0HQOHs87&3v9D-H@2PI=4P;Ug7#Z`76V$jHOO>um!brgb$simUT+}vgmYd%F8PFOS}|EDhUe9(wNhh(&c?lnHLIs4b?&=~zw$vwg2arpy%T%t|aFguc1Th*O7K7pg!oz9vnA+r*&opmUjFj>>u zAc#uqK8MO-lB;Im#(*tJK7u|%#wP4R6BlbD;;D7DqFj?1gC>)}L-laJ*?=A^lr(Ik=z zSyd&I`C%leOjRxBXEx51T<%q=r`#bo%=2w*Zo*WsWOY=R(ys|O@?5!!UnRHpkgBSZ zv#VB}N#>QyA`+OWnE?0xo+aSm+@AJns@&?7$pr9_U{$DmoY@wm28|q**PXS;V-c64 zz*kq4atXJbH0ixOja9`iHVkIgl*!9iIoTz?J6cOjCmZ%+Rm~9>gj`QFq=VU6m0j}% zVT1?L8-9xWh#0KDut+QuvFy zY$(fw$1^;8i^Ka)W`P7KIzF&PWx}(#Jb2B}(+ezH=yQj}|AkOmYeE$;$%(TZ^cXu8 z{9_k=c$QKID1g3rS}9j613YfUr;)&8l`2a8HK9;JWlL~WkX5T*n9s{$yr+U0Th5nG zCA=QEk*KlnzMPlmYGC*T;fVu}c~uh*Q?e?R#3zBtp=)3th)>)pSGF3SIbg0KPnb52 zuJ-GRGHQY+%DT_3=J-6isKc#>c~dM!4f1WNU_xaz$%wQ|Uaf-Mxjo$mMYIafB+^oV zXRAOSA)mjdhdPHWGLs&fG}s<%mn$K>LA1pfhfCPy4)~qtcSB(s1b8vO=xLi4$}$mK zSrE?gVzrvt=Cn^$k^x<`>JL@z1(rt6ni9&4B8jLJa_X{fleO%Zmz3(D!f3YJxlXU7 z&I_bt8mfY8$=5m6WYt)!JIaNc+Arp~^jW*2XciR{=BS{Q*SjL7xZ7e0^G)nZRN#*% z^ft?MhL@R&25z&SU>_kK9EK=iKfz}7ppsFff&Fi&7uTRR;sTJ8|FqAHZO9F4y z?3U=VeibWQ%hp8Ebc7?2R=gQgDprxRU4^ox=ro8}{Cp8+OkS?EkuYb~X@69ZHx)f$ zrO#HFN{6M|s3~UZ7mPy5lq@8!WP~Zb#LkZtv^KLw80Xp~o|GY^6(;2g zE!CT=v!aqTm~3Lxaac8rL!7Q-gJ3BABjL z&3PFotAts%CdLVAWg=r;inU|OJMLn}6~rM2LZte5S91Lt4^(3svY-SPYkQVV!!xi# z9gxT>ih#(%O0r5maV=?=HIi&qE#j5AP4#rCP<7hfQB|m%NK?freo+>cM!8w8Ril!m z9Z_vUSWu+I1(qbFw#h4OzC=|>XdT=FC+?Q1BRmCP?kqH<6|JHou?s^wKhNml78+(_ zgK8^Hb1Jp6JYSWVII?<8nksSq)uAdx|Y7 z6FVHv;Rm$@`Lwx}F6PUY(L zoJwvga;Ka+eZCmqkm4x?Nwtu}H5mL(F~6R7C!*Q3SV86T>a}c@6|S&KSs^!2FI0>P zLnA6ucr{$Vz~iaJ`6Wv&&o8Deaj9EZjFxP9nM!SyIMT|D(33Og{N4cYUq>GROjplB z@cXafRmI*IeF(jA6Mbx9m&fK$=a|G*7*ioTmf>~d06L;FDY0X4eC8Nzb?R+Ju{@FI z`f6sbFH#A4S1pd#QhBO)c=3n_spR}(ZPnjbk?-OtCx$U>VRFumxN6@dzd#RE9E3&-&9^v zEmL(iZqR0y#5^8BB4iPUBL$nGB=VQEB5_n63&hiK!@G5B~IjWiup`eLO} zOqO*|*-}NV)R5MuBLb7DMvY`;g_5@+vnq?WSV?KjX1&uqufgFm>GUO@TWnRe#w+WnDkdNT1QzTGO;=SI)@XP((Amo`jmvHR#6)oR@9zPJBup5Unoj)!Wo?) z5ASO%daErGuIO^vIzLvd7)*_d&*BIhS^O+37H?#N(xAa5swLC4OhKBGNmL45zDSkT zfO_xTj?E znqq8|Rv$6D;+9IGPOaJb4tB_vl11UsM5akZeUfU_R|RkHpxdWSK1(*ifyYOyK4TKb zg))0F;i_{*0dK&h^kUn6NL#g5ObY0TB3X`_RfIKq_whg&)?%(KZ}{A1ZZMtVgh_OD zt3paEtYaXml6m#eljZPmq%hZ$sYp;*G5InMVL@!y=9E>1%_j6HrV5Ub(XX>f&1p|M zBnV0})J#ng(CVV*qAr)TIy3@hx^9J+a{-T5V<{I(EUml}@MmQfU7(TUiTr%4#a>?@ z7o}^$DVASTl1csWE+emqCj^dqgDNrVY#EbOVx0D(3ejCnkRbeQw|j$ zI$fPe6HPF8Jg%+kCDTOe%!3G*%T$|5=;X4jL!-83VgB~pb+y5?lp9o3aX2+5;4=pW zk;E!$rmT(EEs0WX8g$Do$r@GQ zcQR&Ch|9`(G^%vW&8m1joVZ3*5Qo?-N5xlbz^ll3!^PJ`J(;jr>?`R-9)--}@_N11 zScS@~7)>0dlr2kH<1V?GwQEP^9He29iP^URppwf zS`Ee2dLAop<|g>DdcXtsc^)-y3-h&n9nX>XdMrG*H(4sen-?BD_#~nVsgMmXwOxX6 z#W+>+#8r8n!x)NB6}S?+#V^yzt#O{m;7U!&Z0fX41h3UKa!$>uNd?oofVb$31h^8v zwJeD>Z>klrl#IN+l~_^0N(USEWrboK$I@+=NBt5ePyikAN+9<&rvzO;Mv{uatoe(Bv4G3~P zA}RZ?wuC%O#d)9~$HQ;ez*#s6X935+{}&$cEC1;#^u4G_#?dJ3c@5RB3-Fazc%Mw( zUIZlh5}{cvi7GX^AUo@?h%E+xL}HqX@k$Oa zH4ra3l9pmJrxd9qdU4jnYNYfW2TbX`?66cEHB^+k=>peO$rQ6{na>nX@Vvn(cyrBy z9u=%wQ@(PEA7O=jwPfB$)!S@NQ9YoQ@FhO3LtagYSXEoR0H2i?b7J^7JRh}dvr>mJ z?ssxL(vmXi)$t(INs58EFk0aSrxF5dU0JEf;X@Q^I9K#(lzet@+NF}1;9YPf!Q}_V z`j9-`Q0lmTzd_(kCu8-DtYAr6s=SIqn^u&SUa2V8aF_#A5`VD3ZRq(~w%cx|ss*w} zASE<-d>Oqq8|Gwe1sT^ZaG34pFke`TSi)?#lpj!t5+ZR}#tB-}p@Q6;5O7Pm=}Mff zS7l|If-I_1W(8U`)uhuZOmYsMSSh1cw_I*#d&Kazvc!qRDz<>j-w1MRN`*3?)(IO5 zc@lfxWaA{@12_Z3H*NGciBnljK@5L5({tt91SWsD}O#aWZ_bec*?C{l(C<#>(l#6m>cxTl?@#yl8(v@(0_6+yWT5PSn`6JUP*iTuPfLs z8M7;FD1}p{lGPB1#Q9XKT&+pRq(Wudz*Y+3#fL%?QNuSf-Y)kn2NUTY+SU*9bol9k zpI-EXd|#&m+_9{u1vNd|M{7Z2FZCg_Q0`y)4lD)RKJ24{$Q6_hl=5+(wGXVn0{TIq zg#pl8|JC*BEcCNB#;iH>{Dp$QJ_^3*U|dKUL#JtsxpTm#c?;&C=XEh;l;y)g_|{5> zIe7|mIE}rj17CVtMWq(PrE(#|Bsy17w}332u>}3On_-zXD;@!V>SGLpS6-QH2WKs1 z^kM&W0&@?e2Yr4iBSXR7v}y$xEM-tp*tmEWHBboqlaLCP3XU#ioCN;9kg))yyBKZg zZGDX1Sz!E&zAmuI&_xA5_A?HEmv&P-!NCE>IjEA!a8PC>cx=h!5^(1r!w4uZcJ+d# zLyT$g!)ptu;I+NOJ>Vhi>nmt$dk-!~xHtx1IGd4!(7_n$TE>_?tC{=;)}6#K&KZug zSm4%^80-3o<`L$|ICE%yX$bqbfd88r8Uu{wjC1EOH*aQw2bMFIgE#N%m<#^3oFN<} zXHCFaBP&P7#+hWpMyz4u#*s0=9bp^m;n#OZ8D~vHr3{B_ z@Tad1_`-Qnh8YXKo`w_I2wxh4FTjn4Yq=Em(%HynNb@&?;bM_;CRl=R7BWZSn~v}` zLgp~`-uiDO$G_vRLXM0;3yiIzT4FiuRX+u0cKEIUGR7!_7w)FDqO=u^VG2bAWcwMT zvxSo)a0$YQg17997VsjA!5)o>gh3u(8kCATVR3{b=0!LnF??G|A`*zg(g=LRLdq9% z#X|6yo6!#}cQ8%``^Fi)CqBPcSx97VsxHA_rHn zlFxzfNrHuDh6SwM#TW-nCF2fsHHUHCY_Ra3i~*FUW&Dc*P8Kq9;J}%Tk-6MS(Iodb z0Sbca zNh2pX59~Xe(Y@;;#)AK&3Bd9$Lj^vOGg{GJ38Q-!l$$4TaQQ2RoRtE>f5{ClFwi^E z(^fIwnFX%7U`BWpUBg&62m~y~Zm{3a;Ef7JoIofTk%YnliNqfkhD2O$P{Q%^LSaeR zFAZ_{LP;>l@&889-vt;0Cq8fC5ysHSi7ql@-Fv{ef)RuAak&#bj)NnH@}c__j7bXG z2s0j-gTBLOuqa^f`u+`ovx%`8m82L+Dq&R7q+rIVuTL|U08g=@cILd_n){Z2lyy(EH;c zb1j1ge!c>_t;ElTVJYDAu?&gJ7Yd-ehJq3yCm`Sj`22{U%kv8YBK~iz`P7vR`ialO zgY|@0!OXdf-#-QJ-p%mL;Y{+SpyMh=ga!o_;;zeC$(OF=2r<_cf=!J67&>${jW1r=now~3H}hL1?*|>hrT1^O1J^PR2ty%1;PL)9N}_; zVF523^auH35tkQ|MnZf6cSb=OIQ3Tro$zeJgHCj%SlU=o7|YDIl$Yx)F8u zpZUMg4K7ZQ-tRvqB??`q=YJ~X9&p!ifAH8S%)z#RLnrl*jEXq$4WKYz#Dn*LJP3t6 zKlG7c06uDqNJIQcKqBP@1Q0QQ)x)EY%maIu_qUw*)B_#;Ln9};4yJ?q`2CJq=via^ z>*gG1TNFC~63X#npqFs_ulk=caxePIs(#B5{9%DV*xxx@GARMvOn*1Ddn6(eghW9h z55lyc#}x}WesK^Yqo2!#?xTaISl+M+62tNa zoY)Cx3y_!uJhHL>caV2Tq8o5B{fke0+r_6pzp4MM{|n-vP@Lb*2HcJP<0rnYK?bDg z(Qy9`$`bC#ICW&^=N-&bRx?M4E$H(~|KjB*xbILZ{VUOqSpSc+A-XS2LzQCaKA7rX za^l#G*#`xw{x6yVE#1F(7N3jmEA>+;Gv5euCyY%PIdF7ye;deE`{Cb0ySoK=EB&V* zM-9xY_J4O=%ZbCtarEJzjfOaMW39h*95tY?_kVO;%l|=){g(e0a8yoj0dF+=^T(0f zdirVo=;@~oP* zorAvj5Hn3de|m&@`z*BMF(#XWe)u@^v03P|PcxsQpzl7*JPXSD9P?Xn`U_CZ)1PPl zL_r-dGTUc^*Zw##h@SOl<{v01@-lORf)2jI+(kiIe`k(S(7i{PyX)ZM)>+NqrR{@d z@QZzr3f_2Vuo*o1A#DlBo;BEp!Z!V^dGMrF@u$FI*Wduiy9T9T%gw`W=)Ahi-~zOiS~u5u=xFNfj5a2PIK4zHrZpFaWr9|pUZ z2D9Z%3I4(iYsQbf!^Bu{m1+4>aM}yA=Y!4fbj{a>$C>cmaro;X>K1s2B!**0a_o0h z1P$WMNz5K4sX&T@r!arJ2H3Z2a5*Rj2bY4JtfLFPEjajN>oL9Yz)Sxg>;k-ZD2u_* z=|KhPog8UHjhVrxXMsl!Q(FK%KWGH|Kj>NllK&j+26xm3=Yp;I!7sp%f1cZkf1NiK zNQ8qW@TV<(KSVA3iP7#;Fp49i$`#cflqH4 zG=m@3x6{C9*9@M5kr)E|Zyjv;4INOqZc#h>&8>s>RmW!Az>7~plc#G#eW3H=K_2D- zli(kT!8S1d{@_V-;BRfHVCwzBKLEIDKf3CJ!QojHCc5dP!9OoYC5whQi@S#7A*kD8 zFcwbxp{+9TPx6eR=Zy}XL;;sPJh%cJ85^3yYD}P8F*FT6UooTy>&82}fSEPaxnMW| ze?2ra4V=LmD$c=Lz?#3bE&)$Op$cNcp-!-KVu;p<4~3Q?7n@ie4`GLXoCJ7wVrZfT z8-W=cJQ9WLjgJr22x_;D52?Um*3jr|I08`EqwoiplS5QsogBJw&Tt_d0T=Rymfpu6 zT0Iw>^unMR{981{0)O)~cXirv!A5^8B}m8T58(u6!)EZO#Qfe4c(G#WE7`| zHKI?5ht_-wXsf8r;LyafmEa2DG6vWwS~h~7yL{P8?Z5V*u;+|H%Bros?q%>Z3_mBq z&vN(~fuB*d*FE;rD4_Bu=A*-BjQzL>Y@9c~6wU1%W3>X!&e@B=O*f9w!HQ**eW2&< zaX$Lt)niMV!QLCj)_~vd8CwGOY#p1s^^F_H;AaK;#*M5S1GC|^3fMC|F#xEoDxS{g2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" - }, { "name": "doctrine/dbal", - "version": "3.9.4", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959" + "reference": "c6c16cf787eaba3112203dfcd715fa2059c62282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/c6c16cf787eaba3112203dfcd715fa2059c62282", + "reference": "c6c16cf787eaba3112203dfcd715fa2059c62282", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1|^2", "php": "^7.4 || ^8.0", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, + "conflict": { + "doctrine/cache": "< 1.11" + }, "require-dev": { - "doctrine/coding-standard": "12.0.0", + "doctrine/cache": "^1.11|^2.0", + "doctrine/coding-standard": "13.0.1", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "2.1.1", + "phpstan/phpstan": "2.1.22", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.22", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.2", + "phpunit/phpunit": "9.6.23", + "slevomat/coding-standard": "8.16.2", + "squizlabs/php_codesniffer": "3.13.1", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/console": "^4.4|^5.4|^6.0|^7.0" }, @@ -1214,7 +1108,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.4" + "source": "https://github.com/doctrine/dbal/tree/3.10.2" }, "funding": [ { @@ -1230,7 +1124,7 @@ "type": "tidelift" } ], - "time": "2025-01-16T08:28:55+00:00" + "time": "2025-09-04T23:51:27+00:00" }, { "name": "doctrine/deprecations", @@ -1282,30 +1176,29 @@ }, { "name": "doctrine/event-manager", - "version": "1.2.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "conflict": { "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.24" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" }, "type": "library", "autoload": { @@ -1354,7 +1247,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.2.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" }, "funding": [ { @@ -1370,7 +1263,7 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:51:15+00:00" + "time": "2024-05-22T20:47:39+00:00" }, { "name": "doctrine/lexer", @@ -1600,16 +1493,16 @@ }, { "name": "google/recaptcha", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/google/recaptcha.git", - "reference": "d59a801e98a4e9174814a6d71bbc268dff1202df" + "reference": "56522c261d2e8c58ba416c90f81a4cd9f2ed89b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/google/recaptcha/zipball/d59a801e98a4e9174814a6d71bbc268dff1202df", - "reference": "d59a801e98a4e9174814a6d71bbc268dff1202df", + "url": "https://api.github.com/repos/google/recaptcha/zipball/56522c261d2e8c58ba416c90f81a4cd9f2ed89b9", + "reference": "56522c261d2e8c58ba416c90f81a4cd9f2ed89b9", "shasum": "" }, "require": { @@ -1648,26 +1541,26 @@ "issues": "https://github.com/google/recaptcha/issues", "source": "https://github.com/google/recaptcha" }, - "time": "2023-02-18T17:41:46+00:00" + "time": "2025-06-26T22:21:57+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1758,7 +1651,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -1774,20 +1667,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -1795,7 +1688,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -1841,7 +1734,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -1857,20 +1750,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -1886,7 +1779,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1957,7 +1850,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -1973,20 +1866,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "justinrainbow/json-schema", - "version": "6.4.1", + "version": "6.5.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900" + "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/35d262c94959571e8736db1e5c9bc36ab94ae900", - "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ac0d369c09653cf7af561f6d91a705bc617a87b8", + "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8", "shasum": "" }, "require": { @@ -1996,7 +1889,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "1.2.0", + "json-schema/json-schema-test-suite": "^23.2", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", @@ -2046,9 +1939,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.5.2" }, - "time": "2025-04-04T13:08:07+00:00" + "time": "2025-09-09T09:42:27+00:00" }, { "name": "laminas/laminas-code", @@ -2115,16 +2008,16 @@ }, { "name": "marc-mabe/php-enum", - "version": "v4.7.1", + "version": "v4.7.2", "source": { "type": "git", "url": "https://github.com/marc-mabe/php-enum.git", - "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", - "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", "shasum": "" }, "require": { @@ -2182,9 +2075,9 @@ ], "support": { "issues": "https://github.com/marc-mabe/php-enum/issues", - "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" }, - "time": "2024-11-28T04:54:44+00:00" + "time": "2025-09-14T11:18:39+00:00" }, { "name": "marc1706/fast-image-size", @@ -2927,23 +2820,23 @@ }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", @@ -2988,7 +2881,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { @@ -2996,7 +2889,7 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "s9e/regexp-builder", @@ -3088,16 +2981,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.18.0", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "4970711f25d94306b4835b723b9cc5010170ea37" + "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4970711f25d94306b4835b723b9cc5010170ea37", - "reference": "4970711f25d94306b4835b723b9cc5010170ea37", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/d65a4f61cbe494937afb3150dc73b6e757d400d3", + "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3", "shasum": "" }, "require": { @@ -3125,7 +3018,7 @@ }, "type": "library", "extra": { - "version": "2.18.0" + "version": "2.19.0" }, "autoload": { "psr-4": { @@ -3157,9 +3050,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.18.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.19.0" }, - "time": "2024-07-24T14:50:52+00:00" + "time": "2025-04-26T09:27:34+00:00" }, { "name": "seld/jsonlint", @@ -3401,20 +3294,20 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.2.2", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c" + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/5ac374c3e295c8b917208ff41b4d30f76668478c", - "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12", + "brick/math": "^0.10|^0.11|^0.12|^0.13", "ext-mbstring": "*", "php": ">=8.1" }, @@ -3429,7 +3322,7 @@ "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", "phpstan/phpstan-phpunit": "^1.1|^2.0", "phpstan/phpstan-strict-rules": "^1.3|^2.0", - "phpunit/phpunit": "^10.1|^11.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", "symfony/string": "^6.4|^7.0", @@ -3494,7 +3387,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.2" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" }, "funding": [ { @@ -3506,20 +3399,20 @@ "type": "patreon" } ], - "time": "2025-01-03T09:35:48+00:00" + "time": "2025-06-13T08:35:04+00:00" }, { "name": "symfony/config", - "version": "v6.4.14", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" + "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", + "url": "https://api.github.com/repos/symfony/config/zipball/80e2cf005cf17138c97193be0434cdcfd1b2212e", + "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e", "shasum": "" }, "require": { @@ -3565,7 +3458,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.14" + "source": "https://github.com/symfony/config/tree/v6.4.24" }, "funding": [ { @@ -3576,25 +3469,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-04T11:33:53+00:00" + "time": "2025-07-26T13:50:30+00:00" }, { "name": "symfony/console", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2e4af9c952617cc3f9559ff706aee420a8464c36" + "reference": "273fd29ff30ba0a88ca5fb83f7cf1ab69306adae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2e4af9c952617cc3f9559ff706aee420a8464c36", - "reference": "2e4af9c952617cc3f9559ff706aee420a8464c36", + "url": "https://api.github.com/repos/symfony/console/zipball/273fd29ff30ba0a88ca5fb83f7cf1ab69306adae", + "reference": "273fd29ff30ba0a88ca5fb83f7cf1ab69306adae", "shasum": "" }, "require": { @@ -3659,7 +3556,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.20" + "source": "https://github.com/symfony/console/tree/v6.4.25" }, "funding": [ { @@ -3670,25 +3567,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-03T17:16:38+00:00" + "time": "2025-08-22T10:21:53+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "c49796a9184a532843e78e50df9e55708b92543a" + "reference": "900da8a42eceeb4a13a0ec34caa7db49328daff3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c49796a9184a532843e78e50df9e55708b92543a", - "reference": "c49796a9184a532843e78e50df9e55708b92543a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/900da8a42eceeb4a13a0ec34caa7db49328daff3", + "reference": "900da8a42eceeb4a13a0ec34caa7db49328daff3", "shasum": "" }, "require": { @@ -3740,7 +3641,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.20" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.25" }, "funding": [ { @@ -3751,25 +3652,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-13T09:55:08+00:00" + "time": "2025-08-13T09:41:44+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -3782,7 +3687,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3807,7 +3712,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -3823,20 +3728,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.20", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "aa3bcf4f7674719df078e61cc8062e5b7f752031" + "reference": "30fd0b3cf0e972e82636038ce4db0e4fe777112c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/aa3bcf4f7674719df078e61cc8062e5b7f752031", - "reference": "aa3bcf4f7674719df078e61cc8062e5b7f752031", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/30fd0b3cf0e972e82636038ce4db0e4fe777112c", + "reference": "30fd0b3cf0e972e82636038ce4db0e4fe777112c", "shasum": "" }, "require": { @@ -3882,7 +3787,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.20" + "source": "https://github.com/symfony/error-handler/tree/v6.4.24" }, "funding": [ { @@ -3893,25 +3798,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-01T13:00:38+00:00" + "time": "2025-07-24T08:25:04+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.13", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" + "reference": "b0cf3162020603587363f0551cd3be43958611ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b0cf3162020603587363f0551cd3be43958611ff", + "reference": "b0cf3162020603587363f0551cd3be43958611ff", "shasum": "" }, "require": { @@ -3962,7 +3871,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.25" }, "funding": [ { @@ -3973,25 +3882,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-08-13T09:41:44+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -4005,7 +3918,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -4038,7 +3951,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -4054,20 +3967,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.13", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" + "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8", + "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8", "shasum": "" }, "require": { @@ -4104,7 +4017,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.13" + "source": "https://github.com/symfony/filesystem/tree/v6.4.24" }, "funding": [ { @@ -4115,25 +4028,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/finder", - "version": "v6.4.17", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" + "reference": "73089124388c8510efb8d2d1689285d285937b08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "url": "https://api.github.com/repos/symfony/finder/zipball/73089124388c8510efb8d2d1689285d285937b08", + "reference": "73089124388c8510efb8d2d1689285d285937b08", "shasum": "" }, "require": { @@ -4168,7 +4085,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.17" + "source": "https://github.com/symfony/finder/tree/v6.4.24" }, "funding": [ { @@ -4179,25 +4096,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-29T13:51:37+00:00" + "time": "2025-07-15T12:02:45+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.19", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "3294a433fc9d12ae58128174896b5b1822c28dad" + "reference": "b8e9dce2d8acba3c32af467bb58e0c3656886181" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/3294a433fc9d12ae58128174896b5b1822c28dad", - "reference": "3294a433fc9d12ae58128174896b5b1822c28dad", + "url": "https://api.github.com/repos/symfony/http-client/zipball/b8e9dce2d8acba3c32af467bb58e0c3656886181", + "reference": "b8e9dce2d8acba3c32af467bb58e0c3656886181", "shasum": "" }, "require": { @@ -4205,6 +4126,7 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -4261,7 +4183,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.19" + "source": "https://github.com/symfony/http-client/tree/v6.4.25" }, "funding": [ { @@ -4272,25 +4194,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-13T09:55:13+00:00" + "time": "2025-08-27T07:01:16+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -4303,7 +4229,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -4339,7 +4265,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -4355,20 +4281,20 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.18", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db" + "reference": "6bc974c0035b643aa497c58d46d9e25185e4b272" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0492d6217e5ab48f51fca76f64cf8e78919d0db", - "reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6bc974c0035b643aa497c58d46d9e25185e4b272", + "reference": "6bc974c0035b643aa497c58d46d9e25185e4b272", "shasum": "" }, "require": { @@ -4416,7 +4342,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.18" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.25" }, "funding": [ { @@ -4427,25 +4353,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-09T15:48:56+00:00" + "time": "2025-08-20T06:48:20+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6be6db31bc74693ce5516e1fd5e5ff1171005e37" + "reference": "a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6be6db31bc74693ce5516e1fd5e5ff1171005e37", - "reference": "6be6db31bc74693ce5516e1fd5e5ff1171005e37", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15", + "reference": "a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15", "shasum": "" }, "require": { @@ -4530,7 +4460,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.20" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.25" }, "funding": [ { @@ -4541,25 +4471,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-28T13:27:10+00:00" + "time": "2025-08-29T07:55:45+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.18", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "e93a6ae2767d7f7578c2b7961d9d8e27580b2b11" + "reference": "628b43b45a3e6b15c8a633fb22df547ed9b492a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/e93a6ae2767d7f7578c2b7961d9d8e27580b2b11", - "reference": "e93a6ae2767d7f7578c2b7961d9d8e27580b2b11", + "url": "https://api.github.com/repos/symfony/mailer/zipball/628b43b45a3e6b15c8a633fb22df547ed9b492a2", + "reference": "628b43b45a3e6b15c8a633fb22df547ed9b492a2", "shasum": "" }, "require": { @@ -4610,7 +4544,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.18" + "source": "https://github.com/symfony/mailer/tree/v6.4.25" }, "funding": [ { @@ -4621,25 +4555,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-24T15:27:15+00:00" + "time": "2025-08-13T09:41:44+00:00" }, { "name": "symfony/mime", - "version": "v6.4.19", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ac537b6c55ccc2c749f3c979edfa9ec14aaed4f3" + "reference": "664d5e844a2de5e11c8255d0aef6bc15a9660ac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ac537b6c55ccc2c749f3c979edfa9ec14aaed4f3", - "reference": "ac537b6c55ccc2c749f3c979edfa9ec14aaed4f3", + "url": "https://api.github.com/repos/symfony/mime/zipball/664d5e844a2de5e11c8255d0aef6bc15a9660ac7", + "reference": "664d5e844a2de5e11c8255d0aef6bc15a9660ac7", "shasum": "" }, "require": { @@ -4695,7 +4633,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.19" + "source": "https://github.com/symfony/mime/tree/v6.4.24" }, "funding": [ { @@ -4706,16 +4644,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-17T21:23:52+00:00" + "time": "2025-07-15T12:02:45+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4774,7 +4716,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -4785,6 +4727,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4794,16 +4740,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -4852,7 +4798,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -4863,25 +4809,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -4935,7 +4885,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -4946,16 +4896,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5016,7 +4970,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -5027,6 +4981,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -5036,19 +4994,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -5096,7 +5055,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -5107,16 +5066,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -5172,7 +5135,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -5183,6 +5146,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -5192,16 +5159,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -5252,7 +5219,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -5263,16 +5230,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -5328,7 +5299,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -5339,6 +5310,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -5348,16 +5323,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -5404,7 +5379,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -5415,25 +5390,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { "name": "symfony/process", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + "reference": "6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", - "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "url": "https://api.github.com/repos/symfony/process/zipball/6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8", + "reference": "6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8", "shasum": "" }, "require": { @@ -5465,7 +5444,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.20" + "source": "https://github.com/symfony/process/tree/v6.4.25" }, "funding": [ { @@ -5476,25 +5455,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-10T17:11:00+00:00" + "time": "2025-08-14T06:23:17+00:00" }, { "name": "symfony/proxy-manager-bridge", - "version": "v6.4.13", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "8932b572e147e80fb498045c580eb14215197529" + "reference": "2a14a1539f2854a8adb73319abf8923b1d7a6589" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/8932b572e147e80fb498045c580eb14215197529", - "reference": "8932b572e147e80fb498045c580eb14215197529", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/2a14a1539f2854a8adb73319abf8923b1d7a6589", + "reference": "2a14a1539f2854a8adb73319abf8923b1d7a6589", "shasum": "" }, "require": { @@ -5532,7 +5515,7 @@ "description": "Provides integration for ProxyManager with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.13" + "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.24" }, "funding": [ { @@ -5543,25 +5526,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-07-14T16:38:25+00:00" }, { "name": "symfony/routing", - "version": "v6.4.18", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68" + "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e9bfc94953019089acdfb9be51c1b9142c4afa68", - "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68", + "url": "https://api.github.com/repos/symfony/routing/zipball/e4f94e625c8e6f910aa004a0042f7b2d398278f5", + "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5", "shasum": "" }, "require": { @@ -5615,7 +5602,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.18" + "source": "https://github.com/symfony/routing/tree/v6.4.24" }, "funding": [ { @@ -5626,25 +5613,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-09T08:51:02+00:00" + "time": "2025-07-15T08:46:37+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -5662,7 +5653,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5698,7 +5689,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -5714,20 +5705,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/string", - "version": "v6.4.15", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" + "reference": "7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", + "url": "https://api.github.com/repos/symfony/string/zipball/7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1", + "reference": "7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1", "shasum": "" }, "require": { @@ -5784,7 +5775,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.15" + "source": "https://github.com/symfony/string/tree/v6.4.25" }, "funding": [ { @@ -5795,25 +5786,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T13:31:12+00:00" + "time": "2025-08-22T12:33:20+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -5826,7 +5821,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5862,7 +5857,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -5878,20 +5873,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/twig-bridge", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "bb423dfaa51b6d88b1d64197ae695a0c8ac73778" + "reference": "9d13e87591c9de3221c8d6f23cd9a2b5958607bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/bb423dfaa51b6d88b1d64197ae695a0c8ac73778", - "reference": "bb423dfaa51b6d88b1d64197ae695a0c8ac73778", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9d13e87591c9de3221c8d6f23cd9a2b5958607bf", + "reference": "9d13e87591c9de3221c8d6f23cd9a2b5958607bf", "shasum": "" }, "require": { @@ -5971,7 +5966,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.20" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.25" }, "funding": [ { @@ -5982,25 +5977,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-28T13:08:36+00:00" + "time": "2025-08-13T09:41:44+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.18", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "4ad10cf8b020e77ba665305bb7804389884b4837" + "reference": "c6cd92486e9fc32506370822c57bc02353a5a92c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4ad10cf8b020e77ba665305bb7804389884b4837", - "reference": "4ad10cf8b020e77ba665305bb7804389884b4837", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6cd92486e9fc32506370822c57bc02353a5a92c", + "reference": "c6cd92486e9fc32506370822c57bc02353a5a92c", "shasum": "" }, "require": { @@ -6012,7 +6011,6 @@ "symfony/console": "<5.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^5.4|^6.0|^7.0", "symfony/error-handler": "^6.3|^7.0", "symfony/http-kernel": "^5.4|^6.0|^7.0", @@ -6056,7 +6054,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.18" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.25" }, "funding": [ { @@ -6067,25 +6065,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-17T11:26:11+00:00" + "time": "2025-08-13T09:41:44+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "998df255e9e6a15a36ae35e9c6cd818c17cf92a2" + "reference": "4ff50a1b7c75d1d596aca50899d0c8c7e3de8358" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/998df255e9e6a15a36ae35e9c6cd818c17cf92a2", - "reference": "998df255e9e6a15a36ae35e9c6cd818c17cf92a2", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/4ff50a1b7c75d1d596aca50899d0c8c7e3de8358", + "reference": "4ff50a1b7c75d1d596aca50899d0c8c7e3de8358", "shasum": "" }, "require": { @@ -6133,7 +6135,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.20" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.25" }, "funding": [ { @@ -6144,25 +6146,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-13T09:55:08+00:00" + "time": "2025-08-18T13:06:32+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "28ee818fce4a73ac1474346b94e4b966f665c53f" + "reference": "e54b060bc9c3dc3d4258bf0d165d0064e755f565" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/28ee818fce4a73ac1474346b94e4b966f665c53f", - "reference": "28ee818fce4a73ac1474346b94e4b966f665c53f", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e54b060bc9c3dc3d4258bf0d165d0064e755f565", + "reference": "e54b060bc9c3dc3d4258bf0d165d0064e755f565", "shasum": "" }, "require": { @@ -6205,7 +6211,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.20" + "source": "https://github.com/symfony/yaml/tree/v6.4.25" }, "funding": [ { @@ -6216,25 +6222,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-27T20:15:30+00:00" + "time": "2025-08-26T16:59:00+00:00" }, { "name": "twig/twig", - "version": "v3.20.0", + "version": "v3.21.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3468920399451a384bef53cf7996965f7cd40183" + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183", - "reference": "3468920399451a384bef53cf7996965f7cd40183", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", "shasum": "" }, "require": { @@ -6288,7 +6298,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.20.0" + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" }, "funding": [ { @@ -6300,11 +6310,11 @@ "type": "tidelift" } ], - "time": "2025-02-13T08:34:43+00:00" + "time": "2025-05-03T07:21:55+00:00" }, { "name": "web-token/jwt-key-mgmt", - "version": "3.4.6", + "version": "3.4.8", "source": { "type": "git", "url": "https://github.com/web-token/jwt-key-mgmt.git", @@ -6359,7 +6369,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.4.6" + "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.4.8" }, "funding": [ { @@ -6372,16 +6382,16 @@ }, { "name": "web-token/jwt-library", - "version": "3.4.7", + "version": "3.4.8", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", - "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28" + "reference": "92445671cc788fa5f639898a67c06f9fd0bf491f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28", - "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/92445671cc788fa5f639898a67c06f9fd0bf491f", + "reference": "92445671cc788fa5f639898a67c06f9fd0bf491f", "shasum": "" }, "require": { @@ -6391,7 +6401,7 @@ "paragonie/constant_time_encoding": "^2.6|^3.0", "paragonie/sodium_compat": "^1.20|^2.0", "php": ">=8.1", - "psr/cache": "^3.0", + "psr/cache": "^2.0|^3.0", "psr/clock": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", @@ -6454,7 +6464,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.7" + "source": "https://github.com/web-token/jwt-library/tree/3.4.8" }, "funding": [ { @@ -6466,11 +6476,11 @@ "type": "patreon" } ], - "time": "2024-07-02T16:35:11+00:00" + "time": "2025-05-07T09:11:18+00:00" }, { "name": "web-token/jwt-signature", - "version": "3.4.6", + "version": "3.4.8", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature.git", @@ -6522,7 +6532,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature/tree/3.4.6" + "source": "https://github.com/web-token/jwt-signature/tree/3.4.8" }, "funding": [ { @@ -6535,7 +6545,7 @@ }, { "name": "web-token/jwt-signature-algorithm-ecdsa", - "version": "3.4.6", + "version": "3.4.8", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git", @@ -6588,7 +6598,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.4.6" + "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.4.8" }, "funding": [ { @@ -6601,7 +6611,7 @@ }, { "name": "web-token/jwt-util-ecc", - "version": "3.4.6", + "version": "3.4.8", "source": { "type": "git", "url": "https://github.com/web-token/jwt-util-ecc.git", @@ -6654,7 +6664,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-util-ecc/tree/3.4.6" + "source": "https://github.com/web-token/jwt-util-ecc/tree/3.4.8" }, "funding": [ { @@ -6669,16 +6679,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.4", + "version": "v2.6.5", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "url": "https://api.github.com/repos/amphp/amp/zipball/d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", + "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207", "shasum": "" }, "require": { @@ -6694,11 +6704,6 @@ "vimeo/psalm": "^3.12" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php", @@ -6746,7 +6751,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.4" + "source": "https://github.com/amphp/amp/tree/v2.6.5" }, "funding": [ { @@ -6754,7 +6759,7 @@ "type": "github" } ], - "time": "2024-03-21T18:52:26+00:00" + "time": "2025-09-03T19:41:28+00:00" }, { "name": "amphp/byte-stream", @@ -6967,16 +6972,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -6986,10 +6991,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -7016,7 +7021,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -7024,7 +7029,7 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "laravel/homestead", @@ -7078,20 +7083,21 @@ "issues": "https://github.com/laravel/homestead/issues", "source": "https://github.com/laravel/homestead/tree/v14.5.0" }, + "abandoned": true, "time": "2023-10-06T03:16:47+00:00" }, { "name": "masterminds/html5", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { @@ -7143,9 +7149,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "time": "2024-03-31T07:05:07+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "misantron/dbunit", @@ -7204,16 +7210,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -7252,7 +7258,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -7260,7 +7266,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "netresearch/jsonmapper", @@ -7654,16 +7660,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.2", + "version": "5.6.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", "shasum": "" }, "require": { @@ -7712,9 +7718,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" }, - "time": "2025-04-13T19:20:35+00:00" + "time": "2025-08-01T19:43:32+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -7776,16 +7782,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.1.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", "shasum": "" }, "require": { @@ -7817,9 +7823,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2025-02-19T13:28:12+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "phpunit/php-code-coverage", @@ -8144,16 +8150,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.48", + "version": "10.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541" + "reference": "4b2d546b336876bd9562f24641b08a25335b06b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e0a2bc39f6fae7617989d690d76c48e6d2eb541", - "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b2d546b336876bd9562f24641b08a25335b06b6", + "reference": "4b2d546b336876bd9562f24641b08a25335b06b6", "shasum": "" }, "require": { @@ -8163,7 +8169,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.3", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -8174,13 +8180,13 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.4", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", "sebastian/global-state": "^6.0.2", "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", "sebastian/type": "^4.0.0", "sebastian/version": "^4.0.1" }, @@ -8225,7 +8231,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.48" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.55" }, "funding": [ { @@ -8249,7 +8255,7 @@ "type": "tidelift" } ], - "time": "2025-07-11T04:07:17+00:00" + "time": "2025-09-14T06:19:20+00:00" }, { "name": "psalm/plugin-symfony", @@ -8486,16 +8492,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.3", + "version": "5.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", "shasum": "" }, "require": { @@ -8551,15 +8557,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2024-10-18T14:56:07+00:00" + "time": "2025-09-07T05:25:07+00:00" }, { "name": "sebastian/complexity", @@ -9062,23 +9080,23 @@ }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -9113,15 +9131,28 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { "name": "sebastian/type", @@ -9302,16 +9333,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.0", + "version": "3.13.4", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186" + "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119", + "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119", "shasum": "" }, "require": { @@ -9382,20 +9413,20 @@ "type": "thanks_dev" } ], - "time": "2025-05-11T03:36:00+00:00" + "time": "2025-09-05T05:47:09+00:00" }, { "name": "symfony/browser-kit", - "version": "v6.4.19", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "ce95f3e3239159e7fa3be7690c6ce95a4714637f" + "reference": "3537d17782f8c20795b194acb6859071b60c6fac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ce95f3e3239159e7fa3be7690c6ce95a4714637f", - "reference": "ce95f3e3239159e7fa3be7690c6ce95a4714637f", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/3537d17782f8c20795b194acb6859071b60c6fac", + "reference": "3537d17782f8c20795b194acb6859071b60c6fac", "shasum": "" }, "require": { @@ -9434,7 +9465,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.4.19" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.24" }, "funding": [ { @@ -9445,25 +9476,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-14T11:23:16+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/cache", - "version": "v6.4.20", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "95af448bb7c3d8db02f7b4f5cbf3cb7a6ff1e432" + "reference": "d038cd3054aeaf1c674022a77048b2ef6376a175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/95af448bb7c3d8db02f7b4f5cbf3cb7a6ff1e432", - "reference": "95af448bb7c3d8db02f7b4f5cbf3cb7a6ff1e432", + "url": "https://api.github.com/repos/symfony/cache/zipball/d038cd3054aeaf1c674022a77048b2ef6376a175", + "reference": "d038cd3054aeaf1c674022a77048b2ef6376a175", "shasum": "" }, "require": { @@ -9530,7 +9565,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.20" + "source": "https://github.com/symfony/cache/tree/v6.4.24" }, "funding": [ { @@ -9541,25 +9576,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-08T15:51:34+00:00" + "time": "2025-07-30T09:32:03+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { @@ -9573,7 +9612,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -9606,7 +9645,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -9622,20 +9661,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.13", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e" + "reference": "9b784413143701aa3c94ac1869a159a9e53e8761" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/cb23e97813c5837a041b73a6d63a9ddff0778f5e", - "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/9b784413143701aa3c94ac1869a159a9e53e8761", + "reference": "9b784413143701aa3c94ac1869a159a9e53e8761", "shasum": "" }, "require": { @@ -9671,7 +9710,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.13" + "source": "https://github.com/symfony/css-selector/tree/v6.4.24" }, "funding": [ { @@ -9682,25 +9721,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/dom-crawler", - "version": "v6.4.19", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "19073e3e0bb50cbc1cb286077069b3107085206f" + "reference": "976302990f9f2a6d4c07206836dd4ca77cae9524" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/19073e3e0bb50cbc1cb286077069b3107085206f", - "reference": "19073e3e0bb50cbc1cb286077069b3107085206f", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/976302990f9f2a6d4c07206836dd4ca77cae9524", + "reference": "976302990f9f2a6d4c07206836dd4ca77cae9524", "shasum": "" }, "require": { @@ -9738,7 +9781,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.19" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.25" }, "funding": [ { @@ -9749,25 +9792,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-14T17:58:34+00:00" + "time": "2025-08-05T18:56:08+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.20", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "51418a20079cb25af3fcb8fa8ae1ed82f7fdd1ce" + "reference": "1d6a764b58e4f780df00f71c20ba3a61095ea447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/51418a20079cb25af3fcb8fa8ae1ed82f7fdd1ce", - "reference": "51418a20079cb25af3fcb8fa8ae1ed82f7fdd1ce", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/1d6a764b58e4f780df00f71c20ba3a61095ea447", + "reference": "1d6a764b58e4f780df00f71c20ba3a61095ea447", "shasum": "" }, "require": { @@ -9887,7 +9934,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v6.4.20" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.25" }, "funding": [ { @@ -9898,12 +9945,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-23T16:46:24+00:00" + "time": "2025-08-26T10:44:20+00:00" }, { "name": "theseer/tokenizer", From 9e764b2a98876fb6191cca4bb46bc68ce8603c27 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 16 Sep 2025 22:35:55 +0700 Subject: [PATCH 0884/1214] [ticket/17543] Remove ReflectionProperty::setAccessible() method calls As of PHP 8.1.0, calling this method has no effect; all properties are accessible by default. The method is deprecated since PHP 8.5. PHPBB-17543 --- tests/avatar/driver_gravatar_test.php | 2 -- tests/ban/ban_manager_test.php | 1 - tests/cache/file_driver_test.php | 16 ---------------- tests/captcha/turnstile_test.php | 7 ------- tests/datetime/from_format_test.php | 2 -- tests/dbal/db_tools_test.php | 1 - tests/email/email_parsing_test.php | 1 - tests/extension/extension_base_test.php | 1 - tests/files/types_local_test.php | 1 - tests/files/upload_test.php | 1 - tests/filesystem/helper_realpath_test.php | 1 - tests/filesystem/realpath_test.php | 1 - tests/group/helper_test_case.php | 2 -- tests/language/language_test.php | 2 -- tests/messenger/queue_test.php | 2 -- tests/migrator/get_callable_from_step_test.php | 1 - .../notification_method_email_test.php | 1 - .../notification_method_webpush_test.php | 1 - tests/template/extension_test.php | 1 - tests/template/template_test_case.php | 1 - tests/template/twig_test.php | 1 - tests/text_reparser/plugins/post_text_test.php | 1 - .../plugins/test_row_based_plugin.php | 1 - tests/ucp/controller_webpush_test.php | 2 -- tests/upload/filespec_test.php | 2 -- 25 files changed, 53 deletions(-) diff --git a/tests/avatar/driver_gravatar_test.php b/tests/avatar/driver_gravatar_test.php index 026655e11a6..bc84f356102 100644 --- a/tests/avatar/driver_gravatar_test.php +++ b/tests/avatar/driver_gravatar_test.php @@ -166,13 +166,11 @@ public function test_prepare_form($expected_vars, $request_data, $row) ->willReturn([]); $requestInputReflection = new \ReflectionProperty($request, 'input'); - $requestInputReflection->setAccessible(true); $request_data[request_interface::GET] = $request_data[request_interface::GET] ?? []; $request_data[request_interface::POST] = $request_data[request_interface::POST] ?? []; $request_data[request_interface::REQUEST] = $request_data[request_interface::GET] + $request_data[request_interface::POST]; $requestInputReflection->setValue($request, $request_data); $requestTypeCastHelperReflection = new \ReflectionProperty($request, 'type_cast_helper'); - $requestTypeCastHelperReflection->setAccessible(true); $requestTypeCastHelperReflection->setValue($request, new \phpbb\request\type_cast_helper()); $this->gravatar->prepare_form($request, $this->template, $this->user, $row, $error); diff --git a/tests/ban/ban_manager_test.php b/tests/ban/ban_manager_test.php index cdc64435b50..34a0acdaa35 100644 --- a/tests/ban/ban_manager_test.php +++ b/tests/ban/ban_manager_test.php @@ -411,7 +411,6 @@ public function test_get_banned_users_own_method() $ban_type_ip_reflection = new \ReflectionClass($ban_type_ip); $get_excluded_reflection = $ban_type_ip_reflection->getMethod('get_excluded'); - $get_excluded_reflection->setAccessible(true); $this->assertFalse($get_excluded_reflection->invoke($ban_type_ip)); } diff --git a/tests/cache/file_driver_test.php b/tests/cache/file_driver_test.php index c8fe44be744..f615cea3b01 100644 --- a/tests/cache/file_driver_test.php +++ b/tests/cache/file_driver_test.php @@ -68,7 +68,6 @@ public function test_read_not_readable() $filename = "{$this->cache_dir}unreadable.$phpEx"; @chmod($filename, 0000); $readReflection = new \ReflectionMethod($this->cache_file, '_read'); - $readReflection->setAccessible(true); $this->assertFalse($readReflection->invoke($this->cache_file, 'unreadable')); @chmod($filename, 0600); $this->assertNotFalse($readReflection->invoke($this->cache_file, 'unreadable')); @@ -79,11 +78,9 @@ public function test_read_data_global_invalid() global $phpEx; $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); - $reflectionCacheVars->setAccessible(true); $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); - $reflectionCacheVarExpires->setAccessible(true); $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); // Create file in invalid format @@ -95,7 +92,6 @@ public function test_read_data_global_invalid() file_put_contents($filename, $cache_data); $readReflection = new \ReflectionMethod($this->cache_file, '_read'); - $readReflection->setAccessible(true); $this->assertFalse($readReflection->invoke($this->cache_file, 'data_global')); } @@ -104,11 +100,9 @@ public function test_read_data_global_zero_bytes() global $phpEx; $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); - $reflectionCacheVars->setAccessible(true); $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); - $reflectionCacheVarExpires->setAccessible(true); $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); // Create file in invalid format @@ -120,7 +114,6 @@ public function test_read_data_global_zero_bytes() file_put_contents($filename, $cache_data); $readReflection = new \ReflectionMethod($this->cache_file, '_read'); - $readReflection->setAccessible(true); $this->assertFalse($readReflection->invoke($this->cache_file, 'data_global')); } @@ -129,11 +122,9 @@ public function test_read_data_global_hex_bytes() global $phpEx; $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); - $reflectionCacheVars->setAccessible(true); $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); - $reflectionCacheVarExpires->setAccessible(true); $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() + 86400]); // Create file in invalid format @@ -145,18 +136,15 @@ public function test_read_data_global_hex_bytes() file_put_contents($filename, $cache_data); $readReflection = new \ReflectionMethod($this->cache_file, '_read'); - $readReflection->setAccessible(true); $this->assertFalse($readReflection->invoke($this->cache_file, 'data_global')); } public function test_read_data_global_expired() { $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); - $reflectionCacheVars->setAccessible(true); $reflectionCacheVars->setValue($this->cache_file, ['foo' => 'bar']); $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); - $reflectionCacheVarExpires->setAccessible(true); $reflectionCacheVarExpires->setValue($this->cache_file, ['foo' => time() - 86400]); // Create file in invalid format @@ -167,7 +155,6 @@ public function test_read_data_global_expired() $reflectionCacheVarExpires->setValue($this->cache_file, []); $readReflection = new \ReflectionMethod($this->cache_file, '_read'); - $readReflection->setAccessible(true); $this->assertTrue($readReflection->invoke($this->cache_file, 'data_global')); // Check data, should be empty @@ -177,12 +164,10 @@ public function test_read_data_global_expired() public function test_read_data_global() { $reflectionCacheVars = new \ReflectionProperty($this->cache_file, 'vars'); - $reflectionCacheVars->setAccessible(true); $expectedVars = ['foo' => 'bar']; $reflectionCacheVars->setValue($this->cache_file, $expectedVars); $reflectionCacheVarExpires = new \ReflectionProperty($this->cache_file, 'var_expires'); - $reflectionCacheVarExpires->setAccessible(true); $expectedVarExpires = ['foo' => time() + 86400]; $reflectionCacheVarExpires->setValue($this->cache_file, $expectedVarExpires); @@ -196,7 +181,6 @@ public function test_read_data_global() $this->assertEquals([], $reflectionCacheVarExpires->getValue($this->cache_file)); $readReflection = new \ReflectionMethod($this->cache_file, '_read'); - $readReflection->setAccessible(true); $this->assertTrue($readReflection->invoke($this->cache_file, 'data_global')); // Check data, should be empty diff --git a/tests/captcha/turnstile_test.php b/tests/captcha/turnstile_test.php index 8d3c9958107..c95e3225d2e 100644 --- a/tests/captcha/turnstile_test.php +++ b/tests/captcha/turnstile_test.php @@ -244,7 +244,6 @@ public function test_set_Name(): void { $this->turnstile->set_name('custom_service'); $service_name_property = new \ReflectionProperty($this->turnstile, 'service_name'); - $service_name_property->setAccessible(true); $this->assertEquals('custom_service', $service_name_property->getValue($this->turnstile)); } @@ -275,7 +274,6 @@ public function test_validate_with_response_success(): void // Use reflection to inject the mocked client into the turnstile class $reflection = new \ReflectionClass($this->turnstile); $client_property = $reflection->getProperty('client'); - $client_property->setAccessible(true); $client_property->setValue($this->turnstile, $client_mock); // Validate that the CAPTCHA was solved successfully @@ -301,7 +299,6 @@ public function test_validate_with_guzzle_exception(): void // Use reflection to inject the mocked client into the turnstile class $reflection = new \ReflectionClass($this->turnstile); $client_property = $reflection->getProperty('client'); - $client_property->setAccessible(true); $client_property->setValue($this->turnstile, $client_mock); // Validatation fails due to guzzle exception @@ -333,9 +330,7 @@ public function test_get_client(): void { $turnstile_reflection = new \ReflectionClass($this->turnstile); $get_client_method = $turnstile_reflection->getMethod('get_client'); - $get_client_method->setAccessible(true); $client_property = $turnstile_reflection->getProperty('client'); - $client_property->setAccessible(true); $this->assertFalse($client_property->isInitialized($this->turnstile)); $client = $get_client_method->invoke($this->turnstile); @@ -363,7 +358,6 @@ public function test_validate_with_response_failure(): void // Use reflection to inject the mocked client into the turnstile class $reflection = new \ReflectionClass($this->turnstile); $client_property = $reflection->getProperty('client'); - $client_property->setAccessible(true); $client_property->setValue($this->turnstile, $client_mock); // Validate that the CAPTCHA was not solved @@ -374,7 +368,6 @@ public function test_get_template(): void { // Mock is_solved to return false $is_solved_property = new \ReflectionProperty($this->turnstile, 'solved'); - $is_solved_property->setAccessible(true); $is_solved_property->setValue($this->turnstile, false); // Mock the template assignments diff --git a/tests/datetime/from_format_test.php b/tests/datetime/from_format_test.php index 7c76da32d3b..8b4b39eb906 100644 --- a/tests/datetime/from_format_test.php +++ b/tests/datetime/from_format_test.php @@ -32,11 +32,9 @@ protected function setUp(): void $reflection_class = new ReflectionClass('\phpbb\language\language'); // Set default language files loaded flag to true $common_language_files_loaded_flag = $reflection_class->getProperty('common_language_files_loaded'); - $common_language_files_loaded_flag->setAccessible(true); $common_language_files_loaded_flag->setValue($this->lang, true); // Set up test language data $lang_array = $reflection_class->getProperty('lang'); - $lang_array->setAccessible(true); $lang_array->setValue($this->lang, [ 'datetime' => [ 'TODAY' => 'Today', diff --git a/tests/dbal/db_tools_test.php b/tests/dbal/db_tools_test.php index f6c108cdae7..90fa0eca09c 100644 --- a/tests/dbal/db_tools_test.php +++ b/tests/dbal/db_tools_test.php @@ -509,7 +509,6 @@ public function test_create_index_with_long_name() if ($this->tools instanceof \phpbb\db\tools\mssql) { $max_length_method = new ReflectionMethod('\phpbb\db\tools\mssql', 'get_max_index_name_length'); - $max_length_method->setAccessible(true); $max_index_length = $max_length_method->invoke($this->tools); } diff --git a/tests/email/email_parsing_test.php b/tests/email/email_parsing_test.php index 574babea812..10447f363b8 100644 --- a/tests/email/email_parsing_test.php +++ b/tests/email/email_parsing_test.php @@ -146,7 +146,6 @@ protected function setUp(): void $reflection = new ReflectionObject($this->email); $this->reflection_template_property = $reflection->getProperty('template'); - $this->reflection_template_property->setAccessible(true); } public static function email_parsing_data() diff --git a/tests/extension/extension_base_test.php b/tests/extension/extension_base_test.php index 7dc8c1a45c7..e0d34fee814 100644 --- a/tests/extension/extension_base_test.php +++ b/tests/extension/extension_base_test.php @@ -27,7 +27,6 @@ public static function setUpBeforeClass(): void $reflection_class = new ReflectionClass('\phpbb\extension\base'); self::$reflection_method_get_migration_file_list = $reflection_class->getMethod('get_migration_file_list'); - self::$reflection_method_get_migration_file_list->setAccessible(true); } protected function setUp(): void diff --git a/tests/files/types_local_test.php b/tests/files/types_local_test.php index ea1fa23ecb0..9cdbb50ced7 100644 --- a/tests/files/types_local_test.php +++ b/tests/files/types_local_test.php @@ -146,7 +146,6 @@ public function test_upload_form($filename, $upload_ary, $expected) 'mimetype.extension_guesser' => new \phpbb\mimetype\extension_guesser(), ))); $filespec_local = new ReflectionProperty($filespec, 'local'); - $filespec_local->setAccessible(true); $filespec_local->setValue($filespec, true); $this->container->set('files.filespec', $filespec); $this->factory = new \phpbb\files\factory($this->container); diff --git a/tests/files/upload_test.php b/tests/files/upload_test.php index ab88b1cd83e..beee220fd84 100644 --- a/tests/files/upload_test.php +++ b/tests/files/upload_test.php @@ -76,7 +76,6 @@ public function test_set_disallowed_content() { $upload = new \phpbb\files\upload($this->factory, $this->language, $this->php_ini, $this->request); $disallowed_content = new ReflectionProperty($upload, 'disallowed_content'); - $disallowed_content->setAccessible(true); $upload->set_disallowed_content(array('foo')); $this->assertEquals(array('foo'), $disallowed_content->getValue($upload)); diff --git a/tests/filesystem/helper_realpath_test.php b/tests/filesystem/helper_realpath_test.php index 848a855a4dc..448b2665bca 100644 --- a/tests/filesystem/helper_realpath_test.php +++ b/tests/filesystem/helper_realpath_test.php @@ -22,7 +22,6 @@ static public function setUpBeforeClass(): void parent::setUpBeforeClass(); self::$filesystem_helper_phpbb_own_realpath = new ReflectionMethod('\phpbb\filesystem\helper', 'phpbb_own_realpath'); - self::$filesystem_helper_phpbb_own_realpath->setAccessible(true); } protected function setUp(): void diff --git a/tests/filesystem/realpath_test.php b/tests/filesystem/realpath_test.php index 8119541844f..75e34ea2401 100644 --- a/tests/filesystem/realpath_test.php +++ b/tests/filesystem/realpath_test.php @@ -24,7 +24,6 @@ static public function setUpBeforeClass(): void $reflection_class = new ReflectionClass('\phpbb\filesystem\filesystem'); self::$filesystem_own_realpath = $reflection_class->getMethod('phpbb_own_realpath'); - self::$filesystem_own_realpath->setAccessible(true); } protected function setUp(): void diff --git a/tests/group/helper_test_case.php b/tests/group/helper_test_case.php index 1514c616a93..3ee1d86b860 100644 --- a/tests/group/helper_test_case.php +++ b/tests/group/helper_test_case.php @@ -87,12 +87,10 @@ protected function setup_engine(array $new_config = array()) // Set default language files loaded flag to true $loaded_flag = $reflection_class->getProperty('common_language_files_loaded'); - $loaded_flag->setAccessible(true); $loaded_flag->setValue($lang, true); // Set up test language data $lang_array = $reflection_class->getProperty('lang'); - $lang_array->setAccessible(true); $lang_array->setValue($lang, $this->get_test_language_data_set()); // Set up event dispatcher diff --git a/tests/language/language_test.php b/tests/language/language_test.php index 2b1b2454915..eaf2d5c800c 100644 --- a/tests/language/language_test.php +++ b/tests/language/language_test.php @@ -30,12 +30,10 @@ protected function setUp(): void // Set default language files loaded flag to true $loaded_flag = $reflection_class->getProperty('common_language_files_loaded'); - $loaded_flag->setAccessible(true); $loaded_flag->setValue($this->lang, true); // Set up test language data $lang_array = $reflection_class->getProperty('lang'); - $lang_array->setAccessible(true); $lang_array->setValue($this->lang, $this->get_test_data_set()); } diff --git a/tests/messenger/queue_test.php b/tests/messenger/queue_test.php index a7f336b14a6..457604fb251 100644 --- a/tests/messenger/queue_test.php +++ b/tests/messenger/queue_test.php @@ -191,7 +191,6 @@ public function test_process_no_queue_handling_chmod_exception() $filesystem->method('phpbb_chmod') ->will($this->throwException(new filesystem_exception('Chmod failed'))); $filesystem_reflection = new \ReflectionProperty(queue::class, 'filesystem'); - $filesystem_reflection->setAccessible(true); $filesystem_reflection->setValue($this->messenger_queue, $filesystem); // Process the queue @@ -312,7 +311,6 @@ public function test_save_chmod_fail() $filesystem->method('phpbb_chmod') ->will($this->throwException(new filesystem_exception('Chmod failed'))); $filesystem_reflection = new \ReflectionProperty(queue::class, 'filesystem'); - $filesystem_reflection->setAccessible(true); $filesystem_reflection->setValue($this->messenger_queue, $filesystem); $this->messenger_queue->save(); diff --git a/tests/migrator/get_callable_from_step_test.php b/tests/migrator/get_callable_from_step_test.php index 02ee53e1720..467ec99aa89 100644 --- a/tests/migrator/get_callable_from_step_test.php +++ b/tests/migrator/get_callable_from_step_test.php @@ -137,7 +137,6 @@ protected function call_get_callable_from_step($step) { $class = new ReflectionClass($this->migrator); $method = $class->getMethod('get_callable_from_step'); - $method->setAccessible(true); return $method->invokeArgs($this->migrator, array($step)); } } diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php index 71ebffef893..0634e3abd49 100644 --- a/tests/notification/notification_method_email_test.php +++ b/tests/notification/notification_method_email_test.php @@ -126,7 +126,6 @@ protected function setUp(): void $class = new ReflectionClass($notification_method_email); $empty_queue_method = $class->getMethod('empty_queue'); - $empty_queue_method->setAccessible(true); $this->notification_method_email->method('notify_using_messenger') ->will($this->returnCallback(function () use ($notification_method_email, $empty_queue_method) { diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index a1b7e6325e6..d2e49579121 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -680,7 +680,6 @@ public static function data_set_endpoint_padding(): array public function test_set_endpoint_padding($endpoint, $expected_padding): void { $web_push_reflection = new \ReflectionMethod($this->notification_method_webpush, 'set_endpoint_padding'); - $web_push_reflection->setAccessible(true); $auth = [ 'VAPID' => [ diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index 323c605fb98..c40a58da53f 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -84,7 +84,6 @@ protected function setup_engine(array $new_config = [], string $template_path = $class = new ReflectionClass('\phpbb\avatar\manager'); $enabled_drivers = $class->getProperty('enabled_drivers'); - $enabled_drivers->setAccessible(true); $enabled_drivers->setValue($class, false); $avatar_helper = new phpbb\avatar\helper( $config, diff --git a/tests/template/template_test_case.php b/tests/template/template_test_case.php index 3d3b560e5a1..68a3a5985d7 100644 --- a/tests/template/template_test_case.php +++ b/tests/template/template_test_case.php @@ -35,7 +35,6 @@ public static function setUpBeforeClass(): void $reflection = new ReflectionClass('\phpbb\language\language'); self::$language_reflection_lang = $reflection->getProperty('lang'); - self::$language_reflection_lang->setAccessible(true); } protected function display($handle) diff --git a/tests/template/twig_test.php b/tests/template/twig_test.php index f655cd87020..23a14b4b4f6 100644 --- a/tests/template/twig_test.php +++ b/tests/template/twig_test.php @@ -128,7 +128,6 @@ public function test_set_style() // Get loader instance $template_reflection = new \ReflectionObject($this->template); $loader_reflection = $template_reflection->getProperty('loader'); - $loader_reflection->setAccessible(true); /** @var \phpbb\template\twig\loader $loader */ $loader = $loader_reflection->getValue($this->template); diff --git a/tests/text_reparser/plugins/post_text_test.php b/tests/text_reparser/plugins/post_text_test.php index 8e63ca72563..0333afd95b1 100644 --- a/tests/text_reparser/plugins/post_text_test.php +++ b/tests/text_reparser/plugins/post_text_test.php @@ -74,7 +74,6 @@ public function test_reparse_url(string $input_text, string $expected_text) // Call reparse_record via reflection $reparser = $this->get_reparser(); $reparser_reflection = new \ReflectionMethod($reparser, 'reparse_record'); - $reparser_reflection->setAccessible(true); $reparser_reflection->invoke($reparser, $record); // Retrieve reparsed post text and compare with expectec diff --git a/tests/text_reparser/plugins/test_row_based_plugin.php b/tests/text_reparser/plugins/test_row_based_plugin.php index 60b3e30584f..425054f4c12 100644 --- a/tests/text_reparser/plugins/test_row_based_plugin.php +++ b/tests/text_reparser/plugins/test_row_based_plugin.php @@ -27,7 +27,6 @@ protected function get_rows(array $ids) $reflection_reparser = new ReflectionClass(get_class($reparser)); $table_property = $reflection_reparser->getProperty('table'); - $table_property->setAccessible(true); $sql = 'SELECT ' . $columns['id'] . ' AS id, ' . $columns['text'] . ' AS text FROM ' . $table_property->getValue($reparser) . ' diff --git a/tests/ucp/controller_webpush_test.php b/tests/ucp/controller_webpush_test.php index e1e53738626..9539fa73dcc 100644 --- a/tests/ucp/controller_webpush_test.php +++ b/tests/ucp/controller_webpush_test.php @@ -422,7 +422,6 @@ public function test_check_subscribe_requests_invalid_form_token() $this->expectExceptionMessage('FORM_INVALID'); $check_subscribe_reflection = new ReflectionMethod($this->controller, 'check_subscribe_requests'); - $check_subscribe_reflection->setAccessible(true); $check_subscribe_reflection->invoke($this->controller); } @@ -436,7 +435,6 @@ public function test_check_subscribe_requests_anonymous_user() $this->expectExceptionMessage('NO_AUTH_OPERATION'); $check_subscribe_reflection = new ReflectionMethod($this->controller, 'check_subscribe_requests'); - $check_subscribe_reflection->setAccessible(true); $check_subscribe_reflection->invoke($this->controller); } diff --git a/tests/upload/filespec_test.php b/tests/upload/filespec_test.php index 032ab56803d..ed202757831 100644 --- a/tests/upload/filespec_test.php +++ b/tests/upload/filespec_test.php @@ -83,7 +83,6 @@ protected function setUp(): void private function set_reflection_property($class, $property_name, $value) { $property = new ReflectionProperty($class, $property_name); - $property->setAccessible(true); $property->setValue($class, $value); } @@ -538,7 +537,6 @@ public function test_is_uploaded() $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, new \bantu\IniGetWrapper\IniGetWrapper, new \FastImageSize\FastImageSize(), $this->phpbb_root_path, null); $reflection_filespec = new ReflectionClass($filespec); $plupload_property = $reflection_filespec->getProperty('plupload'); - $plupload_property->setAccessible(true); $plupload_mock = $this->getMockBuilder('\phpbb\plupload\plupload') ->disableOriginalConstructor() ->getMock(); From dac58f53d735e6bb41402c390bd2ac1192446f68 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 16 Sep 2025 13:14:39 -0700 Subject: [PATCH 0885/1214] [ticket/17542] Optimize and simplify logic PHPBB-17542 --- tests/mock/event_dispatcher.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/mock/event_dispatcher.php b/tests/mock/event_dispatcher.php index 654a08d7f16..2f0f1429053 100644 --- a/tests/mock/event_dispatcher.php +++ b/tests/mock/event_dispatcher.php @@ -24,18 +24,13 @@ public function __construct() public function trigger_event($eventName, $data = array()): array { - // Ensure tests never hard-exit when phpBB calls exit_handler() + $data = (array) $data; + if ($eventName === 'core.exit_handler') { - // Set the override flag so exit_handler() returns instead of exit; - if (is_array($data)) - { - $data['exit_handler_override'] = true; - } - return (array) $data; + $data['exit_handler_override'] = true; } - // Default behaviour of the mock: return the input data unchanged - return (array) $data; + return $data; } } From b2aa276a2a861d77259750faa0207e9a728e4f81 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 16 Sep 2025 23:36:35 +0700 Subject: [PATCH 0886/1214] [ticket/17543] Fix more PHP 8.5 deprecation warnings PHPBB-17543 --- phpBB/includes/functions_posting.php | 2 -- phpBB/phpbb/captcha/plugins/recaptcha_v3.php | 2 +- .../db/doctrine/connection_parameter_factory.php | 13 ++++++++++++- phpBB/phpbb/notification/type/post.php | 2 +- phpBB/phpbb/search/backend/fulltext_native.php | 6 +++--- tests/profilefields/type_bool_test.php | 2 +- tests/profilefields/type_dropdown_test.php | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index f4637c5057c..9559c10cfd0 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -763,8 +763,6 @@ function create_thumbnail($source, $destination, $mimetype) imagewebp($new_image, $destination); break; } - - imagedestroy($new_image); } else { diff --git a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php index e8399a5aef6..b62c561a676 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha_v3.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha_v3.php @@ -325,7 +325,7 @@ protected function recaptcha_verify_token() $token = $request->variable('recaptcha_token', '', true); $action = $request->variable('recaptcha_action', '', true); $action = in_array($action, self::$actions) ? $action : reset(self::$actions); - $threshold = (double) $config["recaptcha_v3_threshold_{$action}"] ?? 0.5; + $threshold = (float) $config["recaptcha_v3_threshold_{$action}"] ?? 0.5; // No token was provided, discard spam submissions if (empty($token)) diff --git a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php index ea5a929ba75..30ee850138f 100644 --- a/phpBB/phpbb/db/doctrine/connection_parameter_factory.php +++ b/phpBB/phpbb/db/doctrine/connection_parameter_factory.php @@ -150,7 +150,18 @@ private static function enrich_parameters(array $params) : array if ($params['driver'] === 'pdo_mysql' && extension_loaded('pdo_mysql')) { - $params[\PDO::MYSQL_ATTR_FOUND_ROWS] = true; + // Constant PDO::MYSQL_ATTR_FOUND_ROWS is deprecated since 8.5, use Pdo\Mysql::ATTR_FOUND_ROWS instead + if (class_exists('\Pdo\Mysql')) + { + /** + * @psalm-suppress UndefinedClass + */ + $params[\Pdo\Mysql::ATTR_FOUND_ROWS] = true; + } + else + { + $params[\PDO::MYSQL_ATTR_FOUND_ROWS] = true; + } } return $params; diff --git a/phpBB/phpbb/notification/type/post.php b/phpBB/phpbb/notification/type/post.php index be8c8d75e90..9052f6cbcf7 100644 --- a/phpBB/phpbb/notification/type/post.php +++ b/phpBB/phpbb/notification/type/post.php @@ -386,7 +386,7 @@ public function create_insert_array($type_data, $pre_create_data = array()) // Topics can be "read" before they are public (while awaiting approval). // Make sure that if the user has read the topic, it's marked as read in the notification - if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + if ($this->inherit_read_status && isset($this->user_id, $pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) { $this->notification_read = true; } diff --git a/phpBB/phpbb/search/backend/fulltext_native.php b/phpBB/phpbb/search/backend/fulltext_native.php index 95efc38e432..7416544a36d 100644 --- a/phpBB/phpbb/search/backend/fulltext_native.php +++ b/phpBB/phpbb/search/backend/fulltext_native.php @@ -1577,7 +1577,7 @@ public function tidy(): void // Remove common words if ($this->config['num_posts'] >= 100 && $this->config['fulltext_native_common_thres']) { - $common_threshold = ((double) $this->config['fulltext_native_common_thres']) / 100.0; + $common_threshold = ((float) $this->config['fulltext_native_common_thres']) / 100.0; // First, get the IDs of common words $sql = 'SELECT word_id, word_text FROM ' . $this->search_wordlist_table . ' @@ -2034,14 +2034,14 @@ public function get_acp_options(): array

' . $this->language->lang('COMMON_WORD_THRESHOLD_EXPLAIN') . '
-
%
+
%
'; // These are fields required in the config table return array( 'tpl' => $tpl, - 'config' => array('fulltext_native_load_upd' => 'bool', 'fulltext_native_min_chars' => 'integer:0:255', 'fulltext_native_max_chars' => 'integer:0:255', 'fulltext_native_common_thres' => 'double:0:100') + 'config' => array('fulltext_native_load_upd' => 'bool', 'fulltext_native_min_chars' => 'integer:0:255', 'fulltext_native_max_chars' => 'integer:0:255', 'fulltext_native_common_thres' => 'float:0:100') ); } } diff --git a/tests/profilefields/type_bool_test.php b/tests/profilefields/type_bool_test.php index 49bc45805a7..f102badb31b 100644 --- a/tests/profilefields/type_bool_test.php +++ b/tests/profilefields/type_bool_test.php @@ -187,7 +187,7 @@ public function test_get_profile_value_raw($value, $field_options, $expected, $d public function is_set_callback($field_id, $lang_id, $field_value) { - return isset($this->options[$field_value]); + return isset($field_value, $this->options[$field_value]); } public function get($field_id, $lang_id, $field_value) diff --git a/tests/profilefields/type_dropdown_test.php b/tests/profilefields/type_dropdown_test.php index 17e1a7b3f9a..9e979b80e26 100644 --- a/tests/profilefields/type_dropdown_test.php +++ b/tests/profilefields/type_dropdown_test.php @@ -225,7 +225,7 @@ public function test_get_profile_value_raw($value, $field_options, $expected, $d public function is_set_callback($field_id, $lang_id, $field_value) { - return isset($this->dropdown_options[$field_value]); + return isset($field_value, $this->dropdown_options[$field_value]); } public function get($field_id, $lang_id, $field_value) From f670231faa7676fa7bb516dd8e53b4586a0c0de9 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 17 Sep 2025 21:56:54 +0700 Subject: [PATCH 0887/1214] [ticket/17543] Adjust PHPUnit XML configuration Define source code directories to test and restrict reporting of deprecation errors to them only to avoid getting this kind of errors from the 3rd party software and temporary/cache files. PHPBB-17543 --- .github/phpunit-mariadb-github.xml | 12 ++++++++++++ .github/phpunit-mssql-github.xml | 12 ++++++++++++ .github/phpunit-mysql-github.xml | 12 ++++++++++++ .github/phpunit-postgres-github.xml | 12 ++++++++++++ .github/phpunit-psql-windows-github.xml | 12 ++++++++++++ .github/phpunit-sqlite3-github.xml | 12 ++++++++++++ phpunit.xml.dist | 12 +++++++++--- 7 files changed, 81 insertions(+), 3 deletions(-) diff --git a/.github/phpunit-mariadb-github.xml b/.github/phpunit-mariadb-github.xml index e301ced053e..d5ab282b3d1 100644 --- a/.github/phpunit-mariadb-github.xml +++ b/.github/phpunit-mariadb-github.xml @@ -23,6 +23,18 @@ slow + + + ../phpBB/ + ../tests/ + + + ../phpBB/vendor/ + ../phpBB/cache/ + ../phpBB/develop/ + ../phpBB/store/ + + diff --git a/.github/phpunit-mssql-github.xml b/.github/phpunit-mssql-github.xml index b3061cce0e5..bd240fa534b 100644 --- a/.github/phpunit-mssql-github.xml +++ b/.github/phpunit-mssql-github.xml @@ -23,6 +23,18 @@ slow + + + ../phpBB/ + ../tests/ + + + ../phpBB/vendor/ + ../phpBB/cache/ + ../phpBB/develop/ + ../phpBB/store/ + + diff --git a/.github/phpunit-mysql-github.xml b/.github/phpunit-mysql-github.xml index e15519e8b2b..ff42cf36dfd 100644 --- a/.github/phpunit-mysql-github.xml +++ b/.github/phpunit-mysql-github.xml @@ -23,6 +23,18 @@ slow + + + ../phpBB/ + ../tests/ + + + ../phpBB/vendor/ + ../phpBB/cache/ + ../phpBB/develop/ + ../phpBB/store/ + + diff --git a/.github/phpunit-postgres-github.xml b/.github/phpunit-postgres-github.xml index 57333dacc5e..1636901039f 100644 --- a/.github/phpunit-postgres-github.xml +++ b/.github/phpunit-postgres-github.xml @@ -23,6 +23,18 @@ slow + + + ../phpBB/ + ../tests/ + + + ../phpBB/vendor/ + ../phpBB/cache/ + ../phpBB/develop/ + ../phpBB/store/ + + diff --git a/.github/phpunit-psql-windows-github.xml b/.github/phpunit-psql-windows-github.xml index ab0c0600100..0b80da07748 100644 --- a/.github/phpunit-psql-windows-github.xml +++ b/.github/phpunit-psql-windows-github.xml @@ -23,6 +23,18 @@ slow + + + ../phpBB/ + ../tests/ + + + ../phpBB/vendor/ + ../phpBB/cache/ + ../phpBB/develop/ + ../phpBB/store/ + + diff --git a/.github/phpunit-sqlite3-github.xml b/.github/phpunit-sqlite3-github.xml index 5c7e931b482..1ade12292c3 100644 --- a/.github/phpunit-sqlite3-github.xml +++ b/.github/phpunit-sqlite3-github.xml @@ -23,6 +23,18 @@ slow + + + ../phpBB/ + ../tests/ + + + ../phpBB/vendor/ + ../phpBB/cache/ + ../phpBB/develop/ + ../phpBB/store/ + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 58d0d5c1dfb..6240baa3208 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,10 +26,16 @@ slow - + - ./phpBB/includes/ - ./phpBB/phpbb/ + ./phpBB/ + ./tests/ + + ./phpBB/vendor/ + ./phpBB/cache/ + ./phpBB/develop/ + ./phpBB/store/ + From e74521a3cf231fb1600fe39bc658da83d2e8431a Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 17 Sep 2025 22:15:47 +0700 Subject: [PATCH 0888/1214] [ticket/17543] Enable bz2 and fileinfo PHP extensions for Windows tests PHPBB-17543 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a021cd1b44..2b1b5703d1f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -522,7 +522,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, intl, gd, exif, iconv, pgsql, pdo_pgsql, sodium + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, intl, gd, exif, iconv, pgsql, pdo_pgsql, sodium, bz2, fileinfo ini-values: upload_tmp_dir=${{ runner.temp }}, sys_temp_dir=${{ runner.temp }} coverage: none From a00538d251281789f389f57140e7a6d1de697c3e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 18 Sep 2025 16:39:27 +0200 Subject: [PATCH 0889/1214] [ticket/17175] Correctly define variable for item link PHPBB-17175 --- phpBB/styles/prosilver/template/search_results.html | 8 ++++---- phpBB/styles/prosilver/template/viewforum_body.html | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/phpBB/styles/prosilver/template/search_results.html b/phpBB/styles/prosilver/template/search_results.html index 06a4e32c43a..9e8a3c3e9bd 100644 --- a/phpBB/styles/prosilver/template/search_results.html +++ b/phpBB/styles/prosilver/template/search_results.html @@ -81,12 +81,12 @@

{SEARCH_TITLE} - + - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index e8b77ab2ce9..7937fb5683f 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '4.0.0-a1-dev'); +@define('PHPBB_VERSION', '4.0.0-a1'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/phpbbcli.php b/phpBB/install/phpbbcli.php index c13efb604bb..d85d6d05dfb 100755 --- a/phpBB/install/phpbbcli.php +++ b/phpBB/install/phpbbcli.php @@ -23,7 +23,7 @@ define('IN_PHPBB', true); define('IN_INSTALL', true); define('PHPBB_ENVIRONMENT', 'production'); -define('PHPBB_VERSION', '4.0.0-a1-dev'); +define('PHPBB_VERSION', '4.0.0-a1'); $phpbb_root_path = __DIR__ . '/../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 0c4910fb788..dbdad251180 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -299,7 +299,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_last INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '4.0.0-a1-dev'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '4.0.0-a1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_repositories', '[]'); From b3877642ac48022efff022642ffe42cd38d02987 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 11:40:00 +0200 Subject: [PATCH 0910/1214] [prep-release-4.0.0] Update version numbers to 4.0.0 --- phpBB/install/convertors/convert_phpbb20.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 6576275d6aa..84b31feac61 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -40,7 +40,7 @@ $convertor_data = array( 'forum_name' => 'phpBB 2.0.x', 'version' => '1.0.3', - 'phpbb_version' => '4.0.0-a1-dev', + 'phpbb_version' => '4.0.0', 'author' => 'phpBB Limited', 'dbms' => $dbms, 'dbhost' => $dbhost, From d8854ee4981d0614e578a5c0e8c5ef082f5a997f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 11:44:16 +0200 Subject: [PATCH 0911/1214] [prep-release-4.0.0] Add migration for 4.0.0-a1 --- phpBB/phpbb/db/migration/data/v400/v400a1.php | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 phpBB/phpbb/db/migration/data/v400/v400a1.php diff --git a/phpBB/phpbb/db/migration/data/v400/v400a1.php b/phpBB/phpbb/db/migration/data/v400/v400a1.php new file mode 100644 index 00000000000..adbaaed24be --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/v400a1.php @@ -0,0 +1,68 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +class v400a1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '4.0.0-a1', '>='); + } + + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v33x\v3315', + '\phpbb\db\migration\data\v400\add_mention_settings', + '\phpbb\db\migration\data\v400\remove_remote_avatar', + '\phpbb\db\migration\data\v400\increase_avatar_size', + '\phpbb\db\migration\data\v400\remove_img_link', + '\phpbb\db\migration\data\v400\rename_duplicated_index_names', + '\phpbb\db\migration\data\v400\add_bbcode_font_icon', + '\phpbb\db\migration\data\v400\font_awesome_6_upgrade', + '\phpbb\db\migration\data\v400\remove_broken_captcha', + '\phpbb\db\migration\data\v400\turnstile_captcha', + '\phpbb\db\migration\data\v400\add_audio_files_attachment_group', + '\phpbb\db\migration\data\v400\add_webpush_token', + '\phpbb\db\migration\data\v400\remove_remote_upload', + '\phpbb\db\migration\data\v400\remove_notify_type', + '\phpbb\db\migration\data\v400\remove_template_php', + '\phpbb\db\migration\data\v400\storage_adapter_local_subfolders_remove', + '\phpbb\db\migration\data\v400\add_webpush_options', + '\phpbb\db\migration\data\v400\remove_max_img_size', + '\phpbb\db\migration\data\v400\hidpi_smilies', + '\phpbb\db\migration\data\v400\extensions_composer_2', + '\phpbb\db\migration\data\v400\add_storage_permission', + '\phpbb\db\migration\data\v400\hidpi_icons', + '\phpbb\db\migration\data\v400\qa_captcha', + '\phpbb\db\migration\data\v400\search_backend_update', + '\phpbb\db\migration\data\v400\remove_flash_v2', + '\phpbb\db\migration\data\v400\add_disable_board_access_config', + '\phpbb\db\migration\data\v400\remove_dbms_version_config', + '\phpbb\db\migration\data\v400\ban_table_p2', + '\phpbb\db\migration\data\v400\extensions_composer_3', + '\phpbb\db\migration\data\v400\acp_storage_module', + '\phpbb\db\migration\data\v400\add_video_files_attachment_group', + '\phpbb\db\migration\data\v400\remove_attachment_download_mode', + '\phpbb\db\migration\data\v400\remove_smtp_auth_method', + ]; + } + + public function update_data() + { + return [ + ['config.update', ['version', '4.0.0-a1']], + ]; + } +} From 6b90b3e82d421a3a48324636968ad04b017f1a8c Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 11:44:17 +0200 Subject: [PATCH 0912/1214] [prep-release-4.0.0] Update stylesheet hashes for 4.0.0-a1 --- phpBB/styles/prosilver/theme/stylesheet.css | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/phpBB/styles/prosilver/theme/stylesheet.css b/phpBB/styles/prosilver/theme/stylesheet.css index 0f0cf4b667c..27853ad399d 100644 --- a/phpBB/styles/prosilver/theme/stylesheet.css +++ b/phpBB/styles/prosilver/theme/stylesheet.css @@ -8,16 +8,16 @@ */ @import url("normalize.css?hash=e6c08715"); -@import url("base.css?hash=9b6738c8"); +@import url("base.css?hash=e4336923"); @import url("utilities.css?hash=b95e1ad4"); -@import url("icons.css?hash=8acd6188"); -@import url("common.css?hash=0aba47c9"); -@import url("buttons.css?hash=a034a326"); -@import url("links.css?hash=93969a21"); +@import url("icons.css?hash=cf9a9308"); +@import url("common.css?hash=b2c7c02b"); +@import url("buttons.css?hash=be0468e7"); +@import url("links.css?hash=ecc09d7a"); @import url("mentions.css?hash=308fbc69"); -@import url("content.css?hash=c0febad3"); -@import url("cp.css?hash=b02034ec"); -@import url("forms.css?hash=d6ca85f9"); -@import url("colours.css?hash=0d8bc6be"); -@import url("responsive.css?hash=749e01e6"); +@import url("content.css?hash=46dafcea"); +@import url("cp.css?hash=9ed1a111"); +@import url("forms.css?hash=e6a667ba"); +@import url("colours.css?hash=f1c629cc"); +@import url("responsive.css?hash=c6136396"); @import url("bidi.css?hash=c1b99d9a"); From 660de84a06e5d853ff6e86a3e04991e35d086f20 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 11:46:14 +0200 Subject: [PATCH 0913/1214] [prep-release-4.0.0] Update changelog for 4.0.0-a1 --- phpBB/docs/CHANGELOG.html | 435 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 0935611a293..3f42cefefb8 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -50,6 +50,7 @@

Changelog

  1. Changelog
      +
    • Changes since 3.3.x
    • Changes since 3.3.15-RC1
    • Changes since 3.3.14
    • Changes since 3.3.14-RC1
    • @@ -175,6 +176,440 @@

      Changelog

      +

      Changes since 3.3.15

      +

      Bug

      +
        +
      • [PHPBB-11449] - Get rid of recursive calls in class phpbb_notification_manager
      • +
      • [PHPBB-12479] - Remove deprecated functions from bc file
      • +
      • [PHPBB-13276] - INCLUDEJS and INCLUDECSS do not obey PHPBB_USE_BOARD_URL_PATH
      • +
      • [PHPBB-14401] - Sphinx: Remove ending slash from binlog_path
      • +
      • [PHPBB-14404] - Set board_timezone for admin on install
      • +
      • [PHPBB-14601] - Fix composer.lock on master
      • +
      • [PHPBB-14782] - Quick Links > Your Posts gives mysql error
      • +
      • [PHPBB-14936] - Missing language variable INST_ERR_DB
      • +
      • [PHPBB-15099] - Remove not needed attribute from includeCSS and includeJS
      • +
      • [PHPBB-15154] - Missing constants during CLI installation
      • +
      • [PHPBB-15198] - Fix phpBB and PHP version info displayed in the ACP
      • +
      • [PHPBB-15233] - Avatars return should be standardized to not return html
      • +
      • [PHPBB-15234] - notification menu gr/avatar sizes are wrong
      • +
      • [PHPBB-15255] - Language variables of filesystem exception should moved
      • +
      • [PHPBB-15285] - Travis tests are failing due to trusty changes
      • +
      • [PHPBB-15314] - Wrong class constructor definition for convertor component
      • +
      • [PHPBB-15319] - Database update v310\style_update_p2 fails to drop sequences
      • +
      • [PHPBB-15336] - bidi.css causing travis failure on master
      • +
      • [PHPBB-15339] - Missing acp_send_statistics -> Upgrading to 3.2.0 fails for phpBB 3.0.5
      • +
      • [PHPBB-15357] - phpBB core should provide composer/installers to extensions
      • +
      • [PHPBB-15369] - Bump composer dependency version
      • +
      • [PHPBB-15400] - app.php urls aren't working in vagrant because of nginx
      • +
      • [PHPBB-15411] - Use lowercase in hex color code
      • +
      • [PHPBB-15415] - Styling typo in ucp_notifications.html
      • +
      • [PHPBB-15491] - Outdated linkes in installer support page
      • +
      • [PHPBB-15500] - Docs outdated for new PHP 5.4.7 requirement
      • +
      • [PHPBB-15502] - Errors in migrations in 3.2.2 release
      • +
      • [PHPBB-15507] - PHP 7.2 Warning
      • +
      • [PHPBB-15512] - Avoid reparsing non-existent polls
      • +
      • [PHPBB-15525] - composer.json License is Invalid/Deprecated
      • +
      • [PHPBB-15526] - Cast bbcode ID to integer
      • +
      • [PHPBB-15527] - Cannot interpret the BBCode definition
      • +
      • [PHPBB-15532] - Update pull request template
      • +
      • [PHPBB-15533] - Typo in viewtopic_topic_tools.html
      • +
      • [PHPBB-15536] - When using the catalog, extensions are installed in vendor-ext/ instead of ext/
      • +
      • [PHPBB-15559] - phpbb\report\report_handler_pm.php:56
      • +
      • [PHPBB-15600] - Ban reasons are not escaped in mcp_ban.html template
      • +
      • [PHPBB-15647] - post content line-height was not large enough
      • +
      • [PHPBB-15679] - notifications ucp front-page looks like shit due to poor cp styling
      • +
      • [PHPBB-15684] - Fix Broken Migration
      • +
      • [PHPBB-15686] - Update homestead for vagrant
      • +
      • [PHPBB-15688] - Event location in attachments download
      • +
      • [PHPBB-15720] - Redirections dont need to escape ampersands
      • +
      • [PHPBB-15725] - Testing framework fails to install extensions because of outdated assertion
      • +
      • [PHPBB-15755] - Broken events in /phpbb/attachment/delete.php
      • +
      • [PHPBB-15760] - write_stream never close connection
      • +
      • [PHPBB-15765] - Add debug.url_generator
      • +
      • [PHPBB-15795] - Broken Migration
      • +
      • [PHPBB-15846] - Wrong id for downloadable extension group
      • +
      • [PHPBB-15892] - Undefined $storage_attachment in parse_attachments() when using inline image attachment
      • +
      • [PHPBB-15919] - Lint test throws PHP warnings due to node modules folder
      • +
      • [PHPBB-15922] - Remove support for APC cache
      • +
      • [PHPBB-15935] - Check if APCu is installed before installing it
      • +
      • [PHPBB-15938] - Static attributes blacklist references no longer existing classes
      • +
      • [PHPBB-15959] - Travis Network Test is Failing for news.cnet.com
      • +
      • [PHPBB-16043] - SQL errors result in fatal error in specific cases
      • +
      • [PHPBB-16056] - JPEG dimensions undetectable for some kind of jpeg files
      • +
      • [PHPBB-16213] - vendor and phpbb folders should have .htaccess files
      • +
      • [PHPBB-16238] - Remove S_LOGIN_REDIRECT work around
      • +
      • [PHPBB-16309] - Smilies with a left square bracket do not work
      • +
      • [PHPBB-16346] - Random code errors
      • +
      • [PHPBB-16349] - Fix merge issues in acp_attachments and posting_attach_body.html
      • +
      • [PHPBB-16445] - unnecessary hr breaks content flow and semantics
      • +
      • [PHPBB-16477] - Padding-right is missing in icon.css
      • +
      • [PHPBB-16569] - Massive margin between posts when zoomed out in browser
      • +
      • [PHPBB-16587] - Replace sami with doctum
      • +
      • [PHPBB-16639] - Wrong use of make_path_relative
      • +
      • [PHPBB-16650] - Fatal error in Extensions catalog
      • +
      • [PHPBB-16666] - Windows tests in github actions can't write file in postgres
      • +
      • [PHPBB-16667] - Remove unused create_schema install task
      • +
      • [PHPBB-16688] - PHP fatal error on installing extension via catalog
      • +
      • [PHPBB-16717] - Fix installer bugs
      • +
      • [PHPBB-16732] - Vagrant is not working
      • +
      • [PHPBB-16749] - SQL error on installation
      • +
      • [PHPBB-16761] - Missing closing a tag in pagination
      • +
      • [PHPBB-16762] - Fix paging render issues in topicrow paging
      • +
      • [PHPBB-16763] - fix mine pip alignment on forum icons
      • +
      • [PHPBB-16778] - bidi.css loading was removed by a merge somewhere
      • +
      • [PHPBB-16786] - PHPUnit configuration validates against a deprecated schema
      • +
      • [PHPBB-16796] - misalignment on index and viewforum for topics and posts titles
      • +
      • [PHPBB-16817] - Oauth functional tests using incorrect selectors on master
      • +
      • [PHPBB-16835] - mentions_templates.html missing in ACP
      • +
      • [PHPBB-16836] - disk_free_space(): The system cannot find the path specified - PHP 8
      • +
      • [PHPBB-16857] - Remove leftover code for flash
      • +
      • [PHPBB-16891] - Controller Helper Routing in ACP can break Extension Installation
      • +
      • [PHPBB-16906] - Error running phpbb with vagrant
      • +
      • [PHPBB-16923] - Fix composer.json requirements
      • +
      • [PHPBB-16935] - Sphinx search references moved classes with old namespace
      • +
      • [PHPBB-16938] - Unexistent css property in inline style
      • +
      • [PHPBB-16943] - Composer error on installing extensions via catalog
      • +
      • [PHPBB-16960] - Migrations table not populated at the end of installation
      • +
      • [PHPBB-17022] - Link to SQL Report page is generated incorrectly
      • +
      • [PHPBB-17024] - Installing extension from catalog not possible with latest composer
      • +
      • [PHPBB-17049] - Minimum stability config entry missing from migrations
      • +
      • [PHPBB-17052] - Composer >=2.3.10 unable to load installer plugin for extension catalog
      • +
      • [PHPBB-17062] - Cron task service fail because service is being instantiated incorrectly
      • +
      • [PHPBB-17067] - Untranslated string in CLI command
      • +
      • [PHPBB-17069] - Array with duplicate keys
      • +
      • [PHPBB-17074] - Condition to avoid creation of roles with same name is broken
      • +
      • [PHPBB-17085] - Invalid icons referenced in template files
      • +
      • [PHPBB-17086] - phpBB / Codespaces support
      • +
      • [PHPBB-17105] - Extensions Can't Be Installed
      • +
      • [PHPBB-17142] - Installation errors when using MSSQL+ IIS + PHP 8.2
      • +
      • [PHPBB-17148] - phpBB3.3.10 Setup does not support PostgreSQL 8.3
      • +
      • [PHPBB-17157] - Read Topic Permission With Search permission
      • +
      • [PHPBB-17181] - If statement to highlight Reported PMS on the view message page doesn't work.
      • +
      • [PHPBB-17194] - Php version in vagrant configuration is not set correctly
      • +
      • [PHPBB-17209] - Mentions migration fails if standard roles can't be found
      • +
      • [PHPBB-17297] - Lazy Avatar Loading Broken
      • +
      • [PHPBB-17301] - Wrong length parameter for fread in phpbb/cache/driver/file.php can lead to unusable forum
      • +
      • [PHPBB-17306] - Wrong declaration of function input values
      • +
      • [PHPBB-17313] - Web Push Settings Module Update Error
      • +
      • [PHPBB-17318] - Avatars do not show in Notifications Dropdown
      • +
      • [PHPBB-17321] - Webpush worker not updated when its script is changed
      • +
      • [PHPBB-17323] - If there is no posts, search index fails
      • +
      • [PHPBB-17328] - Exporting events in master not possible
      • +
      • [PHPBB-17330] - Web Push Notifications Firefox/Android Bug
      • +
      • [PHPBB-17335] - Extension Management Language Bugs
      • +
      • [PHPBB-17336] - Extension Catalog Bugging on "Dev" Minimum Stability
      • +
      • [PHPBB-17341] - acp_forums_copy_perm adm template file is not used anymore
      • +
      • [PHPBB-17343] - Push Notification in Wrong Language
      • +
      • [PHPBB-17351] - phpBB2 password hashes incorrectly handled during rehash cron
      • +
      • [PHPBB-17353] - Gravatar avatar src is not image src
      • +
      • [PHPBB-17364] - Array to String error in ACP Forums
      • +
      • [PHPBB-17373] - Additional horizontal line at the bottom of the quick links
      • +
      • [PHPBB-17379] - Custom BBCode ID is offset due to removed flash bbcode
      • +
      • [PHPBB-17384] - Passing E_USER_ERROR to trigger_error() is deprecated in PHP 8.4
      • +
      • [PHPBB-17399] - Add select attribute for British English the default language
      • +
      • [PHPBB-17451] - Web Push Notification Updates Last Active Time
      • +
      • [PHPBB-17454] - Push Notification Site Names with Emoji
      • +
      • [PHPBB-17457] - app.php causing multiple sessions
      • +
      • [PHPBB-17460] - View quoted post FontAwesome icon is not rendered
      • +
      • [PHPBB-17468] - Reset password feature is not restricted to email
      • +
      • [PHPBB-17487] - PHP critical Uncaught Error when sending email via board
      • +
      • [PHPBB-17488] - PHP critical Uncaught Error when MySQL PDO driver is not enabled
      • +
      • [PHPBB-17489] - Email notifications won't be sent due to messenger queue issues
      • +
      • [PHPBB-17496] - PHP 8.4 deprecation error can break code execution
      • +
      • [PHPBB-17504] - Tests fail because of changed label of ondrej/php repo
      • +
      • [PHPBB-17507] - General SQL error on installing remove_jabber.php migration with PostgreSQL
      • +
      • [PHPBB-17508] - PHP warning on editing custom profile fields
      • +
      • [PHPBB-17510] - CodeSniffer ruleset is reported as DEPRECATED
      • +
      • [PHPBB-17519] - Cron URLs are encoded incorrectly
      • +
      • [PHPBB-17527] - PHP fatal error when decorating Twig phpBB extension service in phpBB extension
      • +
      • [PHPBB-17528] - Uncaught exception when adding autoincrement column in PostgreSQL
      • +
      • [PHPBB-17529] - Installer timeout if config.php doesn't exist
      • +
      • [PHPBB-17533] - Reverting migrations may cause restoring incorrect data and throw "module exists" exceptions
      • +
      • [PHPBB-17537] - Caching of extensions autoloader ConfigCache not working
      • +
      +

      Epic

      +
        +
      • [PHPBB-17009] - Add support for web push notifications
      • +
      +

      Improvement

      +
        +
      • [PHPBB-10824] - Styles should use the same composer.json format for metadata as extensions
      • +
      • [PHPBB-11063] - Change version check to SSL
      • +
      • [PHPBB-11515] - Add interface for lock classes and add new methods
      • +
      • [PHPBB-11838] - OAuth registration from ucp_register
      • +
      • [PHPBB-12439] - Unify behavior of sql_multi_insert for different dbms types
      • +
      • [PHPBB-12591] - Improve breadcrumb functionality with new "Home page"/"Forum index" link
      • +
      • [PHPBB-12623] - Remove the DEBUG constant
      • +
      • [PHPBB-12960] - Remove broken CAPTCHAs
      • +
      • [PHPBB-13162] - Add truncate table functionality to DBAL
      • +
      • [PHPBB-14095] - Replace loading.gif with CSS animations
      • +
      • [PHPBB-14131] - Completely overhaul all colors in Colours.css
      • +
      • [PHPBB-14336] - add title text to online status icon
      • +
      • [PHPBB-14573] - Extend breadcrumb to support more pages
      • +
      • [PHPBB-14771] - Support playing audio files directly in the browser
      • +
      • [PHPBB-14865] - Use stylelint.io to manage css standards
      • +
      • [PHPBB-14948] - Changes 3.3 PHP requirement to 7.1
      • +
      • [PHPBB-14970] - Use stylelint.io in travis CI builds
      • +
      • [PHPBB-14972] - PHP 7.2 compatibility: wrong sizeof/count parameter type
      • +
      • [PHPBB-14981] - Upgrade normalize to version 5
      • +
      • [PHPBB-15115] - Improve & Update the css linting & error checking
      • +
      • [PHPBB-15116] - Fix admin.css lint errors
      • +
      • [PHPBB-15148] - convert contact menu to svg
      • +
      • [PHPBB-15160] - Set correct gitignore path for node modules
      • +
      • [PHPBB-15274] - Migration "custom" tool does not allow parameters
      • +
      • [PHPBB-15291] - Allow short array notation in event declarations
      • +
      • [PHPBB-15295] - Restore MyIsam and mysql environments to test suite
      • +
      • [PHPBB-15330] - Twig function to know if a language string is defined
      • +
      • [PHPBB-15343] - Improve control of forum row
      • +
      • [PHPBB-15344] - upgrade stylelint
      • +
      • [PHPBB-15366] - Reference function directly instead of creating a method
      • +
      • [PHPBB-15371] - Split uploaded files into subdirectories
      • +
      • [PHPBB-15388] - Prosilver: Give interior of Category boxes round corners
      • +
      • [PHPBB-15392] - Change dirname(__FILE__) to __DIR__ everywhere
      • +
      • [PHPBB-15402] - Cleanup tweak.css file
      • +
      • [PHPBB-15403] - add useful front-end tools to package.json
      • +
      • [PHPBB-15404] - Simplify and standardize browser support
      • +
      • [PHPBB-15406] - cleanup and standardize rtl
      • +
      • [PHPBB-15413] - Login from any page and redirecting back there
      • +
      • [PHPBB-15414] - Cleanup unnecessary css
      • +
      • [PHPBB-15499] - Drop HHVM support
      • +
      • [PHPBB-15508] - Upgrade Twig to version 2.x
      • +
      • [PHPBB-15510] - Link Orphan attachments in ACP>General to Orphaned attachments page
      • +
      • [PHPBB-15514] - Improve accessibility by adding vital info from explanation to a title
      • +
      • [PHPBB-15518] - Do not attempt to accurately determine whether posters can read private messages in viewtopic
      • +
      • [PHPBB-15528] - Display the version of the installed styles in acp
      • +
      • [PHPBB-15529] - Color groups in ACP
      • +
      • [PHPBB-15531] - Log malformed BBCodes
      • +
      • [PHPBB-15534] - Outdated ACP extensions database link for phpBB 3.2
      • +
      • [PHPBB-15535] - Add S_FIRST_POST to postrow on viewtopic
      • +
      • [PHPBB-15545] - There is no vertical space between format buttons
      • +
      • [PHPBB-15553] - Add a method to get direct link to storage files
      • +
      • [PHPBB-15561] - Add core events for adding columns to MySQL and Postgres search backends
      • +
      • [PHPBB-15563] - Error instaling phpbb with sqlite database
      • +
      • [PHPBB-15569] - Adjust update instructions to suggest file replacement method
      • +
      • [PHPBB-15580] - Remove extra settings in ACP
      • +
      • [PHPBB-15605] - Correct Right-Margin for ribbon image
      • +
      • [PHPBB-15646] - Add support for Argon2i passwords
      • +
      • [PHPBB-15663] - Remove flash support in attachments
      • +
      • [PHPBB-15687] - Fix filenames in attachments downloads
      • +
      • [PHPBB-15689] - Show statistics of storages in acp_storage
      • +
      • [PHPBB-15692] - Move checks if file exist from adapter to storage
      • +
      • [PHPBB-15699] - Move files between remote filesystems when storage configuration is changed
      • +
      • [PHPBB-15714] - Login after register
      • +
      • [PHPBB-15718] - Update CONTRIBUTING.md
      • +
      • [PHPBB-15747] - Change parameter depth for a boolean subfolders
      • +
      • [PHPBB-15768] - Add a license to a repository
      • +
      • [PHPBB-15769] - Rework avatar handling
      • +
      • [PHPBB-15772] - Hide warning message in acp when install dir is present and allow_install_dir is true
      • +
      • [PHPBB-15924] - Move from precise to trusty builds
      • +
      • [PHPBB-15926] - Deny installs on PHP >= 7.3@dev - Increase min. req. to 5.4.7
      • +
      • [PHPBB-16105] - Use "global" reCAPTCHA domain to circumvent blocking in some countries
      • +
      • [PHPBB-16115] - Add PHP 7.4 builds to travis CI
      • +
      • [PHPBB-16198] - Change symfony debug component with symfony ErrorHandler component
      • +
      • [PHPBB-16204] - Remove hooks system
      • +
      • [PHPBB-16207] - Require cookies for sessions
      • +
      • [PHPBB-16237] - Icon overhaul and rework using twig mixin and iconify
      • +
      • [PHPBB-16240] - Remove deprecated template/theme log language strings
      • +
      • [PHPBB-16288] - PHP 8 compatibility
      • +
      • [PHPBB-16313] - Remote http{} block from nginx sample config
      • +
      • [PHPBB-16430] - Permission ordering
      • +
      • [PHPBB-16441] - Remove deprecated core.ucp_register_agreement
      • +
      • [PHPBB-16472] - Remove PhantomJS UI tests
      • +
      • [PHPBB-16473] - managed_with_clean_error_exception has incorrect filename
      • +
      • [PHPBB-16533] - Add core and template events to customize UCP login keys management module
      • +
      • [PHPBB-16549] - Use PHPUnit 9.3+ for PHP 8.0+ tests
      • +
      • [PHPBB-16574] - Remove support for flash BBCode
      • +
      • [PHPBB-16577] - Add documentation about return type hinting to coding guidelines
      • +
      • [PHPBB-16632] - Update composer to version 2
      • +
      • [PHPBB-16636] - Add PHP 8.0 builds to TravisCI
      • +
      • [PHPBB-16659] - Use Github Actions instead of TravisCI and AppVeyor
      • +
      • [PHPBB-16661] - Clean up github actions tasks after merge
      • +
      • [PHPBB-16668] - Clean up and optimize the schema generator
      • +
      • [PHPBB-16675] - Restore checking commit messages
      • +
      • [PHPBB-16687] - Add stylelint checks to GitHub Actions
      • +
      • [PHPBB-16737] - Rewrite acp_search by splitting configuration and indexing
      • +
      • [PHPBB-16748] - Update coding guidelines to place static after visibility qualifiers
      • +
      • [PHPBB-16751] - Add rank-img class for easier targeting
      • +
      • [PHPBB-16754] - Update doctum
      • +
      • [PHPBB-16764] - Remove remote avatar functionality
      • +
      • [PHPBB-16775] - Run xo linting in GitHub Actions
      • +
      • [PHPBB-16777] - Remove the max image width/height settings
      • +
      • [PHPBB-16782] - Adjust mentions bbcode to better distinct between user and group
      • +
      • [PHPBB-16820] - Move ATTACHMENT_CATEGORY_ constants to attachment manager class
      • +
      • [PHPBB-16821] - Keep Symfony up to the latest 5.x version
      • +
      • [PHPBB-16822] - Replace patchwork/utf8 with symfony/polyfill
      • +
      • [PHPBB-16825] - Adjust handling of session ID when requiring cookies
      • +
      • [PHPBB-16833] - Subscribe to forum or topic icons could be more intuitive
      • +
      • [PHPBB-16853] - Remove depreciated 3.2 css classes
      • +
      • [PHPBB-16858] - Update to the latest version of jQuery 3.6.0
      • +
      • [PHPBB-16860] - Breadcrumbs titles are messed up
      • +
      • [PHPBB-16885] - Add filters to Twig - INT and FLOAT
      • +
      • [PHPBB-16898] - Do not restrict the debug error handler to the development environment
      • +
      • [PHPBB-16899] - Add SVG and WEBP image type to ranks, smilies and topic icons
      • +
      • [PHPBB-16909] - Add PHP 8.2 builds to test matrix
      • +
      • [PHPBB-16913] - Add Search Index Progress Bar with Stats
      • +
      • [PHPBB-16920] - Upgrade Symfony to the version 5.4
      • +
      • [PHPBB-16937] - Run psalm in GitHub Actions
      • +
      • [PHPBB-16939] - Wait for postgres service to start in GitHub Actions windows builds
      • +
      • [PHPBB-16940] - Optimize phpBB Native Search
      • +
      • [PHPBB-16941] - Add Sphinx search backend tests
      • +
      • [PHPBB-16944] - Use icon bundle to load Iconify locally instead of loading from Iconify API
      • +
      • [PHPBB-16950] - Make functional download test work without rewrite config
      • +
      • [PHPBB-16955] - Improve psalm baseline results and add baseline
      • +
      • [PHPBB-16965] - Allow empty value as default database server name on installing
      • +
      • [PHPBB-16967] - Deprecate use of PHP and INCLUDEPHP in templates
      • +
      • [PHPBB-17010] - Add notification method webpush
      • +
      • [PHPBB-17025] - Move post destination topic field should not be populated with a zero
      • +
      • [PHPBB-17093] - Add ACP option to decide who can browse board "Disable board"
      • +
      • [PHPBB-17100] - Introduce twig macros for commonly used form elements
      • +
      • [PHPBB-17135] - Use Symfony Mailer to send emails
      • +
      • [PHPBB-17151] - Adjust twig form macros to follow HTML guidelines
      • +
      • [PHPBB-17153] - Remove deprecated avatar functions
      • +
      • [PHPBB-17155] - Open first unread post by default on topic with unread posts
      • +
      • [PHPBB-17176] - Upgrade Symfony to 6.3
      • +
      • [PHPBB-17184] - Storage: Remove split files into subfolders feature
      • +
      • [PHPBB-17193] - Combine unit and functional tests on github actions runs
      • +
      • [PHPBB-17195] - Remove travis CI files
      • +
      • [PHPBB-17230] - Update doctum for PHP 8.1 support
      • +
      • [PHPBB-17236] - Update symfony dependencies to improve PHP 8.3 compatibility
      • +
      • [PHPBB-17277] - Add template events to UCP
      • +
      • [PHPBB-17279] - Use only needed iconify icon packages
      • +
      • [PHPBB-17283] - Remove iconify support
      • +
      • [PHPBB-17291] - Use icon function in ACP templates
      • +
      • [PHPBB-17293] - Update composer and dependencies to latest versions
      • +
      • [PHPBB-17300] - Better Topic Icons
      • +
      • [PHPBB-17304] - HiDPI smilies for phpBB
      • +
      • [PHPBB-17308] - Rename tracker project key to PHPBB-
      • +
      • [PHPBB-17309] - Update gulp and remove no longer needed gulp packages
      • +
      • [PHPBB-17310] - Update GitHub actions workflows to Node.js 20
      • +
      • [PHPBB-17333] - Push Notification UX Enhancements
      • +
      • [PHPBB-17339] - Add support for push notifications when not logged in
      • +
      • [PHPBB-17340] - Update composer to 2.7.7
      • +
      • [PHPBB-17342] - Add PHP 8.4-dev tests to GitHub Actions
      • +
      • [PHPBB-17344] - UX improvements to web push subscription interface
      • +
      • [PHPBB-17355] - Update gravatar hash to sha256
      • +
      • [PHPBB-17359] - Distinct disabled and not installed extensions in the list
      • +
      • [PHPBB-17363] - Web Push Support for Apple Mobile Devices
      • +
      • [PHPBB-17371] - Web Push UX Updates
      • +
      • [PHPBB-17400] - MSSQL 2017 builds not working on GitHub Actions
      • +
      • [PHPBB-17402] - Add possibility to force reparsing BBCode via CLI
      • +
      • [PHPBB-17414] - Add interface class for CAPTCHA classes
      • +
      • [PHPBB-17415] - Add wrapper for backwards compatibility with legacy CAPTCHAs
      • +
      • [PHPBB-17416] - Add Web Push Notifications FAQ entires
      • +
      • [PHPBB-17418] - HiDPI topic icons
      • +
      • [PHPBB-17434] - Update Vagrantfile to make it work in recent ruby versions
      • +
      • [PHPBB-17445] - Webpush notifications availability shouldn't require email templates
      • +
      • [PHPBB-17449] - Proper emoji handling for progressive web app manifests
      • +
      • [PHPBB-17450] - Remove obsolete requirement for JSON extension
      • +
      • [PHPBB-17459] - Improve prosilver font sizes and backgrounds
      • +
      • [PHPBB-17465] - Add unit tests for handling of web push notifications
      • +
      • [PHPBB-17478] - Add security policy to repository
      • +
      • [PHPBB-17481] - Adjust package generation to be version independent
      • +
      • [PHPBB-17490] - Add unit tests for new Symfony Mailer classes
      • +
      • [PHPBB-17493] - Drop support for Jabber
      • +
      • [PHPBB-17494] - Functional tests don't handle boolean attributes correctly
      • +
      • [PHPBB-17498] - Move GitHub Actions Ubuntu 20.04 runners to Ubuntu 22.04
      • +
      • [PHPBB-17501] - Improve prosilver backgrounds and usability
      • +
      • [PHPBB-17513] - Web push notifications on Safari stop after 3 pushes
      • +
      • [PHPBB-17530] - Use Doctrine driver middleware instead of 'platform' parameter
      • +
      • [PHPBB-17535] - Upgrade PHPUnit to version 10
      • +
      • [PHPBB-17540] - Update windows runners and limit amount of runners on GitHub Actions
      • +
      • [PHPBB-17542] - Test Framework can be exited unexpectedly
      • +
      +

      New Feature

      +
        +
      • [PHPBB-9687] - Refactor ban system
      • +
      • [PHPBB-11150] - Extension Management with Composer
      • +
      • [PHPBB-12683] - Add a CLI command to generate the search index
      • +
      • [PHPBB-13713] - Add ability to @mention specific users in posts
      • +
      • [PHPBB-14169] - Split uploaded files storage into separate class(es)
      • +
      • [PHPBB-15214] - Ability to specify load order of template events
      • +
      • [PHPBB-15538] - Improve and simplify the inclusion of icons
      • +
      • [PHPBB-15565] - fix quote font-size after px conversion
      • +
      • [PHPBB-15851] - Automatic update downloader backend implementation
      • +
      • [PHPBB-16243] - Update template paths
      • +
      • [PHPBB-16856] - Add lang_js() function to twig as replacement for LA_
      • +
      • [PHPBB-16863] - Support playing video attachments
      • +
      • [PHPBB-17173] - Generate signatures for release packages
      • +
      • [PHPBB-17326] - Add icon setting to BBCode edit screen in ACP
      • +
      • [PHPBB-17413] - Add support for Cloudflare Turnstile CAPTCHA
      • +
      • [PHPBB-17515] - Add new event to: ucp_pm_viewmessage.html
      • +
      +

      Security Issue

      +
        +
      • [PHPBB-15570] - Extension version check is restricted to TLS 1.0
      • +
      +

      Sub-task

      + +

      Task

      +
        +
      • [PHPBB-14495] - Build the 3.3 version on the master branch
      • +
      • [PHPBB-14584] - Move deprecated globals and functions to compatibility_*.php
      • +
      • [PHPBB-15213] - Fix stylelint failures in master branch
      • +
      • [PHPBB-15244] - Remove the UNGLOBALISE-related code
      • +
      • [PHPBB-15410] - Remove obsolete code from BBCodes ACP
      • +
      • [PHPBB-15466] - Move Nils in CREDITS.txt
      • +
      • [PHPBB-15516] - Add instructions on running UI tests
      • +
      • [PHPBB-15540] - Refactor search backend classes to Symfony services
      • +
      • [PHPBB-15738] - Remove code related with safe mode
      • +
      • [PHPBB-15874] - Don't access directly to the user lang property
      • +
      • [PHPBB-15927] - Fix ACP table display error
      • +
      • [PHPBB-16112] - Update composer dependencies to latest
      • +
      • [PHPBB-16133] - Upgrade packages in packages.json
      • +
      • [PHPBB-16224] - Update composer dependencies
      • +
      • [PHPBB-16246] - Prettify and update README Automated Testing section
      • +
      • [PHPBB-16284] - Move 4.0 migrations to v400 folder.
      • +
      • [PHPBB-16405] - Update npm dependencies to latest versions
      • +
      • [PHPBB-16572] - Update node modules to latest versions
      • +
      • [PHPBB-16615] - Update node dependencies to resolve dependabot alert
      • +
      • [PHPBB-16616] - Update composer dependencies to latest versions
      • +
      • [PHPBB-16633] - Update pull request template after end of life of 3.2
      • +
      • [PHPBB-16669] - Update node ini dependency to latest version
      • +
      • [PHPBB-16746] - Update node dependencies in master
      • +
      • [PHPBB-16769] - Update composer to latest version
      • +
      • [PHPBB-16790] - Remove unused code in acp,mcp,ucp
      • +
      • [PHPBB-16928] - Update composer and composer dependencies to latest versions
      • +
      • [PHPBB-16987] - Update composer and composer dependencies
      • +
      • [PHPBB-17048] - Update composer and dependencies for 3.3.9
      • +
      • [PHPBB-17066] - Update GitHub Actions configuration to resolve deprecations
      • +
      • [PHPBB-17108] - Update composer dependencies to latest versions
      • +
      • [PHPBB-17110] - Reword "slander" to "libel" in registration legalese
      • +
      • [PHPBB-17149] - Update authors and pull request template
      • +
      • [PHPBB-17154] - Update composer and dependencies to latest versions
      • +
      • [PHPBB-17204] - Update composer and node dependencies
      • +
      • [PHPBB-17280] - Fallback to branch name on branches without ticket ID
      • +
      • [PHPBB-17281] - Update psalm to 5.x version
      • +
      • [PHPBB-17403] - Update composer and node requirements
      • +
      • [PHPBB-17432] - Update dependencies to latest version
      • +
      • [PHPBB-17464] - Remove deprecated meta tags
      • +
      • [PHPBB-17503] - Update composer dependencies
      • +
      • [PHPBB-17509] - Bump DBMS supported versions
      • +
      • [PHPBB-17512] - Add PHP Sniffer coding standard to check union type
      • +
      • [PHPBB-17514] - Remove version 3 specifier from paths and content
      • +
      • [PHPBB-17516] - Remove dependency on CHItA/TopologicalSort
      • +
      • [PHPBB-17517] - Make proper use of eslint
      • +
      • [PHPBB-17543] - Update composer and dependencies
      • +
      +

      Changes since 3.3.15-RC1

      Bug

        From ed6f609b3408a9f0ca2458d4e476200fe9f0ebb4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 12:40:05 +0200 Subject: [PATCH 0914/1214] [prep-release-4.0.0] Only build for latest versions of previous versions --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index c8fafa0e1c3..9ccb75e66cc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -4,7 +4,7 @@ - + From c912d69fda37e8d08d7bf8224fb9b19500b8b796 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 12:40:54 +0200 Subject: [PATCH 0915/1214] [prep-release-4.0.0] Update version numbers to 4.0.0-a1 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 9ccb75e66cc..c8fafa0e1c3 100644 --- a/build/build.xml +++ b/build/build.xml @@ -4,7 +4,7 @@ - + From 9f6bb5c2586587c2dba8137b11af312f0c873853 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 13:16:28 +0200 Subject: [PATCH 0916/1214] [prep-release-4.0.0] Update version numbers to 4.0.0-a1 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index c8fafa0e1c3..9ccb75e66cc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -4,7 +4,7 @@ - + From e362789602bb633bdd5b0aa76ba84f4bc5aa63d4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 15:46:21 +0200 Subject: [PATCH 0917/1214] [prep-release-4.0.0] Do not add rename duplicate index names migration --- phpBB/phpbb/db/migration/data/v400/v400a1.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/phpbb/db/migration/data/v400/v400a1.php b/phpBB/phpbb/db/migration/data/v400/v400a1.php index adbaaed24be..8897d6f279d 100644 --- a/phpBB/phpbb/db/migration/data/v400/v400a1.php +++ b/phpBB/phpbb/db/migration/data/v400/v400a1.php @@ -28,7 +28,6 @@ public static function depends_on() '\phpbb\db\migration\data\v400\remove_remote_avatar', '\phpbb\db\migration\data\v400\increase_avatar_size', '\phpbb\db\migration\data\v400\remove_img_link', - '\phpbb\db\migration\data\v400\rename_duplicated_index_names', '\phpbb\db\migration\data\v400\add_bbcode_font_icon', '\phpbb\db\migration\data\v400\font_awesome_6_upgrade', '\phpbb\db\migration\data\v400\remove_broken_captcha', From 7551729db2bafa2eae5870fd1491cadeb1183d52 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 25 Sep 2025 21:56:41 +0200 Subject: [PATCH 0918/1214] [prep-release-4.0.0] Fix installer icons --- phpBB/adm/style/admin.css | 4 ++++ phpBB/config/installer/container/services.yml | 1 + 2 files changed, 5 insertions(+) diff --git a/phpBB/adm/style/admin.css b/phpBB/adm/style/admin.css index 290eaffcf8d..8ffac325af6 100644 --- a/phpBB/adm/style/admin.css +++ b/phpBB/adm/style/admin.css @@ -663,6 +663,7 @@ li { #menu li span.completed:before { font-family: "Font Awesome 6 Free"; + font-weight: 900; color: #515f77; margin-right: 1px; margin-left: -8px; @@ -676,6 +677,7 @@ li { .rtl #menu li span.completed:before { font-family: "Font Awesome 6 Free"; + font-weight: 900; color: #515f77; margin-right: -8px; margin-left: 1px; @@ -2858,12 +2860,14 @@ fieldset.permissions .permissions-switch { .roles-options > span:after { font-family: "Font Awesome 6 Free"; + font-weight: 900; float: right; content: "\f107"; } .rtl .roles-options > span:after { font-family: "Font Awesome 6 Free"; + font-weight: 900; float: left; content: "\f107"; } diff --git a/phpBB/config/installer/container/services.yml b/phpBB/config/installer/container/services.yml index 7ef423afb11..40e890e01ce 100644 --- a/phpBB/config/installer/container/services.yml +++ b/phpBB/config/installer/container/services.yml @@ -9,6 +9,7 @@ imports: - { resource: ../../default/container/services_php.yml } - { resource: ../../default/container/services_routing.yml } - { resource: ../../default/container/services_twig.yml } + - { resource: ../../default/container/services_twig_extensions.yml } services: assets.bag: From 5c4a7cbed6b5e7e4a44a8200dac593384d76700d Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 27 Sep 2025 10:07:33 +0200 Subject: [PATCH 0919/1214] [prep-release-4.0.0] Update prosilver version --- phpBB/styles/prosilver/composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/prosilver/composer.json b/phpBB/styles/prosilver/composer.json index 761638e5e13..bc7144f407e 100644 --- a/phpBB/styles/prosilver/composer.json +++ b/phpBB/styles/prosilver/composer.json @@ -2,7 +2,7 @@ "name": "phpbb/phpbb-style-prosilver", "description": "phpBB Forum Software default style", "type": "phpbb-style", - "version": "4.0.0-a1-dev", + "version": "4.0.0-a1", "homepage": "https://www.phpbb.com", "license": "GPL-2.0-only", "authors": [ @@ -21,7 +21,7 @@ }, "extra": { "display-name": "prosilver", - "phpbb-version": "4.0.0-a1-dev", + "phpbb-version": "4.0.0-a1", "parent-style": "" } } From 6ee106e317a746702c6c64ec54d736e2636265e7 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 27 Sep 2025 10:09:28 +0200 Subject: [PATCH 0920/1214] [prep-release-4.0.0] Update version numbers to 4.0.0 --- phpBB/styles/prosilver/composer.json | 50 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/phpBB/styles/prosilver/composer.json b/phpBB/styles/prosilver/composer.json index bc7144f407e..7b6ee810963 100644 --- a/phpBB/styles/prosilver/composer.json +++ b/phpBB/styles/prosilver/composer.json @@ -1,27 +1,27 @@ { - "name": "phpbb/phpbb-style-prosilver", - "description": "phpBB Forum Software default style", - "type": "phpbb-style", - "version": "4.0.0-a1", - "homepage": "https://www.phpbb.com", - "license": "GPL-2.0-only", - "authors": [ - { - "name": "phpBB Limited", - "email": "operations@phpbb.com", - "homepage": "https://www.phpbb.com/go/authors" - } - ], - "support": { - "issues": "https://tracker.phpbb.com", - "forum": "https://www.phpbb.com/community/", - "docs": "https://www.phpbb.com/support/docs/", - "irc": "irc://irc.libera.chat/phpbb", - "chat": "https://www.phpbb.com/support/chat/" - }, - "extra": { - "display-name": "prosilver", - "phpbb-version": "4.0.0-a1", - "parent-style": "" - } + "name": "phpbb/phpbb-style-prosilver", + "description": "phpBB Forum Software default style", + "type": "phpbb-style", + "version": "4.0.0", + "homepage": "https://www.phpbb.com", + "license": "GPL-2.0-only", + "authors": [ + { + "name": "phpBB Limited", + "email": "operations@phpbb.com", + "homepage": "https://www.phpbb.com/go/authors" + } + ], + "support": { + "issues": "https://tracker.phpbb.com", + "forum": "https://www.phpbb.com/community/", + "docs": "https://www.phpbb.com/support/docs/", + "irc": "irc://irc.libera.chat/phpbb", + "chat": "https://www.phpbb.com/support/chat/" + }, + "extra": { + "display-name": "prosilver", + "phpbb-version": "4.0.0-a1", + "parent-style": "" + } } From a063b534eb7441f30251e6e470bd255ccd74eb2a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 27 Sep 2025 10:11:06 +0200 Subject: [PATCH 0921/1214] [prep-release-4.0.0] Update changelog for 4.0.0-a1 --- phpBB/docs/CHANGELOG.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 3f42cefefb8..8bb87f26bb2 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -176,7 +176,7 @@

        Changelog

        -

        Changes since 3.3.15

        +

        Changes since 3.3.x

        Bug

        • [PHPBB-11449] - Get rid of recursive calls in class phpbb_notification_manager
        • @@ -326,6 +326,10 @@

          Bug

        • [PHPBB-17529] - Installer timeout if config.php doesn't exist
        • [PHPBB-17533] - Reverting migrations may cause restoring incorrect data and throw "module exists" exceptions
        • [PHPBB-17537] - Caching of extensions autoloader ConfigCache not working
        • +
        • [PHPBB-17546] - Schema generator CLI does not work
        • +
        • [PHPBB-17547] - Login fails after multiple failed attempts due to incorrect Captcha enum value passed to init()
        • +
        • [PHPBB-17548] - Updating to phpBB4 with files missing is giving a error
        • +
        • [PHPBB-17549] - British English language pack has wrong license definition in composer.json

        Epic

          @@ -513,6 +517,7 @@

          Improvement

        • [PHPBB-17513] - Web push notifications on Safari stop after 3 pushes
        • [PHPBB-17530] - Use Doctrine driver middleware instead of 'platform' parameter
        • [PHPBB-17535] - Upgrade PHPUnit to version 10
        • +
        • [PHPBB-17538] - Terms and Policy wording improvements
        • [PHPBB-17540] - Update windows runners and limit amount of runners on GitHub Actions
        • [PHPBB-17542] - Test Framework can be exited unexpectedly
        @@ -608,6 +613,7 @@

        Task

      • [PHPBB-17516] - Remove dependency on CHItA/TopologicalSort
      • [PHPBB-17517] - Make proper use of eslint
      • [PHPBB-17543] - Update composer and dependencies
      • +
      • [PHPBB-17544] - Support Codespaces on master

      Changes since 3.3.15-RC1

      From d54cb419c04163c522808e067d1289b471d174b4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 27 Sep 2025 12:49:41 +0200 Subject: [PATCH 0922/1214] [master] Update versions to 4.0.0-a2-dev --- build/build.xml | 4 ++-- phpBB/includes/constants.php | 2 +- phpBB/install/schemas/schema_data.sql | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/build.xml b/build/build.xml index 9ccb75e66cc..a181092a235 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2,9 +2,9 @@ - + - + diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 7937fb5683f..356faa2f6cd 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -28,7 +28,7 @@ */ // phpBB Version -@define('PHPBB_VERSION', '4.0.0-a1'); +@define('PHPBB_VERSION', '4.0.0-a2-dev'); // QA-related // define('PHPBB_QA', 1); diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index dbdad251180..2da7f8ba33d 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -299,7 +299,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_last INSERT INTO phpbb_config (config_name, config_value) VALUES ('update_hashes_lock', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '4.0.0-a1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '4.0.0-a2-dev'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_repositories', '[]'); From aa674a42a04cad6d2ae1d2e18a23f12e81df3eaf Mon Sep 17 00:00:00 2001 From: rubencm Date: Sun, 11 Apr 2021 06:18:23 +0200 Subject: [PATCH 0923/1214] [ticket/16005] Remove phpbb2 converter PHPBB3-16005 --- phpBB/install/convertors/convert_phpbb20.php | 984 --------- .../install/convertors/functions_phpbb20.php | 1929 ----------------- phpBB/install/convertors/index.htm | 10 + 3 files changed, 10 insertions(+), 2913 deletions(-) delete mode 100644 phpBB/install/convertors/convert_phpbb20.php delete mode 100644 phpBB/install/convertors/functions_phpbb20.php create mode 100644 phpBB/install/convertors/index.htm diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php deleted file mode 100644 index 84b31feac61..00000000000 --- a/phpBB/install/convertors/convert_phpbb20.php +++ /dev/null @@ -1,984 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -use phpbb\messenger\method\messenger_interface; - -/** -* NOTE to potential convertor authors. Please use this file to get -* familiar with the structure since we added some bare explanations here. -* -* Since this file gets included more than once on one page you are not able to add functions to it. -* Instead use a functions_ file. -* -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -$phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); -extract($phpbb_config_php_file->get_all()); -unset($dbpasswd); - -$dbms = \phpbb\config_php_file::convert_30_dbms_to_31($dbms); - -/** -* $convertor_data provides some basic information about this convertor which is -* used on the initial list of convertors and to populate the default settings -*/ -$convertor_data = array( - 'forum_name' => 'phpBB 2.0.x', - 'version' => '1.0.3', - 'phpbb_version' => '4.0.0', - 'author' => 'phpBB Limited', - 'dbms' => $dbms, - 'dbhost' => $dbhost, - 'dbport' => $dbport, - 'dbuser' => $dbuser, - 'dbpasswd' => '', - 'dbname' => $dbname, - 'table_prefix' => 'phpbb_', - 'forum_path' => '../forums', - 'author_notes' => '', -); - -/** -* $tables is a list of the tables (minus prefix) which we expect to find in the -* source forum. It is used to guess the prefix if the specified prefix is incorrect -*/ -$tables = array( - 'auth_access', - 'banlist', - 'categories', - 'disallow', - 'forum_prune', - 'forums', - 'groups', - 'posts', - 'posts_text', - 'privmsgs', - 'privmsgs_text', - 'ranks', - 'smilies', - 'topics', - 'topics_watch', - 'user_group', - 'users', - 'vote_desc', - 'vote_results', - 'vote_voters', - 'words' -); - -/** -* $config_schema details how the board configuration information is stored in the source forum. -* -* 'table_format' can take the value 'file' to indicate a config file. In this case array_name -* is set to indicate the name of the array the config values are stored in -* Example of using a file: -* $config_schema = array( -* 'table_format' => 'file', -* 'filename' => 'NAME OF FILE', // If the file is not in the root directory, the path needs to be added with no leading slash -* 'array_name' => 'NAME OF ARRAY', // Only used if the configuration file stores the setting in an array. -* 'settings' => array( -* 'board_email' => 'SUPPORT_EMAIL', // target config name => source target name -* ) -* ); -* 'table_format' can be an array if the values are stored in a table which is an assosciative array -* (as per phpBB 2.0.x) -* If left empty, values are assumed to be stored in a table where each config setting is -* a column (as per phpBB 1.x) -* -* In either of the latter cases 'table_name' indicates the name of the table in the database -* -* 'settings' is an array which maps the name of the config directive in the source forum -* to the config directive in phpBB. It can either be a direct mapping or use a function. -* Please note that the contents of the old config value are passed to the function, therefore -* an in-built function requiring the variable passed by reference is not able to be used. Since -* empty() is such a function we created the function is_empty() to be used instead. -*/ -$config_schema = array( - 'table_name' => 'config', - 'table_format' => array('config_name' => 'config_value'), - 'settings' => array( - 'allow_bbcode' => 'allow_bbcode', - 'allow_smilies' => 'allow_smilies', - 'allow_sig' => 'allow_sig', - 'allow_namechange' => 'allow_namechange', - 'allow_avatar_local' => 'allow_avatar_local', - 'allow_avatar_upload' => 'allow_avatar_upload', - 'board_disable' => 'board_disable', - 'sitename' => 'phpbb_set_encoding(sitename)', - 'site_desc' => 'phpbb_set_encoding(site_desc)', - 'session_length' => 'session_length', - 'board_email_sig' => 'phpbb_set_encoding(board_email_sig)', - 'posts_per_page' => 'posts_per_page', - 'topics_per_page' => 'topics_per_page', - 'enable_confirm' => 'enable_confirm', - 'board_email_form' => 'board_email_form', - 'override_user_style' => 'override_user_style', - 'hot_threshold' => 'hot_threshold', - 'max_poll_options' => 'max_poll_options', - 'max_sig_chars' => 'max_sig_chars', - 'pm_max_msgs' => 'max_inbox_privmsgs', - 'smtp_delivery' => 'smtp_delivery', - 'smtp_host' => 'smtp_host', - 'smtp_username' => 'smtp_username', - 'smtp_password' => 'smtp_password', - 'require_activation' => 'require_activation', - 'flood_interval' => 'flood_interval', - 'avatar_filesize' => 'avatar_filesize', - 'avatar_max_width' => 'avatar_max_width', - 'avatar_max_height' => 'avatar_max_height', - 'default_dateformat' => 'phpbb_set_encoding(default_dateformat)', - 'board_timezone' => 'phpbb_convert_timezone(board_timezone)', - 'allow_privmsg' => 'not(privmsg_disable)', - 'gzip_compress' => 'gzip_compress', - 'coppa_enable' => '!is_empty(coppa_mail)', - 'coppa_fax' => 'coppa_fax', - 'coppa_mail' => 'coppa_mail', - 'record_online_users' => 'record_online_users', - 'record_online_date' => 'record_online_date', - 'board_startdate' => 'board_startdate', - ) -); - -/** -* $test_file is the name of a file which is present on the source -* forum which can be used to check that the path specified by the -* user was correct -*/ -$test_file = 'modcp.php'; - -/** -* If this is set then we are not generating the first page of information but getting the conversion information. -*/ -if (!$get_info) -{ - // Test to see if the birthday MOD is installed on the source forum - // Niels' birthday mod - if (get_config_value('birthday_required') !== false || get_config_value('bday_require') !== false) - { - define('MOD_BIRTHDAY', true); - } - - // TerraFrost's validated birthday mod - if (get_config_value('bday_require') !== false) - { - define('MOD_BIRTHDAY_TERRA', true); - } - - // Test to see if the attachment MOD is installed on the source forum - // If it is, we will convert this data as well - $src_db->sql_return_on_error(true); - - $sql = "SELECT config_value - FROM {$convert->src_table_prefix}attachments_config - WHERE config_name = 'upload_dir'"; - $result = $src_db->sql_query($sql); - - if ($result && $row = $src_db->sql_fetchrow($result)) - { - // Here the constant is defined - define('MOD_ATTACHMENT', true); - - // Here i add more tables to be checked in the old forum - $tables += array( - 'attachments', - 'attachments_desc', - 'extensions', - 'extension_groups' - ); - - $src_db->sql_freeresult($result); - } - else if ($result) - { - $src_db->sql_freeresult($result); - } - - - /** - * Tests for further MODs can be included here. - * Please use constants for this, prefixing them with MOD_ - */ - - $src_db->sql_return_on_error(false); - - // Now let us set a temporary config variable for user id incrementing - $sql = "SELECT user_id - FROM {$convert->src_table_prefix}users - WHERE user_id = 1"; - $result = $src_db->sql_query($sql); - $user_id = (int) $src_db->sql_fetchfield('user_id'); - $src_db->sql_freeresult($result); - - // If there is a user id 1, we need to increment user ids. :/ - if ($user_id === 1) - { - // Try to get the maximum user id possible... - $sql = "SELECT MAX(user_id) AS max_user_id - FROM {$convert->src_table_prefix}users"; - $result = $src_db->sql_query($sql); - $user_id = (int) $src_db->sql_fetchfield('max_user_id'); - $src_db->sql_freeresult($result); - - $config->set('increment_user_id', ($user_id + 1), false); - } - else - { - $config->set('increment_user_id', 0, false); - } - - // Overwrite maximum avatar width/height - @define('DEFAULT_AVATAR_X_CUSTOM', get_config_value('avatar_max_width')); - @define('DEFAULT_AVATAR_Y_CUSTOM', get_config_value('avatar_max_height')); - - // additional table used only during conversion - @define('USERCONV_TABLE', $table_prefix . 'userconv'); - -/** -* Description on how to use the convertor framework. -* -* 'schema' Syntax Description -* -> 'target' => Target Table. If not specified the next table will be handled -* -> 'primary' => Primary Key. If this is specified then this table is processed in batches -* -> 'query_first' => array('target' or 'src', Query to execute before beginning the process -* (if more than one then specified as array)) -* -> 'function_first' => Function to execute before beginning the process (if more than one then specified as array) -* (This is mostly useful if variables need to be given to the converting process) -* -> 'test_file' => This is not used at the moment but should be filled with a file from the old installation -* -* // DB Functions -* 'distinct' => Add DISTINCT to the select query -* 'where' => Add WHERE to the select query -* 'group_by' => Add GROUP BY to the select query -* 'left_join' => Add LEFT JOIN to the select query (if more than one joins specified as array) -* 'having' => Add HAVING to the select query -* -* // DB INSERT array -* This one consist of three parameters -* First Parameter: -* The key need to be filled within the target table -* If this is empty, the target table gets not assigned the source value -* Second Parameter: -* Source value. If the first parameter is specified, it will be assigned this value. -* If the first parameter is empty, this only gets added to the select query -* Third Parameter: -* Custom Function. Function to execute while storing source value into target table. -* The functions return value get stored. -* The function parameter consist of the value of the second parameter. -* -* types: -* - empty string == execute nothing -* - string == function to execute -* - array == complex execution instructions -* -* Complex execution instructions: -* @todo test complex execution instructions - in theory they will work fine -* -* By defining an array as the third parameter you are able to define some statements to be executed. The key -* is defining what to execute, numbers can be appended... -* -* 'function' => execute function -* 'execute' => run code, whereby all occurrences of {VALUE} get replaced by the last returned value. -* The result *must* be assigned/stored to {RESULT}. -* 'typecast' => typecast value -* -* The returned variables will be made always available to the next function to continue to work with. -* -* example (variable inputted is an integer of 1): -* -* array( -* 'function1' => 'increment_by_one', // returned variable is 2 -* 'typecast' => 'string', // typecast variable to be a string -* 'execute' => '{RESULT} = {VALUE} . ' is good';', // returned variable is '2 is good' -* 'function2' => 'replace_good_with_bad', // returned variable is '2 is bad' -* ), -* -*/ - - $convertor = array( - 'test_file' => 'viewtopic.php', - - 'avatar_path' => get_config_value('avatar_path') . '/', - 'avatar_gallery_path' => get_config_value('avatar_gallery_path') . '/', - 'smilies_path' => get_config_value('smilies_path') . '/', - 'upload_path' => (defined('MOD_ATTACHMENT')) ? phpbb_get_files_dir() . '/' : '', - 'thumbnails' => (defined('MOD_ATTACHMENT')) ? array('thumbs/', 't_') : '', - 'ranks_path' => false, // phpBB 2.0.x had no config value for a ranks path - - // We empty some tables to have clean data available - 'query_first' => array( - array('target', $convert->truncate_statement . SEARCH_RESULTS_TABLE), - array('target', $convert->truncate_statement . SEARCH_WORDLIST_TABLE), - array('target', $convert->truncate_statement . SEARCH_WORDMATCH_TABLE), - array('target', $convert->truncate_statement . LOG_TABLE), - ), - -// with this you are able to import all attachment files on the fly. For large boards this is not an option, therefore commented out by default. -// Instead every file gets copied while processing the corresponding attachment entry. -// if (defined("MOD_ATTACHMENT")) { import_attachment_files(); phpbb_copy_thumbnails(); } - - // phpBB2 allowed some similar usernames to coexist which would have the same - // username_clean in phpBB which is not possible, so we'll give the admin a list - // of user ids and usernames and let him deicde what he wants to do with them - 'execute_first' => ' - phpbb_create_userconv_table(); - import_avatar_gallery(); - if (defined("MOD_ATTACHMENT")) phpbb_import_attach_config(); - phpbb_insert_forums(); - ', - - 'execute_last' => array(' - add_bots(); - ', ' - update_folder_pm_count(); - ', ' - update_unread_count(); - ', (defined('MOD_ATTACHMENT')) ? ' - phpbb_attachment_extension_group_name(); - ' : ' - ', ' - phpbb_convert_authentication(\'start\'); - ', ' - phpbb_convert_authentication(\'first\'); - ', ' - phpbb_convert_authentication(\'second\'); - ', ' - phpbb_convert_authentication(\'third\'); - '), - - 'schema' => array( - array( - 'target' => USERCONV_TABLE, - 'query_first' => array('target', $convert->truncate_statement . USERCONV_TABLE), - - - array('user_id', 'users.user_id', ''), - array('username_clean', 'users.username', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_clean_string')), - ), - - array( - 'target' => (defined('MOD_ATTACHMENT')) ? ATTACHMENTS_TABLE : '', - 'primary' => 'attachments.attach_id', - 'query_first' => (defined('MOD_ATTACHMENT')) ? array('target', $convert->truncate_statement . ATTACHMENTS_TABLE) : '', - 'autoincrement' => 'attach_id', - - array('attach_id', 'attachments.attach_id', ''), - array('post_msg_id', 'attachments.post_id', ''), - array('topic_id', 'posts.topic_id', ''), - array('in_message', 0, ''), - array('is_orphan', 0, ''), - array('poster_id', 'attachments.user_id_1 AS poster_id', 'phpbb_user_id'), - array('physical_filename', 'attachments_desc.physical_filename', 'import_attachment'), - array('real_filename', 'attachments_desc.real_filename', 'phpbb_set_encoding'), - array('download_count', 'attachments_desc.download_count', ''), - array('attach_comment', 'attachments_desc.comment', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_htmlspecialchars')), - array('extension', 'attachments_desc.extension', ''), - array('mimetype', 'attachments_desc.mimetype', ''), - array('filesize', 'attachments_desc.filesize', ''), - array('filetime', 'attachments_desc.filetime', ''), - array('thumbnail', 'attachments_desc.thumbnail', ''), - - 'where' => 'attachments_desc.attach_id = attachments.attach_id AND attachments.privmsgs_id = 0 AND posts.post_id = attachments.post_id', - 'group_by' => 'attachments.attach_id' - ), - - array( - 'target' => (defined('MOD_ATTACHMENT')) ? ATTACHMENTS_TABLE : '', - 'primary' => 'attachments.attach_id', - 'autoincrement' => 'attach_id', - - array('attach_id', 'attachments.attach_id', ''), - array('post_msg_id', 'attachments.privmsgs_id', ''), - array('topic_id', 0, ''), - array('in_message', 1, ''), - array('is_orphan', 0, ''), - array('poster_id', 'attachments.user_id_1 AS poster_id', 'phpbb_user_id'), - array('physical_filename', 'attachments_desc.physical_filename', 'import_attachment'), - array('real_filename', 'attachments_desc.real_filename', 'phpbb_set_encoding'), - array('download_count', 'attachments_desc.download_count', ''), - array('attach_comment', 'attachments_desc.comment', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_htmlspecialchars')), - array('extension', 'attachments_desc.extension', ''), - array('mimetype', 'attachments_desc.mimetype', ''), - array('filesize', 'attachments_desc.filesize', ''), - array('filetime', 'attachments_desc.filetime', ''), - array('thumbnail', 'attachments_desc.thumbnail', ''), - - 'where' => 'attachments_desc.attach_id = attachments.attach_id AND attachments.post_id = 0', - 'group_by' => 'attachments.attach_id' - ), - - array( - 'target' => (defined('MOD_ATTACHMENT')) ? EXTENSIONS_TABLE : '', - 'query_first' => (defined('MOD_ATTACHMENT')) ? array('target', $convert->truncate_statement . EXTENSIONS_TABLE) : '', - 'autoincrement' => 'extension_id', - - array('extension_id', 'extensions.ext_id', ''), - array('group_id', 'extensions.group_id', ''), - array('extension', 'extensions.extension', ''), - ), - - array( - 'target' => (defined('MOD_ATTACHMENT')) ? EXTENSION_GROUPS_TABLE : '', - 'query_first' => (defined('MOD_ATTACHMENT')) ? array('target', $convert->truncate_statement . EXTENSION_GROUPS_TABLE) : '', - 'autoincrement' => 'group_id', - - array('group_id', 'extension_groups.group_id', ''), - array('group_name', 'extension_groups.group_name', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_htmlspecialchars')), - array('cat_id', 'extension_groups.cat_id', 'phpbb_attachment_category'), - array('allow_group', 'extension_groups.allow_group', ''), - array('upload_icon', '', ''), - array('max_filesize', 'extension_groups.max_filesize', ''), - array('allowed_forums', 'extension_groups.forum_permissions', 'phpbb_attachment_forum_perms'), - array('allow_in_pm', 1, ''), - ), - - array( - 'target' => BANS_TABLE, - 'execute_first' => 'phpbb_check_username_collisions();', - 'query_first' => array('target', $convert->truncate_statement . BANS_TABLE), - - array('ban_mode', 'user', ''), - array('ban_item', 'banlist.ban_userid', 'phpbb_user_id'), - array('ban_userid', 'banlist.ban_userid', 'phpbb_user_id'), - array('ban_reason', '', ''), - array('ban_reason_display', '', ''), - - 'where' => "banlist.ban_ip NOT LIKE '%.%' - AND banlist.ban_userid <> 0", - ), - - array( - 'target' => BANS_TABLE, - - array('ban_mode', 'email', ''), - array('ban_item', 'banlist.ban_email', ''), - array('ban_reason', '', ''), - array('ban_reason_display', '', ''), - - 'where' => "banlist.ban_ip NOT LIKE '%.%' - AND banlist.ban_email <> ''", - ), - - array( - 'target' => BANS_TABLE, - - array('ban_mode', 'ip', ''), - array('ban_item', 'banlist.ban_ip', 'decode_ban_ip'), - array('ban_reason', '', ''), - array('ban_reason_display', '', ''), - - 'where' => "banlist.ban_userid = 0 - AND banlist.ban_ip <> ''", - ), - - array( - 'target' => DISALLOW_TABLE, - 'query_first' => array('target', $convert->truncate_statement . DISALLOW_TABLE), - - array('disallow_username', 'disallow.disallow_username', 'phpbb_disallowed_username'), - ), - - array( - 'target' => RANKS_TABLE, - 'query_first' => array('target', $convert->truncate_statement . RANKS_TABLE), - 'autoincrement' => 'rank_id', - - array('rank_id', 'ranks.rank_id', ''), - array('rank_title', 'ranks.rank_title', array('function1' => 'phpbb_set_default_encoding', 'function2' => 'utf8_htmlspecialchars')), - array('rank_min', 'ranks.rank_min', array('typecast' => 'int', 'execute' => '{RESULT} = ({VALUE}[0] < 0) ? 0 : {VALUE}[0];')), - array('rank_special', 'ranks.rank_special', ''), - array('rank_image', 'ranks.rank_image', 'import_rank'), - ), - - array( - 'target' => TOPICS_TABLE, - 'query_first' => array('target', $convert->truncate_statement . TOPICS_TABLE), - 'primary' => 'topics.topic_id', - 'autoincrement' => 'topic_id', - - array('topic_id', 'topics.topic_id', ''), - array('forum_id', 'topics.forum_id', ''), - array('icon_id', 0, ''), - array('topic_poster', 'topics.topic_poster AS poster_id', 'phpbb_user_id'), - array('topic_attachment', ((defined('MOD_ATTACHMENT')) ? 'topics.topic_attachment' : 0), ''), - array('topic_title', 'topics.topic_title', 'phpbb_set_encoding'), - array('topic_time', 'topics.topic_time', ''), - array('topic_views', 'topics.topic_views', ''), - array('topic_posts_approved', 'topics.topic_replies', 'phpbb_topic_replies_to_posts'), - array('topic_posts_unapproved', 0, ''), - array('topic_posts_softdeleted',0, ''), - array('topic_last_post_id', 'topics.topic_last_post_id', ''), - array('topic_status', 'topics.topic_status', 'is_topic_locked'), - array('topic_moved_id', 0, ''), - array('topic_type', 'topics.topic_type', 'phpbb_convert_topic_type'), - array('topic_first_post_id', 'topics.topic_first_post_id', ''), - array('topic_last_view_time', 'posts.post_time', 'intval'), - array('topic_visibility', ITEM_APPROVED, ''), - - array('poll_title', 'vote_desc.vote_text', array('function1' => 'null_to_str', 'function2' => 'phpbb_set_encoding', 'function3' => 'htmlspecialchars_decode', 'function4' => 'utf8_htmlspecialchars')), - array('poll_start', 'vote_desc.vote_start', 'null_to_zero'), - array('poll_length', 'vote_desc.vote_length', 'null_to_zero'), - array('poll_max_options', 1, ''), - array('poll_vote_change', 0, ''), - - 'left_join' => array ( 'topics LEFT JOIN vote_desc ON topics.topic_id = vote_desc.topic_id AND topics.topic_vote = 1', - 'topics LEFT JOIN posts ON topics.topic_last_post_id = posts.post_id', - ), - 'where' => 'topics.topic_moved_id = 0', - ), - - array( - 'target' => TOPICS_TABLE, - 'primary' => 'topics.topic_id', - 'autoincrement' => 'topic_id', - - array('topic_id', 'topics.topic_id', ''), - array('forum_id', 'topics.forum_id', ''), - array('icon_id', 0, ''), - array('topic_poster', 'topics.topic_poster AS poster_id', 'phpbb_user_id'), - array('topic_attachment', ((defined('MOD_ATTACHMENT')) ? 'topics.topic_attachment' : 0), ''), - array('topic_title', 'topics.topic_title', 'phpbb_set_encoding'), - array('topic_time', 'topics.topic_time', ''), - array('topic_views', 'topics.topic_views', ''), - array('topic_posts_approved', 'topics.topic_replies', 'phpbb_topic_replies_to_posts'), - array('topic_posts_unapproved', 0, ''), - array('topic_posts_softdeleted',0, ''), - array('topic_last_post_id', 'topics.topic_last_post_id', ''), - array('topic_status', ITEM_MOVED, ''), - array('topic_moved_id', 'topics.topic_moved_id', ''), - array('topic_type', 'topics.topic_type', 'phpbb_convert_topic_type'), - array('topic_first_post_id', 'topics.topic_first_post_id', ''), - array('topic_visibility', ITEM_APPROVED, ''), - - array('poll_title', 'vote_desc.vote_text', array('function1' => 'null_to_str', 'function2' => 'phpbb_set_encoding', 'function3' => 'htmlspecialchars_decode', 'function4' => 'utf8_htmlspecialchars')), - array('poll_start', 'vote_desc.vote_start', 'null_to_zero'), - array('poll_length', 'vote_desc.vote_length', 'null_to_zero'), - array('poll_max_options', 1, ''), - array('poll_vote_change', 0, ''), - - 'left_join' => 'topics LEFT JOIN vote_desc ON topics.topic_id = vote_desc.topic_id AND topics.topic_vote = 1', - 'where' => 'topics.topic_moved_id <> 0', - ), - - array( - 'target' => TOPICS_WATCH_TABLE, - 'primary' => 'topics_watch.topic_id', - 'query_first' => array('target', $convert->truncate_statement . TOPICS_WATCH_TABLE), - - array('topic_id', 'topics_watch.topic_id', ''), - array('user_id', 'topics_watch.user_id', 'phpbb_user_id'), - array('notify_status', 'topics_watch.notify_status', ''), - ), - - array( - 'target' => SMILIES_TABLE, - 'query_first' => array('target', $convert->truncate_statement . SMILIES_TABLE), - 'autoincrement' => 'smiley_id', - - array('smiley_id', 'smilies.smilies_id', ''), - array('code', 'smilies.code', array('function1' => 'phpbb_smilie_html_decode', 'function2' => 'phpbb_set_encoding', 'function3' => 'utf8_htmlspecialchars')), - array('emotion', 'smilies.emoticon', 'phpbb_set_encoding'), - array('smiley_url', 'smilies.smile_url', 'import_smiley'), - array('smiley_width', 'smilies.smile_url', 'get_smiley_width'), - array('smiley_height', 'smilies.smile_url', 'get_smiley_height'), - array('smiley_order', 'smilies.smilies_id', ''), - array('display_on_posting', 'smilies.smilies_id', 'get_smiley_display'), - - 'order_by' => 'smilies.smilies_id ASC', - ), - - array( - 'target' => POLL_OPTIONS_TABLE, - 'primary' => 'vote_results.vote_option_id', - 'query_first' => array('target', $convert->truncate_statement . POLL_OPTIONS_TABLE), - - array('poll_option_id', 'vote_results.vote_option_id', ''), - array('topic_id', 'vote_desc.topic_id', ''), - array('', 'topics.topic_poster AS poster_id', 'phpbb_user_id'), - array('poll_option_text', 'vote_results.vote_option_text', array('function1' => 'phpbb_set_encoding', 'function2' => 'htmlspecialchars_decode', 'function3' => 'utf8_htmlspecialchars')), - array('poll_option_total', 'vote_results.vote_result', ''), - - 'where' => 'vote_results.vote_id = vote_desc.vote_id', - 'left_join' => 'vote_desc LEFT JOIN topics ON topics.topic_id = vote_desc.topic_id', - ), - - array( - 'target' => POLL_VOTES_TABLE, - 'primary' => 'vote_desc.topic_id', - 'query_first' => array('target', $convert->truncate_statement . POLL_VOTES_TABLE), - - array('poll_option_id', VOTE_CONVERTED, ''), - array('topic_id', 'vote_desc.topic_id', ''), - array('vote_user_id', 'vote_voters.vote_user_id', 'phpbb_user_id'), - array('vote_user_ip', 'vote_voters.vote_user_ip', 'decode_ip'), - - 'where' => 'vote_voters.vote_id = vote_desc.vote_id', - ), - - array( - 'target' => WORDS_TABLE, - 'primary' => 'words.word_id', - 'query_first' => array('target', $convert->truncate_statement . WORDS_TABLE), - 'autoincrement' => 'word_id', - - array('word_id', 'words.word_id', ''), - array('word', 'words.word', 'phpbb_set_encoding'), - array('replacement', 'words.replacement', 'phpbb_set_encoding'), - ), - - array( - 'target' => POSTS_TABLE, - 'primary' => 'posts.post_id', - 'autoincrement' => 'post_id', - 'query_first' => array('target', $convert->truncate_statement . POSTS_TABLE), - 'execute_first' => ' - $config["max_post_chars"] = 0; - $config["min_post_chars"] = 0; - $config["max_quote_depth"] = 0; - ', - - array('post_id', 'posts.post_id', ''), - array('topic_id', 'posts.topic_id', ''), - array('forum_id', 'posts.forum_id', ''), - array('poster_id', 'posts.poster_id', 'phpbb_user_id'), - array('icon_id', 0, ''), - array('poster_ip', 'posts.poster_ip', 'decode_ip'), - array('post_time', 'posts.post_time', ''), - array('enable_bbcode', 'posts.enable_bbcode', ''), - array('', 'posts.enable_html', ''), - array('enable_smilies', 'posts.enable_smilies', ''), - array('enable_sig', 'posts.enable_sig', ''), - array('enable_magic_url', 1, ''), - array('post_username', 'posts.post_username', 'phpbb_set_encoding'), - array('post_subject', 'posts_text.post_subject', 'phpbb_set_encoding'), - array('post_attachment', ((defined('MOD_ATTACHMENT')) ? 'posts.post_attachment' : 0), ''), - array('post_edit_time', 'posts.post_edit_time', array('typecast' => 'int')), - array('post_edit_count', 'posts.post_edit_count', ''), - array('post_edit_reason', '', ''), - array('post_edit_user', '', 'phpbb_post_edit_user'), - array('post_visibility', ITEM_APPROVED, ''), - - array('bbcode_uid', 'posts.post_time', 'make_uid'), - array('post_text', 'posts_text.post_text', 'phpbb_prepare_message'), - array('', 'posts_text.bbcode_uid AS old_bbcode_uid', ''), - array('bbcode_bitfield', '', 'get_bbcode_bitfield'), - array('post_checksum', '', ''), - - // Commented out inline search indexing, this takes up a LOT of time. :D - // @todo We either need to enable this or call the rebuild search functionality post convert -/* array('', '', 'search_indexing'), - array('', 'posts_text.post_text AS message', ''), - array('', 'posts_text.post_subject AS title', ''),*/ - - 'where' => 'posts.post_id = posts_text.post_id' - ), - - array( - 'target' => PRIVMSGS_TABLE, - 'primary' => 'privmsgs.privmsgs_id', - 'autoincrement' => 'msg_id', - 'query_first' => array( - array('target', $convert->truncate_statement . PRIVMSGS_TABLE), - array('target', $convert->truncate_statement . PRIVMSGS_RULES_TABLE), - ), - - 'execute_first' => ' - $config["max_post_chars"] = 0; - $config["min_post_chars"] = 0; - $config["max_quote_depth"] = 0; - ', - - array('msg_id', 'privmsgs.privmsgs_id', ''), - array('root_level', 0, ''), - array('author_id', 'privmsgs.privmsgs_from_userid AS poster_id', 'phpbb_user_id'), - array('icon_id', 0, ''), - array('author_ip', 'privmsgs.privmsgs_ip', 'decode_ip'), - array('message_time', 'privmsgs.privmsgs_date', ''), - array('enable_bbcode', 'privmsgs.privmsgs_enable_bbcode AS enable_bbcode', ''), - array('', 'privmsgs.privmsgs_enable_html AS enable_html', ''), - array('enable_smilies', 'privmsgs.privmsgs_enable_smilies AS enable_smilies', ''), - array('enable_magic_url', 1, ''), - array('enable_sig', 'privmsgs.privmsgs_attach_sig', ''), - array('message_subject', 'privmsgs.privmsgs_subject', 'phpbb_set_encoding'), // Already specialchared in 2.0.x - array('message_attachment', ((defined('MOD_ATTACHMENT')) ? 'privmsgs.privmsgs_attachment' : 0), ''), - array('message_edit_reason', '', ''), - array('message_edit_user', 0, ''), - array('message_edit_time', 0, ''), - array('message_edit_count', 0, ''), - - array('bbcode_uid', 'privmsgs.privmsgs_date AS post_time', 'make_uid'), - array('message_text', 'privmsgs_text.privmsgs_text', 'phpbb_prepare_message'), - array('', 'privmsgs_text.privmsgs_bbcode_uid AS old_bbcode_uid', ''), - array('bbcode_bitfield', '', 'get_bbcode_bitfield'), - array('to_address', 'privmsgs.privmsgs_to_userid', 'phpbb_privmsgs_to_userid'), - array('bcc_address', '', ''), - - 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id' - ), - - array( - 'target' => PRIVMSGS_FOLDER_TABLE, - 'primary' => 'users.user_id', - 'query_first' => array('target', $convert->truncate_statement . PRIVMSGS_FOLDER_TABLE), - - array('user_id', 'users.user_id', 'phpbb_user_id'), - array('folder_name', $user->lang['CONV_SAVED_MESSAGES'], ''), - array('pm_count', 0, ''), - - 'where' => 'users.user_id <> -1', - ), - - // Inbox - array( - 'target' => PRIVMSGS_TO_TABLE, - 'primary' => 'privmsgs.privmsgs_id', - 'query_first' => array('target', $convert->truncate_statement . PRIVMSGS_TO_TABLE), - - array('msg_id', 'privmsgs.privmsgs_id', ''), - array('user_id', 'privmsgs.privmsgs_to_userid', 'phpbb_user_id'), - array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('pm_deleted', 0, ''), - array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), - array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), - array('pm_replied', 0, ''), - array('pm_marked', 0, ''), - array('pm_forwarded', 0, ''), - array('folder_id', PRIVMSGS_INBOX, ''), - - 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id - AND (privmsgs.privmsgs_type = 0 OR privmsgs.privmsgs_type = 1 OR privmsgs.privmsgs_type = 5)', - ), - - // Outbox - array( - 'target' => PRIVMSGS_TO_TABLE, - 'primary' => 'privmsgs.privmsgs_id', - - array('msg_id', 'privmsgs.privmsgs_id', ''), - array('user_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('pm_deleted', 0, ''), - array('pm_new', 0, ''), - array('pm_unread', 0, ''), - array('pm_replied', 0, ''), - array('pm_marked', 0, ''), - array('pm_forwarded', 0, ''), - array('folder_id', PRIVMSGS_OUTBOX, ''), - - 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id - AND (privmsgs.privmsgs_type = 1 OR privmsgs.privmsgs_type = 5)', - ), - - // Sentbox - array( - 'target' => PRIVMSGS_TO_TABLE, - 'primary' => 'privmsgs.privmsgs_id', - - array('msg_id', 'privmsgs.privmsgs_id', ''), - array('user_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('pm_deleted', 0, ''), - array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), - array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), - array('pm_replied', 0, ''), - array('pm_marked', 0, ''), - array('pm_forwarded', 0, ''), - array('folder_id', PRIVMSGS_SENTBOX, ''), - - 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id - AND privmsgs.privmsgs_type = 2', - ), - - // Savebox (SAVED IN) - array( - 'target' => PRIVMSGS_TO_TABLE, - 'primary' => 'privmsgs.privmsgs_id', - - array('msg_id', 'privmsgs.privmsgs_id', ''), - array('user_id', 'privmsgs.privmsgs_to_userid', 'phpbb_user_id'), - array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('pm_deleted', 0, ''), - array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), - array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), - array('pm_replied', 0, ''), - array('pm_marked', 0, ''), - array('pm_forwarded', 0, ''), - array('folder_id', 'privmsgs.privmsgs_to_userid', 'phpbb_get_savebox_id'), - - 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id - AND privmsgs.privmsgs_type = 3', - ), - - // Savebox (SAVED OUT) - array( - 'target' => PRIVMSGS_TO_TABLE, - 'primary' => 'privmsgs.privmsgs_id', - - array('msg_id', 'privmsgs.privmsgs_id', ''), - array('user_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), - array('pm_deleted', 0, ''), - array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), - array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), - array('pm_replied', 0, ''), - array('pm_marked', 0, ''), - array('pm_forwarded', 0, ''), - array('folder_id', 'privmsgs.privmsgs_from_userid', 'phpbb_get_savebox_id'), - - 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id - AND privmsgs.privmsgs_type = 4', - ), - - array( - 'target' => GROUPS_TABLE, - 'autoincrement' => 'group_id', - 'query_first' => array( - array('target', $convert->truncate_statement . GROUPS_TABLE), - array('target', $convert->truncate_statement . TEAMPAGE_TABLE), - ), - - array('group_id', 'groups.group_id', ''), - array('group_type', 'groups.group_type', 'phpbb_convert_group_type'), - array('group_display', 0, ''), - array('group_legend', 0, ''), - array('group_name', 'groups.group_name', 'phpbb_convert_group_name'), // phpbb_set_encoding called in phpbb_convert_group_name - array('group_desc', 'groups.group_description', 'phpbb_set_encoding'), - - 'where' => 'groups.group_single_user = 0', - ), - - array( - 'target' => USER_GROUP_TABLE, - 'query_first' => array('target', $convert->truncate_statement . USER_GROUP_TABLE), - 'execute_first' => ' - add_default_groups(); - add_groups_to_teampage(); - ', - - array('group_id', 'groups.group_id', ''), - array('user_id', 'groups.group_moderator', 'phpbb_user_id'), - array('group_leader', 1, ''), - array('user_pending', 0, ''), - - 'where' => 'groups.group_single_user = 0 AND groups.group_moderator <> 0', - ), - - array( - 'target' => USER_GROUP_TABLE, - - array('group_id', 'user_group.group_id', ''), - array('user_id', 'user_group.user_id', 'phpbb_user_id'), - array('group_leader', 0, ''), - array('user_pending', 'user_group.user_pending', ''), - - 'where' => 'user_group.group_id = groups.group_id AND groups.group_single_user = 0 AND groups.group_moderator <> user_group.user_id', - ), - - array( - 'target' => USERS_TABLE, - 'primary' => 'users.user_id', - 'autoincrement' => 'user_id', - 'query_first' => array( - array('target', 'DELETE FROM ' . USERS_TABLE . ' WHERE user_id <> ' . ANONYMOUS), - array('target', $convert->truncate_statement . BOTS_TABLE), - array('target', $convert->truncate_statement . USER_NOTIFICATIONS_TABLE), - ), - - 'execute_last' => ' - remove_invalid_users(); - ', - - array('user_id', 'users.user_id', 'phpbb_user_id'), - array('', 'users.user_id AS poster_id', 'phpbb_user_id'), - array('user_type', 'users.user_active', 'set_user_type'), - array('group_id', 'users.user_level', 'phpbb_set_primary_group'), - array('user_regdate', 'users.user_regdate', ''), - array('username', 'users.username', 'phpbb_set_default_encoding'), // recode to utf8 with default lang - array('username_clean', 'users.username', array('function1' => 'phpbb_set_default_encoding', 'function2' => 'utf8_clean_string')), - array('user_password', 'users.user_password', 'phpbb_convert_password_hash'), - array('user_posts', 'users.user_posts', 'intval'), - array('user_email', 'users.user_email', 'strtolower'), - array('user_birthday', ((defined('MOD_BIRTHDAY')) ? 'users.user_birthday' : ''), 'phpbb_get_birthday'), - array('user_lastvisit', 'users.user_lastvisit', 'intval'), - array('user_last_active', 'users.user_lastvisit', 'intval'), - array('user_lastmark', 'users.user_lastvisit', 'intval'), - array('user_lang', $config['default_lang'], ''), - array('', 'users.user_lang', ''), - array('user_timezone', 'users.user_timezone', 'phpbb_convert_timezone'), - array('user_dateformat', 'users.user_dateformat', array('function1' => 'phpbb_set_encoding', 'function2' => 'fill_dateformat')), - array('user_inactive_reason', '', 'phpbb_inactive_reason'), - array('user_inactive_time', '', 'phpbb_inactive_time'), - - array('user_rank', 'users.user_rank', 'intval'), - array('user_permissions', '', ''), - - array('user_avatar', 'users.user_avatar', 'phpbb_import_avatar'), - array('user_avatar_type', 'users.user_avatar_type', 'phpbb_avatar_type'), - array('user_avatar_width', 'users.user_avatar', 'phpbb_get_avatar_width'), - array('user_avatar_height', 'users.user_avatar', 'phpbb_get_avatar_height'), - - array('user_new_privmsg', 'users.user_new_privmsg', ''), - array('user_unread_privmsg', 0, ''), //'users.user_unread_privmsg' - array('user_last_privmsg', 'users.user_last_privmsg', 'intval'), - array('user_emailtime', 'users.user_emailtime', 'null_to_zero'), - array('user_notify', 'users.user_notify', 'intval'), - array('user_notify_pm', 'users.user_notify_pm', 'intval'), - array('user_allow_pm', 'users.user_allow_pm', 'intval'), - array('user_allow_viewonline', 'users.user_allow_viewonline', 'intval'), - array('user_allow_viewemail', 'users.user_viewemail', 'intval'), - array('user_actkey', 'users.user_actkey', ''), - array('user_newpasswd', '', ''), // Users need to re-request their password... - array('user_style', $config['default_style'], ''), - - array('user_options', '', 'set_user_options'), - array('', 'users.user_popup_pm AS popuppm', ''), - array('', 'users.user_allowhtml AS html', ''), - array('', 'users.user_allowbbcode AS bbcode', ''), - array('', 'users.user_allowsmile AS smile', ''), - array('', 'users.user_attachsig AS attachsig',''), - - array('user_sig_bbcode_uid', 'users.user_regdate', 'make_uid'), - array('user_sig', 'users.user_sig', 'phpbb_prepare_message'), - array('', 'users.user_sig_bbcode_uid AS old_bbcode_uid', ''), - array('user_sig_bbcode_bitfield', '', 'get_bbcode_bitfield'), - array('', 'users.user_regdate AS post_time', ''), - - array('', 'users.user_notify_pm', 'phpbb_add_notification_options'), - - 'where' => 'users.user_id <> -1', - ), - - array( - 'target' => PROFILE_FIELDS_DATA_TABLE, - 'primary' => 'users.user_id', - 'query_first' => array( - array('target', $convert->truncate_statement . PROFILE_FIELDS_DATA_TABLE), - ), - - array('user_id', 'users.user_id', 'phpbb_user_id'), - array('pf_phpbb_occupation', 'users.user_occ', array('function1' => 'phpbb_set_encoding')), - array('pf_phpbb_interests', 'users.user_interests', array('function1' => 'phpbb_set_encoding')), - array('pf_phpbb_location', 'users.user_from', array('function1' => 'phpbb_set_encoding')), - array('pf_phpbb_icq', 'users.user_icq', array('function1' => 'phpbb_set_encoding')), - array('pf_phpbb_yahoo', 'users.user_yim', array('function1' => 'phpbb_set_encoding')), - array('pf_phpbb_website', 'users.user_website', 'validate_website'), - - 'where' => 'users.user_id <> -1', - ), - ), - ); -} diff --git a/phpBB/install/convertors/functions_phpbb20.php b/phpBB/install/convertors/functions_phpbb20.php deleted file mode 100644 index 450800c3140..00000000000 --- a/phpBB/install/convertors/functions_phpbb20.php +++ /dev/null @@ -1,1929 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -use phpbb\attachment\attachment_category; - -if (!defined('IN_PHPBB')) -{ - exit; -} - -/** -* Helper functions for phpBB 2.0.x to phpBB 3.1.x conversion -*/ - -/** -* Set forum flags - only prune old polls by default -*/ -function phpbb_forum_flags() -{ - // Set forum flags - $forum_flags = 0; - - // FORUM_FLAG_LINK_TRACK - $forum_flags += 0; - - // FORUM_FLAG_PRUNE_POLL - $forum_flags += FORUM_FLAG_PRUNE_POLL; - - // FORUM_FLAG_PRUNE_ANNOUNCE - $forum_flags += 0; - - // FORUM_FLAG_PRUNE_STICKY - $forum_flags += 0; - - // FORUM_FLAG_ACTIVE_TOPICS - $forum_flags += 0; - - // FORUM_FLAG_POST_REVIEW - $forum_flags += FORUM_FLAG_POST_REVIEW; - - return $forum_flags; -} - -/** -* Insert/Convert forums -*/ -function phpbb_insert_forums() -{ - global $db, $src_db, $same_db, $convert, $user; - - $db->sql_query($convert->truncate_statement . FORUMS_TABLE); - - // Determine the highest id used within the old forums table (we add the categories after the forum ids) - $sql = 'SELECT MAX(forum_id) AS max_forum_id - FROM ' . $convert->src_table_prefix . 'forums'; - $result = $src_db->sql_query($sql); - $max_forum_id = (int) $src_db->sql_fetchfield('max_forum_id'); - $src_db->sql_freeresult($result); - - $max_forum_id++; - - // pruning disabled globally? - $sql = "SELECT config_value - FROM {$convert->src_table_prefix}config - WHERE config_name = 'prune_enable'"; - $result = $src_db->sql_query($sql); - $prune_enabled = (int) $src_db->sql_fetchfield('config_value'); - $src_db->sql_freeresult($result); - - // Insert categories - $sql = 'SELECT cat_id, cat_title - FROM ' . $convert->src_table_prefix . 'categories - ORDER BY cat_order'; - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - - $result = $src_db->sql_query($sql); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - switch ($db->get_sql_layer()) - { - case 'mssql_odbc': - case 'mssqlnative': - $db->sql_query('SET IDENTITY_INSERT ' . FORUMS_TABLE . ' ON'); - break; - } - - $cats_added = array(); - while ($row = $src_db->sql_fetchrow($result)) - { - $sql_ary = array( - 'forum_id' => (int) $max_forum_id, - 'forum_name' => ($row['cat_title']) ? htmlspecialchars(phpbb_set_default_encoding($row['cat_title']), ENT_COMPAT, 'UTF-8') : $user->lang['CATEGORY'], - 'parent_id' => 0, - 'forum_parents' => '', - 'forum_desc' => '', - 'forum_type' => FORUM_CAT, - 'forum_status' => ITEM_UNLOCKED, - 'forum_rules' => '', - ); - - $sql = 'SELECT MAX(right_id) AS right_id - FROM ' . FORUMS_TABLE; - $_result = $db->sql_query($sql); - $cat_row = $db->sql_fetchrow($_result); - $db->sql_freeresult($_result); - - $sql_ary['left_id'] = (int) ($cat_row['right_id'] + 1); - $sql_ary['right_id'] = (int) ($cat_row['right_id'] + 2); - - $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); - $db->sql_query($sql); - - $cats_added[$row['cat_id']] = $max_forum_id; - $max_forum_id++; - } - $src_db->sql_freeresult($result); - - // There may be installations having forums with non-existant category ids. - // We try to catch them and add them to an "unknown" category instead of leaving them out. - $sql = 'SELECT cat_id - FROM ' . $convert->src_table_prefix . 'forums - GROUP BY cat_id'; - $result = $src_db->sql_query($sql); - - $unknown_cat_id = false; - while ($row = $src_db->sql_fetchrow($result)) - { - // Catch those categories not been added before - if (!isset($cats_added[$row['cat_id']])) - { - $unknown_cat_id = true; - } - } - $src_db->sql_freeresult($result); - - // Is there at least one category not known? - if ($unknown_cat_id === true) - { - $unknown_cat_id = 'ghost'; - - $sql_ary = array( - 'forum_id' => (int) $max_forum_id, - 'forum_name' => (string) $user->lang['CATEGORY'], - 'parent_id' => 0, - 'forum_parents' => '', - 'forum_desc' => '', - 'forum_type' => FORUM_CAT, - 'forum_status' => ITEM_UNLOCKED, - 'forum_rules' => '', - ); - - $sql = 'SELECT MAX(right_id) AS right_id - FROM ' . FORUMS_TABLE; - $_result = $db->sql_query($sql); - $cat_row = $db->sql_fetchrow($_result); - $db->sql_freeresult($_result); - - $sql_ary['left_id'] = (int) ($cat_row['right_id'] + 1); - $sql_ary['right_id'] = (int) ($cat_row['right_id'] + 2); - - $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); - $db->sql_query($sql); - - $cats_added[$unknown_cat_id] = $max_forum_id; - } - - // Now insert the forums - $sql = 'SELECT f.forum_id, f.forum_name, f.cat_id, f.forum_desc, f.forum_status, f.prune_enable, f.prune_next, fp.prune_days, fp.prune_freq FROM ' . $convert->src_table_prefix . 'forums f - LEFT JOIN ' . $convert->src_table_prefix . 'forum_prune fp ON f.forum_id = fp.forum_id - GROUP BY f.forum_id, f.forum_name, f.cat_id, f.forum_desc, f.forum_status, f.prune_enable, f.prune_next, f.forum_order, fp.prune_days, fp.prune_freq - ORDER BY f.cat_id, f.forum_order'; - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - - $result = $src_db->sql_query($sql); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - while ($row = $src_db->sql_fetchrow($result)) - { - // Some might have forums here with an id not being "possible"... - // To be somewhat friendly we "change" the category id for those to a previously created ghost category - if (!isset($cats_added[$row['cat_id']]) && $unknown_cat_id !== false) - { - $row['cat_id'] = $unknown_cat_id; - } - - if (!isset($cats_added[$row['cat_id']])) - { - continue; - } - - // Define the new forums sql ary - $sql_ary = array( - 'forum_id' => (int) $row['forum_id'], - 'forum_name' => htmlspecialchars(phpbb_set_default_encoding($row['forum_name']), ENT_COMPAT, 'UTF-8'), - 'parent_id' => (int) $cats_added[$row['cat_id']], - 'forum_parents' => '', - 'forum_desc' => htmlspecialchars(phpbb_set_default_encoding($row['forum_desc']), ENT_COMPAT, 'UTF-8'), - 'forum_type' => FORUM_POST, - 'forum_status' => is_item_locked($row['forum_status']), - 'enable_prune' => ($prune_enabled) ? (int) $row['prune_enable'] : 0, - 'prune_next' => (int) null_to_zero($row['prune_next']), - 'prune_days' => (int) null_to_zero($row['prune_days']), - 'prune_viewed' => 0, - 'prune_freq' => (int) null_to_zero($row['prune_freq']), - - 'forum_flags' => phpbb_forum_flags(), - 'forum_options' => 0, - - // Default values - 'forum_desc_bitfield' => '', - 'forum_desc_options' => 7, - 'forum_desc_uid' => '', - 'forum_link' => '', - 'forum_password' => '', - 'forum_style' => 0, - 'forum_image' => '', - 'forum_rules' => '', - 'forum_rules_link' => '', - 'forum_rules_bitfield' => '', - 'forum_rules_options' => 7, - 'forum_rules_uid' => '', - 'forum_topics_per_page' => 0, - 'forum_posts_approved' => 0, - 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 0, - 'forum_topics_approved' => 0, - 'forum_topics_unapproved' => 0, - 'forum_topics_softdeleted' => 0, - 'forum_last_post_id' => 0, - 'forum_last_poster_id' => 0, - 'forum_last_post_subject' => '', - 'forum_last_post_time' => 0, - 'forum_last_poster_name' => '', - 'forum_last_poster_colour' => '', - 'display_on_index' => 1, - 'enable_indexing' => 1, - 'enable_icons' => 0, - ); - - // Now add the forums with proper left/right ids - $sql = 'SELECT left_id, right_id - FROM ' . FORUMS_TABLE . ' - WHERE forum_id = ' . $cats_added[$row['cat_id']]; - $_result = $db->sql_query($sql); - $cat_row = $db->sql_fetchrow($_result); - $db->sql_freeresult($_result); - - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET left_id = left_id + 2, right_id = right_id + 2 - WHERE left_id > ' . $cat_row['right_id']; - $db->sql_query($sql); - - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET right_id = right_id + 2 - WHERE ' . $cat_row['left_id'] . ' BETWEEN left_id AND right_id'; - $db->sql_query($sql); - - $sql_ary['left_id'] = (int) $cat_row['right_id']; - $sql_ary['right_id'] = (int) ($cat_row['right_id'] + 1); - - $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); - $db->sql_query($sql); - } - $src_db->sql_freeresult($result); - - switch ($db->get_sql_layer()) - { - case 'postgres': - $db->sql_query("SELECT SETVAL('" . FORUMS_TABLE . "_seq',(select case when max(forum_id)>0 then max(forum_id)+1 else 1 end from " . FORUMS_TABLE . '));'); - break; - - case 'mssql_odbc': - case 'mssqlnative': - $db->sql_query('SET IDENTITY_INSERT ' . FORUMS_TABLE . ' OFF'); - break; - - case 'oracle': - $result = $db->sql_query('SELECT MAX(forum_id) as max_id FROM ' . FORUMS_TABLE); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - $largest_id = (int) $row['max_id']; - - if ($largest_id) - { - $db->sql_query('DROP SEQUENCE ' . FORUMS_TABLE . '_seq'); - $db->sql_query('CREATE SEQUENCE ' . FORUMS_TABLE . '_seq START WITH ' . ($largest_id + 1)); - } - break; - } -} - -/** -* Function for recoding text with the default language -* -* @param string $text text to recode to utf8 -* @param bool $grab_user_lang if set to true the function tries to use $convert_row['user_lang'] (and falls back to $convert_row['poster_id']) instead of the boards default language -*/ -function phpbb_set_encoding($text, $grab_user_lang = true) -{ - global $lang_enc_array, $convert_row; - global $convert, $phpEx; - - /*static $lang_enc_array = array( - 'korean' => 'euc-kr', - 'serbian' => 'windows-1250', - 'polish' => 'iso-8859-2', - 'kurdish' => 'windows-1254', - 'slovak' => 'Windows-1250', - 'russian' => 'windows-1251', - 'estonian' => 'iso-8859-4', - 'chinese_simplified' => 'gb2312', - 'macedonian' => 'windows-1251', - 'azerbaijani' => 'UTF-8', - 'romanian' => 'iso-8859-2', - 'romanian_diacritice' => 'iso-8859-2', - 'lithuanian' => 'windows-1257', - 'turkish' => 'iso-8859-9', - 'ukrainian' => 'windows-1251', - 'japanese' => 'shift_jis', - 'hungarian' => 'ISO-8859-2', - 'romanian_no_diacritics' => 'iso-8859-2', - 'mongolian' => 'UTF-8', - 'slovenian' => 'windows-1250', - 'bosnian' => 'windows-1250', - 'czech' => 'Windows-1250', - 'farsi' => 'Windows-1256', - 'croatian' => 'windows-1250', - 'greek' => 'iso-8859-7', - 'russian_tu' => 'windows-1251', - 'sakha' => 'UTF-8', - 'serbian_cyrillic' => 'windows-1251', - 'bulgarian' => 'windows-1251', - 'chinese_traditional_taiwan' => 'big5', - 'chinese_traditional' => 'big5', - 'arabic' => 'windows-1256', - 'hebrew' => 'WINDOWS-1255', - 'thai' => 'windows-874', - //'chinese_traditional_taiwan' => 'utf-8' // custom modified, we may have to do an include :-( - );*/ - - if (empty($lang_enc_array)) - { - $lang_enc_array = array(); - } - - $get_lang = trim(get_config_value('default_lang')); - - // Do we need the users language encoding? - if ($grab_user_lang && !empty($convert_row)) - { - if (!empty($convert_row['user_lang'])) - { - $get_lang = trim($convert_row['user_lang']); - } - else if (!empty($convert_row['poster_id'])) - { - global $src_db, $same_db; - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - - $sql = 'SELECT user_lang - FROM ' . $convert->src_table_prefix . 'users - WHERE user_id = ' . (int) $convert_row['poster_id']; - $result = $src_db->sql_query($sql); - $get_lang = (string) $src_db->sql_fetchfield('user_lang'); - $src_db->sql_freeresult($result); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - $get_lang = (!trim($get_lang)) ? trim(get_config_value('default_lang')) : trim($get_lang); - } - } - - if (!isset($lang_enc_array[$get_lang])) - { - $filename = $convert->options['forum_path'] . '/language/lang_' . $get_lang . '/lang_main.' . $phpEx; - - if (!file_exists($filename)) - { - $get_lang = trim(get_config_value('default_lang')); - } - - if (!isset($lang_enc_array[$get_lang])) - { - include($convert->options['forum_path'] . '/language/lang_' . $get_lang . '/lang_main.' . $phpEx); - $lang_enc_array[$get_lang] = $lang['ENCODING']; - unset($lang); - } - } - - return utf8_recode($text, $lang_enc_array[$get_lang]); -} - -/** -* Same as phpbb_set_encoding, but forcing boards default language -*/ -function phpbb_set_default_encoding($text) -{ - return phpbb_set_encoding($text, false); -} - -/** -* Convert Birthday from Birthday MOD to phpBB Format -*/ -function phpbb_get_birthday($birthday = '') -{ - if (defined('MOD_BIRTHDAY_TERRA')) - { - $birthday = (string) $birthday; - - // stored as month, day, year - if (!$birthday) - { - return ' 0- 0- 0'; - } - - // We use the original mod code to retrieve the birthday (not ideal) - preg_match('/(..)(..)(....)/', sprintf('%08d', $birthday), $birthday_parts); - - $month = $birthday_parts[1]; - $day = $birthday_parts[2]; - $year = $birthday_parts[3]; - - return sprintf('%2d-%2d-%4d', $day, $month, $year); - } - else - { - $birthday = (int) $birthday; - - if (!$birthday || $birthday == 999999) - { - return ' 0- 0- 0'; - } - - // The birthday mod from niels is using this code to transform to day/month/year - return sprintf('%2d-%2d-%4d', gmdate('j', $birthday * 86400 + 1), gmdate('n', $birthday * 86400 + 1), gmdate('Y', $birthday * 86400 + 1)); - } -} - -/** -* Return correct user id value -* Everyone's id will be one higher to allow the guest/anonymous user to have a positive id as well -*/ -function phpbb_user_id($user_id) -{ - global $config; - - // Increment user id if the old forum is having a user with the id 1 - if (!isset($config['increment_user_id'])) - { - global $src_db, $same_db, $convert; - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - - // Now let us set a temporary config variable for user id incrementing - $sql = "SELECT user_id - FROM {$convert->src_table_prefix}users - WHERE user_id = 1"; - $result = $src_db->sql_query($sql); - $id = (int) $src_db->sql_fetchfield('user_id'); - $src_db->sql_freeresult($result); - - // Try to get the maximum user id possible... - $sql = "SELECT MAX(user_id) AS max_user_id - FROM {$convert->src_table_prefix}users"; - $result = $src_db->sql_query($sql); - $max_id = (int) $src_db->sql_fetchfield('max_user_id'); - $src_db->sql_freeresult($result); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - // If there is a user id 1, we need to increment user ids. :/ - if ($id === 1) - { - $config->set('increment_user_id', ($max_id + 1), false); - $config['increment_user_id'] = $max_id + 1; - } - else - { - $config->set('increment_user_id', 0, false); - $config['increment_user_id'] = 0; - } - } - - // If the old user id is -1 in 2.0.x it is the anonymous user... - if ($user_id == -1) - { - return ANONYMOUS; - } - - if (!empty($config['increment_user_id']) && $user_id == 1) - { - return $config['increment_user_id']; - } - - // A user id of 0 can happen, for example within the ban table if no user is banned... - // Within the posts and topics table this can be "dangerous" but is the fault of the user - // having mods installed (a poster id of 0 is not possible in 2.0.x). - // Therefore, we return the user id "as is". - - return (int) $user_id; -} - -/** -* Return correct user id value -* Everyone's id will be one higher to allow the guest/anonymous user to have a positive id as well -*/ -function phpbb_topic_replies_to_posts($num_replies) -{ - return (int) $num_replies + 1; -} - -/* Copy additional table fields from old forum to new forum if user wants this (for Mod compatibility for example) -function phpbb_copy_table_fields() -{ -} -*/ - -/** -* Convert authentication -* user, group and forum table has to be filled in order to work -*/ -function phpbb_convert_authentication($mode) -{ - global $db, $src_db, $same_db, $convert, $config; - - if ($mode == 'start') - { - $db->sql_query($convert->truncate_statement . ACL_USERS_TABLE); - $db->sql_query($convert->truncate_statement . ACL_GROUPS_TABLE); - - // What we will do is handling all 2.0.x admins as founder to replicate what is common in 2.0.x. - // After conversion the main admin need to make sure he is removing permissions and the founder status if wanted. - - // Grab user ids of users with user_level of ADMIN - $sql = "SELECT user_id - FROM {$convert->src_table_prefix}users - WHERE user_level = 1 - ORDER BY user_regdate ASC"; - $result = $src_db->sql_query($sql); - - while ($row = $src_db->sql_fetchrow($result)) - { - $user_id = (int) phpbb_user_id($row['user_id']); - // Set founder admin... - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_type = ' . USER_FOUNDER . " - WHERE user_id = $user_id"; - $db->sql_query($sql); - } - $src_db->sql_freeresult($result); - - $sql = 'SELECT group_id - FROM ' . GROUPS_TABLE . " - WHERE group_name = '" . $db->sql_escape('BOTS') . "'"; - $result = $db->sql_query($sql); - $bot_group_id = (int) $db->sql_fetchfield('group_id'); - $db->sql_freeresult($result); - } - - // Grab forum auth information - $sql = "SELECT * - FROM {$convert->src_table_prefix}forums"; - $result = $src_db->sql_query($sql); - - $forum_access = array(); - while ($row = $src_db->sql_fetchrow($result)) - { - $forum_access[$row['forum_id']] = $row; - } - $src_db->sql_freeresult($result); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - // Grab user auth information from 2.0.x board - $sql = "SELECT ug.user_id, aa.* - FROM {$convert->src_table_prefix}auth_access aa, {$convert->src_table_prefix}user_group ug, {$convert->src_table_prefix}groups g, {$convert->src_table_prefix}forums f - WHERE g.group_id = aa.group_id - AND g.group_single_user = 1 - AND ug.group_id = g.group_id - AND f.forum_id = aa.forum_id"; - $result = $src_db->sql_query($sql); - - $user_access = array(); - while ($row = $src_db->sql_fetchrow($result)) - { - $user_access[$row['forum_id']][] = $row; - } - $src_db->sql_freeresult($result); - - // Grab group auth information - $sql = "SELECT g.group_id, aa.* - FROM {$convert->src_table_prefix}auth_access aa, {$convert->src_table_prefix}groups g - WHERE g.group_id = aa.group_id - AND g.group_single_user <> 1"; - $result = $src_db->sql_query($sql); - - $group_access = array(); - while ($row = $src_db->sql_fetchrow($result)) - { - $group_access[$row['forum_id']][] = $row; - } - $src_db->sql_freeresult($result); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - // Add Forum Access List - $auth_map = array( - 'auth_view' => array('f_', 'f_list'), - 'auth_read' => array('f_read', 'f_search'), - 'auth_post' => array('f_post', 'f_bbcode', 'f_smilies', 'f_img', 'f_sigs', 'f_postcount', 'f_report', 'f_subscribe', 'f_print', 'f_email'), - 'auth_reply' => 'f_reply', - 'auth_edit' => 'f_edit', - 'auth_delete' => 'f_delete', - 'auth_pollcreate' => 'f_poll', - 'auth_vote' => 'f_vote', - 'auth_announce' => array('f_announce', 'f_announce_global'), - 'auth_sticky' => 'f_sticky', - 'auth_attachments' => array('f_attach', 'f_download'), - 'auth_download' => 'f_download', - ); - - // Define the ACL constants used in 2.0 to make the code slightly more readable - define('AUTH_ALL', 0); - define('AUTH_REG', 1); - define('AUTH_ACL', 2); - define('AUTH_MOD', 3); - define('AUTH_ADMIN', 5); - - // A mapping of the simple permissions used by 2.0 - $simple_auth_ary = array( - 'public' => array( - 'auth_view' => AUTH_ALL, - 'auth_read' => AUTH_ALL, - 'auth_post' => AUTH_ALL, - 'auth_reply' => AUTH_ALL, - 'auth_edit' => AUTH_REG, - 'auth_delete' => AUTH_REG, - 'auth_sticky' => AUTH_MOD, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_REG, - 'auth_pollcreate' => AUTH_REG, - ), - 'registered' => array( - 'auth_view' => AUTH_ALL, - 'auth_read' => AUTH_ALL, - 'auth_post' => AUTH_REG, - 'auth_reply' => AUTH_REG, - 'auth_edit' => AUTH_REG, - 'auth_delete' => AUTH_REG, - 'auth_sticky' => AUTH_MOD, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_REG, - 'auth_pollcreate' => AUTH_REG, - ), - 'registered_hidden' => array( - 'auth_view' => AUTH_REG, - 'auth_read' => AUTH_REG, - 'auth_post' => AUTH_REG, - 'auth_reply' => AUTH_REG, - 'auth_edit' => AUTH_REG, - 'auth_delete' => AUTH_REG, - 'auth_sticky' => AUTH_MOD, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_REG, - 'auth_pollcreate' => AUTH_REG, - ), - 'private' => array( - 'auth_view' => AUTH_ALL, - 'auth_read' => AUTH_ACL, - 'auth_post' => AUTH_ACL, - 'auth_reply' => AUTH_ACL, - 'auth_edit' => AUTH_ACL, - 'auth_delete' => AUTH_ACL, - 'auth_sticky' => AUTH_ACL, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_ACL, - 'auth_pollcreate' => AUTH_ACL, - ), - 'private_hidden' => array( - 'auth_view' => AUTH_ACL, - 'auth_read' => AUTH_ACL, - 'auth_post' => AUTH_ACL, - 'auth_reply' => AUTH_ACL, - 'auth_edit' => AUTH_ACL, - 'auth_delete' => AUTH_ACL, - 'auth_sticky' => AUTH_ACL, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_ACL, - 'auth_pollcreate' => AUTH_ACL, - ), - 'moderator' => array( - 'auth_view' => AUTH_ALL, - 'auth_read' => AUTH_MOD, - 'auth_post' => AUTH_MOD, - 'auth_reply' => AUTH_MOD, - 'auth_edit' => AUTH_MOD, - 'auth_delete' => AUTH_MOD, - 'auth_sticky' => AUTH_MOD, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_MOD, - 'auth_pollcreate' => AUTH_MOD, - ), - 'moderator_hidden' => array( - 'auth_view' => AUTH_MOD, - 'auth_read' => AUTH_MOD, - 'auth_post' => AUTH_MOD, - 'auth_reply' => AUTH_MOD, - 'auth_edit' => AUTH_MOD, - 'auth_delete' => AUTH_MOD, - 'auth_sticky' => AUTH_MOD, - 'auth_announce' => AUTH_MOD, - 'auth_vote' => AUTH_MOD, - 'auth_pollcreate' => AUTH_MOD, - ), - ); - - if ($mode == 'start') - { - user_group_auth('guests', 'SELECT user_id, {GUESTS} FROM ' . USERS_TABLE . ' WHERE user_id = ' . ANONYMOUS, false); - user_group_auth('registered', 'SELECT user_id, {REGISTERED} FROM ' . USERS_TABLE . ' WHERE user_id <> ' . ANONYMOUS . " AND group_id <> $bot_group_id", false); - - // Selecting from old table - if (!empty($config['increment_user_id'])) - { - $auth_sql = 'SELECT user_id, {ADMINISTRATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id <> 1'; - user_group_auth('administrators', $auth_sql, true); - - $auth_sql = 'SELECT ' . $config['increment_user_id'] . ' as user_id, {ADMINISTRATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id = 1'; - user_group_auth('administrators', $auth_sql, true); - } - else - { - $auth_sql = 'SELECT user_id, {ADMINISTRATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1'; - user_group_auth('administrators', $auth_sql, true); - } - - if (!empty($config['increment_user_id'])) - { - $auth_sql = 'SELECT user_id, {GLOBAL_MODERATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id <> 1'; - user_group_auth('global_moderators', $auth_sql, true); - - $auth_sql = 'SELECT ' . $config['increment_user_id'] . ' as user_id, {GLOBAL_MODERATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id = 1'; - user_group_auth('global_moderators', $auth_sql, true); - } - else - { - $auth_sql = 'SELECT user_id, {GLOBAL_MODERATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1'; - user_group_auth('global_moderators', $auth_sql, true); - } - } - else if ($mode == 'first') - { - // Go through all 2.0.x forums - foreach ($forum_access as $forum) - { - $new_forum_id = (int) $forum['forum_id']; - - // Administrators have full access to all forums whatever happens - mass_auth('group_role', $new_forum_id, 'administrators', 'FORUM_FULL'); - - $matched_type = ''; - foreach ($simple_auth_ary as $key => $auth_levels) - { - $matched = 1; - foreach ($auth_levels as $k => $level) - { - if ($forum[$k] != $auth_levels[$k]) - { - $matched = 0; - } - } - - if ($matched) - { - $matched_type = $key; - break; - } - } - - switch ($matched_type) - { - case 'public': - mass_auth('group_role', $new_forum_id, 'guests', 'FORUM_LIMITED'); - mass_auth('group_role', $new_forum_id, 'registered', 'FORUM_LIMITED_POLLS'); - mass_auth('group_role', $new_forum_id, 'bots', 'FORUM_BOT'); - break; - - case 'registered': - mass_auth('group_role', $new_forum_id, 'guests', 'FORUM_READONLY'); - mass_auth('group_role', $new_forum_id, 'bots', 'FORUM_BOT'); - - // no break; - - case 'registered_hidden': - mass_auth('group_role', $new_forum_id, 'registered', 'FORUM_POLLS'); - break; - - case 'private': - case 'private_hidden': - case 'moderator': - case 'moderator_hidden': - default: - // The permissions don't match a simple set, so we're going to have to map them directly - - // No post approval for all, in 2.0.x this feature does not exist - mass_auth('group', $new_forum_id, 'guests', 'f_noapprove', ACL_YES); - mass_auth('group', $new_forum_id, 'registered', 'f_noapprove', ACL_YES); - - // Go through authentication map - foreach ($auth_map as $old_auth_key => $new_acl) - { - // If old authentication key does not exist we continue - // This is helpful for mods adding additional authentication fields, we need to add them to the auth_map array - if (!isset($forum[$old_auth_key])) - { - continue; - } - - // Now set the new ACL correctly - switch ($forum[$old_auth_key]) - { - // AUTH_ALL - case AUTH_ALL: - mass_auth('group', $new_forum_id, 'guests', $new_acl, ACL_YES); - mass_auth('group', $new_forum_id, 'bots', $new_acl, ACL_YES); - mass_auth('group', $new_forum_id, 'registered', $new_acl, ACL_YES); - break; - - // AUTH_REG - case AUTH_REG: - mass_auth('group', $new_forum_id, 'registered', $new_acl, ACL_YES); - break; - - // AUTH_ACL - case AUTH_ACL: - // Go through the old group access list for this forum - if (isset($group_access[$forum['forum_id']])) - { - foreach ($group_access[$forum['forum_id']] as $index => $access) - { - // We only check for ACL_YES equivalence entry - if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) - { - mass_auth('group', $new_forum_id, (int) $access['group_id'], $new_acl, ACL_YES); - } - } - } - - if (isset($user_access[$forum['forum_id']])) - { - foreach ($user_access[$forum['forum_id']] as $index => $access) - { - // We only check for ACL_YES equivalence entry - if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) - { - mass_auth('user', $new_forum_id, (int) phpbb_user_id($access['user_id']), $new_acl, ACL_YES); - } - } - } - break; - - // AUTH_MOD - case AUTH_MOD: - if (isset($group_access[$forum['forum_id']])) - { - foreach ($group_access[$forum['forum_id']] as $index => $access) - { - // We only check for ACL_YES equivalence entry - if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) - { - mass_auth('group', $new_forum_id, (int) $access['group_id'], $new_acl, ACL_YES); - } - } - } - - if (isset($user_access[$forum['forum_id']])) - { - foreach ($user_access[$forum['forum_id']] as $index => $access) - { - // We only check for ACL_YES equivalence entry - if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) - { - mass_auth('user', $new_forum_id, (int) phpbb_user_id($access['user_id']), $new_acl, ACL_YES); - } - } - } - break; - } - } - break; - } - } - } - else if ($mode == 'second') - { - // Assign permission roles and other default permissions - - // guests having u_download and u_search ability - $db->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) SELECT ' . get_group_id('guests') . ', 0, auth_option_id, 0, 1 FROM ' . ACL_OPTIONS_TABLE . " WHERE auth_option IN ('u_', 'u_download', 'u_search')"); - - // administrators/global mods having full user features - mass_auth('group_role', 0, 'administrators', 'USER_FULL'); - mass_auth('group_role', 0, 'global_moderators', 'USER_FULL'); - - // By default all converted administrators are given full access - mass_auth('group_role', 0, 'administrators', 'ADMIN_FULL'); - - // All registered users are assigned the standard user role - mass_auth('group_role', 0, 'registered', 'USER_STANDARD'); - mass_auth('group_role', 0, 'registered_coppa', 'USER_STANDARD'); - - // Instead of administrators being global moderators we give the MOD_FULL role to global mods (admins already assigned to this group) - mass_auth('group_role', 0, 'global_moderators', 'MOD_FULL'); - - // And now those who have had their avatar rights removed get assigned a more restrictive role - $sql = 'SELECT user_id FROM ' . $convert->src_table_prefix . 'users - WHERE user_allowavatar = 0 - AND user_id > 0'; - $result = $src_db->sql_query($sql); - - while ($row = $src_db->sql_fetchrow($result)) - { - mass_auth('user_role', 0, (int) phpbb_user_id($row['user_id']), 'USER_NOAVATAR'); - } - $src_db->sql_freeresult($result); - - // And the same for those who have had their PM rights removed - $sql = 'SELECT user_id FROM ' . $convert->src_table_prefix . 'users - WHERE user_allow_pm = 0 - AND user_id > 0'; - $result = $src_db->sql_query($sql); - - while ($row = $src_db->sql_fetchrow($result)) - { - mass_auth('user_role', 0, (int) phpbb_user_id($row['user_id']), 'USER_NOPM'); - } - $src_db->sql_freeresult($result); - } - else if ($mode == 'third') - { - // And now the moderators - // We make sure that they have at least standard access to the forums they moderate in addition to the moderating permissions - - $mod_post_map = array( - 'auth_announce' => array('f_announce', 'f_announce_global'), - 'auth_sticky' => 'f_sticky' - ); - - foreach ($user_access as $forum_id => $access_map) - { - $forum_id = (int) $forum_id; - - foreach ($access_map as $access) - { - if (isset($access['auth_mod']) && $access['auth_mod'] == 1) - { - mass_auth('user_role', $forum_id, (int) phpbb_user_id($access['user_id']), 'MOD_STANDARD'); - mass_auth('user_role', $forum_id, (int) phpbb_user_id($access['user_id']), 'FORUM_STANDARD'); - foreach ($mod_post_map as $old => $new) - { - if (isset($forum_access[$forum_id]) && isset($forum_access[$forum_id][$old]) && $forum_access[$forum_id][$old] == AUTH_MOD) - { - mass_auth('user', $forum_id, (int) phpbb_user_id($access['user_id']), $new, ACL_YES); - } - } - } - } - } - - foreach ($group_access as $forum_id => $access_map) - { - $forum_id = (int) $forum_id; - - foreach ($access_map as $access) - { - if (isset($access['auth_mod']) && $access['auth_mod'] == 1) - { - mass_auth('group_role', $forum_id, (int) $access['group_id'], 'MOD_STANDARD'); - mass_auth('group_role', $forum_id, (int) $access['group_id'], 'FORUM_STANDARD'); - foreach ($mod_post_map as $old => $new) - { - if (isset($forum_access[$forum_id]) && isset($forum_access[$forum_id][$old]) && $forum_access[$forum_id][$old] == AUTH_MOD) - { - mass_auth('group', $forum_id, (int) $access['group_id'], $new, ACL_YES); - } - } - } - } - } - - // We grant everyone readonly access to the categories to ensure that the forums are visible - $sql = 'SELECT forum_id, forum_name, parent_id, left_id, right_id - FROM ' . FORUMS_TABLE . ' - ORDER BY left_id ASC'; - $result = $db->sql_query($sql); - - $parent_forums = $forums = array(); - while ($row = $db->sql_fetchrow($result)) - { - if ($row['parent_id'] == 0) - { - mass_auth('group_role', $row['forum_id'], 'administrators', 'FORUM_FULL'); - mass_auth('group_role', $row['forum_id'], 'global_moderators', 'FORUM_FULL'); - $parent_forums[] = $row; - } - else - { - $forums[] = $row; - } - } - $db->sql_freeresult($result); - - global $auth; - - // Let us see which groups have access to these forums... - foreach ($parent_forums as $row) - { - // Get the children - $branch = $forum_ids = array(); - - foreach ($forums as $key => $_row) - { - if ($_row['left_id'] > $row['left_id'] && $_row['left_id'] < $row['right_id']) - { - $branch[] = $_row; - $forum_ids[] = $_row['forum_id']; - continue; - } - } - - if (count($forum_ids)) - { - // Now make sure the user is able to read these forums - $hold_ary = $auth->acl_group_raw_data(false, 'f_list', $forum_ids); - - if (empty($hold_ary)) - { - continue; - } - - foreach ($hold_ary as $g_id => $f_id_ary) - { - $set_group = false; - - foreach ($f_id_ary as $f_id => $auth_ary) - { - foreach ($auth_ary as $auth_option => $setting) - { - if ($setting == ACL_YES) - { - $set_group = true; - break 2; - } - } - } - - if ($set_group) - { - mass_auth('group', $row['forum_id'], $g_id, 'f_list', ACL_YES); - } - } - } - } - } -} - -/** -* Set primary group. -* Really simple and only based on user_level (remaining groups will be assigned later) -*/ -function phpbb_set_primary_group($user_level) -{ - global $convert_row; - - if ($user_level == 1) - { - return get_group_id('administrators'); - } -/* else if ($user_level == 2) - { - return get_group_id('global_moderators'); - } - else if ($user_level == 0 && $convert_row['user_active'])*/ - else if ($convert_row['user_active']) - { - return get_group_id('registered'); - } - - return 0; -} - -/** -* Convert the group name, making sure to avoid conflicts with 3.0 special groups -*/ -function phpbb_convert_group_name($group_name) -{ - $default_groups = array( - 'GUESTS', - 'REGISTERED', - 'REGISTERED_COPPA', - 'GLOBAL_MODERATORS', - 'ADMINISTRATORS', - 'BOTS', - ); - - if (in_array(strtoupper($group_name), $default_groups)) - { - return 'phpBB2 - ' . $group_name; - } - - return phpbb_set_default_encoding($group_name); -} - -/** -* Convert the group type constants -*/ -function phpbb_convert_group_type($group_type) -{ - switch ($group_type) - { - case 0: - return GROUP_OPEN; - break; - - case 1: - return GROUP_CLOSED; - break; - - case 2: - return GROUP_HIDDEN; - break; - } - - // Never return GROUP_SPECIAL here, because only phpBB's default groups are allowed to have this type set. - return GROUP_HIDDEN; -} - -/** -* Convert the topic type constants -*/ -function phpbb_convert_topic_type($topic_type) -{ - switch ($topic_type) - { - case 0: - return POST_NORMAL; - break; - - case 1: - return POST_STICKY; - break; - - case 2: - return POST_ANNOUNCE; - break; - - case 3: - return POST_GLOBAL; - break; - } - - return POST_NORMAL; -} - -function phpbb_replace_size($matches) -{ - return '[size=' . min(200, ceil(100.0 * (((double) $matches[1])/12.0))) . ':' . $matches[2] . ']'; -} - -/** -* Reparse the message stripping out the bbcode_uid values and adding new ones and setting the bitfield -* @todo What do we want to do about HTML in messages - currently it gets converted to the entities, but there may be some objections to this -*/ -function phpbb_prepare_message($message) -{ - global $convert, $user, $convert_row, $message_parser; - - if (!$message) - { - $convert->row['mp_bbcode_bitfield'] = $convert_row['mp_bbcode_bitfield'] = 0; - return ''; - } - - // Decode phpBB 2.0.x Message - if (isset($convert->row['old_bbcode_uid']) && $convert->row['old_bbcode_uid'] != '') - { - // Adjust size... - if (strpos($message, '[size=') !== false) - { - $message = preg_replace_callback('/\[size=(\d*):(' . $convert->row['old_bbcode_uid'] . ')\]/', 'phpbb_replace_size', $message); - } - - $message = preg_replace('/\:(([a-z0-9]:)?)' . $convert->row['old_bbcode_uid'] . '/s', '', $message); - } - - if (strpos($message, '[quote=') !== false) - { - $message = preg_replace('/\[quote="(.*?)"\]/s', '[quote="\1"]', $message); - $message = preg_replace('/\[quote=\\\"(.*?)\\\"\]/s', '[quote="\1"]', $message); - - // let's hope that this solves more problems than it causes. Deal with escaped quotes. - $message = str_replace('\"', '"', $message); - $message = str_replace('\"', '"', $message); - } - - $message = str_replace('
      ', "\n", $message); - $message = str_replace('<', '<', $message); - $message = str_replace('>', '>', $message); - - // make the post UTF-8 - $message = phpbb_set_encoding($message); - - $message_parser->warn_msg = array(); // Reset the errors from the previous message - $message_parser->bbcode_uid = make_uid($convert->row['post_time']); - $message_parser->message = $message; - unset($message); - - // Make sure options are set. -// $enable_html = (!isset($row['enable_html'])) ? false : $row['enable_html']; - $enable_bbcode = (!isset($convert->row['enable_bbcode'])) ? true : $convert->row['enable_bbcode']; - $enable_smilies = (!isset($convert->row['enable_smilies'])) ? true : $convert->row['enable_smilies']; - $enable_magic_url = (!isset($convert->row['enable_magic_url'])) ? true : $convert->row['enable_magic_url']; - - // parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post') - $message_parser->parse($enable_bbcode, $enable_magic_url, $enable_smilies); - - if (count($message_parser->warn_msg)) - { - $msg_id = isset($convert->row['post_id']) ? $convert->row['post_id'] : $convert->row['privmsgs_id']; - $convert->p_master->error('' . $user->lang['POST_ID'] . ': ' . $msg_id . ' ' . $user->lang['CONV_ERROR_MESSAGE_PARSER'] . ':

      ' . implode('
      ', $message_parser->warn_msg), __LINE__, __FILE__, true); - } - - $convert->row['mp_bbcode_bitfield'] = $convert_row['mp_bbcode_bitfield'] = $message_parser->bbcode_bitfield; - - $message = $message_parser->message; - unset($message_parser->message); - - return $message; -} - -/** -* Return the bitfield calculated by the previous function -*/ -function get_bbcode_bitfield() -{ - global $convert_row; - - return $convert_row['mp_bbcode_bitfield']; -} - -/** -* Determine the last user to edit a post -* In practice we only tracked edits by the original poster in 2.0.x so this will only be set if they had edited their own post -*/ -function phpbb_post_edit_user() -{ - global $convert_row; - - if (isset($convert_row['post_edit_count'])) - { - return phpbb_user_id($convert_row['poster_id']); - } - - return 0; -} - -/** -* Obtain the path to uploaded files on the 2.0.x forum -* This is only used if the Attachment MOD was installed -*/ -function phpbb_get_files_dir() -{ - if (!defined('MOD_ATTACHMENT')) - { - return ''; - } - - global $src_db, $same_db, $convert, $user; - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - $sql = 'SELECT config_value AS upload_dir - FROM ' . $convert->src_table_prefix . "attachments_config - WHERE config_name = 'upload_dir'"; - $result = $src_db->sql_query($sql); - $upload_path = $src_db->sql_fetchfield('upload_dir'); - $src_db->sql_freeresult($result); - - $sql = 'SELECT config_value AS ftp_upload - FROM ' . $convert->src_table_prefix . "attachments_config - WHERE config_name = 'allow_ftp_upload'"; - $result = $src_db->sql_query($sql); - $ftp_upload = (int) $src_db->sql_fetchfield('ftp_upload'); - $src_db->sql_freeresult($result); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - if ($ftp_upload) - { - $convert->p_master->error($user->lang['CONV_ERROR_ATTACH_FTP_DIR'], __LINE__, __FILE__); - } - - return $upload_path; -} - -/** -* Copy thumbnails of uploaded images from the 2.0.x forum -* This is only used if the Attachment MOD was installed -*/ -function phpbb_copy_thumbnails() -{ - global $convert, $config, $phpbb_root_path; - - $src_path = $convert->options['forum_path'] . '/' . phpbb_get_files_dir() . '/thumbs/'; - - if ($handle = @opendir($src_path)) - { - while ($entry = readdir($handle)) - { - if ($entry[0] == '.') - { - continue; - } - - if (is_dir($src_path . $entry)) - { - continue; - } - else - { - copy_file($src_path . $entry, $config['upload_path'] . '/' . preg_replace('/^t_/', 'thumb_', $entry)); - @unlink($phpbb_root_path . $config['upload_path'] . '/thumbs/' . $entry); - } - } - closedir($handle); - } -} - -/** -* Convert the attachment category constants -* This is only used if the Attachment MOD was installed -*/ -function phpbb_attachment_category($cat_id) -{ - switch ($cat_id) - { - case 1: - return attachment_category::IMAGE; - break; - - case 2: - return ATTACHMENT_CATEGORY_WM; - break; - } - - return attachment_category::NONE; -} - -/** -* Convert the attachment extension names -* This is only used if the Attachment MOD was installed -*/ -function phpbb_attachment_extension_group_name() -{ - global $db, $phpbb_root_path, $phpEx; - - // Update file extension group names to use language strings. - $sql = 'SELECT lang_dir - FROM ' . LANG_TABLE; - $result = $db->sql_query($sql); - - $extension_groups_updated = array(); - while ($row = $db->sql_fetchrow($result)) - { - $lang_dir = basename($row['lang_dir']); - $lang_file = $phpbb_root_path . 'language/' . $lang_dir . '/acp/attachments.' . $phpEx; - - if (!file_exists($lang_file)) - { - continue; - } - - $lang = array(); - include($lang_file); - - foreach ($lang as $lang_key => $lang_val) - { - if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) - { - continue; - } - - $sql_ary = array( - 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' - ); - - $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' - SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " - WHERE group_name = '" . $db->sql_escape($lang_val) . "'"; - $db->sql_query($sql); - - $extension_groups_updated[$lang_key] = true; - } - } - $db->sql_freeresult($result); -} - -/** -* Obtain list of forums in which different attachment categories can be used -*/ -function phpbb_attachment_forum_perms($forum_permissions) -{ - if (empty($forum_permissions)) - { - return ''; - } - - // Decode forum permissions - $forum_ids = array(); - - $one_char_encoding = '#'; - $two_char_encoding = '.'; - - $auth_len = 1; - for ($pos = 0; $pos < strlen($forum_permissions); $pos += $auth_len) - { - $forum_auth = substr($forum_permissions, $pos, 1); - if ($forum_auth == $one_char_encoding) - { - $auth_len = 1; - continue; - } - else if ($forum_auth == $two_char_encoding) - { - $auth_len = 2; - $pos--; - continue; - } - - $forum_auth = substr($forum_permissions, $pos, $auth_len); - $forum_id = base64_unpack($forum_auth); - - $forum_ids[] = (int) $forum_id; - } - - if (count($forum_ids)) - { - return attachment_forum_perms($forum_ids); - } - - return ''; -} - -/** -* Convert the avatar type constants -*/ -function phpbb_avatar_type($type) -{ - switch ($type) - { - case 1: - return AVATAR_UPLOAD; - - case 3: - return AVATAR_GALLERY; - } - - return 0; -} - - -/** -* Just undos the replacing of '<' and '>' -*/ -function phpbb_smilie_html_decode($code) -{ - $code = str_replace('<', '<', $code); - return str_replace('>', '>', $code); -} - -/** -* Transfer avatars, copying the image if it was uploaded -*/ -function phpbb_import_avatar($user_avatar) -{ - global $convert_row; - - if (!$convert_row['user_avatar_type']) - { - return ''; - } - else if ($convert_row['user_avatar_type'] == 1) - { - // Uploaded avatar - return import_avatar($user_avatar, false, $convert_row['user_id']); - } - else if ($convert_row['user_avatar_type'] == 3) - { - // Gallery avatar - return $user_avatar; - } - - return ''; -} - - -/** -* Find out about the avatar's dimensions -*/ -function phpbb_get_avatar_height($user_avatar) -{ - global $convert_row; - - if (empty($convert_row['user_avatar_type'])) - { - return 0; - } - return get_avatar_height($user_avatar, 'phpbb_avatar_type', $convert_row['user_avatar_type']); -} - - -/** -* Find out about the avatar's dimensions -*/ -function phpbb_get_avatar_width($user_avatar) -{ - global $convert_row; - - if (empty($convert_row['user_avatar_type'])) - { - return 0; - } - - return get_avatar_width($user_avatar, 'phpbb_avatar_type', $convert_row['user_avatar_type']); -} - - -/** -* Calculate the correct to_address field for private messages -*/ -function phpbb_privmsgs_to_userid($to_userid) -{ - return 'u_' . phpbb_user_id($to_userid); -} - -/** -* Calculate whether a private message was unread using the bitfield -*/ -function phpbb_unread_pm($pm_type) -{ - return ($pm_type == 5) ? 1 : 0; -} - -/** -* Calculate whether a private message was new using the bitfield -*/ -function phpbb_new_pm($pm_type) -{ - return ($pm_type == 1) ? 1 : 0; -} - -/** -* Obtain the folder_id for the custom folder created to replace the savebox from 2.0.x (used to store saved private messages) -*/ -function phpbb_get_savebox_id($user_id) -{ - global $db; - - $user_id = phpbb_user_id($user_id); - - // Only one custom folder, check only one - $sql = 'SELECT folder_id - FROM ' . PRIVMSGS_FOLDER_TABLE . ' - WHERE user_id = ' . $user_id; - $result = $db->sql_query_limit($sql, 1); - $folder_id = (int) $db->sql_fetchfield('folder_id'); - $db->sql_freeresult($result); - - return $folder_id; -} - -/** -* Transfer attachment specific configuration options -* These were not stored in the main config table on 2.0.x -* This is only used if the Attachment MOD was installed -*/ -function phpbb_import_attach_config() -{ - global $src_db, $same_db, $convert, $config; - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'binary'"); - } - - $sql = 'SELECT * - FROM ' . $convert->src_table_prefix . 'attachments_config'; - $result = $src_db->sql_query($sql); - - if ($convert->mysql_convert && $same_db) - { - $src_db->sql_query("SET NAMES 'utf8'"); - } - - $attach_config = array(); - while ($row = $src_db->sql_fetchrow($result)) - { - $attach_config[$row['config_name']] = $row['config_value']; - } - $src_db->sql_freeresult($result); - - $config->set('allow_attachments', 1); - - // old attachment mod? Must be very old if this entry do not exist... - if (!empty($attach_config['display_order'])) - { - $config->set('display_order', $attach_config['display_order']); - } - $config->set('max_filesize', $attach_config['max_filesize']); - $config->set('max_filesize_pm', $attach_config['max_filesize_pm']); - $config->set('attachment_quota', $attach_config['attachment_quota']); - $config->set('max_attachments', $attach_config['max_attachments']); - $config->set('max_attachments_pm', $attach_config['max_attachments_pm']); - $config->set('allow_pm_attach', $attach_config['allow_pm_attach']); - - $config->set('img_display_inlined', $attach_config['img_display_inlined']); - $config->set('img_max_width', $attach_config['img_max_width']); - $config->set('img_max_height', $attach_config['img_max_height']); - $config->set('img_create_thumbnail', $attach_config['img_create_thumbnail']); - $config->set('img_max_thumb_width', 400); - $config->set('img_min_thumb_filesize', $attach_config['img_min_thumb_filesize']); -} - -/** -* Calculate the date a user became inactive -*/ -function phpbb_inactive_time() -{ - global $convert_row; - - if ($convert_row['user_active']) - { - return 0; - } - - if ($convert_row['user_lastvisit']) - { - return $convert_row['user_lastvisit']; - } - - return $convert_row['user_regdate']; -} - -/** -* Calculate the reason a user became inactive -* We can't actually tell the difference between a manual deactivation and one for profile changes -* from the data available to assume the latter -*/ -function phpbb_inactive_reason() -{ - global $convert_row; - - if ($convert_row['user_active']) - { - return 0; - } - - if ($convert_row['user_lastvisit']) - { - return INACTIVE_PROFILE; - } - - return INACTIVE_REGISTER; -} - -/** -* Adjust 2.0.x disallowed names to 3.0.x format -*/ -function phpbb_disallowed_username($username) -{ - // Replace * with % - $username = phpbb_set_default_encoding(str_replace('*', '%', $username)); - return utf8_htmlspecialchars($username); -} - -/** -* Checks whether there are any usernames on the old board that would map to the same -* username_clean on phpBB. Prints out a list if any exist and exits. -*/ -function phpbb_create_userconv_table() -{ - global $db; - - switch ($db->get_sql_layer()) - { - case 'mysqli': - $map_dbms = 'mysql_41'; - break; - - case 'mssql_odbc': - case 'mssqlnative': - $map_dbms = 'mssql'; - break; - - default: - $map_dbms = $db->get_sql_layer(); - break; - } - - // create a temporary table in which we store the clean usernames - $drop_sql = 'DROP TABLE ' . USERCONV_TABLE; - switch ($map_dbms) - { - case 'mssql': - $create_sql = 'CREATE TABLE [' . USERCONV_TABLE . '] ( - [user_id] [int] NOT NULL , - [username_clean] [varchar] (255) DEFAULT (\'\') NOT NULL - )'; - break; - - case 'mysql_41': - $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( - user_id mediumint(8) NOT NULL, - username_clean varchar(255) DEFAULT \'\' NOT NULL - ) CHARACTER SET `utf8` COLLATE `utf8_bin`'; - break; - - case 'oracle': - $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( - user_id number(8) NOT NULL, - username_clean varchar2(255) DEFAULT \'\' - )'; - break; - - case 'postgres': - $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( - user_id INT4 DEFAULT \'0\', - username_clean varchar_ci DEFAULT \'\' NOT NULL - )'; - break; - - case 'sqlite3': - $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( - user_id INTEGER NOT NULL DEFAULT \'0\', - username_clean varchar(255) NOT NULL DEFAULT \'\' - )'; - break; - } - - $db->sql_return_on_error(true); - $db->sql_query($drop_sql); - $db->sql_return_on_error(false); - $db->sql_query($create_sql); -} - -function phpbb_check_username_collisions() -{ - global $db, $src_db, $convert, $user, $lang; - - // now find the clean version of the usernames that collide - $sql = 'SELECT username_clean - FROM ' . USERCONV_TABLE .' - GROUP BY username_clean - HAVING COUNT(user_id) > 1'; - $result = $db->sql_query($sql); - - $colliding_names = array(); - while ($row = $db->sql_fetchrow($result)) - { - $colliding_names[] = $row['username_clean']; - } - $db->sql_freeresult($result); - - // there was at least one collision, the admin will have to solve it before conversion can continue - if (count($colliding_names)) - { - $sql = 'SELECT user_id, username_clean - FROM ' . USERCONV_TABLE . ' - WHERE ' . $db->sql_in_set('username_clean', $colliding_names); - $result = $db->sql_query($sql); - unset($colliding_names); - - $colliding_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $colliding_user_ids[(int) $row['user_id']] = $row['username_clean']; - } - $db->sql_freeresult($result); - - $sql = 'SELECT username, user_id, user_posts - FROM ' . $convert->src_table_prefix . 'users - WHERE ' . $src_db->sql_in_set('user_id', array_keys($colliding_user_ids)); - $result = $src_db->sql_query($sql); - - $colliding_users = array(); - while ($row = $src_db->sql_fetchrow($result)) - { - $row['user_id'] = (int) $row['user_id']; - if (isset($colliding_user_ids[$row['user_id']])) - { - $colliding_users[$colliding_user_ids[$row['user_id']]][] = $row; - } - } - $src_db->sql_freeresult($result); - unset($colliding_user_ids); - - $list = ''; - foreach ($colliding_users as $username_clean => $users) - { - $list .= sprintf($user->lang['COLLIDING_CLEAN_USERNAME'], $username_clean) . "
      \n"; - foreach ($users as $i => $row) - { - $list .= sprintf($user->lang['COLLIDING_USER'], $row['user_id'], phpbb_set_default_encoding($row['username']), $row['user_posts']) . "
      \n"; - } - } - - $lang['INST_ERR_FATAL'] = $user->lang['CONV_ERR_FATAL']; - $convert->p_master->error('' . $user->lang['COLLIDING_USERNAMES_FOUND'] . '

      ' . $list . '', __LINE__, __FILE__); - } - - $drop_sql = 'DROP TABLE ' . USERCONV_TABLE; - $db->sql_query($drop_sql); -} - -function phpbb_convert_timezone($timezone) -{ - return \phpbb\db\migration\data\v310\timezone::convert_phpbb30_timezone($timezone, 0); -} - -function phpbb_add_notification_options($user_notify_pm) -{ - global $convert_row, $db; - - $user_id = phpbb_user_id($convert_row['user_id']); - if ($user_id == ANONYMOUS) - { - return; - } - - $rows = array(); - - $rows[] = array( - 'item_type' => 'post', - 'item_id' => 0, - 'user_id' => (int) $user_id, - 'notify' => 1, - 'method' => 'email', - ); - $rows[] = array( - 'item_type' => 'topic', - 'item_id' => 0, - 'user_id' => (int) $user_id, - 'notify' => 1, - 'method' => 'email', - ); - if ($user_notify_pm) - { - $rows[] = array( - 'item_type' => 'pm', - 'item_id' => 0, - 'user_id' => (int) $user_id, - 'notify' => 1, - 'method' => 'email', - ); - } - - $db->sql_multi_insert(USER_NOTIFICATIONS_TABLE, $rows); -} - -function phpbb_convert_password_hash($hash) -{ - global $phpbb_container; - - /* @var $manager \phpbb\passwords\manager */ - $manager = $phpbb_container->get('passwords.manager'); - $hash = $manager->hash($hash, '$H$'); - - return '$CP$' . $hash; -} diff --git a/phpBB/install/convertors/index.htm b/phpBB/install/convertors/index.htm new file mode 100644 index 00000000000..ee1f723a7dd --- /dev/null +++ b/phpBB/install/convertors/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + From 391bda011e9985b799608ecab89e807fc4314875 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 26 Sep 2025 15:38:13 +0200 Subject: [PATCH 0924/1214] [ticket/16005] Remove references that don't make sense anymore PHPBB-16005 --- phpBB/includes/functions_convert.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_convert.php b/phpBB/includes/functions_convert.php index 8a161edfa75..9292281c1f3 100644 --- a/phpBB/includes/functions_convert.php +++ b/phpBB/includes/functions_convert.php @@ -1022,7 +1022,7 @@ function user_group_auth($group, $select_query, $use_src_db) /** * Retrieves configuration information from the source forum and caches it as an array * Both database and file driven configuration formats can be handled -* (the type used is specified in $config_schema, see convert_phpbb20.php for more details) +* (the type used is specified in $config_schema) */ function get_config() { @@ -1110,7 +1110,7 @@ function get_config() /** * Transfers the relevant configuration information from the source forum -* The mapping of fields is specified in $config_schema, see convert_phpbb20.php for more details +* The mapping of fields is specified in $config_schema */ function restore_config($schema) { From 56e393c80547aaddbeeb8d129fda925e78179937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Fri, 28 Sep 2018 23:28:06 +0200 Subject: [PATCH 0925/1214] [ticket/15813] Team page to controller PHPBB3-15813 --- phpBB/config/default/container/services.yml | 1 + .../default/container/services_members.yml | 16 + phpBB/config/default/routing/members.yml | 4 + phpBB/config/default/routing/routing.yml | 3 + phpBB/memberlist.php | 276 +------------ phpBB/phpbb/members/controller/team.php | 389 ++++++++++++++++++ 6 files changed, 418 insertions(+), 271 deletions(-) create mode 100644 phpBB/config/default/container/services_members.yml create mode 100644 phpBB/config/default/routing/members.yml create mode 100644 phpBB/phpbb/members/controller/team.php diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index ecf8f3109e7..e3652c0dbf8 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -19,6 +19,7 @@ imports: - { resource: services_http.yml } - { resource: services_language.yml } - { resource: services_mention.yml } + - { resource: services_members.yml } - { resource: services_messenger.yml } - { resource: services_migrator.yml } - { resource: services_mimetype_guesser.yml } diff --git a/phpBB/config/default/container/services_members.yml b/phpBB/config/default/container/services_members.yml new file mode 100644 index 00000000000..5eca54f6ea2 --- /dev/null +++ b/phpBB/config/default/container/services_members.yml @@ -0,0 +1,16 @@ +services: + +# Controllers + members.controller.team: + class: phpbb\members\controller\team + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@dispatcher' + - '@group_helper' + - '@controller.helper' + - '@language' + - '@request' + - '@template' + - '@user' diff --git a/phpBB/config/default/routing/members.yml b/phpBB/config/default/routing/members.yml new file mode 100644 index 00000000000..a254a43a29b --- /dev/null +++ b/phpBB/config/default/routing/members.yml @@ -0,0 +1,4 @@ +phpbb_members_team: + path: /team + defaults: + _controller: members.controller.team:handle diff --git a/phpBB/config/default/routing/routing.yml b/phpBB/config/default/routing/routing.yml index 46d20e45270..8e28174b165 100644 --- a/phpBB/config/default/routing/routing.yml +++ b/phpBB/config/default/routing/routing.yml @@ -28,6 +28,9 @@ phpbb_manifest_controller: path: /manifest defaults: { _controller: manifest.controller:handle } +phpbb_members_routing: + resource: members.yml + phpbb_mention_controller: path: /mention methods: [GET, POST] diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index e41d751106a..055971c6dda 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -62,6 +62,11 @@ case 'contactadmin': break; + case 'team': + send_status_line(301, 'Moved Permanently'); + redirect($controller_helper->route('phpbb_members_team', [], false)); + break; + case 'livesearch': if (!$config['allow_live_searches']) { @@ -102,277 +107,6 @@ // What do you want to do today? ... oops, I think that line is taken ... switch ($mode) { - case 'team': - // Display a listing of board admins, moderators - if (!function_exists('user_get_id_name')) - { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); - } - - $page_title = $user->lang['THE_TEAM']; - $template_html = 'memberlist_team.html'; - - $sql = 'SELECT * - FROM ' . TEAMPAGE_TABLE . ' - ORDER BY teampage_position ASC'; - $result = $db->sql_query($sql, 3600); - $teampage_data = $db->sql_fetchrowset($result); - $db->sql_freeresult($result); - - $sql_ary = array( - 'SELECT' => 'g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id as ug_user_id, t.teampage_id', - - 'FROM' => array(GROUPS_TABLE => 'g'), - - 'LEFT_JOIN' => array( - array( - 'FROM' => array(TEAMPAGE_TABLE => 't'), - 'ON' => 't.group_id = g.group_id', - ), - array( - 'FROM' => array(USER_GROUP_TABLE => 'ug'), - 'ON' => 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $user->data['user_id'], - ), - ), - ); - - $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); - - $group_ids = $groups_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - if ($row['group_type'] == GROUP_HIDDEN && !$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $user->data['user_id']) - { - $row['group_name'] = $user->lang['GROUP_UNDISCLOSED']; - $row['u_group'] = ''; - } - else - { - $row['group_name'] = $group_helper->get_name($row['group_name']); - $row['u_group'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $row['group_id']); - } - - if ($row['teampage_id']) - { - // Only put groups into the array we want to display. - // We are fetching all groups, to ensure we got all data for default groups. - $group_ids[] = (int) $row['group_id']; - } - $groups_ary[(int) $row['group_id']] = $row; - } - $db->sql_freeresult($result); - - $sql_ary = array( - 'SELECT' => 'u.user_id, u.group_id as default_group, u.username, u.username_clean, u.user_colour, u.user_type, u.user_rank, u.user_posts, u.user_allow_pm, g.group_id', - - 'FROM' => array( - USER_GROUP_TABLE => 'ug', - ), - - 'LEFT_JOIN' => array( - array( - 'FROM' => array(USERS_TABLE => 'u'), - 'ON' => 'ug.user_id = u.user_id', - ), - array( - 'FROM' => array(GROUPS_TABLE => 'g'), - 'ON' => 'ug.group_id = g.group_id', - ), - ), - - 'WHERE' => $db->sql_in_set('g.group_id', $group_ids, false, true) . ' AND ug.user_pending = 0', - - 'ORDER_BY' => 'u.username_clean ASC', - ); - - /** - * Modify the query used to get the users for the team page - * - * @event core.memberlist_team_modify_query - * @var array sql_ary Array containing the query - * @var array group_ids Array of group ids - * @var array teampage_data The teampage data - * @since 3.1.3-RC1 - */ - $vars = array( - 'sql_ary', - 'group_ids', - 'teampage_data', - ); - extract($phpbb_dispatcher->trigger_event('core.memberlist_team_modify_query', compact($vars))); - - $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); - - $user_ary = $user_ids = $group_users = array(); - while ($row = $db->sql_fetchrow($result)) - { - $row['forums'] = ''; - $row['forums_ary'] = array(); - $user_ary[(int) $row['user_id']] = $row; - $user_ids[] = (int) $row['user_id']; - $group_users[(int) $row['group_id']][] = (int) $row['user_id']; - } - $db->sql_freeresult($result); - - $user_ids = array_unique($user_ids); - - if (!empty($user_ids) && $config['teampage_forums']) - { - $template->assign_var('S_DISPLAY_MODERATOR_FORUMS', true); - // Get all moderators - $perm_ary = $auth->acl_get_list($user_ids, array('m_'), false); - - foreach ($perm_ary as $forum_id => $forum_ary) - { - foreach ($forum_ary as $auth_option => $id_ary) - { - foreach ($id_ary as $id) - { - if (!$forum_id) - { - $user_ary[$id]['forums'] = $user->lang['ALL_FORUMS']; - } - else - { - $user_ary[$id]['forums_ary'][] = $forum_id; - } - } - } - } - - $sql = 'SELECT forum_id, forum_name - FROM ' . FORUMS_TABLE; - $result = $db->sql_query($sql); - - $forums = array(); - while ($row = $db->sql_fetchrow($result)) - { - $forums[$row['forum_id']] = $row['forum_name']; - } - $db->sql_freeresult($result); - - foreach ($user_ary as $user_id => $user_data) - { - if (!$user_data['forums']) - { - foreach ($user_data['forums_ary'] as $forum_id) - { - $user_ary[$user_id]['forums_options'] = true; - if (isset($forums[$forum_id])) - { - if ($auth->acl_get('f_list', $forum_id)) - { - $user_ary[$user_id]['forums'] .= ''; - } - } - } - } - } - } - - $parent_team = 0; - foreach ($teampage_data as $team_data) - { - // If this team entry has no group, it's a category - if (!$team_data['group_id']) - { - $template->assign_block_vars('group', array( - 'GROUP_NAME' => $team_data['teampage_name'], - )); - - $parent_team = (int) $team_data['teampage_id']; - continue; - } - - $group_data = $groups_ary[(int) $team_data['group_id']]; - $group_id = (int) $team_data['group_id']; - - if (!$team_data['teampage_parent']) - { - // If the group does not have a parent category, we display the groupname as category - $template->assign_block_vars('group', array( - 'GROUP_NAME' => $group_data['group_name'], - 'GROUP_COLOR' => $group_data['group_colour'], - 'U_GROUP' => $group_data['u_group'], - )); - } - - // Display group members. - if (!empty($group_users[$group_id])) - { - foreach ($group_users[$group_id] as $user_id) - { - if (isset($user_ary[$user_id])) - { - $row = $user_ary[$user_id]; - if ($config['teampage_memberships'] == 1 && ($group_id != $groups_ary[$row['default_group']]['group_id']) && $groups_ary[$row['default_group']]['teampage_id']) - { - // Display users in their primary group, instead of the first group, when it is displayed on the teampage. - continue; - } - - $user_rank_data = phpbb_get_user_rank($row, (($row['user_id'] == ANONYMOUS) ? false : $row['user_posts'])); - - $template_vars = array( - 'USER_ID' => $row['user_id'], - 'FORUMS' => $row['forums'], - 'FORUM_OPTIONS' => (isset($row['forums_options'])) ? true : false, - 'RANK_TITLE' => $user_rank_data['title'], - - 'GROUP_NAME' => $groups_ary[$row['default_group']]['group_name'], - 'GROUP_COLOR' => $groups_ary[$row['default_group']]['group_colour'], - 'U_GROUP' => $groups_ary[$row['default_group']]['u_group'], - - 'RANK_IMG' => $user_rank_data['img'], - 'RANK_IMG_SRC' => $user_rank_data['img_src'], - - 'S_INACTIVE' => $row['user_type'] == USER_INACTIVE, - - 'U_PM' => ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $row['user_id']) : '', - - 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), - 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), - 'USER_COLOR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), - 'U_VIEW_PROFILE' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), - ); - - /** - * Modify the template vars for displaying the user in the groups on the teampage - * - * @event core.memberlist_team_modify_template_vars - * @var array template_vars Array containing the query - * @var array row Array containing the action user row - * @var array groups_ary Array of groups with all users that should be displayed - * @since 3.1.3-RC1 - */ - $vars = array( - 'template_vars', - 'row', - 'groups_ary', - ); - extract($phpbb_dispatcher->trigger_event('core.memberlist_team_modify_template_vars', compact($vars))); - - $template->assign_block_vars('group.user', $template_vars); - - if ($config['teampage_memberships'] != 2) - { - unset($user_ary[$user_id]); - } - } - } - } - } - - $template->assign_block_vars('navlinks', array( - 'BREADCRUMB_NAME' => $page_title, - 'U_BREADCRUMB' => append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=team"), - )); - - $template->assign_vars(array( - 'PM_IMG' => $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE'])) - ); - break; - case 'viewprofile': // Display a profile if ($user_id == ANONYMOUS && !$username) diff --git a/phpBB/phpbb/members/controller/team.php b/phpBB/phpbb/members/controller/team.php new file mode 100644 index 00000000000..5ca0a20e747 --- /dev/null +++ b/phpBB/phpbb/members/controller/team.php @@ -0,0 +1,389 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\members\controller; + +use \phpbb\auth\auth; +use \phpbb\config\config; +use \phpbb\db\driver\driver_interface; +use \phpbb\event\dispatcher; +use \phpbb\group\helper as group_helper; +use \phpbb\controller\helper; +use \phpbb\language\language; +use \phpbb\request\request_interface; +use \phpbb\template\template; +use \phpbb\user; + +class team +{ + /** + * @var auth + */ + protected $auth; + + /** + * @var config + */ + protected $config; + + /** + * @var driver_interface + */ + protected $db; + + /** + * @var dispatcher + */ + protected $dispatcher; + + /** + * @var group_helper + */ + protected $group_helper; + + /** + * @var helper + */ + protected $helper; + + /** + * @var language + */ + protected $language; + + /** + * @var request_interface + */ + protected $request; + + /** + * @var template + */ + protected $template; + + /** + * @var user + */ + protected $user; + + public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, group_helper $group_helper, helper $helper, language $language, request_interface $request, template $template, user $user) + { + $this->auth = $auth; + $this->config = $config; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->group_helper = $group_helper; + $this->helper = $helper; + $this->language = $language; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * Controller for /team route + * + * @return \Symfony\Component\HttpFoundation\Response a Symfony response object + */ + public function handle() + { + global $phpbb_root_path, $phpEx; + + // Display a listing of board admins, moderators + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + + // Load language strings + $this->language->add_lang('memberlist'); + + // Can this user view profiles/memberlist? + if (!$this->auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_VIEW_USERS'); + } + + login_box('', $this->language->lang('LOGIN_EXPLAIN_TEAM')); + } + + $sql = 'SELECT * + FROM ' . TEAMPAGE_TABLE . ' + ORDER BY teampage_position ASC'; + $result = $this->db->sql_query($sql, 3600); + $teampage_data = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + $sql_ary = array( + 'SELECT' => 'g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id as ug_user_id, t.teampage_id', + + 'FROM' => array(GROUPS_TABLE => 'g'), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(TEAMPAGE_TABLE => 't'), + 'ON' => 't.group_id = g.group_id', + ), + array( + 'FROM' => array(USER_GROUP_TABLE => 'ug'), + 'ON' => 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $this->user->data['user_id'], + ), + ), + ); + + $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary)); + + $group_ids = $groups_ary = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['group_type'] == GROUP_HIDDEN && !$this->auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $this->user->data['user_id']) + { + $row['group_name'] = $this->language->lang('GROUP_UNDISCLOSED'); + $row['u_group'] = ''; + } + else + { + $row['group_name'] = $this->group_helper->get_name($row['group_name']); + $row['u_group'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $row['group_id']); + } + + if ($row['teampage_id']) + { + // Only put groups into the array we want to display. + // We are fetching all groups, to ensure we got all data for default groups. + $group_ids[] = (int) $row['group_id']; + } + $groups_ary[(int) $row['group_id']] = $row; + } + $this->db->sql_freeresult($result); + + $sql_ary = array( + 'SELECT' => 'u.user_id, u.group_id as default_group, u.username, u.username_clean, u.user_colour, u.user_type, u.user_rank, u.user_posts, u.user_allow_pm, g.group_id', + + 'FROM' => array( + USER_GROUP_TABLE => 'ug', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(USERS_TABLE => 'u'), + 'ON' => 'ug.user_id = u.user_id', + ), + array( + 'FROM' => array(GROUPS_TABLE => 'g'), + 'ON' => 'ug.group_id = g.group_id', + ), + ), + + 'WHERE' => $this->db->sql_in_set('g.group_id', $group_ids, false, true), + + 'ORDER_BY' => 'u.username_clean ASC', + ); + + /** + * Modify the query used to get the users for the team page + * + * @event core.memberlist_team_modify_query + * @var array sql_ary Array containing the query + * @var array group_ids Array of group ids + * @var array teampage_data The teampage data + * @since 3.1.3-RC1 + */ + $vars = array( + 'sql_ary', + 'group_ids', + 'teampage_data', + ); + extract($this->dispatcher->trigger_event('core.memberlist_team_modify_query', compact($vars))); + + $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary)); + + $user_ary = $user_ids = $group_users = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['forums'] = ''; + $row['forums_ary'] = array(); + $user_ary[(int) $row['user_id']] = $row; + $user_ids[] = (int) $row['user_id']; + $group_users[(int) $row['group_id']][] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + $user_ids = array_unique($user_ids); + + if (!empty($user_ids) && $this->config['teampage_forums']) + { + $this->template->assign_var('S_DISPLAY_MODERATOR_FORUMS', true); + // Get all moderators + $perm_ary = $this->auth->acl_get_list($user_ids, array('m_'), false); + + foreach ($perm_ary as $forum_id => $forum_ary) + { + foreach ($forum_ary as $this->auth_option => $id_ary) + { + foreach ($id_ary as $id) + { + if (!$forum_id) + { + $user_ary[$id]['forums'] = $this->language->lang('ALL_FORUMS'); + } + else + { + $user_ary[$id]['forums_ary'][] = $forum_id; + } + } + } + } + + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE; + $result = $this->db->sql_query($sql); + + $forums = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $forums[$row['forum_id']] = $row['forum_name']; + } + $this->db->sql_freeresult($result); + + foreach ($user_ary as $user_id => $user_data) + { + if (!$user_data['forums']) + { + foreach ($user_data['forums_ary'] as $forum_id) + { + $user_ary[$user_id]['forums_options'] = true; + if (isset($forums[$forum_id])) + { + if ($this->auth->acl_get('f_list', $forum_id)) + { + $user_ary[$user_id]['forums'] .= ''; + } + } + } + } + } + } + + $parent_team = 0; + foreach ($teampage_data as $team_data) + { + // If this team entry has no group, it's a category + if (!$team_data['group_id']) + { + $this->template->assign_block_vars('group', array( + 'GROUP_NAME' => $team_data['teampage_name'], + )); + + $parent_team = (int) $team_data['teampage_id']; + continue; + } + + $group_data = $groups_ary[(int) $team_data['group_id']]; + $group_id = (int) $team_data['group_id']; + + if (!$team_data['teampage_parent']) + { + // If the group does not have a parent category, we display the groupname as category + $this->template->assign_block_vars('group', array( + 'GROUP_NAME' => $group_data['group_name'], + 'GROUP_COLOR' => $group_data['group_colour'], + 'U_GROUP' => $group_data['u_group'], + )); + } + + // Display group members. + if (!empty($group_users[$group_id])) + { + foreach ($group_users[$group_id] as $user_id) + { + if (isset($user_ary[$user_id])) + { + $row = $user_ary[$user_id]; + if ($this->config['teampage_memberships'] == 1 && ($group_id != $groups_ary[$row['default_group']]['group_id']) && $groups_ary[$row['default_group']]['teampage_id']) + { + // Display users in their primary group, instead of the first group, when it is displayed on the teampage. + continue; + } + + $user_rank_data = phpbb_get_user_rank($row, (($row['user_id'] == ANONYMOUS) ? false : $row['user_posts'])); + + $template_vars = array( + 'USER_ID' => $row['user_id'], + 'FORUMS' => $row['forums'], + 'FORUM_OPTIONS' => (isset($row['forums_options'])) ? true : false, + 'RANK_TITLE' => $user_rank_data['title'], + + 'GROUP_NAME' => $groups_ary[$row['default_group']]['group_name'], + 'GROUP_COLOR' => $groups_ary[$row['default_group']]['group_colour'], + 'U_GROUP' => $groups_ary[$row['default_group']]['u_group'], + + 'RANK_IMG' => $user_rank_data['img'], + 'RANK_IMG_SRC' => $user_rank_data['img_src'], + + 'S_INACTIVE' => $row['user_type'] == USER_INACTIVE, + + 'U_PM' => ($this->config['allow_privmsg'] && $this->auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $this->auth->acl_gets('a_', 'm_') || $this->auth->acl_getf_global('m_'))) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $row['user_id']) : '', + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'USER_COLOR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + 'U_VIEW_PROFILE' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), + ); + + /** + * Modify the template vars for displaying the user in the groups on the teampage + * + * @event core.memberlist_team_modify_template_vars + * @var array template_vars Array containing the query + * @var array row Array containing the action user row + * @var array groups_ary Array of groups with all users that should be displayed + * @since 3.1.3-RC1 + */ + $vars = array( + 'template_vars', + 'row', + 'groups_ary', + ); + extract($this->dispatcher->trigger_event('core.memberlist_team_modify_template_vars', compact($vars))); + + $this->template->assign_block_vars('group.user', $template_vars); + + if ($this->config['teampage_memberships'] != 2) + { + unset($user_ary[$user_id]); + } + } + } + } + } + + $this->template->assign_vars(array( + 'PM_IMG' => $this->user->img('icon_contact_pm', $this->language->lang('SEND_PRIVATE_MESSAGE'))) + ); + + // Breadcrums + $this->template->assign_block_vars('navlinks', array( + 'BREADCRUMB_NAME' => $this->language->lang('THE_TEAM'), + 'U_BREADCRUMB' => $this->helper->route('phpbb_members_team'), + )); + + // Render + return $this->helper->render('memberlist_team.html', $this->language->lang('THE_TEAM')); + } + +} From af4d6ece154eb8dd24184c0678a98afd1feb9ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 1 Oct 2018 17:01:37 +0200 Subject: [PATCH 0926/1214] [ticket/15813] Add make_jumpbox PHPBB3-15813 --- phpBB/phpbb/members/controller/team.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpBB/phpbb/members/controller/team.php b/phpBB/phpbb/members/controller/team.php index 5ca0a20e747..1774260a593 100644 --- a/phpBB/phpbb/members/controller/team.php +++ b/phpBB/phpbb/members/controller/team.php @@ -382,6 +382,8 @@ public function handle() 'U_BREADCRUMB' => $this->helper->route('phpbb_members_team'), )); + make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + // Render return $this->helper->render('memberlist_team.html', $this->language->lang('THE_TEAM')); } From b1b3a066dcea4d1c0066becf2a26f545203b9013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 1 Oct 2018 21:09:31 +0200 Subject: [PATCH 0927/1214] [ticket/15813] Update routes PHPBB3-15813 --- phpBB/includes/functions.php | 2 +- tests/functional/memberlist_test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 3bb60af3cc9..45a8fc3c7b4 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3866,7 +3866,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id = 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'), 'U_DELETE_COOKIES' => $controller_helper->route('phpbb_ucp_delete_cookies_controller'), 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '', - 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'), + 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid($controller_helper->route('phpbb_members_team')), 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'), 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), diff --git a/tests/functional/memberlist_test.php b/tests/functional/memberlist_test.php index 5497c240375..21b16a666bc 100644 --- a/tests/functional/memberlist_test.php +++ b/tests/functional/memberlist_test.php @@ -43,7 +43,7 @@ public function test_viewprofile() protected function get_memberlist_leaders_table_crawler() { - $crawler = self::request('GET', 'memberlist.php?mode=team&sid=' . $this->sid); + $crawler = self::request('GET', 'team?sid=' . $this->sid); return $crawler->filter('.forumbg-table'); } From 97d7b447c31f0267aac7633ad75e468e6d34eb5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Mon, 1 Oct 2018 22:07:07 +0200 Subject: [PATCH 0928/1214] [ticket/15813] Remove unused parameter PHPBB3-15813 --- phpBB/config/default/container/services_members.yml | 1 - phpBB/phpbb/members/controller/team.php | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/phpBB/config/default/container/services_members.yml b/phpBB/config/default/container/services_members.yml index 5eca54f6ea2..a75015e2396 100644 --- a/phpBB/config/default/container/services_members.yml +++ b/phpBB/config/default/container/services_members.yml @@ -11,6 +11,5 @@ services: - '@group_helper' - '@controller.helper' - '@language' - - '@request' - '@template' - '@user' diff --git a/phpBB/phpbb/members/controller/team.php b/phpBB/phpbb/members/controller/team.php index 1774260a593..9b0f5d7df39 100644 --- a/phpBB/phpbb/members/controller/team.php +++ b/phpBB/phpbb/members/controller/team.php @@ -20,7 +20,6 @@ use \phpbb\group\helper as group_helper; use \phpbb\controller\helper; use \phpbb\language\language; -use \phpbb\request\request_interface; use \phpbb\template\template; use \phpbb\user; @@ -61,11 +60,6 @@ class team */ protected $language; - /** - * @var request_interface - */ - protected $request; - /** * @var template */ @@ -76,7 +70,7 @@ class team */ protected $user; - public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, group_helper $group_helper, helper $helper, language $language, request_interface $request, template $template, user $user) + public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, group_helper $group_helper, helper $helper, language $language, template $template, user $user) { $this->auth = $auth; $this->config = $config; @@ -85,7 +79,6 @@ public function __construct(auth $auth, config $config, driver_interface $db, di $this->group_helper = $group_helper; $this->helper = $helper; $this->language = $language; - $this->request = $request; $this->template = $template; $this->user = $user; } From 5dd3bfc576780aa24d6eaf373fc89394909b59bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Calvo?= Date: Wed, 3 Oct 2018 23:13:09 +0200 Subject: [PATCH 0929/1214] [ticket/15813] Don't use trigger_error on controllers PHPBB3-15813 --- phpBB/phpbb/members/controller/team.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpBB/phpbb/members/controller/team.php b/phpBB/phpbb/members/controller/team.php index 9b0f5d7df39..8fa26444676 100644 --- a/phpBB/phpbb/members/controller/team.php +++ b/phpBB/phpbb/members/controller/team.php @@ -107,8 +107,7 @@ public function handle() { if ($this->user->data['user_id'] != ANONYMOUS) { - send_status_line(403, 'Forbidden'); - trigger_error('NO_VIEW_USERS'); + throw new http_exception(403, 'NO_VIEW_USERS'); } login_box('', $this->language->lang('LOGIN_EXPLAIN_TEAM')); From b2ca53f64f6f1791983a4cc6c232d8c4683aa0fd Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Thu, 1 Nov 2018 20:37:17 +0000 Subject: [PATCH 0930/1214] [ticket/15813] Remove append_sid from route PHPBB3-15813 --- phpBB/includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 45a8fc3c7b4..188f98d7e99 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3866,7 +3866,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id = 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'), 'U_DELETE_COOKIES' => $controller_helper->route('phpbb_ucp_delete_cookies_controller'), 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '', - 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid($controller_helper->route('phpbb_members_team')), + 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : $controller_helper->route('phpbb_members_team'), 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'), 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), From 4b71f0323afbd4ae436d9052b35ff231cd215393 Mon Sep 17 00:00:00 2001 From: Derky Date: Sat, 27 Sep 2025 17:27:36 +0200 Subject: [PATCH 0931/1214] [ticket/17554] Mock Extensions Catalog cache in tests PHPBB-17554 --- tests/functional/extension_acp_test.php | 22 +++++++++++- .../fixtures/files/extensions_catalog.json | 34 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/functional/fixtures/files/extensions_catalog.json diff --git a/tests/functional/extension_acp_test.php b/tests/functional/extension_acp_test.php index 8f76a91b957..94106e0872e 100644 --- a/tests/functional/extension_acp_test.php +++ b/tests/functional/extension_acp_test.php @@ -79,6 +79,18 @@ protected function setUp(): void $this->add_lang(['acp/common', 'acp/extensions']); } + /** + * Mocks the extensions catalog cache used in phpBB/phpbb/composer/manager.php + * with a predefined fixture so no external calls are made. + */ + protected function mock_extensions_catalog_cache():void { + $fixture_file = __DIR__ . '/fixtures/files/extensions_catalog.json'; + $package_type = 'phpbb-extension'; + + $available_extensions = json_decode(file_get_contents($fixture_file), true); + $this->cache->put('_composer_' . $package_type . '_available', $available_extensions, 24*60*60); + } + public function test_list() { $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid); @@ -247,6 +259,7 @@ public function test_actions() public function test_extensions_catalog() { // Access extensions catalog main page + $this->mock_extensions_catalog_cache(); $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=catalog&sid=' . $this->sid); $this->assertContainsLang('ACP_EXTENSIONS_CATALOG', $this->get_content()); @@ -260,6 +273,7 @@ public function test_extensions_catalog() $this->assertContainsLang('CONFIG_UPDATED', $crawler->filter('div[class="successbox"] > p')->text()); // Revisit extensions catalog main page after configuration change + $this->mock_extensions_catalog_cache(); $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=catalog&sid=' . $this->sid); $this->assertContainsLang('ACP_EXTENSIONS_CATALOG', $this->get_content()); @@ -270,12 +284,17 @@ public function test_extensions_catalog() public function test_extensions_catalog_installing_extension() { // Let's check the overview, multiple packages should be listed + $this->mock_extensions_catalog_cache(); $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=catalog&sid=' . $this->sid); $this->assertContainsLang('ACP_EXTENSIONS_CATALOG', $this->get_content()); $this->assertGreaterThan(1, $crawler->filter('tr')->count()); $this->assertGreaterThan(1, $crawler->selectLink($this->lang('INSTALL'))->count()); - $pages = (int) $crawler->filter('div.pagination li:nth-last-child(2) a')->first()->text(); + $pages = 1; + $pagination = $crawler->filter('div.pagination li:nth-last-child(2) a'); + if ($pagination->count() > 0) { + $pages = (int) $pagination->first()->text(); + } // Get Install links for both extensions $extension_filter = function($crawler, $extension_name, &$install_link) @@ -297,6 +316,7 @@ function ($node, $i) use ($extension_name) { if ($i != 0) { + $this->mock_extensions_catalog_cache(); $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&start=' . $i * 20 . '&mode=catalog&sid=' . $this->sid); } diff --git a/tests/functional/fixtures/files/extensions_catalog.json b/tests/functional/fixtures/files/extensions_catalog.json new file mode 100644 index 00000000000..f2683e7709a --- /dev/null +++ b/tests/functional/fixtures/files/extensions_catalog.json @@ -0,0 +1,34 @@ +[ + { + "name": "extension1\/vendor", + "display_name": "Extension 1", + "composer_name": "extension1\/vendor", + "version": "1.0.0", + "description": "Dummy extension 1 for testing.", + "url": "https:\/\/example.com\/extension1", + "authors": [ + { + "name": "Author One", + "email": "author1@example.com", + "homepage": "https:\/\/author1.example.com", + "role": "Developer" + } + ] + }, + { + "name": "phpbb\/viglink", + "display_name": "VigLink", + "composer_name": "phpbb\/viglink", + "version": "dev-master", + "description": "The VigLink extension for phpBB makes it possible to earn revenue, without any change to the user experience, when users post and follow links to commercial sites.", + "url": "https:\/\/www.phpbb.com\/", + "authors": [ + { + "name": "Author Two", + "email": "author2@example.com", + "homepage": "https:\/\/author2.example.com", + "role": "Developer" + } + ] + } +] From a8d71f60e6f075e940e6fc4f8d1beb65c420e3dd Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 27 Sep 2025 15:45:15 +0200 Subject: [PATCH 0932/1214] [ticket/15813] Fix code style PHPBB3-15813 --- phpBB/memberlist.php | 3 ++ phpBB/phpbb/members/controller/team.php | 39 +++++++++++++------------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 055971c6dda..adeec2e6432 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -43,6 +43,9 @@ $group_id = $request->variable('g', 0); $topic_id = $request->variable('t', 0); +/** @var \phpbb\controller\helper $controller_helper */ +$controller_helper = $phpbb_container->get('controller.helper'); + // Redirect when old mode is used if ($mode == 'leaders') { diff --git a/phpBB/phpbb/members/controller/team.php b/phpBB/phpbb/members/controller/team.php index 8fa26444676..a536f98e703 100644 --- a/phpBB/phpbb/members/controller/team.php +++ b/phpBB/phpbb/members/controller/team.php @@ -1,27 +1,28 @@ - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ namespace phpbb\members\controller; -use \phpbb\auth\auth; -use \phpbb\config\config; -use \phpbb\db\driver\driver_interface; -use \phpbb\event\dispatcher; -use \phpbb\group\helper as group_helper; -use \phpbb\controller\helper; -use \phpbb\language\language; -use \phpbb\template\template; -use \phpbb\user; +use phpbb\auth\auth; +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\event\dispatcher; +use phpbb\exception\http_exception; +use phpbb\group\helper as group_helper; +use phpbb\controller\helper; +use phpbb\language\language; +use phpbb\template\template; +use phpbb\user; class team { From 74b1de0bc244db56fd105e91c9e9f453332d9eb6 Mon Sep 17 00:00:00 2001 From: Patrick Webster Date: Sat, 27 Sep 2025 17:07:36 -0500 Subject: [PATCH 0933/1214] [ticket/17477] Support non-prefixed whois servers Referral servers lacking the whois:// prefix are not currently supported. This ticket adds support for those servers. At the moment, this is mainly an issue for RIPE entries on ARIN. They will now be detected and followed. PHPBB-17477 --- phpBB/includes/functions_user.php | 3 ++- tests/functions_user/whois_test.php | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index a87b79d3a65..1c615fa9d0d 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -1482,7 +1482,8 @@ function user_ipwhois($ip) $match = array(); // Test for referrals from $whois_host to other whois databases, roll on rwhois - if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match)) + // Search for referral servers with or without the "whois://" prefix + if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match) || preg_match('#ReferralServer:[\x20]*([^/]+)$#im', $ipwhois, $match)) { if (strpos($match[1], ':') !== false) { diff --git a/tests/functions_user/whois_test.php b/tests/functions_user/whois_test.php index 4dd72311bb5..70873f6b1a9 100644 --- a/tests/functions_user/whois_test.php +++ b/tests/functions_user/whois_test.php @@ -33,8 +33,10 @@ public function setUp(): void public function ips_data() { return [ - ['2001:4860:4860::8888'], // Google public DNS - ['64.233.161.139'], // google.com + ['2001:4860:4860::8888'], // Google public DNS (ARIN) + ['64.233.161.139'], // google.com (ARIN) + ['1.1.1.1'], // Cloudflare (APNIC via whois:// referral) + ['213.133.116.44'], // Hetzner (RIPE via non-whois:// referral) ]; } @@ -47,5 +49,6 @@ public function test_ip_whois($ip) $this->assertStringNotContainsString('Query terms are ambiguous', $ip_whois); $this->assertStringNotContainsString('no entries found', $ip_whois); $this->assertStringNotContainsString('ERROR', $ip_whois); + $this->assertStringNotContainsString('Allocated to RIPE NCC', $ip_whois); // This only shows if the referral isn't found } } From 7bec5486530a4d17b957b86bc654d4def3296eac Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Sun, 28 Sep 2025 11:53:33 +0200 Subject: [PATCH 0934/1214] [ticket/17539] Changes default password complexity to PASS_TYPE_ALPHA PHPBB-17539 --- phpBB/install/schemas/schema_data.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 2da7f8ba33d..d567d8296b2 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -230,7 +230,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_search_author_ INSERT INTO phpbb_config (config_name, config_value) VALUES ('new_member_group_default', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('new_member_post_limit', '3'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('override_user_style', '0'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('pass_complex', 'PASS_TYPE_ANY'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('pass_complex', 'PASS_TYPE_ALPHA'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('plupload_salt', 'phpbb_plupload'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('pm_edit_time', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('pm_max_boxes', '4'); From 31665995b03d9bed34fa0f76d73b5ebf8e2b396a Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Sun, 28 Sep 2025 12:17:52 +0200 Subject: [PATCH 0935/1214] [ticket/17536] Addings four events to acp_forums.html fieldset PHPBB-17536 --- phpBB/adm/style/acp_forums.html | 4 ++++ phpBB/docs/events.md | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/phpBB/adm/style/acp_forums.html b/phpBB/adm/style/acp_forums.html index f51ce987767..915751b4256 100644 --- a/phpBB/adm/style/acp_forums.html +++ b/phpBB/adm/style/acp_forums.html @@ -189,11 +189,13 @@

      {L_WARNING}

      {L_GENERAL_FORUM_SETTINGS} + {% EVENT acp_forum_cat_options_prepend %}

      {L_DISPLAY_ACTIVE_TOPICS_EXPLAIN}
      + {% EVENT acp_forum_cat_options_append %}
      @@ -307,6 +309,7 @@

      {L_WARNING}

      diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 35c3e59c6f3..38d441bba53 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -108,12 +108,36 @@ acp_ext_list_not_installed_title_after * Changed: 3.3.14 Renamed from acp_ext_list_available_title_after * Purpose: Add text after not installed extensions section title. +acp_forums_cat_options_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.3.16-RC1 +* Purpose: Add additional settings to a forum type 'category' within 'General forum settings' fieldset + +acp_forums_cat_options_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.3.16-RC1 +* Purpose: Add additional settings to a forum type 'category' within 'General forum settings' fieldset + acp_forums_custom_settings === * Location: adm/style/acp_forums.html * Since: 3.1.6-RC1 * Purpose: Add its own box (fieldset) for extension settings +acp_forums_link_options_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.3.16-RC1 +* Purpose: Add additional settings to a forum type 'link' within 'General forum settings' fieldset + +acp_forums_link_options_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.3.16-RC1 +* Purpose: Add additional settings to a forum type 'link' within 'General forum settings' fieldset + acp_forums_main_settings_append === * Location: adm/style/acp_forums.html From 0eab040b971047770af7d9d98c56a6ae65fc1b21 Mon Sep 17 00:00:00 2001 From: Derky Date: Sun, 28 Sep 2025 13:28:18 +0200 Subject: [PATCH 0936/1214] [ticket/17554] Prevent opening Extension Catalog in test_all_acp_module_links PHPBB-17554 --- tests/functional/acp_test.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/functional/acp_test.php b/tests/functional/acp_test.php index bf37fc7203f..d390d3158bd 100644 --- a/tests/functional/acp_test.php +++ b/tests/functional/acp_test.php @@ -58,6 +58,12 @@ function ($node, $i) // Browse all ACP submodules' modes foreach ($acp_submodules as $acp_submodule) { + // Don't click the ACP Extensions Catalog to prevent calling an external HTTP service in the test suite + if ($acp_submodule->getNode()->textContent === $this->lang('ACP_EXTENSIONS_CATALOG')) + { + continue; + } + self::$client->click($acp_submodule); self::assert_response_html(); } From 6c55b75616a4b82c53a41168959f101031718ff9 Mon Sep 17 00:00:00 2001 From: Derky Date: Sun, 28 Sep 2025 13:44:50 +0200 Subject: [PATCH 0937/1214] [ticket/17557] Add error suppression to avatars in storage_track migration PHPBB-17557 --- phpBB/phpbb/db/migration/data/v400/storage_track.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/phpbb/db/migration/data/v400/storage_track.php b/phpBB/phpbb/db/migration/data/v400/storage_track.php index 7d2b4dec202..35ebda12395 100644 --- a/phpBB/phpbb/db/migration/data/v400/storage_track.php +++ b/phpBB/phpbb/db/migration/data/v400/storage_track.php @@ -98,7 +98,7 @@ public function track_avatars() $filename = $this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext; $files[] = [ 'file_path' => $filename, - 'filesize' => filesize($this->phpbb_root_path . $this->config['storage\\avatar\\config\\path'] . '/' . $filename), + 'filesize' => @filesize($this->phpbb_root_path . $this->config['storage\\avatar\\config\\path'] . '/' . $filename), ]; if (count($files) >= self::BATCH_SIZE) From 3a105548a9580edf1b541a17b3995c62f61079b8 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 28 Sep 2025 10:09:59 -0700 Subject: [PATCH 0938/1214] [ticket/17555] Optimize windows tests PHPBB-17555 --- .github/phpunit-psql-windows-github.xml | 5 +- .github/workflows/tests.yml | 64 +++++++++++++++---- .../phpbb_functional_test_case.php | 16 +++-- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/.github/phpunit-psql-windows-github.xml b/.github/phpunit-psql-windows-github.xml index 0b80da07748..179dfe6bd3a 100644 --- a/.github/phpunit-psql-windows-github.xml +++ b/.github/phpunit-psql-windows-github.xml @@ -7,7 +7,10 @@ bootstrap="../tests/bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" cacheDirectory=".phpunit.cache" - backupStaticProperties="false"> + backupStaticProperties="false" + timeoutForSmallTests="60" + timeoutForMediumTests="300" + timeoutForLargeTests="600"> ../tests diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2b1b5703d1f..018b4891937 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -544,6 +544,9 @@ jobs: GITHUB_WORKSPACE: ${{ github.workspace }} TEMP_DIR: ${{ runner.temp }} run: | + # Disable Windows Defender real-time monitoring early to improve performance + Set-MpPreference -DisableRealtimeMonitoring $true + Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole, IIS-WebServer, IIS-CommonHttpFeatures, IIS-ManagementConsole, IIS-HttpErrors, IIS-HttpRedirect, IIS-WindowsAuthentication, IIS-StaticContent, IIS-DefaultDocument, IIS-HttpCompressionStatic, IIS-DirectoryBrowsing, IIS-WebServerManagementTools, IIS-CGI -All Set-Service wuauserv -StartupType Manual (Get-Content ${env:GITHUB_WORKSPACE}\phpBB\web.config).replace("", "`n`t`n`t`t`n`t") | Set-Content ${env:GITHUB_WORKSPACE}\phpBB\web.config @@ -564,16 +567,38 @@ jobs: New-WebHandler -Name "PHP-FastCGI" -Path "*.php" -Modules FastCgiModule -ScriptProcessor "C:\tools\php\php-cgi.exe" -Verb '*' -ResourceType Either iisreset NET START W3SVC - mkdir "${env:GITHUB_WORKSPACE}\phpBB\cache\test" - mkdir "${env:GITHUB_WORKSPACE}\phpBB\cache\installer" - icacls "${env:GITHUB_WORKSPACE}\phpBB\cache" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\files" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\store" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\ext" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\vendor-ext" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\composer-ext.json" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\composer-ext.lock" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\images\avatars\upload" /grant Users:F /T + + # Wait for IIS to be ready and test connectivity + Start-Sleep -Seconds 10 + try { + $response = Invoke-WebRequest -Uri "http://phpbb.test/" -UseBasicParsing -TimeoutSec 30 + Write-Host "Web server is responding: $($response.StatusCode)" + } catch { + Write-Host "Web server test failed: $_" + } + + # Create directories and set permissions more efficiently + $dirs = @("cache\test", "cache\installer") + foreach ($dir in $dirs) { + New-Item -Path "${env:GITHUB_WORKSPACE}\phpBB\$dir" -ItemType Directory -Force + } + + # Set permissions in batch for better performance + $paths = @("cache", "files", "store", "ext", "vendor-ext", "images\avatars\upload") + foreach ($path in $paths) { + if (Test-Path "${env:GITHUB_WORKSPACE}\phpBB\$path") { + icacls "${env:GITHUB_WORKSPACE}\phpBB\$path" /grant Users:F /T /Q + } + } + + # Set permissions for specific files + $files = @("composer-ext.json", "composer-ext.lock") + foreach ($file in $files) { + if (Test-Path "${env:GITHUB_WORKSPACE}\phpBB\$file") { + icacls "${env:GITHUB_WORKSPACE}\phpBB\$file" /grant Users:F /Q + } + } + $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS_IUSRS", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl = Get-ACL "${env:TEMP_DIR}" $acl.AddAccessRule($accessRule) @@ -586,7 +611,7 @@ jobs: $postgreSqlSvc = Get-Service "postgresql*" Set-Service $postgreSqlSvc.Name -StartupType manual $runningStatus = [System.ServiceProcess.ServiceControllerStatus]::Running - $maxStartTimeout = New-TimeSpan -Seconds 30 + $maxStartTimeout = New-TimeSpan -Seconds 120 try { $postgreSqlSvc.Start() $postgreSqlSvc.WaitForStatus($runningStatus, $maxStartTimeout) @@ -595,12 +620,17 @@ jobs: } [System.Environment]::SetEnvironmentVariable('PATH',$Env:PATH+";${env:PGBIN}") $env:PGPASSWORD = 'root' - psql -c 'ALTER SYSTEM SET hot_standby = on;' -U postgres - psql -c 'ALTER SYSTEM SET wal_level = minimal;' -U postgres + + # Optimize PostgreSQL for testing performance + psql -c "ALTER SYSTEM SET fsync = off;" -U postgres + psql -c "ALTER SYSTEM SET synchronous_commit = off;" -U postgres + psql -c "ALTER SYSTEM SET checkpoint_completion_target = 0.9;" -U postgres + psql -c "ALTER SYSTEM SET wal_buffers = '16MB';" -U postgres + psql -c "ALTER SYSTEM SET shared_buffers = '128MB';" -U postgres psql -c 'DROP DATABASE IF EXISTS phpbb_tests;' -U postgres psql -c 'create database phpbb_tests;' -U postgres + Set-MpPreference -ExclusionPath "${env:PGDATA}" # Exclude PGDATA directory from Windows Defender - Set-MpPreference -DisableRealtimeMonitoring $true - name: Setup node uses: actions/setup-node@v4 @@ -615,5 +645,11 @@ jobs: phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --display-all-issues --stop-on-error --exclude-group functional,slow - name: Run functional tests if: ${{ matrix.type == 'functional' }} + timeout-minutes: 45 + env: + PHPBB_FUNCTIONAL_TIMEOUT: 30 + SYMFONY_DEPRECATIONS_HELPER: disabled + PHPBB_TEST_REDIS_HOST: '' + PHPBB_TEST_MEMCACHED_HOST: '' run: | phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --display-all-issues --stop-on-error --group functional diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index 980f397a2a7..022bbd1e0cc 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -129,11 +129,17 @@ protected function setUp(): void $this->bootstrap(); self::$cookieJar = new CookieJar; - // Force native client on windows platform - self::$http_client = strtolower(substr(PHP_OS, 0, 3)) === 'win' ? new NativeHttpClient() : HttpClient::create(); - self::$http_client->withOptions([ - 'timeout' => 60, - ]); + // Optimize HTTP client for Windows platform + if (strtolower(substr(PHP_OS, 0, 3)) === 'win') { + self::$http_client = new NativeHttpClient([ + 'timeout' => 30, + 'max_duration' => 60, + ]); + } else { + self::$http_client = HttpClient::create([ + 'timeout' => 60, + ]); + } self::$client = new HttpBrowser(self::$http_client, null, self::$cookieJar); // Clear the language array so that things From 784b3181daa034e278a3a55c7f13a894b99a7eb9 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 29 Sep 2025 08:13:34 -0700 Subject: [PATCH 0939/1214] [ticket/17559] Ensure extension catalog repos are stored correct PHPBB-17559 --- phpBB/install/schemas/schema_data.sql | 2 +- phpBB/phpbb/db/migration/data/v400/extensions_composer_2.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 2da7f8ba33d..76c02fd25ae 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -302,7 +302,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '4.0.0-a2-dev'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_repositories', '[]'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_repositories', '["https://satis.phpbb.com/","https://www.phpbb.com/customise/db/composer/"]'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_packagist', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_json_file', 'composer-ext.json'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_vendor_dir', 'vendor-ext/'); diff --git a/phpBB/phpbb/db/migration/data/v400/extensions_composer_2.php b/phpBB/phpbb/db/migration/data/v400/extensions_composer_2.php index fe4b9f4cefb..9c2500949ca 100644 --- a/phpBB/phpbb/db/migration/data/v400/extensions_composer_2.php +++ b/phpBB/phpbb/db/migration/data/v400/extensions_composer_2.php @@ -24,7 +24,7 @@ public function effectively_installed() public function update_data() { - $repositories = json_decode($this->config['exts_composer_repositories'], true); + $repositories = json_decode($this->config['exts_composer_repositories'], true) ?: []; $repositories[] = 'https://satis.phpbb.com'; $repositories = array_unique($repositories); From dc22ad07a28bb0567f37ba6e7c6c81f917429337 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 29 Sep 2025 20:10:27 +0200 Subject: [PATCH 0940/1214] [ticket/17555] Apply windows runner adjustments from master PHPBB-17555 --- .github/phpunit-psql-windows-github.xml | 21 +++++---- .github/workflows/tests.yml | 60 ++++++++++++++++++++----- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/.github/phpunit-psql-windows-github.xml b/.github/phpunit-psql-windows-github.xml index 5f131bae80f..88fce1693b4 100644 --- a/.github/phpunit-psql-windows-github.xml +++ b/.github/phpunit-psql-windows-github.xml @@ -1,14 +1,17 @@ + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + verbose="true" + bootstrap="../tests/bootstrap.php" + timeoutForSmallTests="60" + timeoutForMediumTests="300" + timeoutForLargeTests="600">> ../tests diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e070cabb928..9dac7ee0c5b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -553,6 +553,9 @@ jobs: GITHUB_WORKSPACE: ${{ github.workspace }} TEMP_DIR: ${{ runner.temp }} run: | + # Disable Windows Defender real-time monitoring early to improve performance + Set-MpPreference -DisableRealtimeMonitoring $true + Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole, IIS-WebServer, IIS-CommonHttpFeatures, IIS-ManagementConsole, IIS-HttpErrors, IIS-HttpRedirect, IIS-WindowsAuthentication, IIS-StaticContent, IIS-DefaultDocument, IIS-HttpCompressionStatic, IIS-DirectoryBrowsing, IIS-WebServerManagementTools, IIS-CGI -All Set-Service wuauserv -StartupType Manual (Get-Content ${env:GITHUB_WORKSPACE}\phpBB\web.config).replace("", "`n`t`n`t`t`n`t") | Set-Content ${env:GITHUB_WORKSPACE}\phpBB\web.config @@ -573,12 +576,38 @@ jobs: New-WebHandler -Name "PHP-FastCGI" -Path "*.php" -Modules FastCgiModule -ScriptProcessor "C:\tools\php\php-cgi.exe" -Verb '*' -ResourceType Either iisreset NET START W3SVC - mkdir "${env:GITHUB_WORKSPACE}\phpBB\cache\test" - mkdir "${env:GITHUB_WORKSPACE}\phpBB\cache\installer" - icacls "${env:GITHUB_WORKSPACE}\phpBB\cache" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\files" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\store" /grant Users:F /T - icacls "${env:GITHUB_WORKSPACE}\phpBB\images\avatars\upload" /grant Users:F /T + + # Wait for IIS to be ready and test connectivity + Start-Sleep -Seconds 10 + try { + $response = Invoke-WebRequest -Uri "http://phpbb.test/" -UseBasicParsing -TimeoutSec 30 + Write-Host "Web server is responding: $($response.StatusCode)" + } catch { + Write-Host "Web server test failed: $_" + } + + # Create directories and set permissions more efficiently + $dirs = @("cache\test", "cache\installer") + foreach ($dir in $dirs) { + New-Item -Path "${env:GITHUB_WORKSPACE}\phpBB\$dir" -ItemType Directory -Force + } + + # Set permissions in batch for better performance + $paths = @("cache", "files", "store", "ext", "vendor-ext", "images\avatars\upload") + foreach ($path in $paths) { + if (Test-Path "${env:GITHUB_WORKSPACE}\phpBB\$path") { + icacls "${env:GITHUB_WORKSPACE}\phpBB\$path" /grant Users:F /T /Q + } + } + + # Set permissions for specific files + $files = @("composer-ext.json", "composer-ext.lock") + foreach ($file in $files) { + if (Test-Path "${env:GITHUB_WORKSPACE}\phpBB\$file") { + icacls "${env:GITHUB_WORKSPACE}\phpBB\$file" /grant Users:F /Q + } + } + $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS_IUSRS", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl = Get-ACL "${env:TEMP_DIR}" $acl.AddAccessRule($accessRule) @@ -593,7 +622,7 @@ jobs: $postgreSqlSvc = Get-Service "postgresql*" Set-Service $postgreSqlSvc.Name -StartupType manual $runningStatus = [System.ServiceProcess.ServiceControllerStatus]::Running - $maxStartTimeout = New-TimeSpan -Seconds 30 + $maxStartTimeout = New-TimeSpan -Seconds 120 try { $postgreSqlSvc.Start() $postgreSqlSvc.WaitForStatus($runningStatus, $maxStartTimeout) @@ -602,17 +631,28 @@ jobs: } [System.Environment]::SetEnvironmentVariable('PATH',$Env:PATH+";${env:PGBIN}") $env:PGPASSWORD = 'root' - psql -c 'ALTER SYSTEM SET hot_standby = on;' -U postgres - psql -c 'ALTER SYSTEM SET wal_level = minimal;' -U postgres + + # Optimize PostgreSQL for testing performance + psql -c "ALTER SYSTEM SET fsync = off;" -U postgres + psql -c "ALTER SYSTEM SET synchronous_commit = off;" -U postgres + psql -c "ALTER SYSTEM SET checkpoint_completion_target = 0.9;" -U postgres + psql -c "ALTER SYSTEM SET wal_buffers = '16MB';" -U postgres + psql -c "ALTER SYSTEM SET shared_buffers = '128MB';" -U postgres psql -c 'DROP DATABASE IF EXISTS phpbb_tests;' -U postgres psql -c 'create database phpbb_tests;' -U postgres + Set-MpPreference -ExclusionPath "${env:PGDATA}" # Exclude PGDATA directory from Windows Defender - Set-MpPreference -DisableRealtimeMonitoring $true - name: Run unit tests if: ${{ matrix.type == 'unit' }} run: | phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --verbose --stop-on-error --exclude-group functional - name: Run unit tests if: ${{ matrix.type == 'functional' }} + timeout-minutes: 45 + env: + PHPBB_FUNCTIONAL_TIMEOUT: 30 + SYMFONY_DEPRECATIONS_HELPER: disabled + PHPBB_TEST_REDIS_HOST: '' + PHPBB_TEST_MEMCACHED_HOST: '' run: | phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --verbose --stop-on-error --group functional From 1a92461e4b5a733405847ab3a0b8303dfae873d9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 1 Oct 2025 21:24:06 +0200 Subject: [PATCH 0941/1214] [ticket/17539] Set pass type to any for functional reg tests PHPBB-17539 --- tests/functional/registration_test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/registration_test.php b/tests/functional/registration_test.php index 9b77feaa86b..e47d8193b5e 100644 --- a/tests/functional/registration_test.php +++ b/tests/functional/registration_test.php @@ -24,6 +24,7 @@ public function test_disable_captcha_on_registration() $crawler = self::request('GET', "adm/index.php?i=acp_board&mode=registration&sid={$this->sid}"); $form = $crawler->selectButton('Submit')->form(); $form['config[enable_confirm]']->setValue('0'); + $form['config[pass_complex]']->setValue('PASS_TYPE_ANY'); $crawler = self::submit($form); $this->assertContainsLang('CONFIG_UPDATED', $crawler->filter('#main .successbox')->text()); From ed5359ce97835909cb2304c11de6bced61a56182 Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Sat, 4 Oct 2025 18:22:54 +0200 Subject: [PATCH 0942/1214] [ticket/17536] Change order in events.md of new events PHPBB-17536 --- phpBB/docs/events.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 38d441bba53..0209c05b26f 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -108,13 +108,13 @@ acp_ext_list_not_installed_title_after * Changed: 3.3.14 Renamed from acp_ext_list_available_title_after * Purpose: Add text after not installed extensions section title. -acp_forums_cat_options_prepend +acp_forums_cat_options_append === * Location: adm/style/acp_forums.html * Since: 3.3.16-RC1 * Purpose: Add additional settings to a forum type 'category' within 'General forum settings' fieldset -acp_forums_cat_options_append +acp_forums_cat_options_prepend === * Location: adm/style/acp_forums.html * Since: 3.3.16-RC1 @@ -126,13 +126,13 @@ acp_forums_custom_settings * Since: 3.1.6-RC1 * Purpose: Add its own box (fieldset) for extension settings -acp_forums_link_options_prepend +acp_forums_link_options_append === * Location: adm/style/acp_forums.html * Since: 3.3.16-RC1 * Purpose: Add additional settings to a forum type 'link' within 'General forum settings' fieldset -acp_forums_link_options_append +acp_forums_link_options_prepend === * Location: adm/style/acp_forums.html * Since: 3.3.16-RC1 From fd74f70468edc73328ba8746c9b9e64cbb48a3ba Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Sat, 4 Oct 2025 18:34:09 +0200 Subject: [PATCH 0943/1214] [ticket/17536] Fix typo in new acp_forums event names PHPBB-17536 --- phpBB/adm/style/acp_forums.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpBB/adm/style/acp_forums.html b/phpBB/adm/style/acp_forums.html index 915751b4256..c1d00209552 100644 --- a/phpBB/adm/style/acp_forums.html +++ b/phpBB/adm/style/acp_forums.html @@ -189,13 +189,13 @@

      {L_WARNING}

      {L_GENERAL_FORUM_SETTINGS} - {% EVENT acp_forum_cat_options_prepend %} + {% EVENT acp_forums_cat_options_prepend %}

      {L_DISPLAY_ACTIVE_TOPICS_EXPLAIN}
      - {% EVENT acp_forum_cat_options_append %} + {% EVENT acp_forums_cat_options_append %}
      @@ -309,7 +309,7 @@

      {L_WARNING}

      From db86ad60cbf23e70badf5c5446a0d57eedf8a918 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 4 Oct 2025 19:52:43 +0200 Subject: [PATCH 0944/1214] [ticket/17555] Use windows-2025 runner for 3.3.x branch PHPBB-17555 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9dac7ee0c5b..dade39ed281 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -474,7 +474,7 @@ jobs: # Test with IIS & PostgreSQL on Windows windows-tests: - runs-on: windows-latest + runs-on: windows-2025 strategy: matrix: include: From 17e4fd206e7bc0b39b87f9f508030f581c67a5fb Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 4 Oct 2025 20:42:11 +0200 Subject: [PATCH 0945/1214] [ticket/17555] Fix typo in phpunit config PHPBB-17555 --- .github/phpunit-psql-windows-github.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/phpunit-psql-windows-github.xml b/.github/phpunit-psql-windows-github.xml index 88fce1693b4..470f3cd498e 100644 --- a/.github/phpunit-psql-windows-github.xml +++ b/.github/phpunit-psql-windows-github.xml @@ -11,7 +11,7 @@ bootstrap="../tests/bootstrap.php" timeoutForSmallTests="60" timeoutForMediumTests="300" - timeoutForLargeTests="600">> + timeoutForLargeTests="600"> ../tests From 8e6096fb14e8f5908281d19b780a6840dae03bdb Mon Sep 17 00:00:00 2001 From: Derky Date: Sun, 5 Oct 2025 11:40:41 +0200 Subject: [PATCH 0946/1214] [ticket/17554] Remove escaping in json PHPBB-17554 --- .../fixtures/files/extensions_catalog.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/functional/fixtures/files/extensions_catalog.json b/tests/functional/fixtures/files/extensions_catalog.json index f2683e7709a..1ab64d98dff 100644 --- a/tests/functional/fixtures/files/extensions_catalog.json +++ b/tests/functional/fixtures/files/extensions_catalog.json @@ -1,32 +1,32 @@ [ { - "name": "extension1\/vendor", + "name": "extension1/vendor", "display_name": "Extension 1", - "composer_name": "extension1\/vendor", + "composer_name": "extension1/vendor", "version": "1.0.0", "description": "Dummy extension 1 for testing.", - "url": "https:\/\/example.com\/extension1", + "url": "https://example.com/extension1", "authors": [ { "name": "Author One", "email": "author1@example.com", - "homepage": "https:\/\/author1.example.com", + "homepage": "https://author1.example.com", "role": "Developer" } ] }, { - "name": "phpbb\/viglink", + "name": "phpbb/viglink", "display_name": "VigLink", - "composer_name": "phpbb\/viglink", + "composer_name": "phpbb/viglink", "version": "dev-master", "description": "The VigLink extension for phpBB makes it possible to earn revenue, without any change to the user experience, when users post and follow links to commercial sites.", - "url": "https:\/\/www.phpbb.com\/", + "url": "https://www.phpbb.com/", "authors": [ { "name": "Author Two", "email": "author2@example.com", - "homepage": "https:\/\/author2.example.com", + "homepage": "https://author2.example.com", "role": "Developer" } ] From b104af78c32492345fd28b5179c1db9fa8291e23 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Oct 2025 17:16:04 +0200 Subject: [PATCH 0947/1214] [ticket/17555] Only run unit tests on windows runners PHPBB-17555 --- .github/workflows/tests.yml | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dade39ed281..3b4a08a8f9e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -480,42 +480,18 @@ jobs: include: - php: '7.4' db: "postgres" - type: 'unit' - php: '8.0' db: "postgres" - type: 'unit' - php: '8.1' db: "postgres" - type: 'unit' - php: '8.2' db: "postgres" - type: 'unit' - php: '8.3' db: "postgres" - type: 'unit' - php: '8.4' db: "postgres" - type: 'unit' - - php: '7.4' - db: "postgres" - type: 'functional' - - php: '8.0' - db: "postgres" - type: 'functional' - - php: '8.1' - db: "postgres" - type: 'functional' - - php: '8.2' - db: "postgres" - type: 'functional' - - php: '8.3' - db: "postgres" - type: 'functional' - - php: '8.4' - db: "postgres" - type: 'functional' - name: Windows - PHP ${{ matrix.php }} - ${{ matrix.db }} - ${{ matrix.type }} + name: Windows - PHP ${{ matrix.php }} - ${{ matrix.db }} steps: - name: Prepare git for Windows @@ -643,16 +619,5 @@ jobs: Set-MpPreference -ExclusionPath "${env:PGDATA}" # Exclude PGDATA directory from Windows Defender - name: Run unit tests - if: ${{ matrix.type == 'unit' }} run: | phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --verbose --stop-on-error --exclude-group functional - - name: Run unit tests - if: ${{ matrix.type == 'functional' }} - timeout-minutes: 45 - env: - PHPBB_FUNCTIONAL_TIMEOUT: 30 - SYMFONY_DEPRECATIONS_HELPER: disabled - PHPBB_TEST_REDIS_HOST: '' - PHPBB_TEST_MEMCACHED_HOST: '' - run: | - phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --verbose --stop-on-error --group functional From 7e22d7ad24cf78b1629b3f8c32f3ae066b990fd2 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Oct 2025 20:53:21 +0200 Subject: [PATCH 0948/1214] [ticket/17555] Reduce callbacks to external URLs in unit tests PHPBB-17555 --- tests/avatar/manager_test.php | 10 +++++++--- tests/console/update/check_test.php | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php index dbcf6dae7e9..669c5515116 100644 --- a/tests/avatar/manager_test.php +++ b/tests/avatar/manager_test.php @@ -57,7 +57,11 @@ protected function setUp(): void new \phpbb\mimetype\content_guesser, ); $guesser = new \phpbb\mimetype\guesser($guessers); - $imagesize = new \FastImageSize\FastImageSize(); + $imagesize = $this->getMockBuilder('\FastImageSize\FastImageSize') + ->setMethods(['getImageSize']) + ->getMock(); + $imagesize->method('getImageSize') + ->willReturn(['width' => 80, 'height' => 80, 'mime' => 'image/jpeg']); $dispatcher = new phpbb_mock_event_dispatcher(); $phpbb_dispatcher = $dispatcher; @@ -405,9 +409,9 @@ public function data_remote_avatar_url() { return array( array('127.0.0.1:91?foo.jpg', 80, 80, array('AVATAR_URL_INVALID')), - array(gethostbyname('secure.gravatar.com') . '/avatar/55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), + array('127.0.0.1/avatar/55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), array('secure.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80), - array(gethostbyname('secure.gravatar.com') . ':120/avatar/55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), + array('127.0.0.1:120/avatar/55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), array('secure.gravatar.com:80/avatar/55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), array('secure.gravatar.com:80?55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), array('secure.gravatar.com?55502f40dc8b7c769880b10874abc9d0.jpg', 80, 80, array('AVATAR_URL_INVALID')), // should be a 404 diff --git a/tests/console/update/check_test.php b/tests/console/update/check_test.php index 2578eb21ccb..5768808464b 100644 --- a/tests/console/update/check_test.php +++ b/tests/console/update/check_test.php @@ -94,7 +94,32 @@ public function get_command_tester($current_version) ->getMock(); $config = new \phpbb\config\config(array('version' => $current_version)); - $this->version_helper = new \phpbb\version_helper($cache, $config, new \phpbb\file_downloader()); + $this->version_helper = $this->getMockBuilder('\phpbb\version_helper') + ->setConstructorArgs([$cache, $config, new \phpbb\file_downloader()]) + ->setMethods(['get_latest_version', 'get_suggested_updates']) + ->getMock(); + $this->version_helper->method('get_suggested_updates') + ->willReturnCallback(function($force_update = false, $force_cache = false) use ($config) + { + if ($config['version'] === '100000') + { + return []; + } + else if ($config['version'] === '0') + { + return [ + [ + 'version' => '100000', + 'stability' => 'stable', + 'download_url' => 'https://www.phpbb.com/downloads/', + ], + ]; + } + else + { + throw new \phpbb\exception\runtime_exception('VERSIONCHECK_FAIL'); + } + }); $container = new phpbb_mock_container_builder; $container->set('version_helper', $this->version_helper); From b3b5e328f91181363cfe8259ace7ec628caa19d4 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Oct 2025 21:52:57 +0200 Subject: [PATCH 0949/1214] [ticket/17555] Properly mock version helper return PHPBB-17555 --- tests/console/update/check_test.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/console/update/check_test.php b/tests/console/update/check_test.php index 5768808464b..a00b49f0d11 100644 --- a/tests/console/update/check_test.php +++ b/tests/console/update/check_test.php @@ -109,9 +109,10 @@ public function get_command_tester($current_version) { return [ [ - 'version' => '100000', - 'stability' => 'stable', - 'download_url' => 'https://www.phpbb.com/downloads/', + 'current' => '100000', + 'announcement' => 'https://www.phpbb.com/downloads/', + 'eol' => null, + 'security' => false, ], ]; } From 2a65b0ee2eda609d65e5725313b9e467e6de88fd Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 6 Oct 2025 17:06:30 +0200 Subject: [PATCH 0950/1214] [ticket/17555] Use retries for windows tests PHPBB-17555 --- .github/workflows/tests.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3b4a08a8f9e..b6e7f1fe9f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -619,5 +619,8 @@ jobs: Set-MpPreference -ExclusionPath "${env:PGDATA}" # Exclude PGDATA directory from Windows Defender - name: Run unit tests - run: | - phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --verbose --stop-on-error --exclude-group functional + uses: nick-fields/retry@v3 + with: + timeout_minutes: 15 + max_attempts: 3 + command: phpBB/vendor/bin/phpunit --configuration .github/phpunit-psql-windows-github.xml --verbose --stop-on-error --exclude-group functional From fcc0f081ce7a3d7ece3b9983f2fd59eb0ddf60b3 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 5 Oct 2025 17:48:20 +0200 Subject: [PATCH 0951/1214] [ticket/17564] Adjust text-shadow of header profile for better contrast PHPBB-17564 --- phpBB/styles/prosilver/theme/colours.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 2a66af4b062..d45583f3c27 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -128,7 +128,7 @@ th a:hover { } .header-profile { - text-shadow: 0 0 1.75rem #eaf8ff; + text-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.5); } .panel { From 3a0060552a66ed8e761561029f430991fbc243bc Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 3 Oct 2025 22:26:04 +0200 Subject: [PATCH 0952/1214] [ticket/15085] Add HTTP authentication subscriber for feed controllers PHPBB-15085 --- .../default/container/services_feed.yml | 10 + .../phpbb/feed/event/http_auth_subscriber.php | 242 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 phpBB/phpbb/feed/event/http_auth_subscriber.php diff --git a/phpBB/config/default/container/services_feed.yml b/phpBB/config/default/container/services_feed.yml index f32d0cb4d30..2f27f6915b4 100644 --- a/phpBB/config/default/container/services_feed.yml +++ b/phpBB/config/default/container/services_feed.yml @@ -15,6 +15,16 @@ services: - '@language' - '%core.php_ext%' + feed.http_auth_subscriber: + class: phpbb\feed\event\http_auth_subscriber + arguments: + - '@auth' + - '@config' + - '@request' + - '@user' + tags: + - { name: kernel.event_subscriber } + feed.helper: class: phpbb\feed\helper arguments: diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php new file mode 100644 index 00000000000..8b3f03b0bdb --- /dev/null +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -0,0 +1,242 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\feed\event; + +use phpbb\auth\auth; +use phpbb\config\config; +use phpbb\request\request_interface; +use phpbb\user; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Event subscriber for HTTP authentication on feed routes + */ +class http_auth_subscriber implements EventSubscriberInterface +{ + /** @var auth */ + protected $auth; + + /** @var config */ + protected $config; + + /** @var request_interface */ + protected $request; + + /** @var user */ + protected $user; + + /** + * Constructor + * + * @param auth $auth Auth object + * @param config $config Config object + * @param request_interface $request Request object + * @param user $user User object + */ + public function __construct(auth $auth, config $config, request_interface $request, user $user) + { + $this->auth = $auth; + $this->config = $config; + $this->request = $request; + $this->user = $user; + } + + /** + * Handle HTTP authentication for feed routes + * + * @param GetResponseEvent $event + * @return void + */ + public function on_kernel_request(GetResponseEvent $event) + { + $request = $event->getRequest(); + $route = $request->attributes->get('_route'); + + // Only apply to feed routes + if (strpos($route, 'phpbb_feed_') !== 0) + { + return; + } + + // Check if HTTP authentication is enabled + if (!$this->config['feed_http_auth']) + { + return; + } + + // User is already logged in, no need to authenticate + if (!empty($this->user->data['is_registered'])) + { + return; + } + + // Get HTTP authentication credentials + $username = $this->get_http_username(); + $password = $this->get_http_password(); + + // If no credentials provided, send authentication challenge + if ($username === null || $password === null) + { + $this->send_auth_challenge($event); + return; + } + + // Attempt to login with the provided credentials + $auth_result = $this->auth->login($username, $password, false, true, false); + + if ($auth_result['status'] == LOGIN_SUCCESS) + { + return; + } + else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS) + { + // Too many login attempts + $response = new Response('', Response::HTTP_UNAUTHORIZED); + $event->setResponse($response); + return; + } + + // Authentication failed, send challenge + $this->send_auth_challenge($event); + } + + /** + * Get HTTP username from request headers + * + * @return string|null + */ + protected function get_http_username() + { + $username_keys = array( + 'PHP_AUTH_USER', + 'Authorization', + 'REMOTE_USER', + 'REDIRECT_REMOTE_USER', + 'HTTP_AUTHORIZATION', + 'REDIRECT_HTTP_AUTHORIZATION', + 'REMOTE_AUTHORIZATION', + 'REDIRECT_REMOTE_AUTHORIZATION', + 'AUTH_USER', + ); + + foreach ($username_keys as $key) + { + if ($this->request->is_set($key, request_interface::SERVER)) + { + $username = html_entity_decode($this->request->server($key), ENT_COMPAT); + + // Decode Basic authentication header + if (strpos($username, 'Basic ') === 0) + { + $credentials = base64_decode(substr($username, 6)); + if (strpos($credentials, ':') !== false) + { + list($username, ) = explode(':', $credentials, 2); + } + } + + return $username; + } + } + + return null; + } + + /** + * Get HTTP password from request headers + * + * @return string|null + */ + protected function get_http_password() + { + $password_keys = array( + 'PHP_AUTH_PW', + 'REMOTE_PASSWORD', + 'AUTH_PASSWORD', + ); + + foreach ($password_keys as $key) + { + if ($this->request->is_set($key, request_interface::SERVER)) + { + return html_entity_decode($this->request->server($key), ENT_COMPAT); + } + } + + // Check if password is in Authorization header (Basic auth) + $username_keys = array( + 'PHP_AUTH_USER', + 'Authorization', + 'REMOTE_USER', + 'REDIRECT_REMOTE_USER', + 'HTTP_AUTHORIZATION', + 'REDIRECT_HTTP_AUTHORIZATION', + 'REMOTE_AUTHORIZATION', + 'REDIRECT_REMOTE_AUTHORIZATION', + 'AUTH_USER', + ); + + foreach ($username_keys as $key) + { + if ($this->request->is_set($key, request_interface::SERVER)) + { + $auth_header = html_entity_decode($this->request->server($key), ENT_COMPAT); + + // Decode Basic authentication header + if (strpos($auth_header, 'Basic ') === 0) + { + $credentials = base64_decode(substr($auth_header, 6)); + if (strpos($credentials, ':') !== false) + { + list(, $password) = explode(':', $credentials, 2); + return $password; + } + } + } + } + + return null; + } + + /** + * Send HTTP authentication challenge + * + * @param GetResponseEvent $event + * @return void + */ + protected function send_auth_challenge(GetResponseEvent $event) + { + $realm = $this->config['sitename']; + // Filter out non-ASCII characters per RFC2616 + $realm = preg_replace('/[\x80-\xFF]/', '?', $realm); + + $response = new Response('', Response::HTTP_UNAUTHORIZED); + $response->headers->set('WWW-Authenticate', 'Basic realm="' . $realm . '"'); + $event->setResponse($response); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + // Priority should be high to run after session_begin but before controller + KernelEvents::REQUEST => array('on_kernel_request', 5), + ); + } +} From f4700b0a38e3b5edbbfe3126727415cf7af871fb Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 3 Oct 2025 22:26:58 +0200 Subject: [PATCH 0953/1214] [ticket/15085] Add unit tests for HTTP authentication subscriber PHPBB-15085 --- .phpunit.result.cache | 1 + tests/feed/http_auth_subscriber_test.php | 159 +++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 .phpunit.result.cache create mode 100644 tests/feed/http_auth_subscriber_test.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 00000000000..aac751d9fb3 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":[],"times":{"phpbb\\feed\\event\\http_auth_subscriber_test::test_subscriber_events":0.006,"phpbb\\feed\\event\\http_auth_subscriber_test::test_non_feed_route_skipped":0.005,"phpbb\\feed\\event\\http_auth_subscriber_test::test_http_auth_disabled":0.001,"phpbb\\feed\\event\\http_auth_subscriber_test::test_user_already_logged_in":0.001}} \ No newline at end of file diff --git a/tests/feed/http_auth_subscriber_test.php b/tests/feed/http_auth_subscriber_test.php new file mode 100644 index 00000000000..d3f00aef68a --- /dev/null +++ b/tests/feed/http_auth_subscriber_test.php @@ -0,0 +1,159 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\feed\event; + +class http_auth_subscriber_test extends \phpbb_test_case +{ + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\auth\auth */ + protected $auth; + + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\config\config */ + protected $config; + + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\request\request_interface */ + protected $request; + + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\user */ + protected $user; + + /** @var http_auth_subscriber */ + protected $subscriber; + + protected function setUp(): void + { + parent::setUp(); + + $this->auth = $this->getMockBuilder('\phpbb\auth\auth') + ->disableOriginalConstructor() + ->getMock(); + + $this->config = new \phpbb\config\config(array( + 'feed_http_auth' => 1, + 'sitename' => 'Test Site', + )); + + $this->request = $this->getMockBuilder('\phpbb\request\request_interface') + ->getMock(); + + $this->user = $this->getMockBuilder('\phpbb\user') + ->disableOriginalConstructor() + ->getMock(); + + $this->user->data = array('is_registered' => false); + + $this->subscriber = new http_auth_subscriber( + $this->auth, + $this->config, + $this->request, + $this->user + ); + } + + public function test_subscriber_events() + { + $events = http_auth_subscriber::getSubscribedEvents(); + $this->assertArrayHasKey(\Symfony\Component\HttpKernel\KernelEvents::REQUEST, $events); + } + + public function test_non_feed_route_skipped() + { + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('not_a_feed_route'); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + $event->expects($this->never()) + ->method('setResponse'); + + $this->subscriber->on_kernel_request($event); + } + + public function test_http_auth_disabled() + { + $this->config['feed_http_auth'] = 0; + + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + $event->expects($this->never()) + ->method('setResponse'); + + $this->subscriber->on_kernel_request($event); + } + + public function test_user_already_logged_in() + { + $this->user->data = array('is_registered' => true); + + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + $event->expects($this->never()) + ->method('setResponse'); + + $this->subscriber->on_kernel_request($event); + } +} From 3dbc1b28b59a0b073fe2d72cfae2dc3ff22bc6ff Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 3 Oct 2025 22:27:29 +0200 Subject: [PATCH 0954/1214] [ticket/15085] Reload ACL after HTTP authentication and update gitignore PHPBB-15085 --- .gitignore | 1 + .phpunit.result.cache | 1 - phpBB/phpbb/feed/event/http_auth_subscriber.php | 2 ++ 3 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 .phpunit.result.cache diff --git a/.gitignore b/.gitignore index 15125b68cc6..eda70c06051 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ node_modules .idea *.DS_Store* /.vscode +/.phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index aac751d9fb3..00000000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":[],"times":{"phpbb\\feed\\event\\http_auth_subscriber_test::test_subscriber_events":0.006,"phpbb\\feed\\event\\http_auth_subscriber_test::test_non_feed_route_skipped":0.005,"phpbb\\feed\\event\\http_auth_subscriber_test::test_http_auth_disabled":0.001,"phpbb\\feed\\event\\http_auth_subscriber_test::test_user_already_logged_in":0.001}} \ No newline at end of file diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php index 8b3f03b0bdb..3c35c769c84 100644 --- a/phpBB/phpbb/feed/event/http_auth_subscriber.php +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -100,6 +100,8 @@ public function on_kernel_request(GetResponseEvent $event) if ($auth_result['status'] == LOGIN_SUCCESS) { + // Reload ACL for the newly logged-in user + $this->auth->acl($this->user->data); return; } else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS) From 752ce67da0b1772cd1f773e6505a9d0c7315dfcd Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 3 Oct 2025 22:28:06 +0200 Subject: [PATCH 0955/1214] [ticket/15085] Add HTTPS requirement for HTTP authentication on feeds PHPBB-15085 --- .../phpbb/feed/event/http_auth_subscriber.php | 6 +++ tests/feed/http_auth_subscriber_test.php | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php index 3c35c769c84..6aedfb361bc 100644 --- a/phpBB/phpbb/feed/event/http_auth_subscriber.php +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -72,6 +72,12 @@ public function on_kernel_request(GetResponseEvent $event) return; } + // Only allow HTTP authentication in secure context (HTTPS) + if (!$request->isSecure()) + { + return; + } + // Check if HTTP authentication is enabled if (!$this->config['feed_http_auth']) { diff --git a/tests/feed/http_auth_subscriber_test.php b/tests/feed/http_auth_subscriber_test.php index d3f00aef68a..ee50ec9088d 100644 --- a/tests/feed/http_auth_subscriber_test.php +++ b/tests/feed/http_auth_subscriber_test.php @@ -95,6 +95,39 @@ public function test_non_feed_route_skipped() $this->subscriber->on_kernel_request($event); } + public function test_insecure_connection_skipped() + { + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(false); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + $event->expects($this->never()) + ->method('setResponse'); + + $this->subscriber->on_kernel_request($event); + } + public function test_http_auth_disabled() { $this->config['feed_http_auth'] = 0; @@ -112,6 +145,10 @@ public function test_http_auth_disabled() ->with('_route') ->willReturn('phpbb_feed_overall'); + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(true); + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') ->disableOriginalConstructor() ->getMock(); @@ -143,6 +180,10 @@ public function test_user_already_logged_in() ->with('_route') ->willReturn('phpbb_feed_overall'); + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(true); + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') ->disableOriginalConstructor() ->getMock(); From 82b72016aa2cfea16336b4913e2d105323efdcc1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 3 Oct 2025 22:20:33 +0200 Subject: [PATCH 0956/1214] [ticket/15085] Clean up code and output error with language PHPBB-15085 --- .../default/container/services_feed.yml | 1 + .../phpbb/feed/event/http_auth_subscriber.php | 124 ++++++------------ 2 files changed, 43 insertions(+), 82 deletions(-) diff --git a/phpBB/config/default/container/services_feed.yml b/phpBB/config/default/container/services_feed.yml index 2f27f6915b4..8a468959052 100644 --- a/phpBB/config/default/container/services_feed.yml +++ b/phpBB/config/default/container/services_feed.yml @@ -20,6 +20,7 @@ services: arguments: - '@auth' - '@config' + - '@language' - '@request' - '@user' tags: diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php index 6aedfb361bc..b2937a3c22e 100644 --- a/phpBB/phpbb/feed/event/http_auth_subscriber.php +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -15,6 +15,7 @@ use phpbb\auth\auth; use phpbb\config\config; +use phpbb\language\language; use phpbb\request\request_interface; use phpbb\user; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -33,6 +34,9 @@ class http_auth_subscriber implements EventSubscriberInterface /** @var config */ protected $config; + /** @var language */ + protected $language; + /** @var request_interface */ protected $request; @@ -44,13 +48,15 @@ class http_auth_subscriber implements EventSubscriberInterface * * @param auth $auth Auth object * @param config $config Config object + * @param language $language Language object * @param request_interface $request Request object * @param user $user User object */ - public function __construct(auth $auth, config $config, request_interface $request, user $user) + public function __construct(auth $auth, config $config, language $language, request_interface $request, user $user) { $this->auth = $auth; $this->config = $config; + $this->language = $language; $this->request = $request; $this->user = $user; } @@ -63,6 +69,12 @@ public function __construct(auth $auth, config $config, request_interface $reque */ public function on_kernel_request(GetResponseEvent $event) { + // Check if HTTP authentication is enabled + if (!$this->config['feed_http_auth']) + { + return; + } + $request = $event->getRequest(); $route = $request->attributes->get('_route'); @@ -78,12 +90,6 @@ public function on_kernel_request(GetResponseEvent $event) return; } - // Check if HTTP authentication is enabled - if (!$this->config['feed_http_auth']) - { - return; - } - // User is already logged in, no need to authenticate if (!empty($this->user->data['is_registered'])) { @@ -91,8 +97,7 @@ public function on_kernel_request(GetResponseEvent $event) } // Get HTTP authentication credentials - $username = $this->get_http_username(); - $password = $this->get_http_password(); + [$username, $password] = $this->get_credentials(); // If no credentials provided, send authentication challenge if ($username === null || $password === null) @@ -113,7 +118,7 @@ public function on_kernel_request(GetResponseEvent $event) else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS) { // Too many login attempts - $response = new Response('', Response::HTTP_UNAUTHORIZED); + $response = new Response($this->language->lang('NOT_AUTHORISED'), Response::HTTP_UNAUTHORIZED); $event->setResponse($response); return; } @@ -123,13 +128,13 @@ public function on_kernel_request(GetResponseEvent $event) } /** - * Get HTTP username from request headers + * Retrieve HTTP authentication credentials from server variables * - * @return string|null + * @return array [username, password] Array containing the username and password, or null if not found */ - protected function get_http_username() + protected function get_credentials(): array { - $username_keys = array( + $username_keys = [ 'PHP_AUTH_USER', 'Authorization', 'REMOTE_USER', @@ -139,85 +144,41 @@ protected function get_http_username() 'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION', 'AUTH_USER', - ); + ]; + + $password_keys = [ + 'PHP_AUTH_PW', + 'REMOTE_PASSWORD', + 'AUTH_PASSWORD', + ]; + $username = null; foreach ($username_keys as $key) { if ($this->request->is_set($key, request_interface::SERVER)) { - $username = html_entity_decode($this->request->server($key), ENT_COMPAT); - - // Decode Basic authentication header - if (strpos($username, 'Basic ') === 0) - { - $credentials = base64_decode(substr($username, 6)); - if (strpos($credentials, ':') !== false) - { - list($username, ) = explode(':', $credentials, 2); - } - } - - return $username; + $username = htmlspecialchars_decode($this->request->server($key)); + break; } } - return null; - } - - /** - * Get HTTP password from request headers - * - * @return string|null - */ - protected function get_http_password() - { - $password_keys = array( - 'PHP_AUTH_PW', - 'REMOTE_PASSWORD', - 'AUTH_PASSWORD', - ); - + $password = null; foreach ($password_keys as $key) { if ($this->request->is_set($key, request_interface::SERVER)) { - return html_entity_decode($this->request->server($key), ENT_COMPAT); + $password = htmlspecialchars_decode($this->request->server($key)); + break; } } - // Check if password is in Authorization header (Basic auth) - $username_keys = array( - 'PHP_AUTH_USER', - 'Authorization', - 'REMOTE_USER', - 'REDIRECT_REMOTE_USER', - 'HTTP_AUTHORIZATION', - 'REDIRECT_HTTP_AUTHORIZATION', - 'REMOTE_AUTHORIZATION', - 'REDIRECT_REMOTE_AUTHORIZATION', - 'AUTH_USER', - ); - - foreach ($username_keys as $key) + // Decode Basic authentication header if needed + if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0) { - if ($this->request->is_set($key, request_interface::SERVER)) - { - $auth_header = html_entity_decode($this->request->server($key), ENT_COMPAT); - - // Decode Basic authentication header - if (strpos($auth_header, 'Basic ') === 0) - { - $credentials = base64_decode(substr($auth_header, 6)); - if (strpos($credentials, ':') !== false) - { - list(, $password) = explode(':', $credentials, 2); - return $password; - } - } - } + [$username, $password] = explode(':', base64_decode(substr($username, 6)), 2); } - return null; + return [$username, $password]; } /** @@ -232,19 +193,18 @@ protected function send_auth_challenge(GetResponseEvent $event) // Filter out non-ASCII characters per RFC2616 $realm = preg_replace('/[\x80-\xFF]/', '?', $realm); - $response = new Response('', Response::HTTP_UNAUTHORIZED); - $response->headers->set('WWW-Authenticate', 'Basic realm="' . $realm . '"'); + $response = new Response($this->language->lang('NOT_AUTHORISED'), Response::HTTP_UNAUTHORIZED); + $response->headers->set('WWW-Authenticate', 'Basic realm="' . $realm . ' - Feed"'); $event->setResponse($response); } /** * {@inheritdoc} */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { - return array( - // Priority should be high to run after session_begin but before controller - KernelEvents::REQUEST => array('on_kernel_request', 5), - ); + return [ + KernelEvents::REQUEST => ['on_kernel_request', 5], + ]; } } From 2bc90da6876ebf2d272117199a5c398355836e50 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 4 Oct 2025 13:21:06 +0200 Subject: [PATCH 0957/1214] [ticket/15085] Extend unit tests for http_auth_subscriber PHPBB-15085 --- .../phpbb/feed/event/http_auth_subscriber.php | 1 + tests/feed/http_auth_subscriber_test.php | 315 ++++++++++++++++-- 2 files changed, 297 insertions(+), 19 deletions(-) diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php index b2937a3c22e..14426f3aad7 100644 --- a/phpBB/phpbb/feed/event/http_auth_subscriber.php +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -190,6 +190,7 @@ protected function get_credentials(): array protected function send_auth_challenge(GetResponseEvent $event) { $realm = $this->config['sitename']; + // Filter out non-ASCII characters per RFC2616 $realm = preg_replace('/[\x80-\xFF]/', '?', $realm); diff --git a/tests/feed/http_auth_subscriber_test.php b/tests/feed/http_auth_subscriber_test.php index ee50ec9088d..f25859c5351 100644 --- a/tests/feed/http_auth_subscriber_test.php +++ b/tests/feed/http_auth_subscriber_test.php @@ -1,26 +1,33 @@ -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -namespace phpbb\feed\event; - -class http_auth_subscriber_test extends \phpbb_test_case + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +class phpbb_feed_http_auth_subscriber_test extends \phpbb_test_case { /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\auth\auth */ protected $auth; - /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\config\config */ + /** @var \PHPUnit\Framework\MockObject\MockObject|config */ protected $config; + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\language\language */ + protected $language; + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\request\request_interface */ protected $request; @@ -37,12 +44,26 @@ protected function setUp(): void $this->auth = $this->getMockBuilder('\phpbb\auth\auth') ->disableOriginalConstructor() ->getMock(); - - $this->config = new \phpbb\config\config(array( + $this->auth->method('login') + ->willReturnMap([ + ['valid_user', 'valid_password', false, true, false, ['status' => LOGIN_SUCCESS]], + ['invalid_user', 'invalid_password', false, true, false, ['status' => LOGIN_ERROR_USERNAME]], + ['attempts_user', 'valid_password', false, true, false, ['status' => LOGIN_ERROR_ATTEMPTS]], + ]); + + $this->config = new config(array( 'feed_http_auth' => 1, 'sitename' => 'Test Site', )); + $this->language = $this->getMockBuilder('\phpbb\language\language') + ->disableOriginalConstructor() + ->getMock(); + $this->language->method('lang') + ->willReturnMap([ + ['NOT_AUTHORISED', 'NOT_AUTHORISED'], + ]); + $this->request = $this->getMockBuilder('\phpbb\request\request_interface') ->getMock(); @@ -55,6 +76,7 @@ protected function setUp(): void $this->subscriber = new http_auth_subscriber( $this->auth, $this->config, + $this->language, $this->request, $this->user ); @@ -132,6 +154,37 @@ public function test_http_auth_disabled() { $this->config['feed_http_auth'] = 0; + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->never()) + ->method('get'); + + $request->expects($this->never()) + ->method('isSecure'); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->never()) + ->method('getRequest'); + + $event->expects($this->never()) + ->method('setResponse'); + + $this->subscriber->on_kernel_request($event); + } + + public function test_user_already_logged_in() + { + $this->user->data = array('is_registered' => true); + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') ->disableOriginalConstructor() ->getMock(); @@ -163,9 +216,9 @@ public function test_http_auth_disabled() $this->subscriber->on_kernel_request($event); } - public function test_user_already_logged_in() + public function test_no_credentials() { - $this->user->data = array('is_registered' => true); + $this->user->data = ['is_registered' => false]; $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') ->disableOriginalConstructor() @@ -192,9 +245,233 @@ public function test_user_already_logged_in() ->method('getRequest') ->willReturn($request); + /** @var Response $response */ + $response = null; + $event->expects($this->once()) + ->method('setResponse') + ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Response')) + ->will($this->returnCallback(function ($newResponse) use (&$response) { + $response = $newResponse; + })); + + $this->subscriber->on_kernel_request($event); + + $this->assertEquals(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); + $this->assertEquals('NOT_AUTHORISED', $response->getContent()); + $this->assertTrue($response->headers->has('WWW-Authenticate')); + } + + public function test_valid_credentials() + { + $this->user->data = ['is_registered' => false]; + + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $this->request->method('is_set') + ->willReturnMap([ + ['PHP_AUTH_USER', request_interface::SERVER, true], + ['PHP_AUTH_PW', request_interface::SERVER, true], + ]); + + $this->request->method('server') + ->willReturnMap([ + ['PHP_AUTH_USER', '', 'valid_user'], + ['PHP_AUTH_PW', '', 'valid_password'], + ]); + + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(true); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + /** @var Response $response */ + $response = null; $event->expects($this->never()) ->method('setResponse'); $this->subscriber->on_kernel_request($event); + + $this->assertNull($response); + } + + public function test_valid_credentials_base64() + { + $this->user->data = ['is_registered' => false]; + + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $this->request->method('is_set') + ->willReturnMap([ + ['Authorization', request_interface::SERVER, true], + ]); + + $this->request->method('server') + ->willReturnMap([ + ['Authorization', '', 'Basic dmFsaWRfdXNlcjp2YWxpZF9wYXNzd29yZA=='], + ]); + + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(true); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + /** @var Response $response */ + $response = null; + $event->expects($this->never()) + ->method('setResponse'); + + $this->subscriber->on_kernel_request($event); + + $this->assertNull($response); + } + + public function test_too_many_attempts() + { + $this->user->data = ['is_registered' => false]; + + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $this->request->method('is_set') + ->willReturnMap([ + ['PHP_AUTH_USER', request_interface::SERVER, true], + ['PHP_AUTH_PW', request_interface::SERVER, true], + ]); + + $this->request->method('server') + ->willReturnMap([ + ['PHP_AUTH_USER', '', 'attempts_user'], + ['PHP_AUTH_PW', '', 'valid_password'], + ]); + + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(true); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + /** @var Response $response */ + $response = null; + $event->expects($this->once()) + ->method('setResponse') + ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Response')) + ->will($this->returnCallback(function ($newResponse) use (&$response) { + $response = $newResponse; + })); + + $this->subscriber->on_kernel_request($event); + + $this->assertEquals(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); + $this->assertEquals('NOT_AUTHORISED', $response->getContent()); + $this->assertFalse($response->headers->has('WWW-Authenticate')); + } + + public function test_wrong_credentials() + { + $this->user->data = ['is_registered' => false]; + + $request = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes = $this->getMockBuilder('\Symfony\Component\HttpFoundation\ParameterBag') + ->disableOriginalConstructor() + ->getMock(); + + $request->attributes->expects($this->once()) + ->method('get') + ->with('_route') + ->willReturn('phpbb_feed_overall'); + + $this->request->method('is_set') + ->willReturnMap([ + ['PHP_AUTH_USER', request_interface::SERVER, true], + ['PHP_AUTH_PW', request_interface::SERVER, true], + ]); + + $this->request->method('server') + ->willReturnMap([ + ['PHP_AUTH_USER', '', 'invalid_user'], + ['PHP_AUTH_PW', '', 'invalid_password'], + ]); + + $request->expects($this->once()) + ->method('isSecure') + ->willReturn(true); + + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + /** @var Response $response */ + $response = null; + $event->expects($this->once()) + ->method('setResponse') + ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Response')) + ->will($this->returnCallback(function ($newResponse) use (&$response) { + $response = $newResponse; + })); + + $this->subscriber->on_kernel_request($event); + + $this->assertEquals(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); + $this->assertEquals('NOT_AUTHORISED', $response->getContent()); + $this->assertTrue($response->headers->has('WWW-Authenticate')); } } From 8ef5c5549db69abe0f06f967fb1aed17c67f867f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 4 Oct 2025 13:27:54 +0200 Subject: [PATCH 0958/1214] [ticket/15085] Add info that HTTP auth is only possible via https PHPBB-15085 --- phpBB/language/en/acp/board.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index e99632e4424..bd0e8599de7 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -298,7 +298,7 @@ 'ACP_FEED_OVERALL_FORUMS_EXPLAIN' => 'Enables the “All forums” feed, which displays a list of forums.', 'ACP_FEED_HTTP_AUTH' => 'Allow HTTP Authentication', - 'ACP_FEED_HTTP_AUTH_EXPLAIN' => 'Enables HTTP authentication, which allows users to receive content that is hidden to guest users by adding the auth=http parameter to the feed URL. Please note that some PHP setups require additional changes to the .htaccess file. Instructions can be found in that file.', + 'ACP_FEED_HTTP_AUTH_EXPLAIN' => 'Enables HTTP authentication, allowing users to access content hidden from guests by adding the auth=http parameter to the feed URL. Please note that some PHP configurations may require additional changes to the .htaccess file; refer to that file for guidance. HTTP authentication is only supported over encrypted (https) connections.', 'ACP_FEED_ITEM_STATISTICS' => 'Item statistics', 'ACP_FEED_ITEM_STATISTICS_EXPLAIN' => 'Display individual statistics underneath feed items
      (e.g. posted by, date and time, replies, views)', 'ACP_FEED_EXCLUDE_ID' => 'Exclude these forums', From 033fb28fdc3bc707eee04fab99808612e7db46ca Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 4 Oct 2025 13:36:27 +0200 Subject: [PATCH 0959/1214] [ticket/15085] Improve commit message check hook output PHPBB-15085 --- git-tools/hooks/commit-msg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-tools/hooks/commit-msg b/git-tools/hooks/commit-msg index 6405d5b7c80..95b8e313edd 100755 --- a/git-tools/hooks/commit-msg +++ b/git-tools/hooks/commit-msg @@ -337,7 +337,7 @@ done # If EOF is expected exit cleanly echo "$expecting" | grep -q "eof" || ( # Unexpected EOF, error - complain "Unexpected EOF encountered" >&2; + complain "Expected to see footer (e.g. PHPBB-12345) or description line, but reached end of file." >&2; quit $ERR_EOF; ) && ( # Do post scan checks From 82a7e6db1908f422991fe650a015ac33f93f87ee Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 7 Oct 2025 12:30:46 +0700 Subject: [PATCH 0960/1214] [ticket/17565] Fix exporting PM as CSV for Excel PHPBB-17565 --- phpBB/includes/ucp/ucp_pm_viewfolder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/ucp/ucp_pm_viewfolder.php b/phpBB/includes/ucp/ucp_pm_viewfolder.php index 4b6377e0b74..b5da2d23feb 100644 --- a/phpBB/includes/ucp/ucp_pm_viewfolder.php +++ b/phpBB/includes/ucp/ucp_pm_viewfolder.php @@ -314,7 +314,7 @@ function view_folder($id, $mode, $folder_id, $folder) $newline = "\n"; } - $string = ''; + $string = $export_type == 'CSV_EXCEL' ? "\xEF\xBB\xBF" : ''; // Add UTF-8 BOM mark for Excel foreach ($data as $value) { $recipients = $value['to']; From a9b62f338a5a8bc15038d8962c1d97a70f8a1cb0 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 7 Oct 2025 19:44:01 +0200 Subject: [PATCH 0961/1214] [ticket/15085] Output login error with attempts info in http auth PHPBB-15085 --- phpBB/phpbb/feed/event/http_auth_subscriber.php | 2 +- tests/feed/http_auth_subscriber_test.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php index 14426f3aad7..4ff411666e7 100644 --- a/phpBB/phpbb/feed/event/http_auth_subscriber.php +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -118,7 +118,7 @@ public function on_kernel_request(GetResponseEvent $event) else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS) { // Too many login attempts - $response = new Response($this->language->lang('NOT_AUTHORISED'), Response::HTTP_UNAUTHORIZED); + $response = new Response($this->language->lang('LOGIN_ERROR_ATTEMPTS'), Response::HTTP_UNAUTHORIZED); $event->setResponse($response); return; } diff --git a/tests/feed/http_auth_subscriber_test.php b/tests/feed/http_auth_subscriber_test.php index f25859c5351..5fede1572b7 100644 --- a/tests/feed/http_auth_subscriber_test.php +++ b/tests/feed/http_auth_subscriber_test.php @@ -62,6 +62,7 @@ protected function setUp(): void $this->language->method('lang') ->willReturnMap([ ['NOT_AUTHORISED', 'NOT_AUTHORISED'], + ['LOGIN_ERROR_ATTEMPTS', 'LOGIN_ERROR_ATTEMPTS'] ]); $this->request = $this->getMockBuilder('\phpbb\request\request_interface') @@ -414,7 +415,7 @@ public function test_too_many_attempts() $this->subscriber->on_kernel_request($event); $this->assertEquals(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); - $this->assertEquals('NOT_AUTHORISED', $response->getContent()); + $this->assertEquals('LOGIN_ERROR_ATTEMPTS', $response->getContent()); $this->assertFalse($response->headers->has('WWW-Authenticate')); } From edf90678025778d6dfb24d8a4ae3b12050e2c926 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Tue, 7 Oct 2025 21:31:30 +0200 Subject: [PATCH 0962/1214] [ticket/15085] Use ResponseEvent in http auth subscriber and update tests PHPBB-15085 --- .../phpbb/feed/event/http_auth_subscriber.php | 10 +++--- tests/feed/http_auth_subscriber_test.php | 31 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/phpBB/phpbb/feed/event/http_auth_subscriber.php b/phpBB/phpbb/feed/event/http_auth_subscriber.php index 4ff411666e7..901164814c3 100644 --- a/phpBB/phpbb/feed/event/http_auth_subscriber.php +++ b/phpBB/phpbb/feed/event/http_auth_subscriber.php @@ -20,7 +20,7 @@ use phpbb\user; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; /** @@ -64,10 +64,10 @@ public function __construct(auth $auth, config $config, language $language, requ /** * Handle HTTP authentication for feed routes * - * @param GetResponseEvent $event + * @param RequestEvent $event * @return void */ - public function on_kernel_request(GetResponseEvent $event) + public function on_kernel_request(RequestEvent $event) { // Check if HTTP authentication is enabled if (!$this->config['feed_http_auth']) @@ -184,10 +184,10 @@ protected function get_credentials(): array /** * Send HTTP authentication challenge * - * @param GetResponseEvent $event + * @param RequestEvent $event * @return void */ - protected function send_auth_challenge(GetResponseEvent $event) + protected function send_auth_challenge(RequestEvent $event) { $realm = $this->config['sitename']; diff --git a/tests/feed/http_auth_subscriber_test.php b/tests/feed/http_auth_subscriber_test.php index 5fede1572b7..5e1c11b07c7 100644 --- a/tests/feed/http_auth_subscriber_test.php +++ b/tests/feed/http_auth_subscriber_test.php @@ -104,7 +104,8 @@ public function test_non_feed_route_skipped() ->with('_route') ->willReturn('not_a_feed_route'); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') + ->onlyMethods(['getRequest', 'setResponse']) ->disableOriginalConstructor() ->getMock(); @@ -137,7 +138,7 @@ public function test_insecure_connection_skipped() ->method('isSecure') ->willReturn(false); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -169,7 +170,7 @@ public function test_http_auth_disabled() $request->expects($this->never()) ->method('isSecure'); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -203,7 +204,7 @@ public function test_user_already_logged_in() ->method('isSecure') ->willReturn(true); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -238,7 +239,7 @@ public function test_no_credentials() ->method('isSecure') ->willReturn(true); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -251,9 +252,9 @@ public function test_no_credentials() $event->expects($this->once()) ->method('setResponse') ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Response')) - ->will($this->returnCallback(function ($newResponse) use (&$response) { + ->willReturnCallback(function ($newResponse) use (&$response) { $response = $newResponse; - })); + }); $this->subscriber->on_kernel_request($event); @@ -295,7 +296,7 @@ public function test_valid_credentials() ->method('isSecure') ->willReturn(true); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -344,7 +345,7 @@ public function test_valid_credentials_base64() ->method('isSecure') ->willReturn(true); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -395,7 +396,7 @@ public function test_too_many_attempts() ->method('isSecure') ->willReturn(true); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -408,9 +409,9 @@ public function test_too_many_attempts() $event->expects($this->once()) ->method('setResponse') ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Response')) - ->will($this->returnCallback(function ($newResponse) use (&$response) { + ->willReturnCallback(function ($newResponse) use (&$response) { $response = $newResponse; - })); + }); $this->subscriber->on_kernel_request($event); @@ -452,7 +453,7 @@ public function test_wrong_credentials() ->method('isSecure') ->willReturn(true); - $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\GetResponseEvent') + $event = $this->getMockBuilder('\Symfony\Component\HttpKernel\Event\RequestEvent') ->disableOriginalConstructor() ->getMock(); @@ -465,9 +466,9 @@ public function test_wrong_credentials() $event->expects($this->once()) ->method('setResponse') ->with($this->isInstanceOf('\Symfony\Component\HttpFoundation\Response')) - ->will($this->returnCallback(function ($newResponse) use (&$response) { + ->willReturnCallback(function ($newResponse) use (&$response) { $response = $newResponse; - })); + }); $this->subscriber->on_kernel_request($event); From 8e39a61d07c002d58da9073cfcb5e40abc343e2c Mon Sep 17 00:00:00 2001 From: cabot Date: Tue, 14 Oct 2025 17:55:30 +0200 Subject: [PATCH 0963/1214] [ticket/17567] Change wrap's min-width PHPBB-17567 --- phpBB/styles/prosilver/theme/common.css | 2 +- phpBB/styles/prosilver/theme/responsive.css | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 26c3b390554..9c9a32b17c5 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -154,7 +154,7 @@ a:hover { border: solid transparent; border-width: 0 1px 1px; border-radius: 0 0 8px 8px; - min-width: 625px; + min-width: 320px; max-width: 1152px; margin: 0 auto; padding: 0 15px 15px; diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index ce5462455e0..9ba363a5c05 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -182,8 +182,7 @@ .wrap { border: none; border-radius: 0; - min-width: 390px; - margin: 0 !important; + box-sizing: border-box; padding: 0 5px; } @@ -863,7 +862,7 @@ } } -@media (max-width: 1220px) { +@media (min-width: 751px) and (max-width: 1200px) { .wrap { margin: 0 12px; } From 888df78de50e0c28933b4e3fdf71440a4ce982a1 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 18 Oct 2025 22:56:16 +0700 Subject: [PATCH 0964/1214] [ticket/17560] Fix diff engine PHP fatal error PHPBB-17560 --- phpBB/includes/diff/diff.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/diff/diff.php b/phpBB/includes/diff/diff.php index 7c92d375701..83ebd83da29 100644 --- a/phpBB/includes/diff/diff.php +++ b/phpBB/includes/diff/diff.php @@ -768,12 +768,12 @@ class diff3_op /** * @var array|mixed */ - protected $final1; + public $final1; /** * @var array|mixed */ - protected $final2; + public $final2; /** * @var false From 4be8e8c92fa98a08a1bfa008f61fc04e4e5b3087 Mon Sep 17 00:00:00 2001 From: rxu Date: Sun, 19 Oct 2025 16:11:21 +0700 Subject: [PATCH 0965/1214] [ticket/17560] Add unit test PHPBB-17560 --- tests/diff/diff_files_test.php | 110 ++++++++++++++++++ .../install/update/new/test_files_diff.php | 3 + .../install/update/old/test_files_diff.php | 3 + tests/diff/fixtures/test_files_diff.php | 4 + 4 files changed, 120 insertions(+) create mode 100644 tests/diff/diff_files_test.php create mode 100644 tests/diff/fixtures/install/update/new/test_files_diff.php create mode 100644 tests/diff/fixtures/install/update/old/test_files_diff.php create mode 100644 tests/diff/fixtures/test_files_diff.php diff --git a/tests/diff/diff_files_test.php b/tests/diff/diff_files_test.php new file mode 100644 index 00000000000..c25537db41d --- /dev/null +++ b/tests/diff/diff_files_test.php @@ -0,0 +1,110 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +class diff_files_test extends phpbb_test_case +{ + /** + * @var \phpbb\install\module\update_filesystem\task\diff_files + */ + protected $diff_task; + + /** + * @var \phpbb\install\helper\config + */ + protected $config; + + /** + * @var phpbb_mock_container_builder + */ + protected $container; + + + /** + * @var \phpbb\request\request + */ + protected $request; + + private static $helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $phpEx; + + protected function setUp(): void + { + $this->phpbb_root_path = __DIR__ . '/../../phpBB/'; + $this->phpEx = 'php'; + + $language = new \phpbb\language\language( + new \phpbb\language\language_file_loader($this->phpbb_root_path, $this->phpEx) + ); + $this->request = new \phpbb\request\request(); + $update_helper = new \phpbb\install\helper\update_helper($this->phpbb_root_path); + $this->container = new \phpbb\install\helper\container_factory($language, $this->request, $update_helper, $this->phpbb_root_path, $this->phpEx); + + $iohandler = $this->createMock('\phpbb\install\helper\iohandler\iohandler_interface'); + + $this->config = new \phpbb\install\helper\config(new \phpbb\filesystem\filesystem(), new \bantu\IniGetWrapper\IniGetWrapper(), ''); + $update_files['update_with_diff'] = [ + 'test_files_diff.php', + ]; + $this->config->set('update_files', $update_files); + + $this->diff_task = new \phpbb\install\module\update_filesystem\task\diff_files($this->container, $this->config, $iohandler, $update_helper, $this->phpbb_root_path, $this->phpEx); + } + + protected function tearDown(): void + { + $disable_super_globals = $this->request->super_globals_disabled(); + + // This is needed because \phpbb\install\helper\container_factory disables it + if ($disable_super_globals) + { + $this->request->enable_super_globals(); + } + } + + public static function setUpBeforeClass(): void + { + $phpbb_root_path = __DIR__ . '/../../phpBB/'; + + parent::setUpBeforeClass(); + + self::$helper = new phpbb_test_case_helpers(__CLASS__); + self::$helper->copy_dir(__DIR__ . '/fixtures/', $phpbb_root_path); + } + + public static function tearDownAfterClass(): void + { + $phpbb_root_path = __DIR__ . '/../../phpBB/'; + + parent::tearDownAfterClass(); + + self::$helper->empty_dir($phpbb_root_path . 'install/update'); + rmdir($phpbb_root_path . 'install/update'); + unlink($phpbb_root_path . 'test_files_diff.php'); + } + + public function test_diff_files() + { + $this->diff_task->run(); + + $this->assertEmpty($this->config->get('update_files')); + } +} diff --git a/tests/diff/fixtures/install/update/new/test_files_diff.php b/tests/diff/fixtures/install/update/new/test_files_diff.php new file mode 100644 index 00000000000..6daabbe92e8 --- /dev/null +++ b/tests/diff/fixtures/install/update/new/test_files_diff.php @@ -0,0 +1,3 @@ + Date: Sun, 19 Oct 2025 14:31:45 +0200 Subject: [PATCH 0966/1214] [ticket/17569] Fix responsive display in navbar footer PHPBB-17569 --- phpBB/styles/prosilver/theme/common.css | 4 ++++ phpBB/styles/prosilver/theme/responsive.css | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 26c3b390554..d55d4e339b6 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -320,6 +320,10 @@ ul.linklist li.responsive-menu { margin: 0 5px 0 0; } +.nav-footer .responsive-menu { + display: none; +} + .hasjs ul.linklist.leftside, .hasjs ul.linklist.rightside { max-width: 48%; diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index ce5462455e0..68abef814d7 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -781,7 +781,6 @@ display: block !important; } - .nav-footer.linklist .responsive-menu, .nav-main.linklist .responsive-menu .in-menu { display: none !important; } From cac26d96c5582074d4ae977fb7c937acfc7a1374 Mon Sep 17 00:00:00 2001 From: rxu Date: Sun, 19 Oct 2025 19:00:38 +0700 Subject: [PATCH 0967/1214] [ticket/17560] Refactor unit test PHPBB-17560 --- tests/diff/diff_files_test.php | 153 ++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 67 deletions(-) diff --git a/tests/diff/diff_files_test.php b/tests/diff/diff_files_test.php index c25537db41d..b7e3a47ca08 100644 --- a/tests/diff/diff_files_test.php +++ b/tests/diff/diff_files_test.php @@ -11,100 +11,119 @@ * */ +include_once(__DIR__ . '/../../phpBB/includes/diff/diff.php'); +include_once(__DIR__ . '/../../phpBB/includes/diff/engine.php'); + class diff_files_test extends phpbb_test_case { /** - * @var \phpbb\install\module\update_filesystem\task\diff_files - */ - protected $diff_task; - - /** - * @var \phpbb\install\helper\config + * @var string */ - protected $config; + protected $filename; /** * @var phpbb_mock_container_builder */ - protected $container; - + protected $old_path; /** - * @var \phpbb\request\request + * @var \phpbb\install\helper\config */ - protected $request; - - private static $helper; + protected $new_path; /** * @var string */ - protected $phpbb_root_path; + protected $path; /** - * @var string + * @var array */ - protected $phpEx; + protected $update_files = []; protected function setUp(): void { - $this->phpbb_root_path = __DIR__ . '/../../phpBB/'; - $this->phpEx = 'php'; - - $language = new \phpbb\language\language( - new \phpbb\language\language_file_loader($this->phpbb_root_path, $this->phpEx) - ); - $this->request = new \phpbb\request\request(); - $update_helper = new \phpbb\install\helper\update_helper($this->phpbb_root_path); - $this->container = new \phpbb\install\helper\container_factory($language, $this->request, $update_helper, $this->phpbb_root_path, $this->phpEx); - - $iohandler = $this->createMock('\phpbb\install\helper\iohandler\iohandler_interface'); - - $this->config = new \phpbb\install\helper\config(new \phpbb\filesystem\filesystem(), new \bantu\IniGetWrapper\IniGetWrapper(), ''); - $update_files['update_with_diff'] = [ - 'test_files_diff.php', + $this->filename = 'test_files_diff.php'; + $this->path = __DIR__ . '/fixtures/'; + $this->old_path = $this->path . 'install/update/old/'; + $this->new_path = $this->path . 'install/update/new/'; + $this->update_files = [ + $this->filename, ]; - $this->config->set('update_files', $update_files); - - $this->diff_task = new \phpbb\install\module\update_filesystem\task\diff_files($this->container, $this->config, $iohandler, $update_helper, $this->phpbb_root_path, $this->phpEx); } - protected function tearDown(): void + public function test_diff_files() { - $disable_super_globals = $this->request->super_globals_disabled(); - - // This is needed because \phpbb\install\helper\container_factory disables it - if ($disable_super_globals) + foreach ($this->update_files as $key => $filename) { - $this->request->enable_super_globals(); + $merge_conflicts = $file_contents = []; + + $file_to_diff = $this->old_path . $filename; + $file_contents[0] = file_get_contents($file_to_diff); + $this->assertNotFalse($file_contents[0], "File $file_to_diff is empty"); + + $filenames = [ + $this->path . $filename, + $this->new_path . $filename + ]; + + foreach ($filenames as $file_to_diff) + { + $file_contents[] = file_get_contents($file_to_diff); + $this->assertNotFalse($file_contents[count($file_contents) - 1], "File $file_to_diff is empty"); + } + + $diff = new \diff3($file_contents[0], $file_contents[1], $file_contents[2]); + + $file_is_merged = $diff->merged_output() === $file_contents[1]; + + // Handle conflicts + if ($diff->get_num_conflicts() !== 0) + { + // Check if current file content is merge of new or original file + $tmp = [ + 'file1' => $file_contents[1], + 'file2' => implode("\n", $diff->merged_new_output()), + ]; + + $diff2 = new \diff($tmp['file1'], $tmp['file2']); + $empty = $diff2->is_empty(); + + if (!$empty) + { + unset($tmp, $diff2); + + // We check if the user merged with his output + $tmp = [ + 'file1' => $file_contents[1], + 'file2' => implode("\n", $diff->merged_orig_output()), + ]; + + $diff2 = new \diff($tmp['file1'], $tmp['file2']); + $empty = $diff2->is_empty(); + } + + unset($diff2); + + if (!$empty && in_array($filename, $merge_conflicts)) + { + $merge_conflicts[] = $filename; + } + else + { + $file_is_merged = true; + } + } + + if ($file_is_merged) + { + unset($this->update_files[$key]); + } + + unset($file_contents); + unset($diff); } - } - - public static function setUpBeforeClass(): void - { - $phpbb_root_path = __DIR__ . '/../../phpBB/'; - - parent::setUpBeforeClass(); - - self::$helper = new phpbb_test_case_helpers(__CLASS__); - self::$helper->copy_dir(__DIR__ . '/fixtures/', $phpbb_root_path); - } - - public static function tearDownAfterClass(): void - { - $phpbb_root_path = __DIR__ . '/../../phpBB/'; - - parent::tearDownAfterClass(); - - self::$helper->empty_dir($phpbb_root_path . 'install/update'); - rmdir($phpbb_root_path . 'install/update'); - unlink($phpbb_root_path . 'test_files_diff.php'); - } - - public function test_diff_files() - { - $this->diff_task->run(); - $this->assertEmpty($this->config->get('update_files')); + $this->assertEquals([], $this->update_files); } } From 93b89d64fd97d0ccba2638aebbbef0c99f08c6a3 Mon Sep 17 00:00:00 2001 From: rxu Date: Sun, 19 Oct 2025 20:14:56 +0700 Subject: [PATCH 0968/1214] [ticket/17560] Fix coding style PHPBB-17560 --- phpBB/includes/diff/engine.php | 130 +++++++++++------- .../update_filesystem/task/diff_files.php | 4 +- 2 files changed, 82 insertions(+), 52 deletions(-) diff --git a/phpBB/includes/diff/engine.php b/phpBB/includes/diff/engine.php index 0d73db02dad..8fcff6c4950 100644 --- a/phpBB/includes/diff/engine.php +++ b/phpBB/includes/diff/engine.php @@ -1,68 +1,98 @@ -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ /** -* @ignore -*/ + * @ignore + */ if (!defined('IN_PHPBB')) { exit; } /** -* Code from pear.php.net, Text_Diff-1.1.0 package -* http://pear.php.net/package/Text_Diff/ (native engine) -* -* Modified by phpBB Limited to meet our coding standards -* and being able to integrate into phpBB -* -* Class used internally by Text_Diff to actually compute the diffs. This -* class is implemented using native PHP code. -* -* The algorithm used here is mostly lifted from the perl module -* Algorithm::Diff (version 1.06) by Ned Konz, which is available at: -* http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip -* -* More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html -* -* Some ideas (and a bit of code) are taken from analyze.c, of GNU -* diffutils-2.7, which can be found at: -* ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz -* -* Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from -* Geoffrey T. Dairiki . The original PHP version of this -* code was written by him, and is used/adapted with his permission. -* -* Copyright 2004-2008 The Horde Project (http://www.horde.org/) -* -* @author Geoffrey T. Dairiki -* @package diff -* -* @access private -*/ + * Code from pear.php.net, Text_Diff-1.1.0 package + * http://pear.php.net/package/Text_Diff/ (native engine) + * + * Modified by phpBB Limited to meet our coding standards + * and being able to integrate into phpBB + * + * Class used internally by Text_Diff to actually compute the diffs. This + * class is implemented using native PHP code. + * + * The algorithm used here is mostly lifted from the perl module + * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: + * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip + * + * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html + * + * Some ideas (and a bit of code) are taken from analyze.c, of GNU + * diffutils-2.7, which can be found at: + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz + * + * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from + * Geoffrey T. Dairiki . The original PHP version of this + * code was written by him, and is used/adapted with his permission. + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * @author Geoffrey T. Dairiki + * @package diff + * + * @access private + */ class diff_engine { + /** var array */ + protected $in_seq; + + /** var int */ + protected $lcs; + + /** var array */ + protected $seq; + /** - * If set to true we trim all lines before we compare them. This ensures that sole space/tab changes do not trigger diffs. - */ - var $skip_whitespace_changes = true; + * bool + * + * If set to true we trim all lines before we compare them. This ensures that sole space/tab changes do not trigger diffs. + */ + protected $skip_whitespace_changes = true; + + /** var array */ + protected $xchanged; + + /** var array */ + protected $xind; + + /** var array */ + protected $xv; + + /** var array */ + protected $ychanged; + + /** var array */ + protected $yind; + + /** var array */ + protected $yv; function diff(&$from_lines, &$to_lines, $preserve_cr = true) { - // Remove empty lines... - // If preserve_cr is true, we basically only change \r\n and bare \r to \n to get the same carriage returns for both files - // If it is false, we try to only use \n once per line and ommit all empty lines to be able to get a proper data diff - + /* + * Remove empty lines... + * If preserve_cr is true, we basically only change \r\n and bare \r to \n to get the same carriage returns for both files + * If it is false, we try to only use \n once per line and ommit all empty lines to be able to get a proper data diff + */ if (is_array($from_lines)) { $from_lines = implode("\n", $from_lines); @@ -87,7 +117,7 @@ function diff(&$from_lines, &$to_lines, $preserve_cr = true) $n_from = count($from_lines); $n_to = count($to_lines); - $this->xchanged = $this->ychanged = $this->xv = $this->yv = $this->xind = $this->yind = array(); + $this->xchanged = $this->ychanged = $this->xv = $this->yv = $this->xind = $this->yind = []; unset($this->seq, $this->in_seq, $this->lcs); // Skip leading common lines. diff --git a/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php b/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php index e4ed6a77140..45caa0d3cfa 100644 --- a/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php +++ b/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php @@ -187,8 +187,8 @@ public function run() if (!$empty && in_array($filename, $merge_conflicts)) { - $merge_conflicts[] = $filename; - } + $merge_conflicts[] = $filename; + } else { $file_is_merged = true; From 22ce4641064e57b63c0b78d1bfb38b53061394e0 Mon Sep 17 00:00:00 2001 From: cabot Date: Mon, 20 Oct 2025 14:38:00 +0200 Subject: [PATCH 0969/1214] [ticket/17570] Replace old CSS accessibility hack PHPBB-17570 --- phpBB/styles/prosilver/theme/bidi.css | 7 +------ phpBB/styles/prosilver/theme/content.css | 9 +++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/phpBB/styles/prosilver/theme/bidi.css b/phpBB/styles/prosilver/theme/bidi.css index 4b284439694..af18e1b17c8 100644 --- a/phpBB/styles/prosilver/theme/bidi.css +++ b/phpBB/styles/prosilver/theme/bidi.css @@ -395,11 +395,6 @@ li.breadcrumbs span:first-child > a { border-left: none; } -.rtl ul.topiclist dfn { - left: auto; - right: -999px; -} - .rtl ul.topiclist li.row dt a.subforum { padding-right: 12px; background-position: right; @@ -1071,7 +1066,7 @@ li.breadcrumbs span:first-child > a { .captcha-panel dd.captcha { margin-right: 0; } - + .rtl p.responsive-center { float: none; text-align: center; diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index f436391d1c3..916d3d8028e 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -77,9 +77,14 @@ ul.topiclist li.row dd { ul.topiclist dfn { /* Labels for post/view counts */ + border: 0; position: absolute; - left: -999px; - width: 990px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + width: 1px; + height: 1px; + margin: -1px; + padding: 0; } .forum-image { From 9466f505b662b9b486bbe628a962a300190833f7 Mon Sep 17 00:00:00 2001 From: Prosk8er Date: Fri, 24 Oct 2025 21:45:00 -0400 Subject: [PATCH 0970/1214] [ticket/17572] meta apple-mobile-web-app-capable depreciated is deprecated. Please include add to overall_header.html PHPBB-17572 --- phpBB/styles/prosilver/template/overall_header.html | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index 71df3ccd4d8..cb94e8b11d3 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -6,6 +6,7 @@ + {META} <!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --> From 6e3f16f2305cf7895472f28530bac76519171c07 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 25 Oct 2025 19:10:51 +0200 Subject: [PATCH 0971/1214] [ticket/17571] Fix undefined user_id in event dispatcher PHPBB3-17571 --- phpBB/phpbb/storage/controller/attachment.php | 1 + 1 file changed, 1 insertion(+) diff --git a/phpBB/phpbb/storage/controller/attachment.php b/phpBB/phpbb/storage/controller/attachment.php index 7aed1ccdb37..610b4b17779 100644 --- a/phpBB/phpbb/storage/controller/attachment.php +++ b/phpBB/phpbb/storage/controller/attachment.php @@ -370,6 +370,7 @@ protected function phpbb_download_handle_pm_auth(int $msg_id): void } $allowed = $this->phpbb_download_check_pm_auth($msg_id); + $user_id = $this->user->data['user_id']; /** * Event to modify PM attachments download auth From d9788b09c3a23de13b23f72ce734601706a3f5e6 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sun, 26 Oct 2025 11:24:42 +0100 Subject: [PATCH 0972/1214] [ticket/17573] Skip tests when dbms is null PHPBB3-17573 --- tests/migrator/schema_generator_test.php | 10 +++++++++- tests/test_framework/phpbb_database_test_case.php | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/migrator/schema_generator_test.php b/tests/migrator/schema_generator_test.php index e7129c4cc3e..2df7bf05d36 100644 --- a/tests/migrator/schema_generator_test.php +++ b/tests/migrator/schema_generator_test.php @@ -57,7 +57,15 @@ protected function setUp(): void $this->config = new \phpbb\config\config(array()); $this->db = new \phpbb\db\driver\sqlite3(); - $this->doctrine_db = \phpbb\db\doctrine\connection_factory::get_connection(new phpbb_mock_config_php_file()); + // Some local setups may not configure a DB driver. If the configured + // 'dbms' is null, skip this test instead of failing with a TypeError in + // the connection factory which expects a non-null driver name. + $mock_config = new phpbb_mock_config_php_file(); + if ($mock_config->get('dbms') === null) + { + self::markTestSkipped('Skipping schema generator tests: dbms is not configured (null) in local environment.'); + } + $this->doctrine_db = \phpbb\db\doctrine\connection_factory::get_connection($mock_config); $factory = new \phpbb\db\tools\factory(); $this->db_tools = $factory->get($this->doctrine_db); $this->db_tools->set_table_prefix($this->table_prefix); diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php index 6da764adfb0..de8a605f2af 100644 --- a/tests/test_framework/phpbb_database_test_case.php +++ b/tests/test_framework/phpbb_database_test_case.php @@ -66,7 +66,15 @@ public static function setUpBeforeClass(): void global $table_prefix; $db = new \phpbb\db\driver\sqlite3(); - $doctrine = \phpbb\db\doctrine\connection_factory::get_connection(new phpbb_mock_config_php_file()); + // Some local setups may not configure a DB driver. If the configured + // 'dbms' is null, skip this test instead of failing with a TypeError in + // the connection factory which expects a non-null driver name. + $mock_config = new phpbb_mock_config_php_file(); + if ($mock_config->get('dbms') === null) + { + self::markTestSkipped('Skipping schema generator tests: dbms is not configured (null) in local environment.'); + } + $doctrine = \phpbb\db\doctrine\connection_factory::get_connection($mock_config); $factory = new \phpbb\db\tools\factory(); $db_tools = $factory->get($doctrine, true); $db_tools->set_table_prefix($table_prefix); From 4f9312809718200ad1817a25a70713bbf94acfb4 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 26 Oct 2025 08:07:14 -0700 Subject: [PATCH 0973/1214] [ticket/17575] Fix web manifest scope path creation PHPBB-17575 --- phpBB/config/default/container/services.yml | 1 - phpBB/phpbb/manifest.php | 23 ++-- tests/manifest/controller_test.php | 138 ++++++++++++++++++++ 3 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 tests/manifest/controller_test.php diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index ecf8f3109e7..6efad8827af 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -163,7 +163,6 @@ services: class: phpbb\manifest arguments: - '@config' - - '@path_helper' - '@event_dispatcher' - '@user' diff --git a/phpBB/phpbb/manifest.php b/phpBB/phpbb/manifest.php index 6b48138517e..72a830abf38 100644 --- a/phpBB/phpbb/manifest.php +++ b/phpBB/phpbb/manifest.php @@ -22,9 +22,6 @@ class manifest /** @var config */ protected $config; - /** @var path_helper */ - protected $path_helper; - /** @var dispatcher_interface */ protected $phpbb_dispatcher; @@ -35,14 +32,12 @@ class manifest * Constructor for manifest controller * * @param config $config - * @param path_helper $path_helper * @param dispatcher_interface $phpbb_dispatcher * @param user $user */ - public function __construct(config $config, path_helper $path_helper, dispatcher_interface $phpbb_dispatcher, user $user) + public function __construct(config $config, dispatcher_interface $phpbb_dispatcher, user $user) { $this->config = $config; - $this->path_helper = $path_helper; $this->phpbb_dispatcher = $phpbb_dispatcher; $this->user = $user; } @@ -54,7 +49,12 @@ public function __construct(config $config, path_helper $path_helper, dispatcher */ public function handle(): JsonResponse { - $board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : $this->path_helper->get_web_root_path(); + // Get the board URL and extract the path component + $board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : (parse_url(generate_board_url())['path'] ?? ''); + + // Ensure path ends with '/' for PWA scope + $scope = rtrim($board_path, '/\\') . '/'; + $start_url = $scope; $sitename = html_entity_decode($this->config['sitename'], ENT_QUOTES, 'UTF-8'); $sitename_short = html_entity_decode($this->config['sitename_short'], ENT_QUOTES, 'UTF-8'); @@ -64,8 +64,8 @@ public function handle(): JsonResponse 'short_name' => $sitename_short ?: utf8_substr($sitename, 0, 12), 'display' => 'standalone', 'orientation' => 'portrait', - 'start_url' => $board_path, - 'scope' => $board_path, + 'start_url' => $start_url, + 'scope' => $scope, ]; /** @@ -73,12 +73,13 @@ public function handle(): JsonResponse * * @event core.modify_manifest * @var array manifest Array of manifest members - * @var string board_path Path to the board root + * @var string scope PWA scope path + * @var string start_url PWA start URL * @var string sitename Full name of the board * @var string sitename_short Shortened name of the board * @since 4.0.0-a1 */ - $vars = ['manifest', 'board_path', 'sitename', 'sitename_short']; + $vars = ['manifest', 'scope', 'start_url', 'sitename', 'sitename_short']; extract($this->phpbb_dispatcher->trigger_event('core.modify_manifest', compact($vars))); $response = new JsonResponse($manifest); diff --git a/tests/manifest/controller_test.php b/tests/manifest/controller_test.php new file mode 100644 index 00000000000..6b98002a1e2 --- /dev/null +++ b/tests/manifest/controller_test.php @@ -0,0 +1,138 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +use Symfony\Component\HttpFoundation\JsonResponse; + +class phpbb_manifest_controller_test extends phpbb_test_case +{ + protected $config; + protected $user; + protected $manifest; + + protected function setUp(): void + { + global $config, $user, $phpbb_root_path, $phpEx; + + parent::setUp(); + + $config = $this->config = new phpbb\config\config([ + 'sitename' => 'phpBB Testing Framework', + 'sitename_short' => '', + 'force_server_vars' => false, + 'script_path' => '', + ]); + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $lang_loader = new phpbb\language\language_file_loader($phpbb_root_path, $phpEx); + $language = new phpbb\language\language($lang_loader); + $user = $this->user = new phpbb\user($language, '\phpbb\datetime'); + + $this->manifest = new phpbb\manifest($this->config, $phpbb_dispatcher, $this->user); + } + + public static function manifest_data() + { + return [ + 'using board url root path' => [ + [ + 'force_server_vars' => false, + ], + [ + 'name' => 'phpBB Testing Framework', + 'short_name' => 'phpBB Testin', + 'display' => 'standalone', + 'orientation' => 'portrait', + 'start_url' => '/', + 'scope' => '/', + ], + ], + 'using script path' => [ + [ + 'force_server_vars' => true, + 'script_path' => '/foo/', + ], + [ + 'name' => 'phpBB Testing Framework', + 'short_name' => 'phpBB Testin', + 'display' => 'standalone', + 'orientation' => 'portrait', + 'start_url' => '/foo/', + 'scope' => '/foo/', + ], + ], + 'with shortname' => [ + [ + 'sitename_short' => 'phpBB Test', + ], + [ + 'name' => 'phpBB Testing Framework', + 'short_name' => 'phpBB Test', + 'display' => 'standalone', + 'orientation' => 'portrait', + 'start_url' => '/', + 'scope' => '/', + ], + ], + 'without shortname or script path' => [ + [], + [ + 'name' => 'phpBB Testing Framework', + 'short_name' => 'phpBB Testin', + 'display' => 'standalone', + 'orientation' => 'portrait', + 'start_url' => '/', + 'scope' => '/', + ], + ], + ]; + } + + /** + * @dataProvider manifest_data + */ + public function test_manifest($configs, $expected) + { + foreach ($configs as $key => $value) + { + $this->config->set($key, $value); + } + + $response = $this->manifest->handle(); + + $this->assertInstanceOf(JsonResponse::class, $response); + + $this->assertEquals($expected, json_decode($response->getContent(), true)); + } + + public static function manifest_with_bot_data() + { + return [ + 'is a bot' => [true, 'yes'], + 'not a bot' => [false, null], + ]; + } + + /** + * @dataProvider manifest_with_bot_data + */ + public function test_manifest_with_bot($is_bot, $expected) + { + $this->user->data['is_bot'] = $is_bot; + + $response = $this->manifest->handle(); + + $this->assertInstanceOf(JsonResponse::class, $response); + + $this->assertEquals($expected, $response->headers->get('X-PHPBB-IS-BOT')); + } +} From 215256019ef0001f48d0a44fa560a83b367ab92c Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Mon, 23 Dec 2024 23:25:40 +0100 Subject: [PATCH 0974/1214] [ticket/17447] Add http range requests to attachments downloads PHPBB-17447 --- phpBB/develop/adjust_avatars.php | 5 +- phpBB/phpbb/storage/controller/attachment.php | 161 ++++++++++++------ phpBB/phpbb/storage/controller/avatar.php | 6 +- phpBB/phpbb/storage/controller/controller.php | 59 +++---- 4 files changed, 137 insertions(+), 94 deletions(-) diff --git a/phpBB/develop/adjust_avatars.php b/phpBB/develop/adjust_avatars.php index a6b326109dd..f470305e5eb 100644 --- a/phpBB/develop/adjust_avatars.php +++ b/phpBB/develop/adjust_avatars.php @@ -4,6 +4,9 @@ * * You should make a backup from your users table and the avatar directory in case something goes wrong */ + +use phpbb\storage\provider\local; + die("Please read the first lines of this script for instructions on how to enable it"); set_time_limit(0); @@ -30,7 +33,7 @@ die('database not up to date'); } -if (!isset($config['storage\\avatar\\config\\path']) || $config['storage\\avatar\\config\\path'] !== 'phpbb\\storage\\provider\\local') +if (!isset($config['storage\\avatar\\provider']) || $config['storage\\avatar\\provider'] !== local::class) { die('use local provider'); } diff --git a/phpBB/phpbb/storage/controller/attachment.php b/phpBB/phpbb/storage/controller/attachment.php index 7aed1ccdb37..2ac36f839fb 100644 --- a/phpBB/phpbb/storage/controller/attachment.php +++ b/phpBB/phpbb/storage/controller/attachment.php @@ -24,8 +24,11 @@ use phpbb\language\language; use phpbb\mimetype\extension_guesser; use phpbb\request\request; +use phpbb\storage\provider\local; use phpbb\storage\storage; use phpbb\user; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\HeaderUtils; use Symfony\Component\HttpFoundation\Request as symfony_request; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; @@ -35,26 +38,41 @@ /** * Controller for /download/attachment/{id} routes */ -class attachment extends controller +class attachment { /** @var auth */ protected $auth; + /** @var service */ + protected $cache; + /** @var config */ protected $config; /** @var content_visibility */ protected $content_visibility; + /** @var driver_interface */ + protected $db; + /** @var dispatcher_interface */ protected $dispatcher; + /** @var extension_guesser */ + protected $extension_guesser; + /** @var language */ protected $language; /** @var request */ protected $request; + /** @var storage */ + protected $storage; + + /** @var symfony_request */ + protected $symfony_request; + /** @var user */ protected $user; @@ -76,14 +94,17 @@ class attachment extends controller */ public function __construct(auth $auth, service $cache, config $config, content_visibility $content_visibility, driver_interface $db, dispatcher_interface $dispatcher, extension_guesser $extension_guesser, language $language, request $request, storage $storage, symfony_request $symfony_request, user $user) { - parent::__construct($cache, $db, $extension_guesser, $storage, $symfony_request); - $this->auth = $auth; + $this->cache = $cache; $this->config = $config; $this->content_visibility = $content_visibility; + $this->db = $db; $this->dispatcher = $dispatcher; + $this->extension_guesser = $extension_guesser; $this->language = $language; $this->request = $request; + $this->storage = $storage; + $this->symfony_request = $symfony_request; $this->user = $user; } @@ -110,15 +131,7 @@ public function handle_attachment(int $id, string $filename): Response throw new http_exception(404, 'NO_ATTACHMENT_SELECTED'); } - $sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id, - is_orphan, physical_filename, real_filename, extension, mimetype, - filesize, filetime - FROM ' . ATTACHMENTS_TABLE . " - WHERE attach_id = $attach_id" . - (($filename) ? " AND real_filename = '" . $this->db->sql_escape($filename) . "'" : ''); - $result = $this->db->sql_query($sql); - $attachment = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); + $attachment = $this->get_attachment($attach_id, $filename); if (!$attachment) { @@ -157,12 +170,7 @@ public function handle_attachment(int $id, string $filename): Response { $this->phpbb_download_handle_forum_auth($attachment['topic_id']); - $sql = 'SELECT forum_id, poster_id, post_visibility - FROM ' . POSTS_TABLE . ' - WHERE post_id = ' . (int) $attachment['post_msg_id']; - $result = $this->db->sql_query($sql); - $post_row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); + $post_row = $this->get_post((int) $attachment['post_msg_id']); if (!$post_row || !$this->content_visibility->is_visible('post', $post_row['forum_id'], $post_row)) { @@ -173,11 +181,11 @@ public function handle_attachment(int $id, string $filename): Response else { // Attachment is in a private message. - $post_row = array('forum_id' => false); + $post_row = ['forum_id' => false]; $this->phpbb_download_handle_pm_auth( $attachment['post_msg_id']); } - $extensions = array(); + $extensions = []; if (!extension_allowed($post_row['forum_id'], $attachment['extension'], $extensions)) { throw new http_exception(403, 'EXTENSION_DISABLED_AFTER_POSTING', [$attachment['extension']]); @@ -216,13 +224,13 @@ public function handle_attachment(int $id, string $filename): Response * @changed 3.3.0-a1 Remove display_cat variable * @changed 3.3.0-a1 Remove mode variable */ - $vars = array( + $vars = [ 'attach_id', 'attachment', 'extensions', 'thumbnail', 'redirect', - ); + ]; extract($this->dispatcher->trigger_event('core.download_file_send_to_browser_before', compact($vars))); // If the redirect variable have been overwritten, do redirect there @@ -247,26 +255,15 @@ public function handle_attachment(int $id, string $filename): Response * @changed 3.3.0-a1 Removed size variable * @changed 3.3.0-a1 Removed filename variable */ - $vars = array( + $vars = [ 'attachment', - ); + ]; extract($this->dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars))); - // TODO: The next lines should go better in prepare, also the mimetype is handled by the storage table - // so probably can be removed - - $response = new StreamedResponse(); - - // Content-type header - $response->headers->set('Content-Type', $attachment['mimetype']); - - // Display file types in browser and force download for others - if (strpos($attachment['mimetype'], 'image') !== false - || strpos($attachment['mimetype'], 'audio') !== false - || strpos($attachment['mimetype'], 'video') !== false - ) + // Display images in browser and force download for others + if ($display_cat == attachment_category::IMAGE && str_starts_with($attachment['mimetype'], 'image')) { - $disposition = $response->headers->makeDisposition( + $disposition = HeaderUtils::makeDisposition( ResponseHeaderBag::DISPOSITION_INLINE, $attachment['real_filename'], $this->filenameFallback($attachment['real_filename']) @@ -274,40 +271,91 @@ public function handle_attachment(int $id, string $filename): Response } else { - $disposition = $response->headers->makeDisposition( + // Correct the mime type - we force application/octet-stream for all files, except images + $attachment['mimetype'] = 'application/octet-stream'; + + $disposition = HeaderUtils::makeDisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT, $attachment['real_filename'], $this->filenameFallback($attachment['real_filename']) ); } + if ($this->config['storage\\attachment\\provider'] === local::class) + { + $response = new BinaryFileResponse($this->config['storage\\attachment\\config\\path'] . '/' . $attachment['physical_filename']); + } + else + { + $response = new StreamedResponse(); + + $fp = $this->storage->read($attachment['physical_filename']); + + $output = fopen('php://output', 'w+b'); + + $response->setCallback(function () use ($fp, $output) { + stream_copy_to_stream($fp, $output); + fclose($fp); + fclose($output); + flush(); + + // Terminate script to avoid the execution of terminate events + // This avoids possible errors with db connection closed + exit; + }); + } + + // Close db connection and unload cache + $this->cache->unload(); + $this->db->sql_close(); + + // Don't cache attachments on proxies + $response->setPrivate(); + + // Content-type and content-disposition headers + $response->headers->set('Content-Type', $attachment['mimetype']); $response->headers->set('Content-Disposition', $disposition); - // Set expires header for browser cache - $time = new \DateTime(); - $response->setExpires($time->modify('+1 year')); + @set_time_limit(0); - return parent::handle($attachment['physical_filename']); + return $response; } - /** - * Remove non valid characters https://github.com/symfony/http-foundation/commit/c7df9082ee7205548a97031683bc6550b5dc9551 - */ - protected function filenameFallback($filename): string + protected function get_attachment(int $attach_id, string $filename): array|null { - $filename = (string) preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\\/'], '', $filename); + $sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id, + is_orphan, physical_filename, real_filename, extension, mimetype, + filesize, filetime + FROM ' . ATTACHMENTS_TABLE . " + WHERE attach_id = $attach_id" . + (($filename) ? " AND real_filename = '" . $this->db->sql_escape($filename) . "'" : ''); + $result = $this->db->sql_query($sql); + $attachment = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); - return !empty($filename) ? $filename : 'File'; + return $attachment ?: null; + } + + protected function get_post(int $post_id): array|null + { + $sql = 'SELECT forum_id, poster_id, post_visibility + FROM ' . POSTS_TABLE . ' + WHERE post_id = ' . (int) $post_id; + $result = $this->db->sql_query($sql); + $post_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $post_row ?: null; } /** - * {@inheritdoc} + * Remove non valid characters https://github.com/symfony/http-foundation/commit/c7df9082ee7205548a97031683bc6550b5dc9551 */ - protected function prepare(StreamedResponse $response, string $file): void + protected function filenameFallback($filename): string { - $response->setPrivate(); // By default, should be private, but make sure of it + $filename = (string) preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\\/'], '', $filename); - parent::prepare($response, $file); + return !empty($filename) ? $filename : 'File'; } /** @@ -380,7 +428,7 @@ protected function phpbb_download_handle_pm_auth(int $msg_id): void * @var int user_id The user id for auth check * @since 3.1.11-RC1 */ - $vars = array('allowed', 'msg_id', 'user_id'); + $vars = ['allowed', 'msg_id', 'user_id']; extract($this->dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars))); if (!$allowed) @@ -407,7 +455,8 @@ protected function phpbb_download_check_pm_auth(int $msg_id): bool AND ( user_id = ' . (int) $user_id . ' OR author_id = ' . (int) $user_id . ' - )'; + ) + '; $result = $this->db->sql_query_limit($sql, 1); $allowed = (bool) $this->db->sql_fetchfield('msg_id'); $this->db->sql_freeresult($result); @@ -460,7 +509,7 @@ protected function download_allowed(): bool unset($url); $allowed = !$this->config['secure_allow_deny']; - $iplist = array(); + $iplist = []; if (($ip_ary = @gethostbynamel($hostname)) !== false) { diff --git a/phpBB/phpbb/storage/controller/avatar.php b/phpBB/phpbb/storage/controller/avatar.php index ba8a8a4e957..c4406f73888 100644 --- a/phpBB/phpbb/storage/controller/avatar.php +++ b/phpBB/phpbb/storage/controller/avatar.php @@ -18,10 +18,10 @@ use phpbb\db\driver\driver_interface; use phpbb\mimetype\extension_guesser; use phpbb\storage\storage; +use Symfony\Component\HttpFoundation\HeaderUtils; use Symfony\Component\HttpFoundation\Request as symfony_request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; -use Symfony\Component\HttpFoundation\StreamedResponse; /** * Controller for /download/avatar/{file} routes @@ -98,11 +98,11 @@ protected function decode_filename(string $file): string /** * {@inheritdoc} */ - protected function prepare(StreamedResponse $response, string $file): void + protected function prepare(Response $response, string $file): void { $response->setPublic(); - $disposition = $response->headers->makeDisposition( + $disposition = HeaderUtils::makeDisposition( ResponseHeaderBag::DISPOSITION_INLINE, rawurlencode($file) ); diff --git a/phpBB/phpbb/storage/controller/controller.php b/phpBB/phpbb/storage/controller/controller.php index 155d3df7a6a..bdf59ae2ddb 100644 --- a/phpBB/phpbb/storage/controller/controller.php +++ b/phpBB/phpbb/storage/controller/controller.php @@ -72,8 +72,6 @@ public function __construct(service $cache, driver_interface $db, extension_gues */ public function handle(string $file): Response { - $response = new StreamedResponse(); - if (!static::is_allowed($file)) { throw new http_exception(403, 'Forbidden'); @@ -84,6 +82,21 @@ public function handle(string $file): Response throw new http_exception(404, 'Not Found'); } + $response = new StreamedResponse(); + $fp = $this->storage->read($file); + $output = fopen('php://output', 'w+b'); + + $response->setCallback(function () use ($fp, $output) { + stream_copy_to_stream($fp, $output); + fclose($fp); + fclose($output); + flush(); + + // Terminate script to avoid the execution of terminate events + // This avoids possible errors with db connection closed + exit; + }); + static::prepare($response, $file); if (headers_sent()) @@ -91,6 +104,14 @@ public function handle(string $file): Response throw new http_exception(500, 'Headers already sent'); } + // Close db connection and unload cache + $this->cache->unload(); + $this->db->sql_close(); + + $response->isNotModified($this->symfony_request); + + @set_time_limit(0); + return $response; } @@ -121,13 +142,13 @@ protected function file_exists(string $file): bool /** * Prepare response * - * @param StreamedResponse $response + * @param Response $response * @param string $file File path * * @return void * @throws storage_exception when there is an error reading the file */ - protected function prepare(StreamedResponse $response, string $file): void + protected function prepare(Response $response, string $file): void { // Add Content-Type header if (!$response->headers->has('Content-Type')) @@ -157,35 +178,5 @@ protected function prepare(StreamedResponse $response, string $file): void } } - @set_time_limit(0); - - $fp = $this->storage->read($file); - - // Close db connection - $this->file_gc(); - - $output = fopen('php://output', 'w+b'); - - $response->setCallback(function () use ($fp, $output) { - stream_copy_to_stream($fp, $output); - fclose($fp); - fclose($output); - flush(); - - // Terminate script to avoid the execution of terminate events - // This avoids possible errors with db connection closed - exit; - }); - - $response->isNotModified($this->symfony_request); - } - - /** - * Garbage Collection - */ - protected function file_gc(): void - { - $this->cache->unload(); // Equivalent to $this->cache->get_driver()->unload(); - $this->db->sql_close(); } } From ce4240d0982adfc623b7ff47aa90691d2b524009 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 25 Oct 2025 20:10:57 +0200 Subject: [PATCH 0975/1214] [ticket/15813] Short array syntax, avoid globals and other small changes PHPBB3-15813 --- .../default/container/services_members.yml | 7 + phpBB/config/default/routing/routing.yml | 1 + phpBB/memberlist.php | 2 +- phpBB/phpbb/members/controller/team.php | 193 +++++++++++------- tests/functional/memberlist_test.php | 2 +- 5 files changed, 134 insertions(+), 71 deletions(-) diff --git a/phpBB/config/default/container/services_members.yml b/phpBB/config/default/container/services_members.yml index a75015e2396..edacb697cf6 100644 --- a/phpBB/config/default/container/services_members.yml +++ b/phpBB/config/default/container/services_members.yml @@ -13,3 +13,10 @@ services: - '@language' - '@template' - '@user' + - '%core.root_path%' + - '%core.php_ext%' + - '%tables.forums%' + - '%tables.groups%' + - '%tables.teampage%' + - '%tables.user_group%' + - '%tables.users%' diff --git a/phpBB/config/default/routing/routing.yml b/phpBB/config/default/routing/routing.yml index 8e28174b165..3a6f6fbcb1e 100644 --- a/phpBB/config/default/routing/routing.yml +++ b/phpBB/config/default/routing/routing.yml @@ -30,6 +30,7 @@ phpbb_manifest_controller: phpbb_members_routing: resource: members.yml + prefix: /members phpbb_mention_controller: path: /mention diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index adeec2e6432..1170411f98e 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -50,7 +50,7 @@ if ($mode == 'leaders') { send_status_line(301, 'Moved Permanently'); - redirect(append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team')); + redirect($controller_helper->route('phpbb_members_team', [], false)); } // Check our mode... diff --git a/phpBB/phpbb/members/controller/team.php b/phpBB/phpbb/members/controller/team.php index a536f98e703..1aac91d70cf 100644 --- a/phpBB/phpbb/members/controller/team.php +++ b/phpBB/phpbb/members/controller/team.php @@ -23,6 +23,7 @@ use phpbb\language\language; use phpbb\template\template; use phpbb\user; +use Symfony\Component\HttpFoundation\Response; class team { @@ -71,7 +72,63 @@ class team */ protected $user; - public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, group_helper $group_helper, helper $helper, language $language, template $template, user $user) + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * @var string + */ + protected $forums_table; + + /** + * @var string + */ + protected $groups_table; + + /** + * @var string + */ + protected $teampage_table; + + /** + * @var string + */ + protected $user_group_table; + + /** + * @var string + */ + protected $users_table; + + + /** + * Constructor + * + * @param auth $auth Authentication service + * @param config $config Configuration + * @param driver_interface $db Database driver + * @param dispatcher $dispatcher Event dispatcher + * @param group_helper $group_helper Group helper + * @param helper $helper Controller helper + * @param language $language Language service + * @param template $template Template service + * @param user $user User object + * @param string $phpbb_root_path Path to phpBB root + * @param string $phpEx PHP file extension + * @param string $forums_table Table name for forums + * @param string $groups_table Table name for groups + * @param string $teampage_table Table name for teampage + * @param string $user_group_table Table name for user_group + * @param string $users_table Table name for users + */ + public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, group_helper $group_helper, helper $helper, language $language, template $template, user $user, string $phpbb_root_path, string $phpEx, string $forums_table, string $groups_table, string $teampage_table, string $user_group_table, string $users_table) { $this->auth = $auth; $this->config = $config; @@ -82,23 +139,31 @@ public function __construct(auth $auth, config $config, driver_interface $db, di $this->language = $language; $this->template = $template; $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + $this->forums_table = $forums_table; + $this->groups_table = $groups_table; + $this->teampage_table = $teampage_table; + $this->user_group_table = $user_group_table; + $this->users_table = $users_table; } /** * Controller for /team route * - * @return \Symfony\Component\HttpFoundation\Response a Symfony response object + * @return Response a Symfony response object */ - public function handle() + public function handle() : Response { - global $phpbb_root_path, $phpEx; - // Display a listing of board admins, moderators if (!function_exists('user_get_id_name')) { - include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + if (!function_exists('phpbb_get_user_rank')) + { + include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); } - include($phpbb_root_path . 'includes/functions_display.' . $phpEx); // Load language strings $this->language->add_lang('memberlist'); @@ -115,32 +180,32 @@ public function handle() } $sql = 'SELECT * - FROM ' . TEAMPAGE_TABLE . ' + FROM ' . $this->teampage_table . ' ORDER BY teampage_position ASC'; $result = $this->db->sql_query($sql, 3600); $teampage_data = $this->db->sql_fetchrowset($result); $this->db->sql_freeresult($result); - $sql_ary = array( + $sql_ary = [ 'SELECT' => 'g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id as ug_user_id, t.teampage_id', - 'FROM' => array(GROUPS_TABLE => 'g'), + 'FROM' => [$this->groups_table => 'g'], - 'LEFT_JOIN' => array( - array( - 'FROM' => array(TEAMPAGE_TABLE => 't'), + 'LEFT_JOIN' => [ + [ + 'FROM' => [$this->teampage_table => 't'], 'ON' => 't.group_id = g.group_id', - ), - array( - 'FROM' => array(USER_GROUP_TABLE => 'ug'), + ], + [ + 'FROM' => [$this->user_group_table => 'ug'], 'ON' => 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $this->user->data['user_id'], - ), - ), - ); + ], + ], + ]; $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary)); - $group_ids = $groups_ary = array(); + $group_ids = $groups_ary = []; while ($row = $this->db->sql_fetchrow($result)) { if ($row['group_type'] == GROUP_HIDDEN && !$this->auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $this->user->data['user_id']) @@ -151,7 +216,7 @@ public function handle() else { $row['group_name'] = $this->group_helper->get_name($row['group_name']); - $row['u_group'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $row['group_id']); + $row['u_group'] = append_sid("{$this->phpbb_root_path}memberlist.{$this->php_ext}", 'mode=group&g=' . $row['group_id']); } if ($row['teampage_id']) @@ -164,28 +229,28 @@ public function handle() } $this->db->sql_freeresult($result); - $sql_ary = array( + $sql_ary = [ 'SELECT' => 'u.user_id, u.group_id as default_group, u.username, u.username_clean, u.user_colour, u.user_type, u.user_rank, u.user_posts, u.user_allow_pm, g.group_id', - 'FROM' => array( - USER_GROUP_TABLE => 'ug', - ), + 'FROM' => [ + $this->user_group_table => 'ug', + ], - 'LEFT_JOIN' => array( - array( - 'FROM' => array(USERS_TABLE => 'u'), + 'LEFT_JOIN' => [ + [ + 'FROM' => [$this->users_table => 'u'], 'ON' => 'ug.user_id = u.user_id', - ), - array( - 'FROM' => array(GROUPS_TABLE => 'g'), + ], + [ + 'FROM' => [$this->groups_table => 'g'], 'ON' => 'ug.group_id = g.group_id', - ), - ), + ], + ], - 'WHERE' => $this->db->sql_in_set('g.group_id', $group_ids, false, true), + 'WHERE' => $this->db->sql_in_set('g.group_id', $group_ids, false, true) . ' AND ug.user_pending = 0', 'ORDER_BY' => 'u.username_clean ASC', - ); + ]; /** * Modify the query used to get the users for the team page @@ -196,20 +261,16 @@ public function handle() * @var array teampage_data The teampage data * @since 3.1.3-RC1 */ - $vars = array( - 'sql_ary', - 'group_ids', - 'teampage_data', - ); + $vars = ['sql_ary', 'group_ids', 'teampage_data']; extract($this->dispatcher->trigger_event('core.memberlist_team_modify_query', compact($vars))); $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary)); - $user_ary = $user_ids = $group_users = array(); + $user_ary = $user_ids = $group_users = []; while ($row = $this->db->sql_fetchrow($result)) { $row['forums'] = ''; - $row['forums_ary'] = array(); + $row['forums_ary'] = []; $user_ary[(int) $row['user_id']] = $row; $user_ids[] = (int) $row['user_id']; $group_users[(int) $row['group_id']][] = (int) $row['user_id']; @@ -222,11 +283,11 @@ public function handle() { $this->template->assign_var('S_DISPLAY_MODERATOR_FORUMS', true); // Get all moderators - $perm_ary = $this->auth->acl_get_list($user_ids, array('m_'), false); + $perm_ary = $this->auth->acl_get_list($user_ids, ['m_'], false); foreach ($perm_ary as $forum_id => $forum_ary) { - foreach ($forum_ary as $this->auth_option => $id_ary) + foreach ($forum_ary as $id_ary) { foreach ($id_ary as $id) { @@ -243,10 +304,10 @@ public function handle() } $sql = 'SELECT forum_id, forum_name - FROM ' . FORUMS_TABLE; + FROM ' . $this->forums_table; $result = $this->db->sql_query($sql); - $forums = array(); + $forums = []; while ($row = $this->db->sql_fetchrow($result)) { $forums[$row['forum_id']] = $row['forum_name']; @@ -272,17 +333,15 @@ public function handle() } } - $parent_team = 0; foreach ($teampage_data as $team_data) { // If this team entry has no group, it's a category if (!$team_data['group_id']) { - $this->template->assign_block_vars('group', array( + $this->template->assign_block_vars('group', [ 'GROUP_NAME' => $team_data['teampage_name'], - )); + ]); - $parent_team = (int) $team_data['teampage_id']; continue; } @@ -292,11 +351,11 @@ public function handle() if (!$team_data['teampage_parent']) { // If the group does not have a parent category, we display the groupname as category - $this->template->assign_block_vars('group', array( + $this->template->assign_block_vars('group', [ 'GROUP_NAME' => $group_data['group_name'], 'GROUP_COLOR' => $group_data['group_colour'], 'U_GROUP' => $group_data['u_group'], - )); + ]); } // Display group members. @@ -315,7 +374,7 @@ public function handle() $user_rank_data = phpbb_get_user_rank($row, (($row['user_id'] == ANONYMOUS) ? false : $row['user_posts'])); - $template_vars = array( + $template_vars = [ 'USER_ID' => $row['user_id'], 'FORUMS' => $row['forums'], 'FORUM_OPTIONS' => (isset($row['forums_options'])) ? true : false, @@ -330,13 +389,13 @@ public function handle() 'S_INACTIVE' => $row['user_type'] == USER_INACTIVE, - 'U_PM' => ($this->config['allow_privmsg'] && $this->auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $this->auth->acl_gets('a_', 'm_') || $this->auth->acl_getf_global('m_'))) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $row['user_id']) : '', + 'U_PM' => ($this->config['allow_privmsg'] && $this->auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $this->auth->acl_gets('a_', 'm_') || $this->auth->acl_getf_global('m_'))) ? append_sid("{$this->phpbb_root_path}ucp.{$this->php_ext}", 'i=pm&mode=compose&u=' . $row['user_id']) : '', 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), 'USER_COLOR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), 'U_VIEW_PROFILE' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), - ); + ]; /** * Modify the template vars for displaying the user in the groups on the teampage @@ -347,11 +406,7 @@ public function handle() * @var array groups_ary Array of groups with all users that should be displayed * @since 3.1.3-RC1 */ - $vars = array( - 'template_vars', - 'row', - 'groups_ary', - ); + $vars = ['template_vars', 'row', 'groups_ary']; extract($this->dispatcher->trigger_event('core.memberlist_team_modify_template_vars', compact($vars))); $this->template->assign_block_vars('group.user', $template_vars); @@ -365,17 +420,17 @@ public function handle() } } - $this->template->assign_vars(array( - 'PM_IMG' => $this->user->img('icon_contact_pm', $this->language->lang('SEND_PRIVATE_MESSAGE'))) - ); + $this->template->assign_vars([ + 'PM_IMG' => $this->user->img('icon_contact_pm', $this->language->lang('SEND_PRIVATE_MESSAGE')), + ]); - // Breadcrums - $this->template->assign_block_vars('navlinks', array( - 'BREADCRUMB_NAME' => $this->language->lang('THE_TEAM'), - 'U_BREADCRUMB' => $this->helper->route('phpbb_members_team'), - )); + // Breadcrumbs + $this->template->assign_block_vars('navlinks', [ + 'BREADCRUMB_NAME' => $this->language->lang('THE_TEAM'), + 'U_BREADCRUMB' => $this->helper->route('phpbb_members_team'), + ]); - make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + make_jumpbox(append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}")); // Render return $this->helper->render('memberlist_team.html', $this->language->lang('THE_TEAM')); diff --git a/tests/functional/memberlist_test.php b/tests/functional/memberlist_test.php index 21b16a666bc..0847aadd5c5 100644 --- a/tests/functional/memberlist_test.php +++ b/tests/functional/memberlist_test.php @@ -43,7 +43,7 @@ public function test_viewprofile() protected function get_memberlist_leaders_table_crawler() { - $crawler = self::request('GET', 'team?sid=' . $this->sid); + $crawler = self::request('GET', 'members/team'); return $crawler->filter('.forumbg-table'); } From 20e48f7c3b1a657589aa1b77e49d9d62ac2d942e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 27 Sep 2025 16:48:26 +0200 Subject: [PATCH 0976/1214] [ticket/17553] Only remove jabber notifications settings PHPBB-17553 --- .../db/migration/data/v400/remove_jabber.php | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php index 17bf49d7534..ab27b1bc067 100644 --- a/phpBB/phpbb/db/migration/data/v400/remove_jabber.php +++ b/phpBB/phpbb/db/migration/data/v400/remove_jabber.php @@ -114,26 +114,12 @@ public function revert_data(): array ]; } - public function move_jabber_to_email_notifications(int|null $start) + public function move_jabber_to_email_notifications() { - $limit = 1000; + $sql = 'DELETE FROM ' . $this->tables['user_notifications'] . " + WHERE method = 'notification.method.jabber'"; + $this->db->sql_query($sql); - $sql = 'SELECT id FROM ' . $this->tables['user_notifications'] . " - WHERE method = 'notification.method.jabber' - ORDER BY id ASC"; - $result = $this->db->sql_query_limit($sql, $limit, $start ?: 0); - $rowset = $this->db->sql_fetchrowset($result); - $this->db->sql_freeresult($result); - $ids_array = array_column($rowset, 'id'); - - if (count($ids_array)) - { - $sql = 'UPDATE ' . $this->tables['user_notifications'] . ' - SET ' . $this->db->sql_build_array('UPDATE', ['method' => 'notification.method.email']) . ' - WHERE ' . $this->db->sql_in_set('id', $ids_array); - $this->db->sql_query($sql); - } - - return count($ids_array) < $limit ? true : $start + $limit; + return true; } } From 9ca7a86dfe21daae2883e67c183e73df5b64d5c0 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 27 Sep 2025 17:36:46 +0200 Subject: [PATCH 0977/1214] [ticket/17553] Adjust unit tests for jabber migration PHPBB-17553 --- tests/migrations/remove_jabber_migration_test.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/migrations/remove_jabber_migration_test.php b/tests/migrations/remove_jabber_migration_test.php index e80a5cbbaac..bf1962b6675 100644 --- a/tests/migrations/remove_jabber_migration_test.php +++ b/tests/migrations/remove_jabber_migration_test.php @@ -42,13 +42,13 @@ public function test_remove_jabber_migration() WHERE method = 'notification.method.jabber'"; $this->db->sql_query($sql); $this->assertFalse($this->db->sql_fetchfield('id')); - + $sql = "SELECT id FROM phpbb_user_notifications - WHERE method = 'notification.method.email'"; + WHERE method = 'notification.method.jabber'"; $result = $this->db->sql_query($sql); $rowset = $this->db->sql_fetchrowset($result); $this->db->sql_freeresult($result); - $this->assertEquals(14, count($rowset)); + $this->assertEquals(0, count($rowset)); $sql = "SELECT config_name FROM phpbb_config WHERE config_name = 'jab_enable'"; From 9675d0d3507c57975e5b08620ffb0246d6bc35bf Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 2 Nov 2025 10:26:11 -0800 Subject: [PATCH 0978/1214] [ticket/17563] CodeSniffer throws uninitialized string offset errors PHPBB-17563 --- build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index 56a3e747d40..7cf5f64baf5 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -103,7 +103,7 @@ public function process(File $phpcsFile, $stackPtr) $ok = $this->findClassUsage($phpcsFile, $stackPtr, $tokens, $name_full, $name_short); } - if ($name_full[0] === '\\') + if (!empty($name_full) && $name_full[0] === '\\') { $phpcsFile->addError("There must not be a leading '\\' in use statements.", $stackPtr, 'Malformed'); } From 1f7cf6dcee8f2c5fb893e6ece7d3e24489ad3aa3 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 29 Sep 2025 15:11:14 -0700 Subject: [PATCH 0979/1214] [ticket/17561] Use ext-catalog for compatible extension catalog items PHPBB-17561 --- phpBB/phpbb/composer/installer.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index f5633629945..757a23616b5 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -525,6 +525,12 @@ private function get_compatible_versions(array $compatible_packages, ConstraintI } } + // Check for ext-catalog in 'extra' section - must be present and set to true + if (!isset($extra['ext-catalog']) || $extra['ext-catalog'] !== true) + { + continue; + } + $compatible_packages[$package_name][] = $version; } catch (\Exception $e) From 9fb58ea0b20f0cc10e541cb09c6784eadbf34674 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Tue, 30 Sep 2025 11:19:37 -0700 Subject: [PATCH 0980/1214] [ticket/17561] Require installers 2 to be listed in catalog PHPBB-17561 --- phpBB/phpbb/composer/installer.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/composer/installer.php b/phpBB/phpbb/composer/installer.php index 757a23616b5..7e8943bee49 100644 --- a/phpBB/phpbb/composer/installer.php +++ b/phpBB/phpbb/composer/installer.php @@ -525,8 +525,17 @@ private function get_compatible_versions(array $compatible_packages, ConstraintI } } - // Check for ext-catalog in 'extra' section - must be present and set to true - if (!isset($extra['ext-catalog']) || $extra['ext-catalog'] !== true) + // Check for composer/installers requirement - must support version 2.0 or later + if (isset($requires['composer/installers'])) + { + $installers_constraint = $requires['composer/installers']->getConstraint(); + $min_version_constraint = $version_parser->parseConstraints('>=2.0'); + if (!$min_version_constraint->matches($installers_constraint)) + { + continue; + } + } + else { continue; } From 7350ead28cbb09d8d58ab551e066d13d4fe96f68 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 4 Nov 2025 11:32:09 +0700 Subject: [PATCH 0981/1214] [ticket/17581] Fix PHP warning on bots viewing posts with avatars PHPBB-17581 --- phpBB/styles/prosilver/template/viewtopic_body.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index febc2d35d46..447e32524d6 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -144,7 +144,7 @@

      {POLL_
      - {postrow.POSTER_AVATAR_HTML}{postrow.POSTER_AVATAR} + {postrow.POSTER_AVATAR_HTML}{postrow.POSTER_AVATAR_HTML}
      From 1799af8859f3b3b13ea4faeb0f7fba6e40841c40 Mon Sep 17 00:00:00 2001 From: Christian Schnegelberger Date: Wed, 5 Nov 2025 22:04:41 +0100 Subject: [PATCH 0982/1214] [ticket/17582] Add lang('') to variables in acp_ext_list.html PHPBB-17582 --- phpBB/adm/style/acp_ext_list.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/phpBB/adm/style/acp_ext_list.html b/phpBB/adm/style/acp_ext_list.html index 3264d5e8b33..e1774577247 100644 --- a/phpBB/adm/style/acp_ext_list.html +++ b/phpBB/adm/style/acp_ext_list.html @@ -99,9 +99,9 @@

      {L_EXTENSIONS_ADMIN}

        -
      1. {{ EXTENSION_INSTALLING_EXPLAIN_STEP1 }}
      2. -
      3. {{ EXTENSION_INSTALLING_EXPLAIN_STEP2 }}
      4. -
      5. {{ EXTENSION_INSTALLING_EXPLAIN_STEP3 }}
      6. +
      7. {{ lang('EXTENSION_INSTALLING_EXPLAIN_STEP1') }}
      8. +
      9. {{ lang('EXTENSION_INSTALLING_EXPLAIN_STEP2') }}
      10. +
      11. {{ lang('EXTENSION_INSTALLING_EXPLAIN_STEP3') }}
      @@ -111,10 +111,10 @@

      {L_EXTENSIONS_ADMIN}

        -
      1. {{ EXTENSION_UPDATING_EXPLAIN_STEP1 }}
      2. -
      3. {{ EXTENSION_UPDATING_EXPLAIN_STEP2 }}
      4. -
      5. {{ EXTENSION_UPDATING_EXPLAIN_STEP3 }}
      6. -
      7. {{ EXTENSION_UPDATING_EXPLAIN_STEP4 }}
      8. +
      9. {{ lang('EXTENSION_UPDATING_EXPLAIN_STEP1') }}
      10. +
      11. {{ lang('EXTENSION_UPDATING_EXPLAIN_STEP2') }}
      12. +
      13. {{ lang('EXTENSION_UPDATING_EXPLAIN_STEP3') }}
      14. +
      15. {{ lang('EXTENSION_UPDATING_EXPLAIN_STEP4') }}
      @@ -124,9 +124,9 @@

      {L_EXTENSIONS_ADMIN}

        -
      1. {{ EXTENSION_REMOVING_EXPLAIN_STEP1 }}
      2. -
      3. {{ EXTENSION_REMOVING_EXPLAIN_STEP2 }}
      4. -
      5. {{ EXTENSION_REMOVING_EXPLAIN_STEP3 }}
      6. +
      7. {{ lang('EXTENSION_REMOVING_EXPLAIN_STEP1') }}
      8. +
      9. {{ lang('EXTENSION_REMOVING_EXPLAIN_STEP2') }}
      10. +
      11. {{ lang('EXTENSION_REMOVING_EXPLAIN_STEP3') }}
      From 07deaa04f88e8118e4b5500048b6b4a83690b0b0 Mon Sep 17 00:00:00 2001 From: cabot Date: Thu, 2 Oct 2025 16:20:40 +0200 Subject: [PATCH 0983/1214] [ticket/17562] Fix prosilver base font-size PHPBB-17562 --- phpBB/styles/prosilver/theme/common.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 26c3b390554..e48d7ee5b79 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -5,8 +5,6 @@ /* stylelint-disable selector-no-qualifying-type */ html { - font-size: 100%; - /* Always show a scrollbar for short pages - stops the jump when the scrollbar appears. non-IE browsers */ height: 101%; } From b50082a1653071c8196819e279407233e46815a6 Mon Sep 17 00:00:00 2001 From: Neo-CTC <92761505+Neo-CTC@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:23:00 -0600 Subject: [PATCH 0984/1214] [ticket/17580] Downloading a byte range of 8192 cause fatal errors When a client asks to download part file using a Range header with a range of 8192 bytes, the send_file_to_browser() throws a fatal error as it tries to read zero bytes on line 279. Requesting a Range with a multiple of 8192 also throws a fatal error. PHPBB-17580 --- phpBB/includes/functions_download.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php index 78b56fc755a..90058929860 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -268,12 +268,16 @@ function send_file_to_browser($attachment, $upload_dir, $category) header('Content-Length: ' . $range['bytes_requested']); // First read chunks - while (!feof($fp) && ftell($fp) < $range['byte_pos_end'] - 8192) + while (!feof($fp) && ftell($fp) <= $range['byte_pos_end'] - 8191) { echo fread($fp, 8192); } // Then, read the remainder - echo fread($fp, $range['bytes_requested'] % 8192); + $remainder = $range['bytes_requested'] % 8192; + if ($remainder > 0) + { + echo fread($fp, $remainder); + } } else { From a6356b682a79601e977efd90f122468f966abe51 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 16 Nov 2025 08:41:20 +0100 Subject: [PATCH 0985/1214] [ticket/17580] Change while to be more readable again PHPBB-17580 --- phpBB/includes/functions_download.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php index 90058929860..eb607f020bc 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -268,7 +268,7 @@ function send_file_to_browser($attachment, $upload_dir, $category) header('Content-Length: ' . $range['bytes_requested']); // First read chunks - while (!feof($fp) && ftell($fp) <= $range['byte_pos_end'] - 8191) + while (!feof($fp) && ftell($fp) < $range['byte_pos_end'] - 8192) { echo fread($fp, 8192); } From 87ddd91e440956d37604b2eb8ca50df759455ec4 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 23 Nov 2025 07:49:59 -0800 Subject: [PATCH 0986/1214] [ticket/17584] Exclude tests from some PSR1 code sniffs PHPBB-17584 --- build/code_sniffer/ruleset-php-strict.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/code_sniffer/ruleset-php-strict.xml b/build/code_sniffer/ruleset-php-strict.xml index 9e2f0664d8f..12ac8014fb4 100644 --- a/build/code_sniffer/ruleset-php-strict.xml +++ b/build/code_sniffer/ruleset-php-strict.xml @@ -32,7 +32,9 @@ - + + */tests/* + #', '', $code); + return preg_replace('##', '', $code) ?: ''; } /** @@ -162,7 +162,7 @@ protected function fix_inline_variable_tokens($tokens, $code) return ""; }; - return preg_replace_callback('##', $callback, $code); + return preg_replace_callback('##', $callback, $code) ?: ''; } /** @@ -177,7 +177,7 @@ protected function fix_inline_variable_tokens($tokens, $code) */ protected function add_surrounding_quotes($tokens, $code) { - return preg_replace('##', '', $code); + return preg_replace('##', '', $code) ?: ''; } /** @@ -253,7 +253,7 @@ public function fix_begin_tokens($code, $parent_nodes = array()) return "{% for {$name} in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; }; - return preg_replace_callback('#(.+?)#s', $callback, $code); + return preg_replace_callback('#(.+?)#s', $callback, $code) ?: ''; } /** @@ -285,7 +285,7 @@ protected function fix_if_tokens($code) return ""; }; - return preg_replace_callback('##', $callback, $code); + return preg_replace_callback('##', $callback, $code) ?: ''; } /** @@ -320,7 +320,7 @@ protected function fix_define_tokens($code) // Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~ $code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code); - return $code; + return $code ?: ''; } /** @@ -330,7 +330,7 @@ protected function fix_define_tokens($code) * * @param string $code * @param array $twig_tags All tags we want to create a mask for - * @return string + * @return null|string */ protected function replace_twig_tag_masks($code, $twig_tags) { diff --git a/phpBB/phpbb/textformatter/s9e/factory.php b/phpBB/phpbb/textformatter/s9e/factory.php index 78f8cbe6c66..ecb36fb7e98 100644 --- a/phpBB/phpbb/textformatter/s9e/factory.php +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -647,7 +647,7 @@ protected function merge_templates(array $style_templates) // Return the template as-is if there's only one style or all styles share the same template if (count(array_unique($style_templates)) === 1) { - return end($style_templates); + return end($style_templates) ?: ''; } // Group identical templates together diff --git a/phpBB/phpbb/ucp/controller/webpush.php b/phpBB/phpbb/ucp/controller/webpush.php index 01111f9ca41..61dd0ce1cf4 100644 --- a/phpBB/phpbb/ucp/controller/webpush.php +++ b/phpBB/phpbb/ucp/controller/webpush.php @@ -166,7 +166,7 @@ private function get_user_notifications(): string throw new http_exception(Response::HTTP_BAD_REQUEST, 'AJAX_ERROR_TEXT'); } - return $this->get_notification_data($notification_data); + return $this->get_notification_data($notification_data) ?: ''; } /** @@ -219,7 +219,7 @@ private function get_anonymous_notifications(): string { $this->language->set_user_language($user_lang, true); } - return $this->get_notification_data($notification_data); + return $this->get_notification_data($notification_data) ?: ''; } } @@ -231,9 +231,9 @@ private function get_anonymous_notifications(): string * * @param string $notification_data Encoded data stored in database * - * @return string Data for notification output with javascript + * @return false|string Data for notification output with javascript */ - private function get_notification_data(string $notification_data): string + private function get_notification_data(string $notification_data): false|string { $row_data = json_decode($notification_data, true); diff --git a/psalm.xml b/psalm.xml index a85fe20a03c..2bb877d330b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,7 @@ phpVersion="8.2" findUnusedBaselineEntry="true" findUnusedCode="false" + ensureOverrideAttribute="false" autoloader="build/psalm_bootstrap.php" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" @@ -11,15 +12,15 @@ > + + + - - - - - + + From 129c2436ee30164896a2da6a6d329e5938ec53ee Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 29 Nov 2025 08:19:25 +0100 Subject: [PATCH 0996/1214] [ticket/17556] Update to Symfony 7.4 stable version PHPBB-17556 --- phpBB/composer.json | 38 +- phpBB/composer.lock | 1052 ++++++++++++++++++++++--------------------- 2 files changed, 550 insertions(+), 540 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index 8df501ecd6d..9777e023f07 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -42,23 +42,23 @@ "marc1706/fast-image-size": "^1.1", "minishlink/web-push": "^8.0", "s9e/text-formatter": "^2.0", - "symfony/config": "^7.4@dev", - "symfony/console": "^7.4@dev", - "symfony/dependency-injection": "^7.4@dev", - "symfony/error-handler": "^7.4@dev", - "symfony/event-dispatcher": "^7.4@dev", - "symfony/filesystem": "^7.4@dev", - "symfony/finder": "^7.4@dev", - "symfony/http-foundation": "^7.4@dev", - "symfony/http-kernel": "^7.4@dev", + "symfony/config": "^7.4", + "symfony/console": "^7.4", + "symfony/dependency-injection": "^7.4", + "symfony/error-handler": "^7.4", + "symfony/event-dispatcher": "^7.4", + "symfony/filesystem": "^7.4", + "symfony/finder": "^7.4", + "symfony/http-foundation": "^7.4", + "symfony/http-kernel": "^7.4", "symfony/polyfill-mbstring": "^1.23", - "symfony/mailer": "^7.4@dev", - "symfony/mime": "^7.4@dev", - "symfony/process": "^7.4@dev", + "symfony/mailer": "^7.4", + "symfony/mime": "^7.4", + "symfony/process": "^7.4", "symfony/proxy-manager-bridge": "^6.4", - "symfony/routing": "^7.4@dev", - "symfony/twig-bridge": "^7.4@dev", - "symfony/yaml": "^7.4@dev", + "symfony/routing": "^7.4", + "symfony/twig-bridge": "^7.4", + "symfony/yaml": "^7.4", "twig/twig": "^3.14" }, "require-dev": { @@ -67,10 +67,10 @@ "phing/phing": "~2.4", "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "~3.4", - "symfony/browser-kit": "^7.3", - "symfony/css-selector": "^7.3", - "symfony/dom-crawler": "^7.3", - "symfony/http-client": "^7.3", + "symfony/browser-kit": "^7.4", + "symfony/css-selector": "^7.4", + "symfony/dom-crawler": "^7.4", + "symfony/http-client": "^7.4", "vimeo/psalm": "^6.13.1", "psalm/plugin-symfony": "^v5.1.0" }, diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 9e6eb159409..9df14737d75 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ced4dd12f99cdcc9f7bc1cf55bef24da", + "content-hash": "7f502aca03f40b41dc54e901dcb5ab41", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -173,16 +173,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.8", + "version": "1.5.9", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "719026bb30813accb68271fee7e39552a58e9f65" + "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65", - "reference": "719026bb30813accb68271fee7e39552a58e9f65", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54", + "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54", "shasum": "" }, "require": { @@ -229,7 +229,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.8" + "source": "https://github.com/composer/ca-bundle/tree/1.5.9" }, "funding": [ { @@ -241,26 +241,26 @@ "type": "github" } ], - "time": "2025-08-20T18:49:47+00:00" + "time": "2025-11-06T11:46:17+00:00" }, { "name": "composer/class-map-generator", - "version": "1.6.2", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076" + "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ba9f089655d4cdd64e762a6044f411ccdaec0076", - "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/2373419b7709815ed323ebf18c3c72d03ff4a8a6", + "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6", "shasum": "" }, "require": { "composer/pcre": "^2.1 || ^3.1", "php": "^7.2 || ^8.0", - "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" }, "require-dev": { "phpstan/phpstan": "^1.12 || ^2", @@ -268,7 +268,7 @@ "phpstan/phpstan-phpunit": "^1 || ^2", "phpstan/phpstan-strict-rules": "^1.1 || ^2", "phpunit/phpunit": "^8", - "symfony/filesystem": "^5.4 || ^6" + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" }, "type": "library", "extra": { @@ -298,7 +298,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.6.2" + "source": "https://github.com/composer/class-map-generator/tree/1.7.0" }, "funding": [ { @@ -310,30 +310,31 @@ "type": "github" } ], - "time": "2025-08-20T18:52:43+00:00" + "time": "2025-11-19T10:41:15+00:00" }, { "name": "composer/composer", - "version": "2.8.12", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "3e38919bc9a2c3c026f2151b5e56d04084ce8f0b" + "reference": "8d5358f147c63a3a681b002076deff8c90e0b19d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/3e38919bc9a2c3c026f2151b5e56d04084ce8f0b", - "reference": "3e38919bc9a2c3c026f2151b5e56d04084ce8f0b", + "url": "https://api.github.com/repos/composer/composer/zipball/8d5358f147c63a3a681b002076deff8c90e0b19d", + "reference": "8d5358f147c63a3a681b002076deff8c90e0b19d", "shasum": "" }, "require": { "composer/ca-bundle": "^1.5", "composer/class-map-generator": "^1.4.0", "composer/metadata-minifier": "^1.0", - "composer/pcre": "^2.2 || ^3.2", + "composer/pcre": "^2.3 || ^3.3", "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-json": "*", "justinrainbow/json-schema": "^6.5.1", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", @@ -341,13 +342,14 @@ "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", - "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10", - "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10", - "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", - "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10" + "symfony/polyfill-php84": "^1.30", + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.11.8", @@ -355,12 +357,13 @@ "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.0", "phpstan/phpstan-symfony": "^1.4.0", - "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3" + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" }, "suggest": { - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests" + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" }, "bin": [ "bin/composer" @@ -373,7 +376,7 @@ ] }, "branch-alias": { - "dev-main": "2.8-dev" + "dev-main": "2.9-dev" } }, "autoload": { @@ -408,7 +411,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.8.12" + "source": "https://github.com/composer/composer/tree/2.9.2" }, "funding": [ { @@ -420,7 +423,7 @@ "type": "github" } ], - "time": "2025-09-19T11:41:59+00:00" + "time": "2025-11-19T20:57:25+00:00" }, { "name": "composer/installers", @@ -1014,16 +1017,16 @@ }, { "name": "doctrine/dbal", - "version": "3.10.2", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c6c16cf787eaba3112203dfcd715fa2059c62282" + "reference": "65edaca19a752730f290ec2fb89d593cb40afb43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c6c16cf787eaba3112203dfcd715fa2059c62282", - "reference": "c6c16cf787eaba3112203dfcd715fa2059c62282", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/65edaca19a752730f290ec2fb89d593cb40afb43", + "reference": "65edaca19a752730f290ec2fb89d593cb40afb43", "shasum": "" }, "require": { @@ -1039,14 +1042,14 @@ }, "require-dev": { "doctrine/cache": "^1.11|^2.0", - "doctrine/coding-standard": "13.0.1", + "doctrine/coding-standard": "14.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "2.1.22", + "phpstan/phpstan": "2.1.30", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.23", - "slevomat/coding-standard": "8.16.2", - "squizlabs/php_codesniffer": "3.13.1", + "phpunit/phpunit": "9.6.29", + "slevomat/coding-standard": "8.24.0", + "squizlabs/php_codesniffer": "4.0.0", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/console": "^4.4|^5.4|^6.0|^7.0" }, @@ -1108,7 +1111,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.10.2" + "source": "https://github.com/doctrine/dbal/tree/3.10.3" }, "funding": [ { @@ -1124,7 +1127,7 @@ "type": "tidelift" } ], - "time": "2025-09-04T23:51:27+00:00" + "time": "2025-10-09T09:05:12+00:00" }, { "name": "doctrine/deprecations", @@ -1411,22 +1414,22 @@ }, { "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.18", + "version": "v1.0.19", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f" + "reference": "c20299aa9f48a622052964a75c5a4cef017398b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", - "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", + "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/c20299aa9f48a622052964a75c5a4cef017398b2", + "reference": "c20299aa9f48a622052964a75c5a4cef017398b2", "shasum": "" }, "require": { "laminas/laminas-code": "~3.4.1|^4.0", "php": ">=7.1", - "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0" + "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0|^8.0" }, "conflict": { "laminas/laminas-stdlib": "<3.2.1", @@ -1477,7 +1480,7 @@ ], "support": { "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.18" + "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.19" }, "funding": [ { @@ -1489,7 +1492,7 @@ "type": "tidelift" } ], - "time": "2024-03-20T12:50:41+00:00" + "time": "2025-10-28T10:28:17+00:00" }, { "name": "google/recaptcha", @@ -1870,16 +1873,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.5.2", + "version": "6.6.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8" + "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ac0d369c09653cf7af561f6d91a705bc617a87b8", - "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/3c25fe750c1599716ef26aa997f7c026cee8c4b7", + "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7", "shasum": "" }, "require": { @@ -1939,33 +1942,33 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.5.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.2" }, - "time": "2025-09-09T09:42:27+00:00" + "time": "2025-11-28T15:24:03+00:00" }, { "name": "laminas/laminas-code", - "version": "4.16.0", + "version": "4.17.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "1793e78dad4108b594084d05d1fb818b85b110af" + "reference": "40d61e2899ec17c5d08bbc0a2d586b3ca17ab9bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/1793e78dad4108b594084d05d1fb818b85b110af", - "reference": "1793e78dad4108b594084d05d1fb818b85b110af", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/40d61e2899ec17c5d08bbc0a2d586b3ca17ab9bd", + "reference": "40d61e2899ec17c5d08bbc0a2d586b3ca17ab9bd", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "doctrine/annotations": "^2.0.1", "ext-phar": "*", "laminas/laminas-coding-standard": "^3.0.0", "laminas/laminas-stdlib": "^3.18.0", - "phpunit/phpunit": "^10.5.37", + "phpunit/phpunit": "^10.5.58", "psalm/plugin-phpunit": "^0.19.0", "vimeo/psalm": "^5.15.0" }, @@ -2004,7 +2007,7 @@ "type": "community_bridge" } ], - "time": "2024-11-20T13:15:13+00:00" + "time": "2025-11-01T09:38:14+00:00" }, { "name": "marc-mabe/php-enum", @@ -2277,16 +2280,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "9c3535883f1b60b5d26aeae5914bbec61132ad7f" + "reference": "547e2dc4d45107440e76c17ab5a46e4252460158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/9c3535883f1b60b5d26aeae5914bbec61132ad7f", - "reference": "9c3535883f1b60b5d26aeae5914bbec61132ad7f", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/547e2dc4d45107440e76c17ab5a46e4252460158", + "reference": "547e2dc4d45107440e76c17ab5a46e4252460158", "shasum": "" }, "require": { @@ -2367,9 +2370,9 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v2.2.0" + "source": "https://github.com/paragonie/sodium_compat/tree/v2.4.0" }, - "time": "2025-09-21T18:27:14+00:00" + "time": "2025-10-06T08:47:40+00:00" }, { "name": "psr/cache", @@ -2988,16 +2991,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.19.0", + "version": "2.19.3", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3" + "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/d65a4f61cbe494937afb3150dc73b6e757d400d3", - "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/aee579c12d05ca3053f9b9abdb8c479c0f2fbe69", + "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69", "shasum": "" }, "require": { @@ -3025,7 +3028,7 @@ }, "type": "library", "extra": { - "version": "2.19.0" + "version": "2.19.3" }, "autoload": { "psr-4": { @@ -3057,9 +3060,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.19.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.19.3" }, - "time": "2025-04-26T09:27:34+00:00" + "time": "2025-11-14T21:26:59+00:00" }, { "name": "seld/jsonlint", @@ -3301,20 +3304,20 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" + "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", - "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7", + "reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12|^0.13", + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", "ext-mbstring": "*", "php": ">=8.1" }, @@ -3322,7 +3325,7 @@ "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.28|^0.29", + "infection/infection": "^0.28|^0.29|^0.31", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3|^2.0", "phpstan/phpstan": "^1.8|^2.0", @@ -3332,8 +3335,8 @@ "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/string": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", "symplify/easy-coding-standard": "^12.0" }, "suggest": { @@ -3394,7 +3397,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0" }, "funding": [ { @@ -3406,20 +3409,20 @@ "type": "patreon" } ], - "time": "2025-06-13T08:35:04+00:00" + "time": "2025-10-22T08:24:34+00:00" }, { "name": "symfony/config", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "3a4138903f5283f422626439dd317157b9363fbb" + "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/3a4138903f5283f422626439dd317157b9363fbb", - "reference": "3a4138903f5283f422626439dd317157b9363fbb", + "url": "https://api.github.com/repos/symfony/config/zipball/f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", + "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", "shasum": "" }, "require": { @@ -3465,7 +3468,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/7.4" + "source": "https://github.com/symfony/config/tree/v7.4.0" }, "funding": [ { @@ -3485,20 +3488,20 @@ "type": "tidelift" } ], - "time": "2025-09-23T06:13:50+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/console", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a687b340755a81fe8a7114d45e3665feaa06b8cc" + "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a687b340755a81fe8a7114d45e3665feaa06b8cc", - "reference": "a687b340755a81fe8a7114d45e3665feaa06b8cc", + "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", "shasum": "" }, "require": { @@ -3563,7 +3566,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/7.4" + "source": "https://github.com/symfony/console/tree/v7.4.0" }, "funding": [ { @@ -3583,20 +3586,20 @@ "type": "tidelift" } ], - "time": "2025-09-27T09:05:58+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/dependency-injection", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "0587fdea12f41402bdf39af26a9fb31b235567e0" + "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0587fdea12f41402bdf39af26a9fb31b235567e0", - "reference": "0587fdea12f41402bdf39af26a9fb31b235567e0", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/3972ca7bbd649467b21a54870721b9e9f3652f9b", + "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b", "shasum": "" }, "require": { @@ -3647,7 +3650,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/7.4" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.0" }, "funding": [ { @@ -3667,7 +3670,7 @@ "type": "tidelift" } ], - "time": "2025-09-21T07:58:28+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3738,16 +3741,16 @@ }, { "name": "symfony/error-handler", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "b40ab8ba2e572c512f2c415c795c76383e09001c" + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/b40ab8ba2e572c512f2c415c795c76383e09001c", - "reference": "b40ab8ba2e572c512f2c415c795c76383e09001c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", "shasum": "" }, "require": { @@ -3796,7 +3799,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/7.4" + "source": "https://github.com/symfony/error-handler/tree/v7.4.0" }, "funding": [ { @@ -3816,20 +3819,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:15:23+00:00" + "time": "2025-11-05T14:29:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "c511634ea987e9f96eafe4270a3c5e7cf6f2f747" + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c511634ea987e9f96eafe4270a3c5e7cf6f2f747", - "reference": "c511634ea987e9f96eafe4270a3c5e7cf6f2f747", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", "shasum": "" }, "require": { @@ -3850,6 +3853,7 @@ "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/error-handler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^6.4|^7.0|^8.0" @@ -3880,7 +3884,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/7.4" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" }, "funding": [ { @@ -3900,7 +3904,7 @@ "type": "tidelift" } ], - "time": "2025-08-13T13:43:36+00:00" + "time": "2025-10-28T09:38:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3980,16 +3984,16 @@ }, { "name": "symfony/filesystem", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "633c6fdfb2eac90041c2de30ad1ccff95a671f66" + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/633c6fdfb2eac90041c2de30ad1ccff95a671f66", - "reference": "633c6fdfb2eac90041c2de30ad1ccff95a671f66", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { @@ -4026,7 +4030,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/7.4" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { @@ -4046,20 +4050,20 @@ "type": "tidelift" } ], - "time": "2025-08-12T20:20:59+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/finder", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "c1a41e8ecb0f76b9e6d93deaeb5b85718975b12a" + "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/c1a41e8ecb0f76b9e6d93deaeb5b85718975b12a", - "reference": "c1a41e8ecb0f76b9e6d93deaeb5b85718975b12a", + "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", + "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", "shasum": "" }, "require": { @@ -4094,7 +4098,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/7.4" + "source": "https://github.com/symfony/finder/tree/v7.4.0" }, "funding": [ { @@ -4114,20 +4118,20 @@ "type": "tidelift" } ], - "time": "2025-09-08T21:17:01+00:00" + "time": "2025-11-05T05:42:40+00:00" }, { "name": "symfony/http-client", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" + "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ee5e0e0139ab506f6063a230e631bed677c650a4", + "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4", "shasum": "" }, "require": { @@ -4158,12 +4162,13 @@ "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4194,7 +4199,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.4" + "source": "https://github.com/symfony/http-client/tree/v7.4.0" }, "funding": [ { @@ -4214,7 +4219,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-20T12:32:50+00:00" }, { "name": "symfony/http-client-contracts", @@ -4296,16 +4301,16 @@ }, { "name": "symfony/http-foundation", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "6907afdb22878d2975009e256e8424fa22d2ce4d" + "reference": "769c1720b68e964b13b58529c17d4a385c62167b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6907afdb22878d2975009e256e8424fa22d2ce4d", - "reference": "6907afdb22878d2975009e256e8424fa22d2ce4d", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/769c1720b68e964b13b58529c17d4a385c62167b", + "reference": "769c1720b68e964b13b58529c17d4a385c62167b", "shasum": "" }, "require": { @@ -4354,7 +4359,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/7.4" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.0" }, "funding": [ { @@ -4374,20 +4379,20 @@ "type": "tidelift" } ], - "time": "2025-09-18T15:19:23+00:00" + "time": "2025-11-13T08:49:24+00:00" }, { "name": "symfony/http-kernel", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "10e0595aa528575f6597da1e2929870cd08ef491" + "reference": "7348193cd384495a755554382e4526f27c456085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/10e0595aa528575f6597da1e2929870cd08ef491", - "reference": "10e0595aa528575f6597da1e2929870cd08ef491", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7348193cd384495a755554382e4526f27c456085", + "reference": "7348193cd384495a755554382e4526f27c456085", "shasum": "" }, "require": { @@ -4406,6 +4411,7 @@ "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", "symfony/form": "<6.4", "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", @@ -4472,7 +4478,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/7.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.0" }, "funding": [ { @@ -4492,20 +4498,20 @@ "type": "tidelift" } ], - "time": "2025-09-23T16:26:37+00:00" + "time": "2025-11-27T13:38:24+00:00" }, { "name": "symfony/mailer", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "bee1739939a671e9503f717efa68f6afa11087df" + "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/bee1739939a671e9503f717efa68f6afa11087df", - "reference": "bee1739939a671e9503f717efa68f6afa11087df", + "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", "shasum": "" }, "require": { @@ -4556,7 +4562,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/7.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.0" }, "funding": [ { @@ -4576,20 +4582,20 @@ "type": "tidelift" } ], - "time": "2025-09-18T08:45:38+00:00" + "time": "2025-11-21T15:26:00+00:00" }, { "name": "symfony/mime", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "adb6275d178ac5fabb59ac18d42bbbf887f809bc" + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/adb6275d178ac5fabb59ac18d42bbbf887f809bc", - "reference": "adb6275d178ac5fabb59ac18d42bbbf887f809bc", + "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", "shasum": "" }, "require": { @@ -4645,7 +4651,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/7.4" + "source": "https://github.com/symfony/mime/tree/v7.4.0" }, "funding": [ { @@ -4665,7 +4671,7 @@ "type": "tidelift" } ], - "time": "2025-09-16T08:41:45+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { "name": "symfony/polyfill-ctype", @@ -5413,6 +5419,86 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, { "name": "symfony/polyfill-php85", "version": "v1.33.0", @@ -5495,16 +5581,16 @@ }, { "name": "symfony/process", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c61e87dafe1f427ce8d2e1d87c8b90d4806974cd" + "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c61e87dafe1f427ce8d2e1d87c8b90d4806974cd", - "reference": "c61e87dafe1f427ce8d2e1d87c8b90d4806974cd", + "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", "shasum": "" }, "require": { @@ -5536,7 +5622,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/7.4" + "source": "https://github.com/symfony/process/tree/v7.4.0" }, "funding": [ { @@ -5556,20 +5642,20 @@ "type": "tidelift" } ], - "time": "2025-08-18T09:46:12+00:00" + "time": "2025-10-16T11:21:06+00:00" }, { "name": "symfony/proxy-manager-bridge", - "version": "v6.4.24", + "version": "v6.4.28", "source": { "type": "git", "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "2a14a1539f2854a8adb73319abf8923b1d7a6589" + "reference": "9ecac7f98ad685d474394dbd06dab29bab4e18a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/2a14a1539f2854a8adb73319abf8923b1d7a6589", - "reference": "2a14a1539f2854a8adb73319abf8923b1d7a6589", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/9ecac7f98ad685d474394dbd06dab29bab4e18a6", + "reference": "9ecac7f98ad685d474394dbd06dab29bab4e18a6", "shasum": "" }, "require": { @@ -5607,7 +5693,7 @@ "description": "Provides integration for ProxyManager with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.24" + "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.28" }, "funding": [ { @@ -5627,20 +5713,20 @@ "type": "tidelift" } ], - "time": "2025-07-14T16:38:25+00:00" + "time": "2025-11-02T18:11:54+00:00" }, { "name": "symfony/routing", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0d12667d0f1ffb762337c67650d1c24743dea9fa" + "reference": "4720254cb2644a0b876233d258a32bf017330db7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0d12667d0f1ffb762337c67650d1c24743dea9fa", - "reference": "0d12667d0f1ffb762337c67650d1c24743dea9fa", + "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", + "reference": "4720254cb2644a0b876233d258a32bf017330db7", "shasum": "" }, "require": { @@ -5692,7 +5778,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/7.4" + "source": "https://github.com/symfony/routing/tree/v7.4.0" }, "funding": [ { @@ -5712,20 +5798,20 @@ "type": "tidelift" } ], - "time": "2025-09-09T13:52:22+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -5779,7 +5865,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -5790,31 +5876,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f96476035142921000338bad71e5247fbc138872" + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", - "reference": "f96476035142921000338bad71e5247fbc138872", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -5822,11 +5913,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -5865,7 +5956,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.4" + "source": "https://github.com/symfony/string/tree/v7.4.0" }, "funding": [ { @@ -5885,20 +5976,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:36:48+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -5947,7 +6038,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -5958,25 +6049,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/twig-bridge", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "f5fe218e1cd9015b4492281b9c490d2b96647ca1" + "reference": "e96998da928007554b8b8c02e677861877daced9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/f5fe218e1cd9015b4492281b9c490d2b96647ca1", - "reference": "f5fe218e1cd9015b4492281b9c490d2b96647ca1", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e96998da928007554b8b8c02e677861877daced9", + "reference": "e96998da928007554b8b8c02e677861877daced9", "shasum": "" }, "require": { @@ -6058,7 +6153,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/7.4" + "source": "https://github.com/symfony/twig-bridge/tree/v7.4.0" }, "funding": [ { @@ -6078,20 +6173,20 @@ "type": "tidelift" } ], - "time": "2025-09-24T07:02:42+00:00" + "time": "2025-11-05T14:29:59+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" + "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", + "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", "shasum": "" }, "require": { @@ -6103,10 +6198,10 @@ "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", "twig/twig": "^3.12" }, "bin": [ @@ -6145,7 +6240,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" }, "funding": [ { @@ -6165,20 +6260,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-10-27T20:36:44+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4" + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", - "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", "shasum": "" }, "require": { @@ -6186,9 +6281,9 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6226,7 +6321,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.3.4" + "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" }, "funding": [ { @@ -6246,20 +6341,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-09-11T10:15:23+00:00" }, { "name": "symfony/yaml", - "version": "7.4.x-dev", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0d7c67f404dc22bfa340546f8537cd0eb61eec6a" + "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0d7c67f404dc22bfa340546f8537cd0eb61eec6a", - "reference": "0d7c67f404dc22bfa340546f8537cd0eb61eec6a", + "url": "https://api.github.com/repos/symfony/yaml/zipball/6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810", "shasum": "" }, "require": { @@ -6302,7 +6397,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/7.4" + "source": "https://github.com/symfony/yaml/tree/v7.4.0" }, "funding": [ { @@ -6322,20 +6417,20 @@ "type": "tidelift" } ], - "time": "2025-09-27T09:05:58+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { "name": "twig/twig", - "version": "v3.21.1", + "version": "v3.22.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" + "reference": "4509984193026de413baf4ba80f68590a7f2c51d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d", + "reference": "4509984193026de413baf4ba80f68590a7f2c51d", "shasum": "" }, "require": { @@ -6389,7 +6484,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.21.1" + "source": "https://github.com/twigphp/Twig/tree/v3.22.0" }, "funding": [ { @@ -6401,7 +6496,7 @@ "type": "tidelift" } ], - "time": "2025-05-03T07:21:55+00:00" + "time": "2025-10-29T15:56:47+00:00" }, { "name": "web-token/jwt-key-mgmt", @@ -6473,16 +6568,16 @@ }, { "name": "web-token/jwt-library", - "version": "3.4.8", + "version": "3.4.9", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", - "reference": "92445671cc788fa5f639898a67c06f9fd0bf491f" + "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/92445671cc788fa5f639898a67c06f9fd0bf491f", - "reference": "92445671cc788fa5f639898a67c06f9fd0bf491f", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f", + "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f", "shasum": "" }, "require": { @@ -6555,7 +6650,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.8" + "source": "https://github.com/web-token/jwt-library/tree/3.4.9" }, "funding": [ { @@ -6567,7 +6662,7 @@ "type": "patreon" } ], - "time": "2025-05-07T09:11:18+00:00" + "time": "2025-11-17T20:20:37+00:00" }, { "name": "web-token/jwt-signature", @@ -7942,33 +8037,38 @@ }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "f625804987a0a9112d954f9209d91fec52182344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", + "reference": "f625804987a0a9112d954f9209d91fec52182344", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.6", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -7996,6 +8096,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -8008,9 +8109,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -8020,7 +8123,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.6.0" }, "funding": [ { @@ -8028,26 +8131,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -8055,6 +8157,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -8079,7 +8182,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -8104,7 +8207,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" }, "funding": [ { @@ -8112,7 +8215,7 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "masterminds/html5", @@ -8349,16 +8452,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -8401,9 +8504,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", @@ -8690,16 +8793,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.3", + "version": "5.6.5", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90614c73d3800e187615e2dd236ad0e2a01bf761", + "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761", "shasum": "" }, "require": { @@ -8748,22 +8851,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.5" }, - "time": "2025-08-01T19:43:32+00:00" + "time": "2025-11-27T19:50:05+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { @@ -8806,9 +8909,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -9180,16 +9283,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.57", + "version": "10.5.58", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8e7598bbb17bb5cd80728f4831d3f83223d3a6b3" + "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e7598bbb17bb5cd80728f4831d3f83223d3a6b3", - "reference": "8e7598bbb17bb5cd80728f4831d3f83223d3a6b3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca", + "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca", "shasum": "" }, "require": { @@ -9261,7 +9364,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.57" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58" }, "funding": [ { @@ -9285,7 +9388,7 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:30:38+00:00" + "time": "2025-09-28T12:04:46+00:00" }, { "name": "psalm/plugin-symfony", @@ -9354,16 +9457,16 @@ }, { "name": "revolt/event-loop", - "version": "v1.0.7", + "version": "v1.0.8", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", "shasum": "" }, "require": { @@ -9420,9 +9523,9 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" }, - "time": "2025-01-25T19:27:39+00:00" + "time": "2025-08-27T21:33:23+00:00" }, { "name": "sebastian/cli-parser", @@ -10379,16 +10482,16 @@ }, { "name": "spatie/array-to-xml", - "version": "3.4.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67" + "reference": "7b9202dccfe18d4e3a13303156d6bbcc1c61dabf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7b9202dccfe18d4e3a13303156d6bbcc1c61dabf", + "reference": "7b9202dccfe18d4e3a13303156d6bbcc1c61dabf", "shasum": "" }, "require": { @@ -10431,7 +10534,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.4.0" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.3" }, "funding": [ { @@ -10443,20 +10546,20 @@ "type": "github" } ], - "time": "2024-12-16T12:45:15+00:00" + "time": "2025-11-27T09:08:26+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.4", + "version": "3.13.5", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119" + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119", - "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", "shasum": "" }, "require": { @@ -10473,11 +10576,6 @@ "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -10527,31 +10625,32 @@ "type": "thanks_dev" } ], - "time": "2025-09-05T05:47:09+00:00" + "time": "2025-11-04T16:30:35+00:00" }, { "name": "symfony/browser-kit", - "version": "v7.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419" + "reference": "3bb26dafce31633b1f699894c86379eefc8af5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f0b889b73a845cddef1d25fe207b37fd04cb5419", - "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/3bb26dafce31633b1f699894c86379eefc8af5bb", + "reference": "3bb26dafce31633b1f699894c86379eefc8af5bb", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/dom-crawler": "^6.4|^7.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/dom-crawler": "^6.4|^7.0|^8.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0" + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -10579,7 +10678,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v7.3.2" + "source": "https://github.com/symfony/browser-kit/tree/v7.4.0" }, "funding": [ { @@ -10599,20 +10698,20 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-11-05T14:29:59+00:00" }, { "name": "symfony/cache", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f" + "reference": "a7a1325a5de2e54ddb45fda002ff528162e48293" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", - "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", + "url": "https://api.github.com/repos/symfony/cache/zipball/a7a1325a5de2e54ddb45fda002ff528162e48293", + "reference": "a7a1325a5de2e54ddb45fda002ff528162e48293", "shasum": "" }, "require": { @@ -10620,12 +10719,14 @@ "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^3.6", - "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "conflict": { "doctrine/dbal": "<3.6", + "ext-redis": "<6.1", + "ext-relay": "<0.12.1", "symfony/dependency-injection": "<6.4", "symfony/http-kernel": "<6.4", "symfony/var-dumper": "<6.4" @@ -10640,13 +10741,13 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/filesystem": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -10681,7 +10782,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.4" + "source": "https://github.com/symfony/cache/tree/v7.4.0" }, "funding": [ { @@ -10701,7 +10802,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { "name": "symfony/cache-contracts", @@ -10781,16 +10882,16 @@ }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", "shasum": "" }, "require": { @@ -10826,7 +10927,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" }, "funding": [ { @@ -10837,35 +10938,40 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-10-30T13:39:42+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.3.3", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" + "reference": "8f3e7464fe7e77294686e935956a6a8ccf7442c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", - "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8f3e7464fe7e77294686e935956a6a8ccf7442c4", + "reference": "8f3e7464fe7e77294686e935956a6a8ccf7442c4", "shasum": "" }, "require": { "masterminds/html5": "^2.6", "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0" + "symfony/css-selector": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -10893,7 +10999,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.0" }, "funding": [ { @@ -10913,38 +11019,39 @@ "type": "tidelift" } ], - "time": "2025-08-06T20:13:54+00:00" + "time": "2025-10-31T09:30:03+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140" + "reference": "3c62a3437267ac55bcd40175689c74772250e943" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b13e7cec5a144c8dba6f4233a2c53c00bc29e140", - "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/3c62a3437267ac55bcd40175689c74772250e943", + "reference": "3c62a3437267ac55bcd40175689c74772250e943", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", "php": ">=8.2", - "symfony/cache": "^6.4|^7.0", - "symfony/config": "^7.3", - "symfony/dependency-injection": "^7.2", + "symfony/cache": "^6.4.12|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^7.3", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/filesystem": "^7.1", - "symfony/finder": "^6.4|^7.0", - "symfony/http-foundation": "^7.3", - "symfony/http-kernel": "^7.2", + "symfony/error-handler": "^7.3|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^7.1|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^6.4|^7.0" + "symfony/polyfill-php85": "^1.32", + "symfony/routing": "^7.4|^8.0" }, "conflict": { "doctrine/persistence": "<1.3", @@ -10956,14 +11063,12 @@ "symfony/console": "<6.4", "symfony/dom-crawler": "<6.4", "symfony/dotenv": "<6.4", - "symfony/form": "<6.4", + "symfony/form": "<7.4", "symfony/http-client": "<6.4", - "symfony/json-streamer": ">=7.4", "symfony/lock": "<6.4", "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", + "symfony/messenger": "<7.4", "symfony/mime": "<6.4", - "symfony/object-mapper": ">=7.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", @@ -10978,51 +11083,52 @@ "symfony/validator": "<6.4", "symfony/web-profiler-bundle": "<6.4", "symfony/webhook": "<7.2", - "symfony/workflow": "<7.3.0-beta2" + "symfony/workflow": "<7.4" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "seld/jsonlint": "^1.10", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/dotenv": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/json-streamer": "7.3.*", - "symfony/lock": "^6.4|^7.0", - "symfony/mailer": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/notifier": "^6.4|^7.0", - "symfony/object-mapper": "^v7.3.0-beta2", + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/html-sanitizer": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/json-streamer": "^7.3|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/mailer": "^6.4|^7.0|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/notifier": "^6.4|^7.0|^8.0", + "symfony/object-mapper": "^7.3|^8.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/scheduler": "^6.4.4|^7.0.4", - "symfony/security-bundle": "^6.4|^7.0", - "symfony/semaphore": "^6.4|^7.0", - "symfony/serializer": "^7.2.5", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/string": "^6.4|^7.0", - "symfony/translation": "^7.3", - "symfony/twig-bundle": "^6.4|^7.0", - "symfony/type-info": "^7.1.8", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/webhook": "^7.2", - "symfony/workflow": "^7.3", - "symfony/yaml": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0", + "symfony/scheduler": "^6.4.4|^7.0.4|^8.0", + "symfony/security-bundle": "^6.4|^7.0|^8.0", + "symfony/semaphore": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.2.5|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^7.3|^8.0", + "symfony/twig-bundle": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.1.8|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^6.4|^7.0|^8.0", + "symfony/webhook": "^7.2|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.3|^8.0", "twig/twig": "^3.12" }, "type": "symfony-bundle", @@ -11051,87 +11157,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.3.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-09-17T05:51:54+00:00" - }, - { - "name": "symfony/polyfill-php84", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + "source": "https://github.com/symfony/framework-bundle/tree/v7.4.0" }, "funding": [ { @@ -11151,20 +11177,20 @@ "type": "tidelift" } ], - "time": "2025-06-24T13:30:11+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -11193,7 +11219,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -11201,7 +11227,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "vimeo/psalm", @@ -11323,28 +11349,28 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -11375,30 +11401,14 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "symfony/config": 20, - "symfony/console": 20, - "symfony/dependency-injection": 20, - "symfony/error-handler": 20, - "symfony/event-dispatcher": 20, - "symfony/filesystem": 20, - "symfony/finder": 20, - "symfony/http-foundation": 20, - "symfony/http-kernel": 20, - "symfony/mailer": 20, - "symfony/mime": 20, - "symfony/process": 20, - "symfony/routing": 20, - "symfony/twig-bridge": 20, - "symfony/yaml": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From b8d14180a1b9fb72b311f966f34240ac9de4f260 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 29 Nov 2025 08:47:51 +0100 Subject: [PATCH 0997/1214] [ticket/17556] Remove not needed line PHPBB-17556 --- tests/console/cron/cron_list_test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/console/cron/cron_list_test.php b/tests/console/cron/cron_list_test.php index 66eb4d8b33b..25e8efd74ee 100644 --- a/tests/console/cron/cron_list_test.php +++ b/tests/console/cron/cron_list_test.php @@ -79,7 +79,6 @@ public function get_cron_manager(array $tasks) ->onlyMethods(array('setContext', 'generate')) ->disableOriginalConstructor() ->getMock(); - $mock_router->method('setContext'); $mock_router->method('generate') ->willReturn('foobar'); From b4a163021b47a658836d97588ed8ca9ff2b6698b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 29 Nov 2025 08:53:59 +0100 Subject: [PATCH 0998/1214] [ticket/17556] Remove PHP 8.1 and add PHP 8.5 PHPBB-17556 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8aab1512e8a..ddb18790600 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -494,7 +494,7 @@ jobs: runs-on: windows-2025 strategy: matrix: - php: ['8.1', '8.2', '8.3', '8.4'] + php: ['8.2', '8.3', '8.4', '8.5'] db: ['postgres'] name: Windows - PHP ${{ matrix.php }} - ${{ matrix.db }} From 835815be8df3142de22fe7890b77c09343346034 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 29 Nov 2025 17:06:23 +0100 Subject: [PATCH 0999/1214] [ticket/17556] Try relying on symfony's proxy instantiator PHPBB-17556 --- phpBB/phpbb/di/container_builder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/phpbb/di/container_builder.php b/phpBB/phpbb/di/container_builder.php index 087dbf9649e..66964ea9adf 100644 --- a/phpBB/phpbb/di/container_builder.php +++ b/phpBB/phpbb/di/container_builder.php @@ -542,7 +542,6 @@ protected function dump_container($cache) protected function create_container(array $extensions) { $container = new ContainerBuilder(new ParameterBag($this->get_core_parameters())); - $container->setProxyInstantiator(new proxy_instantiator($this->get_cache_dir())); $extensions_alias = array(); From d4e392d8ab66257437fc686d045f348596958e94 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 29 Nov 2025 21:22:20 +0100 Subject: [PATCH 1000/1214] [ticket/17556] Remove unused proxy instantiator PHPBB-17556 --- phpBB/phpbb/di/proxy_instantiator.php | 72 --------------------------- 1 file changed, 72 deletions(-) delete mode 100644 phpBB/phpbb/di/proxy_instantiator.php diff --git a/phpBB/phpbb/di/proxy_instantiator.php b/phpBB/phpbb/di/proxy_instantiator.php deleted file mode 100644 index d724d2dcd22..00000000000 --- a/phpBB/phpbb/di/proxy_instantiator.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\di; - -use ProxyManager\Configuration; -use ProxyManager\Factory\LazyLoadingValueHolderFactory; -use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; - -/** - * Runtime lazy loading proxy generator extended for allowing use while using - * open_basedir restrictions - * - * Original author: Marco Pivetta - */ -class proxy_instantiator implements InstantiatorInterface -{ - /** - * @var LazyLoadingValueHolderFactory - */ - private $factory; - - /** - * proxy_instantiator constructor - * @param string $cache_dir Cache dir for fall back when using open_basedir - */ - public function __construct($cache_dir) - { - $config = new Configuration(); - - // Prevent trying to write to system temp dir in case of open_basedir - // restrictions being in effect - $tmp_dir = (function_exists('sys_get_temp_dir')) ? sys_get_temp_dir() : ''; - if (empty($tmp_dir) || !@file_exists($tmp_dir) || !@is_writable($tmp_dir)) - { - $config->setProxiesTargetDir($cache_dir); - } - $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - - $this->factory = new LazyLoadingValueHolderFactory($config); - } - - /** - * {@inheritdoc} - */ - public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator): object - { - return $this->factory->createProxy( - $definition->getClass(), - function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($realInstantiator) { - $wrappedInstance = call_user_func($realInstantiator); - - $proxy->setProxyInitializer(null); - - return true; - } - ); - } -} From de7de02ef29f728035e8c335542bbc3eda1b2d72 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 30 Nov 2025 09:05:05 +0100 Subject: [PATCH 1001/1214] [ticket/17556] Remove proxy manager PHPBB-17556 --- phpBB/composer.json | 1 - phpBB/composer.lock | 234 ++------------------------------------------ 2 files changed, 9 insertions(+), 226 deletions(-) diff --git a/phpBB/composer.json b/phpBB/composer.json index 9777e023f07..3762817907c 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -55,7 +55,6 @@ "symfony/mailer": "^7.4", "symfony/mime": "^7.4", "symfony/process": "^7.4", - "symfony/proxy-manager-bridge": "^6.4", "symfony/routing": "^7.4", "symfony/twig-bridge": "^7.4", "symfony/yaml": "^7.4", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 9df14737d75..600ce1dc6a5 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7f502aca03f40b41dc54e901dcb5ab41", + "content-hash": "a100d158a331a9dc8b112c1c850e38b4", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -1017,16 +1017,16 @@ }, { "name": "doctrine/dbal", - "version": "3.10.3", + "version": "3.10.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "65edaca19a752730f290ec2fb89d593cb40afb43" + "reference": "63a46cb5aa6f60991186cc98c1d1b50c09311868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/65edaca19a752730f290ec2fb89d593cb40afb43", - "reference": "65edaca19a752730f290ec2fb89d593cb40afb43", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/63a46cb5aa6f60991186cc98c1d1b50c09311868", + "reference": "63a46cb5aa6f60991186cc98c1d1b50c09311868", "shasum": "" }, "require": { @@ -1050,8 +1050,8 @@ "phpunit/phpunit": "9.6.29", "slevomat/coding-standard": "8.24.0", "squizlabs/php_codesniffer": "4.0.0", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0" + "symfony/cache": "^5.4|^6.0|^7.0|^8.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0|^8.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -1111,7 +1111,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.10.3" + "source": "https://github.com/doctrine/dbal/tree/3.10.4" }, "funding": [ { @@ -1127,7 +1127,7 @@ "type": "tidelift" } ], - "time": "2025-10-09T09:05:12+00:00" + "time": "2025-11-29T10:46:08+00:00" }, { "name": "doctrine/deprecations", @@ -1412,88 +1412,6 @@ ], "time": "2025-03-06T22:45:56+00:00" }, - { - "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.19", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "c20299aa9f48a622052964a75c5a4cef017398b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/c20299aa9f48a622052964a75c5a4cef017398b2", - "reference": "c20299aa9f48a622052964a75c5a4cef017398b2", - "shasum": "" - }, - "require": { - "laminas/laminas-code": "~3.4.1|^4.0", - "php": ">=7.1", - "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0|^8.0" - }, - "conflict": { - "laminas/laminas-stdlib": "<3.2.1", - "zendframework/zend-stdlib": "<3.2.1" - }, - "replace": { - "ocramius/proxy-manager": "^2.1" - }, - "require-dev": { - "ext-phar": "*", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/Ocramius/ProxyManager", - "name": "ocramius/proxy-manager" - } - }, - "autoload": { - "psr-4": { - "ProxyManager\\": "src/ProxyManager" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - } - ], - "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", - "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", - "keywords": [ - "aop", - "lazy loading", - "proxy", - "proxy pattern", - "service proxies" - ], - "support": { - "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.19" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", - "type": "tidelift" - } - ], - "time": "2025-10-28T10:28:17+00:00" - }, { "name": "google/recaptcha", "version": "1.3.1", @@ -1946,69 +1864,6 @@ }, "time": "2025-11-28T15:24:03+00:00" }, - { - "name": "laminas/laminas-code", - "version": "4.17.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-code.git", - "reference": "40d61e2899ec17c5d08bbc0a2d586b3ca17ab9bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/40d61e2899ec17c5d08bbc0a2d586b3ca17ab9bd", - "reference": "40d61e2899ec17c5d08bbc0a2d586b3ca17ab9bd", - "shasum": "" - }, - "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0.1", - "ext-phar": "*", - "laminas/laminas-coding-standard": "^3.0.0", - "laminas/laminas-stdlib": "^3.18.0", - "phpunit/phpunit": "^10.5.58", - "psalm/plugin-phpunit": "^0.19.0", - "vimeo/psalm": "^5.15.0" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Code\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", - "homepage": "https://laminas.dev", - "keywords": [ - "code", - "laminas", - "laminasframework" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2025-11-01T09:38:14+00:00" - }, { "name": "marc-mabe/php-enum", "version": "v4.7.2", @@ -5644,77 +5499,6 @@ ], "time": "2025-10-16T11:21:06+00:00" }, - { - "name": "symfony/proxy-manager-bridge", - "version": "v6.4.28", - "source": { - "type": "git", - "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "9ecac7f98ad685d474394dbd06dab29bab4e18a6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/9ecac7f98ad685d474394dbd06dab29bab4e18a6", - "reference": "9ecac7f98ad685d474394dbd06dab29bab4e18a6", - "shasum": "" - }, - "require": { - "friendsofphp/proxy-manager-lts": "^1.0.2", - "php": ">=8.1", - "symfony/dependency-injection": "^6.3|^7.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "require-dev": { - "symfony/config": "^6.1|^7.0" - }, - "type": "symfony-bridge", - "autoload": { - "psr-4": { - "Symfony\\Bridge\\ProxyManager\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides integration for ProxyManager with various Symfony components", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.28" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-11-02T18:11:54+00:00" - }, { "name": "symfony/routing", "version": "v7.4.0", From e96e2c427a4797d82dfba5d828a26d34cef19697 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 1 Dec 2025 19:22:11 +0100 Subject: [PATCH 1002/1214] [ticket/17556] Use symfony's LazyServiceDumper PHPBB-17556 --- phpBB/phpbb/di/container_builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/phpbb/di/container_builder.php b/phpBB/phpbb/di/container_builder.php index 66964ea9adf..6a1ad68e922 100644 --- a/phpBB/phpbb/di/container_builder.php +++ b/phpBB/phpbb/di/container_builder.php @@ -13,11 +13,11 @@ namespace phpbb\di; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; @@ -517,7 +517,7 @@ protected function dump_container($cache) try { $dumper = new PhpDumper($this->container); - $proxy_dumper = new ProxyDumper(); + $proxy_dumper = new LazyServiceDumper(); $dumper->setProxyDumper($proxy_dumper); $cached_container_dump = $dumper->dump(array( From 5cc35e271923522232099d8db47ed2a2b2937a1f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 1 Dec 2025 19:22:37 +0100 Subject: [PATCH 1003/1214] [ticket/17556] Do not rely on rewrite in tests PHPBB-17556 --- tests/functional/memberlist_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/memberlist_test.php b/tests/functional/memberlist_test.php index 0847aadd5c5..b088b67dbb2 100644 --- a/tests/functional/memberlist_test.php +++ b/tests/functional/memberlist_test.php @@ -43,7 +43,7 @@ public function test_viewprofile() protected function get_memberlist_leaders_table_crawler() { - $crawler = self::request('GET', 'members/team'); + $crawler = self::request('GET', 'app.php/members/team'); return $crawler->filter('.forumbg-table'); } From 0024373d06271a40625a79e0cd65dfba469e8413 Mon Sep 17 00:00:00 2001 From: Neo-CTC <92761505+Neo-CTC@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:51:53 -0600 Subject: [PATCH 1004/1214] [ticket/17589] Check confirm id When a login fails the qa captcha is reset and creates a new `confirm_id`. However, the `confirm_id` is never checked for changes causing the captcha to believe it's still solved and thus no need to generate a new captcha. This commit checks the known `confirm_id` against the `confirm_id` given by the user, marking the captcha solved accordingly. PHPBB-17589 --- phpBB/phpbb/captcha/plugins/qa.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpBB/phpbb/captcha/plugins/qa.php b/phpBB/phpbb/captcha/plugins/qa.php index 966b8d32f25..6cd01388785 100644 --- a/phpBB/phpbb/captcha/plugins/qa.php +++ b/phpBB/phpbb/captcha/plugins/qa.php @@ -573,6 +573,7 @@ function check_answer() global $db, $request; $answer = ($this->question_strict) ? $request->variable('qa_answer', '', true) : utf8_clean_string($request->variable('qa_answer', '', true)); + $confirm_id = $request->variable('qa_confirm_id', ''); $sql = 'SELECT answer_text FROM ' . $this->table_captcha_answers . ' @@ -583,7 +584,7 @@ function check_answer() { $solution = ($this->question_strict) ? $row['answer_text'] : utf8_clean_string($row['answer_text']); - if ($solution === $answer) + if ($solution === $answer && $this->confirm_id === $confirm_id) { $this->solved = true; From cacd24f41fb11804c69bc8cc0e7f0ea6c34c9a41 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 3 Dec 2025 20:09:53 +0100 Subject: [PATCH 1005/1214] [ticket/17556] Handle behavior of new HTML5 dom parser in tests PHPBB-17556 --- tests/functional/acp_smilies_test.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/functional/acp_smilies_test.php b/tests/functional/acp_smilies_test.php index c8aa1d9b7d6..12001c0f0a1 100644 --- a/tests/functional/acp_smilies_test.php +++ b/tests/functional/acp_smilies_test.php @@ -38,6 +38,15 @@ public function test_htmlspecialchars() $crawler = self::submit($form); $html = $crawler->filter('#preview')->html(); - $this->assertMatchesRegularExpression('(]+ alt=">:D" title=">:D"[^>]*>)', $html); + + // Native HTML5 parser (PHP 8.4+) will encode entities + if (\PHP_VERSION_ID >= 80400) + { + $this->assertMatchesRegularExpression('(]+ alt=">:D" title=">:D"[^>]*>)', $html); + } + else + { + $this->assertMatchesRegularExpression('(]+ alt=">:D" title=">:D"[^>]*>)', $html); + } } } From 39c55c66b7968627341776b7aeea3706cf01a7d1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 3 Dec 2025 20:22:49 +0100 Subject: [PATCH 1006/1214] [ticket/17556] Increase minimum PHP version to 8.2 PHPBB-17556 --- phpBB/docs/INSTALL.html | 2 +- phpBB/docs/README.html | 6 +++--- phpBB/includes/startup.php | 6 +++--- phpBB/language/en/install.php | 2 +- phpBB/phpbb/composer.json | 2 +- .../module/requirements/task/check_server_environment.php | 2 +- vagrant/after.sh | 2 +- vagrant/bootstrap.yaml | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/phpBB/docs/INSTALL.html b/phpBB/docs/INSTALL.html index 6d5b976aa45..06557dca6d3 100644 --- a/phpBB/docs/INSTALL.html +++ b/phpBB/docs/INSTALL.html @@ -147,7 +147,7 @@

      Install

    • Oracle
  2. -
  3. PHP 8.1.0+ up to and including PHP 8.3 with support for the database you intend to use.
  4. +
  5. PHP 8.2.0+ up to and including PHP 8.5 with support for the database you intend to use.
  6. The following PHP modules are required:
    • json
    • diff --git a/phpBB/docs/README.html b/phpBB/docs/README.html index e8cc96342cb..80d81b484f1 100644 --- a/phpBB/docs/README.html +++ b/phpBB/docs/README.html @@ -265,7 +265,7 @@

      Readme

      • Your server type/version, e.g. Apache 2.2.3, IIS 7, Sambar, etc.
      • -
      • PHP version and mode of operation, e.g. PHP 8.1.0 as a module, PHP 8.2.3 running as CGI, etc.
      • +
      • PHP version and mode of operation, e.g. PHP 8.2.0 as a module, PHP 8.2.3 running as CGI, etc.
      • DB type/version, e.g. MySQL 5.0.77, PostgreSQL 9.0.6, MSSQL Server 2000 (via ODBC), etc.
      @@ -323,11 +323,11 @@

      Readme

      -

      phpBB 4.0.x takes advantage of new features added in PHP 8.1. We recommend that you upgrade to the latest stable release of PHP to run phpBB. The minimum version required is PHP 8.1.0 and the maximum supported version is the latest stable version of PHP.

      +

      phpBB 4.0.x takes advantage of new features added in PHP 8.2. We recommend that you upgrade to the latest stable release of PHP to run phpBB. The minimum version required is PHP 8.2.0 and the maximum supported version is the latest stable version of PHP.

      Please remember that running any application on a development (unstable, e.g. a beta release) version of PHP can lead to strange/unexpected results which may appear to be bugs in the application. Therefore, we recommend you upgrade to the newest stable version of PHP before running phpBB. If you are running a development version of PHP please check any bugs you find on a system running a stable release before submitting.

      -

      This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQLi 4.1.3, 4.x, 5.x, MariaDB 5.x, PostgreSQL 8.x, Oracle 8 and SQLite 3. Versions of PHP used range from 8.1.x to 8.2.x and 8.3.x.

      +

      This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQLi 4.1.3, 4.x, 5.x, MariaDB 5.x, PostgreSQL 8.x, Oracle 8 and SQLite 3. Versions of PHP used range from 8.2.x to 8.3.x, 8.4.x and 8.5.x.

      7.i. Notice on PHP security issues

      diff --git a/phpBB/includes/startup.php b/phpBB/includes/startup.php index dac52a025f1..d0b3567bd33 100644 --- a/phpBB/includes/startup.php +++ b/phpBB/includes/startup.php @@ -23,11 +23,11 @@ error_reporting($level); /** -* Minimum Requirement: PHP 8.1.0 +* Minimum Requirement: PHP 8.2.0 */ -if (version_compare(PHP_VERSION, '8.1.0', '<')) +if (version_compare(PHP_VERSION, '8.2.0', '<')) { - die('You are running an unsupported PHP version (' . PHP_VERSION . '). Please upgrade to PHP 8.1.0 or higher before trying to install or update to phpBB 4.0'); + die('You are running an unsupported PHP version (' . PHP_VERSION . '). Please upgrade to PHP 8.2.0 or higher before trying to install or update to phpBB 4.0'); } // In PHP 5.3.0 the error level has been raised to E_WARNING which causes problems diff --git a/phpBB/language/en/install.php b/phpBB/language/en/install.php index 750eb672819..efaf8696542 100644 --- a/phpBB/language/en/install.php +++ b/phpBB/language/en/install.php @@ -105,7 +105,7 @@ // Server requirements 'PHP_VERSION_REQD' => 'PHP version', - 'PHP_VERSION_REQD_EXPLAIN' => 'phpBB requires PHP version 7.2.0 or higher.', + 'PHP_VERSION_REQD_EXPLAIN' => 'phpBB requires PHP version 8.2.0 or higher.', 'PHP_GETIMAGESIZE_SUPPORT' => 'PHP getimagesize() function is required', 'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN' => 'In order for phpBB to function correctly, the getimagesize function needs to be available.', 'PCRE_UTF_SUPPORT' => 'PCRE UTF-8 support', diff --git a/phpBB/phpbb/composer.json b/phpBB/phpbb/composer.json index f4805ac3f65..dc3d808b418 100644 --- a/phpBB/phpbb/composer.json +++ b/phpBB/phpbb/composer.json @@ -23,7 +23,7 @@ "classmap": [""] }, "require": { - "php": "^8.1" + "php": "^8.2" }, "extra": { "branch-alias": { diff --git a/phpBB/phpbb/install/module/requirements/task/check_server_environment.php b/phpBB/phpbb/install/module/requirements/task/check_server_environment.php index 9ae953ee8ea..ccff406d145 100644 --- a/phpBB/phpbb/install/module/requirements/task/check_server_environment.php +++ b/phpBB/phpbb/install/module/requirements/task/check_server_environment.php @@ -96,7 +96,7 @@ protected function set_test_passed($is_passed) */ protected function check_php_version() { - if (version_compare(PHP_VERSION, '8.1.0', '<')) + if (version_compare(PHP_VERSION, '8.2.0', '<')) { $this->response_helper->add_error_message('PHP_VERSION_REQD', 'PHP_VERSION_REQD_EXPLAIN'); diff --git a/vagrant/after.sh b/vagrant/after.sh index 829a28a25dd..a6e23d46dd4 100755 --- a/vagrant/after.sh +++ b/vagrant/after.sh @@ -1,6 +1,6 @@ #!/bin/sh -PHP_VERSION="8.1" +PHP_VERSION="8.2" PHPBB_PATH="/home/vagrant/phpbb" PHPBB_CONFIG="${PHPBB_PATH}/phpBB/config.php" PHPBB_INSTALL="${PHPBB_PATH}/vagrant/phpbb-install-config.yml" diff --git a/vagrant/bootstrap.yaml b/vagrant/bootstrap.yaml index 0924206cd53..fd1e90d355c 100644 --- a/vagrant/bootstrap.yaml +++ b/vagrant/bootstrap.yaml @@ -18,7 +18,7 @@ sites: - map: phpbb.app to: "/home/vagrant/phpbb/phpBB" type: apache - php: "8.1" + php: "8.2" # blackfire: # - id: foo From afa19ef74b3c82d05131253ab6e3cc2041072935 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 4 Dec 2025 20:29:49 +0100 Subject: [PATCH 1007/1214] [ticket/17556] Update supported dbms versions PHPBB-17556 --- phpBB/docs/INSTALL.html | 14 +++++++------- phpBB/docs/README.html | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/phpBB/docs/INSTALL.html b/phpBB/docs/INSTALL.html index 06557dca6d3..cb39e0ccf90 100644 --- a/phpBB/docs/INSTALL.html +++ b/phpBB/docs/INSTALL.html @@ -133,18 +133,18 @@

      Install

      -

      phpBB 3.3.x has a few requirements which must be met before you are able to install and use it.

      +

      phpBB 4.0.x has a few requirements which must be met before you are able to install and use it.

      • A webserver or web hosting account running on any major Operating System with support for PHP
      • A SQL database system, one of:
          -
        • MySQL 4.1.3 or above (MySQLi required)
        • -
        • MariaDB 5.1 or above
        • -
        • PostgreSQL 9.4+
        • -
        • SQLite 3.6.15+
        • -
        • MS SQL Server 2000 or above (via ODBC or the native adapter)
        • -
        • Oracle
        • +
        • MySQL 8.0 or above (MySQLi required)
        • +
        • MariaDB 10.2.7 or above
        • +
        • PostgreSQL 9.5 or above
        • +
        • SQLite 3.8.3 or above
        • +
        • MS SQL Server 2012 or above
        • +
        • Oracle 12.1.0.2 or above
      • PHP 8.2.0+ up to and including PHP 8.5 with support for the database you intend to use.
      • diff --git a/phpBB/docs/README.html b/phpBB/docs/README.html index 80d81b484f1..7a5644f764e 100644 --- a/phpBB/docs/README.html +++ b/phpBB/docs/README.html @@ -106,7 +106,6 @@

        Readme

      • Updates from phpBB 3.0 RC1, 3.1 RC1 and 3.2 RC1 to the latest version
      • Note: if using the Advanced Update Package, updates are supported from phpBB 3.0.2 onward. To update a pre-3.0.2 installation, first update to 3.0.2 and then update to the current version.
      • Conversions from phpBB 2.0.x to the latest version
      • -
      • New installations of phpBB 3.2.x - only the latest released version
      • New installations of phpBB 3.3.x - only the latest released version
      @@ -264,9 +263,9 @@

      Readme

      If you do post a new bug (i.e. one that isn't already listed in the bug tracker) first make sure that you have logged in (your username and password are the same as for the community forums) then please include the following details:

        -
      • Your server type/version, e.g. Apache 2.2.3, IIS 7, Sambar, etc.
      • +
      • Your server type/version, e.g. Apache 2.4.65, IIS 10, Sambar, etc.
      • PHP version and mode of operation, e.g. PHP 8.2.0 as a module, PHP 8.2.3 running as CGI, etc.
      • -
      • DB type/version, e.g. MySQL 5.0.77, PostgreSQL 9.0.6, MSSQL Server 2000 (via ODBC), etc.
      • +
      • DB type/version, e.g. MySQL 8.0.44, PostgreSQL 9.5.6, MSSQL Server 2012 (via ODBC), etc.

      The relevant database type/version is listed within the administration control panel.

      @@ -327,7 +326,7 @@

      Readme

      Please remember that running any application on a development (unstable, e.g. a beta release) version of PHP can lead to strange/unexpected results which may appear to be bugs in the application. Therefore, we recommend you upgrade to the newest stable version of PHP before running phpBB. If you are running a development version of PHP please check any bugs you find on a system running a stable release before submitting.

      -

      This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQLi 4.1.3, 4.x, 5.x, MariaDB 5.x, PostgreSQL 8.x, Oracle 8 and SQLite 3. Versions of PHP used range from 8.2.x to 8.3.x, 8.4.x and 8.5.x.

      +

      This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQLi 8.0 to 9.4, MariaDB 10.2 to 10.11, PostgreSQL 9.5 to 15, Oracle 12 and SQLite 3. Versions of PHP used range from 8.2.x to 8.3.x, 8.4.x and 8.5.x.

      7.i. Notice on PHP security issues

      From a3aae2860ce4fa6068a8313cb1003956c315eea9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 6 Dec 2025 11:52:11 +0100 Subject: [PATCH 1008/1214] [ticket/17556] Use new Yaml flags PHPBB-17556 --- phpBB/develop/create_schema_files.php | 4 +++- phpBB/phpbb/install/console/command/install/config/show.php | 2 +- .../phpbb/install/console/command/install/config/validate.php | 2 +- phpBB/phpbb/install/console/command/install/install.php | 2 +- phpBB/phpbb/install/console/command/update/config/show.php | 2 +- .../phpbb/install/console/command/update/config/validate.php | 2 +- phpBB/phpbb/install/console/command/update/update.php | 2 +- .../module/install_database/task/create_schema_file.php | 3 ++- tests/test_framework/phpbb_database_test_case.php | 3 ++- 9 files changed, 13 insertions(+), 9 deletions(-) diff --git a/phpBB/develop/create_schema_files.php b/phpBB/develop/create_schema_files.php index 82d00fc5ad5..7766faaf672 100644 --- a/phpBB/develop/create_schema_files.php +++ b/phpBB/develop/create_schema_files.php @@ -18,6 +18,8 @@ * If you overwrite the original schema files please make sure you save the file with UNIX linefeeds. */ +use Symfony\Component\Yaml\Yaml; + $schema_path = __DIR__ . '/../install/schemas/'; $supported_dbms = array( 'mssql', @@ -87,7 +89,7 @@ public function createSchemaManager($connection): \Doctrine\DBAL\Schema\Abstract $db_tools = $factory->get($db_doctrine, true); $db_tools->set_table_prefix($table_prefix); -$tables_data = \Symfony\Component\Yaml\Yaml::parseFile($phpbb_root_path . '/config/default/container/tables.yml'); +$tables_data = Yaml::parseFile($phpbb_root_path . '/config/default/container/tables.yml', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); $tables = []; foreach ($tables_data['parameters'] as $parameter => $table) diff --git a/phpBB/phpbb/install/console/command/install/config/show.php b/phpBB/phpbb/install/console/command/install/config/show.php index 11c5af6094a..4f89087f04e 100644 --- a/phpBB/phpbb/install/console/command/install/config/show.php +++ b/phpBB/phpbb/install/console/command/install/config/show.php @@ -96,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { - $config = Yaml::parse(file_get_contents($config_file)); + $config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } catch (ParseException $e) { diff --git a/phpBB/phpbb/install/console/command/install/config/validate.php b/phpBB/phpbb/install/console/command/install/config/validate.php index 4a044ecc17e..2521fce7516 100644 --- a/phpBB/phpbb/install/console/command/install/config/validate.php +++ b/phpBB/phpbb/install/console/command/install/config/validate.php @@ -96,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { - $config = Yaml::parse(file_get_contents($config_file)); + $config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } catch (ParseException $e) { diff --git a/phpBB/phpbb/install/console/command/install/install.php b/phpBB/phpbb/install/console/command/install/install.php index 3bc1778a306..8461c7a7189 100644 --- a/phpBB/phpbb/install/console/command/install/install.php +++ b/phpBB/phpbb/install/console/command/install/install.php @@ -126,7 +126,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { - $config = Yaml::parse(file_get_contents($config_file)); + $config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } catch (ParseException $e) { diff --git a/phpBB/phpbb/install/console/command/update/config/show.php b/phpBB/phpbb/install/console/command/update/config/show.php index 1023734cc8d..f35b55ba87c 100644 --- a/phpBB/phpbb/install/console/command/update/config/show.php +++ b/phpBB/phpbb/install/console/command/update/config/show.php @@ -96,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { - $config = Yaml::parse(file_get_contents($config_file)); + $config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } catch (ParseException $e) { diff --git a/phpBB/phpbb/install/console/command/update/config/validate.php b/phpBB/phpbb/install/console/command/update/config/validate.php index 2f27ea1c728..ba36f06fb24 100644 --- a/phpBB/phpbb/install/console/command/update/config/validate.php +++ b/phpBB/phpbb/install/console/command/update/config/validate.php @@ -96,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { - $config = Yaml::parse(file_get_contents($config_file)); + $config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } catch (ParseException $e) { diff --git a/phpBB/phpbb/install/console/command/update/update.php b/phpBB/phpbb/install/console/command/update/update.php index e3ce381cab0..036bf6f766d 100644 --- a/phpBB/phpbb/install/console/command/update/update.php +++ b/phpBB/phpbb/install/console/command/update/update.php @@ -124,7 +124,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { - $config = Yaml::parse(file_get_contents($config_file)); + $config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } catch (ParseException $e) { diff --git a/phpBB/phpbb/install/module/install_database/task/create_schema_file.php b/phpBB/phpbb/install/module/install_database/task/create_schema_file.php index 20615c134a8..250b19f46b5 100644 --- a/phpBB/phpbb/install/module/install_database/task/create_schema_file.php +++ b/phpBB/phpbb/install/module/install_database/task/create_schema_file.php @@ -15,6 +15,7 @@ use phpbb\db\doctrine\connection_factory; use phpbb\install\exception\resource_limit_reached_exception; +use Symfony\Component\Yaml\Yaml; /** * Create database schema @@ -146,7 +147,7 @@ public function run() $factory = new \phpbb\db\tools\factory(); $db_tools = $factory->get($this->db_doctrine, true); $db_tools->set_table_prefix($table_prefix); - $tables_data = \Symfony\Component\Yaml\Yaml::parseFile($this->phpbb_root_path . '/config/default/container/tables.yml'); + $tables_data = Yaml::parseFile($this->phpbb_root_path . '/config/default/container/tables.yml', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); $tables = []; foreach ($tables_data['parameters'] as $parameter => $table) { diff --git a/tests/test_framework/phpbb_database_test_case.php b/tests/test_framework/phpbb_database_test_case.php index de8a605f2af..0df91f9e83b 100644 --- a/tests/test_framework/phpbb_database_test_case.php +++ b/tests/test_framework/phpbb_database_test_case.php @@ -12,6 +12,7 @@ */ use PHPUnit\DbUnit\TestCase; +use Symfony\Component\Yaml\Yaml; abstract class phpbb_database_test_case extends TestCase { @@ -392,7 +393,7 @@ public static function get_core_tables() : array if (empty($core_tables)) { - $tables_yml_data = \Symfony\Component\Yaml\Yaml::parseFile($phpbb_root_path . '/config/default/container/tables.yml'); + $tables_yml_data = Yaml::parseFile($phpbb_root_path . '/config/default/container/tables.yml', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); foreach ($tables_yml_data['parameters'] as $parameter => $table) { From 4cd5589eaa15fe658a3c9089be812659180724b5 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 6 Dec 2025 11:54:02 +0100 Subject: [PATCH 1009/1214] [ticket/17556] Remove mbstring requirement since we have polyfill PHPBB-17556 --- phpBB/docs/INSTALL.html | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/docs/INSTALL.html b/phpBB/docs/INSTALL.html index cb39e0ccf90..0752d2c5071 100644 --- a/phpBB/docs/INSTALL.html +++ b/phpBB/docs/INSTALL.html @@ -151,7 +151,6 @@

      Install

    • The following PHP modules are required:
      • json
      • -
      • mbstring
      • XML support
    • From c4263affa907d9a6329b215cb8e0fdb0e1c7c765 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 7 Dec 2025 14:11:50 -0800 Subject: [PATCH 1010/1214] [ticket/17591] Update Code Sniffer to v4 PHPBB-17591 --- .../Sniffs/Namespaces/UnusedUseSniff.php | 86 +++++++++++++++---- phpBB/composer.json | 2 +- phpBB/composer.lock | 25 +++--- 3 files changed, 78 insertions(+), 35 deletions(-) diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index 7cf5f64baf5..7c5a52e8f96 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -27,6 +27,24 @@ class UnusedUseSniff implements Sniff T_WHITESPACE, ]; + private function getNameTokens() + { + $tokens = [T_NS_SEPARATOR, T_STRING]; + if (defined('T_NAME_QUALIFIED')) + { + $tokens[] = T_NAME_QUALIFIED; + } + if (defined('T_NAME_FULLY_QUALIFIED')) + { + $tokens[] = T_NAME_FULLY_QUALIFIED; + } + if (defined('T_NAME_RELATIVE')) + { + $tokens[] = T_NAME_RELATIVE; + } + return $tokens; + } + /** * {@inheritdoc} */ @@ -71,9 +89,25 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); - $class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), ($stackPtr + 1)); + $class_name_start = $phpcsFile->findNext($this->getNameTokens(), ($stackPtr + 1)); - $class_name_end = $phpcsFile->findNext(self::FIND, ($stackPtr + 1), null, true); + // Handle PHPCS v4 T_NAME_QUALIFIED tokens + if (isset($tokens[$class_name_start]) && + (defined('T_NAME_QUALIFIED') && $tokens[$class_name_start]['code'] === T_NAME_QUALIFIED || + defined('T_NAME_FULLY_QUALIFIED') && $tokens[$class_name_start]['code'] === T_NAME_FULLY_QUALIFIED || + defined('T_NAME_RELATIVE') && $tokens[$class_name_start]['code'] === T_NAME_RELATIVE)) + { + $name_full = $tokens[$class_name_start]['content']; + $class_name_end = $class_name_start + 1; + $parts = explode('\\', $name_full); + $name_short = end($parts); + } + else + { + $class_name_end = $phpcsFile->findNext(self::FIND, ($stackPtr + 1), null, true); + $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start)); + $name_short = $tokens[$class_name_end - 1]['content']; + } $aliasing_as_position = $phpcsFile->findNext(T_AS, $class_name_end, null, false, null, true); if ($aliasing_as_position !== false) @@ -82,11 +116,6 @@ public function process(File $phpcsFile, $stackPtr) $name_short = $tokens[$alias_position]['content']; $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start - 1)); } - else - { - $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start)); - $name_short = $tokens[$class_name_end - 1]['content']; - } if ($tokens[$class_name_start]['content'] === 'function' && $tokens[$class_name_start + 1]['code'] === T_WHITESPACE) @@ -127,9 +156,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name { $old_simple_statement = $simple_statement; - $simple_class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), ($simple_statement + 1)); + $simple_class_name_start = $phpcsFile->findNext($this->getNameTokens(), ($simple_statement + 1)); - if ($simple_class_name_start === false) { + if ($simple_class_name_start === false) + { continue; } @@ -137,7 +167,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name $simple_class_name = trim($phpcsFile->getTokensAsString($simple_class_name_start, ($simple_class_name_end - $simple_class_name_start))); - $ok = $this->check($phpcsFile, $simple_class_name, $class_name_full, $class_name_short, $simple_statement) || $ok; + if (!empty($simple_class_name)) + { + $ok = $this->check($phpcsFile, $simple_class_name, $class_name_full, $class_name_short, $simple_statement) || $ok; + } } } @@ -152,7 +185,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name $paamayim_nekudotayim_class_name = trim($phpcsFile->getTokensAsString($paamayim_nekudotayim_class_name_start + 1, ($paamayim_nekudotayim_class_name_end - $paamayim_nekudotayim_class_name_start))); - $ok = $this->check($phpcsFile, $paamayim_nekudotayim_class_name, $class_name_full, $class_name_short, $paamayim_nekudotayim) || $ok; + if (!empty($paamayim_nekudotayim_class_name)) + { + $ok = $this->check($phpcsFile, $paamayim_nekudotayim_class_name, $class_name_full, $class_name_short, $paamayim_nekudotayim) || $ok; + } } // Checks in implements @@ -166,12 +202,15 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name { $old_implemented_class = $implemented_class; - $implements_class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), ($implemented_class - 1)); + $implements_class_name_start = $phpcsFile->findNext($this->getNameTokens(), ($implemented_class - 1)); $implements_class_name_end = $phpcsFile->findNext(self::FIND, ($implemented_class - 1), null, true); $implements_class_name = trim($phpcsFile->getTokensAsString($implements_class_name_start, ($implements_class_name_end - $implements_class_name_start))); - $ok = $this->check($phpcsFile, $implements_class_name, $class_name_full, $class_name_short, $implements) || $ok; + if (!empty($implements_class_name)) + { + $ok = $this->check($phpcsFile, $implements_class_name, $class_name_full, $class_name_short, $implements) || $ok; + } } } @@ -192,7 +231,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name $params = $phpcsFile->getMethodParameters($function_declaration); foreach ($params as $param) { - $ok = $this->check($phpcsFile, $param['type_hint'], $class_name_full, $class_name_short, $function_declaration) || $ok; + if (!empty($param['type_hint'])) + { + $ok = $this->check($phpcsFile, $param['type_hint'], $class_name_full, $class_name_short, $function_declaration) || $ok; + } } } @@ -202,12 +244,15 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name { $old_catch = $catch; - $caught_class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), $catch + 1); + $caught_class_name_start = $phpcsFile->findNext($this->getNameTokens(), $catch + 1); $caught_class_name_end = $phpcsFile->findNext(self::FIND, $caught_class_name_start + 1, null, true); $caught_class_name = trim($phpcsFile->getTokensAsString($caught_class_name_start, ($caught_class_name_end - $caught_class_name_start))); - $ok = $this->check($phpcsFile, $caught_class_name, $class_name_full, $class_name_short, $catch) || $ok; + if (!empty($caught_class_name)) + { + $ok = $this->check($phpcsFile, $caught_class_name, $class_name_full, $class_name_short, $catch) || $ok; + } } $old_use = $stackPtr; @@ -227,11 +272,14 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name continue; } - $class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), $use + 1, null, false, null, true); + $class_name_start = $phpcsFile->findNext($this->getNameTokens(), $use + 1, null, false, null, true); $class_name_end = $phpcsFile->findNext(self::FIND, $class_name_start + 1, null, true, null, true); $found_name = trim($phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start))); - $ok = $this->check($phpcsFile, $found_name, $class_name_full, $class_name_short, $use) || $ok; + if (!empty($found_name)) + { + $ok = $this->check($phpcsFile, $found_name, $class_name_full, $class_name_short, $use) || $ok; + } } return $ok; @@ -252,7 +300,7 @@ private function findFunctionUsage(File $phpcsFile, $stackPtr, $tokens, $name_fu ); $position = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $position + 1); - if ($found_start === null) + if ($found_start === null || $found_start === false) { continue; } diff --git a/phpBB/composer.json b/phpBB/composer.json index 2f559733f76..42271c2da05 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -62,7 +62,7 @@ "phing/phing": "~2.4", "phpunit/dbunit": "~4.0", "phpunit/phpunit": "^7.0", - "squizlabs/php_codesniffer": "~3.4", + "squizlabs/php_codesniffer": "^4.0", "symfony/browser-kit": "~3.4", "symfony/css-selector": "~3.4", "symfony/dom-crawler": "~3.4" diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 54c3c22aac8..ff18af9dd2d 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eb658f95a4047347f31406a96a021c80", + "content-hash": "b63d9eb2d41744fe5532d47955802f7e", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -4702,37 +4702,32 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186" + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, "bin": [ "bin/phpcbf", "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -4751,7 +4746,7 @@ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", @@ -4782,7 +4777,7 @@ "type": "thanks_dev" } ], - "time": "2025-05-11T03:36:00+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { "name": "symfony/browser-kit", @@ -5110,5 +5105,5 @@ "platform-overrides": { "php": "7.2" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } From a9fcf738b98af275e53774c3ad5a6dee34fa66ff Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 7 Dec 2025 14:22:55 -0800 Subject: [PATCH 1011/1214] [ticket/17591] Allow eval on legacy core files that legitametely use it PHPBB-17591 --- build/code_sniffer/ruleset-php-legacy-core.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build/code_sniffer/ruleset-php-legacy-core.xml b/build/code_sniffer/ruleset-php-legacy-core.xml index 55f2461a04a..c5dd34d5fea 100644 --- a/build/code_sniffer/ruleset-php-legacy-core.xml +++ b/build/code_sniffer/ruleset-php-legacy-core.xml @@ -8,4 +8,11 @@ + + + */install/convert/convertor.php + */includes/functions_module.php + */includes/functions_convert.php + + From 7faf8e55b4ba45211ed24c097c5411fc984a0e45 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sun, 7 Dec 2025 15:01:02 -0800 Subject: [PATCH 1012/1214] [ticket/17591] Sniff ext dir only if it contains PHP files PHPBB-17591 --- build/build.xml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/build/build.xml b/build/build.xml index 3bd584b1b88..f8ea1db2dc5 100644 --- a/build/build.xml +++ b/build/build.xml @@ -106,13 +106,22 @@ --ignore=${sniffIgnoreList} phpBB" dir="." returnProperty="retval-php-legacy" passthru="true" /> - + + + + + + + + + + From eb31be134b00d02706cf2e161a2e8741070f44bd Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Mon, 8 Dec 2025 07:49:37 -0800 Subject: [PATCH 1013/1214] [ticket/17591] Update annotations for bypassing eval where legitimate PHPBB-17591 --- build/code_sniffer/ruleset-php-legacy-core.xml | 7 ------- phpBB/includes/functions_convert.php | 2 ++ phpBB/includes/functions_module.php | 2 ++ phpBB/install/convert/convertor.php | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/build/code_sniffer/ruleset-php-legacy-core.xml b/build/code_sniffer/ruleset-php-legacy-core.xml index c5dd34d5fea..55f2461a04a 100644 --- a/build/code_sniffer/ruleset-php-legacy-core.xml +++ b/build/code_sniffer/ruleset-php-legacy-core.xml @@ -8,11 +8,4 @@ - - - */install/convert/convertor.php - */includes/functions_module.php - */includes/functions_convert.php - - diff --git a/phpBB/includes/functions_convert.php b/phpBB/includes/functions_convert.php index f980ab200d1..9f145b44d36 100644 --- a/phpBB/includes/functions_convert.php +++ b/phpBB/includes/functions_convert.php @@ -1278,7 +1278,9 @@ function restore_config($schema) $var = (empty($m[2]) || empty($convert_config[$m[2]])) ? "''" : "'" . addslashes($convert_config[$m[2]]) . "'"; $exec = '$config_value = ' . $m[1] . '(' . $var . ');'; // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($exec); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } else diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index 573c4146d18..41e660cdfe6 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -469,7 +469,9 @@ static function module_auth($module_auth, $forum_id) $is_auth = false; // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval('$is_auth = (int) (' . $module_auth . ');'); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd return $is_auth; diff --git a/phpBB/install/convert/convertor.php b/phpBB/install/convert/convertor.php index b40b53cdb59..d433333c430 100644 --- a/phpBB/install/convert/convertor.php +++ b/phpBB/install/convert/convertor.php @@ -454,7 +454,9 @@ function convert_data($converter) if (!empty($convert->convertor['execute_first'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($convert->convertor['execute_first']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } @@ -526,7 +528,9 @@ function convert_data($converter) if (!empty($schema['execute_first'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($schema['execute_first']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } @@ -593,7 +597,9 @@ function convert_data($converter) if (!empty($schema['execute_always'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($schema['execute_always']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } @@ -1110,7 +1116,9 @@ function jump($converter, $jump, $last_statement) if (!is_array($convert->convertor['execute_last'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($convert->convertor['execute_last']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } else @@ -1118,7 +1126,9 @@ function jump($converter, $jump, $last_statement) while ($last_statement < count($convert->convertor['execute_last'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($convert->convertor['execute_last'][$last_statement]); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd $this->template->assign_block_vars('checks', array( @@ -1486,7 +1496,9 @@ function process_row(&$schema, &$sql_data, &$insert_values) $execution = str_replace('{RESULT}', '$value', $execution); $execution = str_replace('{VALUE}', '$value', $execution); // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($execution); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } } @@ -1541,7 +1553,9 @@ function process_row(&$schema, &$sql_data, &$insert_values) $execution = str_replace('{RESULT}', '$value', $execution); $execution = str_replace('{VALUE}', '$value', $execution); // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($execution); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } } From 078f40d0f9f7617b5fcfda608fd5ec6703213871 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 10 Dec 2025 10:56:14 +0700 Subject: [PATCH 1014/1214] [ticket/17592] Translate "No match found!!!" if no mention entry was found PHPBB-17592 --- phpBB/assets/javascript/mentions.js | 4 ++++ phpBB/language/en/posting.php | 1 + phpBB/styles/prosilver/template/posting_buttons.html | 2 ++ 3 files changed, 7 insertions(+) diff --git a/phpBB/assets/javascript/mentions.js b/phpBB/assets/javascript/mentions.js index efd57bc9731..803d7053ea6 100644 --- a/phpBB/assets/javascript/mentions.js +++ b/phpBB/assets/javascript/mentions.js @@ -290,6 +290,10 @@ selectClass: 'is-active', itemClass: 'mention-item', menuItemTemplate, + noMatchTemplate() { + const returnTemplate = (typeof mention_no_match_found === 'undefined') ? 'No match found' : mention_no_match_found; + return '
    • ' + returnTemplate + '
    • '; + }, selectTemplate(item) { return '[mention ' + (item.type === 'g' ? 'group_id=' : 'user_id=') + item.id + ']' + item.name + '[/mention]'; }, diff --git a/phpBB/language/en/posting.php b/phpBB/language/en/posting.php index fd629f4aef4..6b96f0977b0 100644 --- a/phpBB/language/en/posting.php +++ b/phpBB/language/en/posting.php @@ -158,6 +158,7 @@ 2 => 'Your images may only be up to %d pixels wide.', ), + 'MENTION_NO_MATCH_FOUND' => 'No match found', 'MESSAGE_BODY_EXPLAIN' => array( 0 => '', // zero means no limit, so we don't view a message here. 1 => 'Enter your message here, it may contain no more than %d character.', diff --git a/phpBB/styles/prosilver/template/posting_buttons.html b/phpBB/styles/prosilver/template/posting_buttons.html index 85e05a0d491..3463f97ad10 100644 --- a/phpBB/styles/prosilver/template/posting_buttons.html +++ b/phpBB/styles/prosilver/template/posting_buttons.html @@ -10,6 +10,8 @@ var bbtags = new Array('[b]','[/b]','[i]','[/i]','[u]','[/u]','[quote]','[/quote]','[code]','[/code]','[list]','[/list]','[list=]','[/list]','[img]','[/img]','[url]','[/url]','[size=]','[/size]', {custom_tags.BBCODE_NAME}); var imageTag = false; + var mention_no_match_found = '{{ lang('MENTION_NO_MATCH_FOUND')|e('js') }}'; + function change_palette() { phpbb.toggleDisplay('colour_palette'); From 1359703d59add10eb47ed4a7689dcfc2439ff541 Mon Sep 17 00:00:00 2001 From: Ruben Calvo Date: Sat, 29 Nov 2025 13:28:44 +0100 Subject: [PATCH 1015/1214] [ticket/17587] Move mark notification read to controller PHPBB-17587 --- .../container/services_notification.yml | 12 ++ .../config/default/routing/notifications.yml | 6 + phpBB/config/default/routing/routing.yml | 4 + phpBB/index.php | 59 -------- .../notification/controller/mark_read.php | 137 ++++++++++++++++++ phpBB/phpbb/notification/type/base.php | 13 +- tests/mock/notification_type_post.php | 3 +- tests/notification/base.php | 1 + .../fixtures/services_notification.yml | 6 + .../notification_method_email_test.php | 1 + .../notification_method_webpush_test.php | 5 + tests/notification/submit_post_base.php | 1 + tests/notification/user_list_trim_test.php | 2 +- tests/ucp/controller_webpush_test.php | 4 + 14 files changed, 188 insertions(+), 66 deletions(-) create mode 100644 phpBB/config/default/routing/notifications.yml create mode 100644 phpBB/phpbb/notification/controller/mark_read.php diff --git a/phpBB/config/default/container/services_notification.yml b/phpBB/config/default/container/services_notification.yml index ccf16223cf9..b3ff08586dd 100644 --- a/phpBB/config/default/container/services_notification.yml +++ b/phpBB/config/default/container/services_notification.yml @@ -26,6 +26,7 @@ services: notification.type.base: abstract: true arguments: + - '@controller.helper' - '@dbal.conn' - '@language' - '@user' @@ -248,3 +249,14 @@ services: - '%tables.push_subscriptions%' tags: - { name: notification.method } + +# ----- Notification's controllers ----- + notification.controller.mark_read: + class: phpbb\notification\controller\mark_read + arguments: + - '@event_dispatcher' + - '@language' + - '@notification_manager' + - '@request' + - '@user' + - '%core.root_path%' diff --git a/phpBB/config/default/routing/notifications.yml b/phpBB/config/default/routing/notifications.yml new file mode 100644 index 00000000000..4a0f3d56ad4 --- /dev/null +++ b/phpBB/config/default/routing/notifications.yml @@ -0,0 +1,6 @@ +phpbb_notifications_mark_read: + path: /mark-read/{id} + defaults: + _controller: notification.controller.mark_read:handle + requirements: + id: \d+ diff --git a/phpBB/config/default/routing/routing.yml b/phpBB/config/default/routing/routing.yml index 3a6f6fbcb1e..2e2638741c3 100644 --- a/phpBB/config/default/routing/routing.yml +++ b/phpBB/config/default/routing/routing.yml @@ -37,6 +37,10 @@ phpbb_mention_controller: methods: [GET, POST] defaults: { _controller: mention.controller:handle } +phpbb_notifications: + resource: notifications.yml + prefix: /notifications + phpbb_report_routing: resource: report.yml diff --git a/phpBB/index.php b/phpBB/index.php index bc7c1a4707f..4353264b213 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -11,9 +11,6 @@ * */ -/** -*/ - /** * @ignore */ @@ -28,62 +25,6 @@ $auth->acl($user->data); $user->setup('viewforum'); -// Mark notifications read -if (($mark_notification = $request->variable('mark_notification', 0))) -{ - if ($user->data['user_id'] == ANONYMOUS) - { - if ($request->is_ajax()) - { - trigger_error('LOGIN_REQUIRED'); - } - login_box('', $user->lang['LOGIN_REQUIRED']); - } - - if (check_link_hash($request->variable('hash', ''), 'mark_notification_read')) - { - /* @var $phpbb_notifications \phpbb\notification\manager */ - $phpbb_notifications = $phpbb_container->get('notification_manager'); - - $notification = $phpbb_notifications->load_notifications('notification.method.board', array( - 'notification_id' => $mark_notification, - )); - - if (isset($notification['notifications'][$mark_notification])) - { - $notification = $notification['notifications'][$mark_notification]; - - $notification->mark_read(); - - /** - * You can use this event to perform additional tasks or redirect user elsewhere. - * - * @event core.index_mark_notification_after - * @var int mark_notification Notification ID - * @var \phpbb\notification\type\type_interface notification Notification instance - * @since 3.2.6-RC1 - */ - $vars = array('mark_notification', 'notification'); - extract($phpbb_dispatcher->trigger_event('core.index_mark_notification_after', compact($vars))); - - if ($request->is_ajax()) - { - $json_response = new \phpbb\json_response(); - $json_response->send(array( - 'success' => true, - )); - } - - if (($redirect = $request->variable('redirect', ''))) - { - redirect(append_sid($phpbb_root_path . $redirect)); - } - - redirect($notification->get_redirect_url()); - } - } -} - display_forums('', $config['load_moderators']); /** @var \phpbb\group\helper $group_helper */ diff --git a/phpBB/phpbb/notification/controller/mark_read.php b/phpBB/phpbb/notification/controller/mark_read.php new file mode 100644 index 00000000000..0371c71f51c --- /dev/null +++ b/phpBB/phpbb/notification/controller/mark_read.php @@ -0,0 +1,137 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\notification\controller; + +use phpbb\event\dispatcher; +use phpbb\exception\http_exception; +use phpbb\language\language; +use phpbb\notification\manager; +use phpbb\notification\type\type_interface; +use phpbb\request\request; +use phpbb\user; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; + +class mark_read +{ + /** @var dispatcher */ + protected $dispatcher; + + /** @var language */ + protected $language; + + /** @var manager */ + protected $manager; + + /** @var request */ + protected $request; + + /** @var user */ + protected $user; + + /** @var string */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param dispatcher $dispatcher Event dispatcher + * @param language $language Language object + * @param manager $manager Notification manager + * @param request $request Request object + * @param user $user User object + * @param string $phpbb_root_path phpBB root path + */ + public function __construct(dispatcher $dispatcher, language $language, manager $manager, request $request, user $user, string $phpbb_root_path) + { + $this->dispatcher = $dispatcher; + $this->language = $language; + $this->manager = $manager; + $this->request = $request; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Mark notification as read + * + * @param int $id Notification ID + * + * @return Response + * + * @throws http_exception + * + * @psalm-suppress InvalidArgument + */ + public function handle(int $id): Response + { + $mark_notification = $id; + + if ($this->user->data['user_id'] == ANONYMOUS) + { + if ($this->request->is_ajax()) + { + throw new http_exception(403, 'LOGIN_REQUIRED'); + } + login_box('', $this->language->lang('LOGIN_REQUIRED')); + } + + if (!check_link_hash($this->request->variable('hash', ''), 'mark_notification_read')) + { + // Link hash error + throw new http_exception(403, 'NOT_AUTHORISED'); + } + + $notification = $this->manager->load_notifications('notification.method.board', [ + 'notification_id' => $mark_notification, + ]); + + if (!isset($notification['notifications'][$mark_notification])) + { + // Notification id not found + throw new http_exception(404, 'PAGE_NOT_FOUND'); + } + + $notification = $notification['notifications'][$mark_notification]; + + $notification->mark_read(); + + /** + * You can use this event to perform additional tasks or redirect user elsewhere. + * + * @event core.index_mark_notification_after + * @var int mark_notification Notification ID + * @var type_interface notification Notification instance + * @since 3.2.6-RC1 + */ + $vars = ['mark_notification', 'notification']; + extract($this->dispatcher->trigger_event('core.index_mark_notification_after', compact($vars))); + + if ($this->request->is_ajax()) + { + $data = [ + 'success' => true, + ]; + return new JsonResponse($data); + } + + if (($redirect = $this->request->variable('redirect', ''))) + { + return new RedirectResponse(append_sid($this->phpbb_root_path . $redirect)); + } + + return new RedirectResponse($notification->get_redirect_url()); + } +} diff --git a/phpBB/phpbb/notification/type/base.php b/phpBB/phpbb/notification/type/base.php index 70fa979a57f..009ea2ff887 100644 --- a/phpBB/phpbb/notification/type/base.php +++ b/phpBB/phpbb/notification/type/base.php @@ -21,6 +21,9 @@ abstract class base implements \phpbb\notification\type\type_interface /** @var \phpbb\notification\manager */ protected $notification_manager; + /** @var \phpbb\controller\helper|null */ + protected $controller_helper; + /** @var \phpbb\db\driver\driver_interface */ protected $db; @@ -76,6 +79,7 @@ abstract class base implements \phpbb\notification\type\type_interface /** * Notification Type Base Constructor * + * @param \phpbb\controller\helper $controller_helper * @param \phpbb\db\driver\driver_interface $db * @param \phpbb\language\language $language * @param \phpbb\user $user @@ -84,8 +88,9 @@ abstract class base implements \phpbb\notification\type\type_interface * @param string $php_ext * @param string $user_notifications_table */ - public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table) + public function __construct(\phpbb\controller\helper $controller_helper, \phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table) { + $this->controller_helper = $controller_helper; $this->db = $db; $this->language = $language; $this->user = $user; @@ -132,7 +137,6 @@ public function __get($name) return $this->data[$name] ?? null; } - /** * Magic method to set data on this notification * @@ -286,13 +290,12 @@ public function prepare_for_display() if ($this->get_url()) { - $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&hash=' . $mark_hash); + $u_mark_read = $this->controller_helper->route('phpbb_notifications_mark_read', ['id' => $this->notification_id, 'hash' => $mark_hash]); } else { $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : ''); - - $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&hash=' . $mark_hash . '&redirect=' . urlencode($redirect)); + $u_mark_read = $this->controller_helper->route('phpbb_notifications_mark_read', ['id' => $this->notification_id, 'hash' => $mark_hash, 'redirect' => $redirect]); } $avatar = $this->get_avatar(); diff --git a/tests/mock/notification_type_post.php b/tests/mock/notification_type_post.php index 4116fecf5e8..fbc82c9845f 100644 --- a/tests/mock/notification_type_post.php +++ b/tests/mock/notification_type_post.php @@ -21,9 +21,10 @@ class phpbb_mock_notification_type_post extends \phpbb\notification\type\post { - public function __construct($user_loader, $db, $cache, $language, $user, $auth, $config, $phpbb_root_path, $php_ext, $notification_types_table, $user_notifications_table) + public function __construct($user_loader, $controller_helper, $db, $cache, $language, $user, $auth, $config, $phpbb_root_path, $php_ext, $notification_types_table, $user_notifications_table) { $this->user_loader = $user_loader; + $this->controller_helper = $controller_helper; $this->db = $db; $this->cache = $cache; $this->language = $language; diff --git a/tests/notification/base.php b/tests/notification/base.php index a018191d66f..4d080296156 100644 --- a/tests/notification/base.php +++ b/tests/notification/base.php @@ -102,6 +102,7 @@ protected function setUp(): void $phpbb_container->set('user', $user); $phpbb_container->set('language', $lang); $phpbb_container->set('config', $this->config); + $phpbb_container->set('controller.helper', $this->createMock('\phpbb\controller\helper')); $phpbb_container->set('dbal.conn', $this->db); $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); diff --git a/tests/notification/fixtures/services_notification.yml b/tests/notification/fixtures/services_notification.yml index 69e6374f4cf..fd256f3016e 100644 --- a/tests/notification/fixtures/services_notification.yml +++ b/tests/notification/fixtures/services_notification.yml @@ -14,6 +14,9 @@ services: config: synthetic: true + controller.helper: + synthetic: true + dbal.conn: synthetic: true @@ -38,6 +41,9 @@ services: groupposition.teampage: synthetic: true + request: + synthetic: true + text_formatter.s9e.factory: synthetic: true diff --git a/tests/notification/notification_method_email_test.php b/tests/notification/notification_method_email_test.php index 0634e3abd49..08819ae995a 100644 --- a/tests/notification/notification_method_email_test.php +++ b/tests/notification/notification_method_email_test.php @@ -79,6 +79,7 @@ protected function setUp(): void $phpbb_container->set('user', $user); $phpbb_container->set('language', $lang); $phpbb_container->set('config', $this->config); + $phpbb_container->set('controller.helper', $this->createMock('\phpbb\controller\helper')); $phpbb_container->set('dbal.conn', $this->db); $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); diff --git a/tests/notification/notification_method_webpush_test.php b/tests/notification/notification_method_webpush_test.php index d2e49579121..f07012dc003 100644 --- a/tests/notification/notification_method_webpush_test.php +++ b/tests/notification/notification_method_webpush_test.php @@ -92,6 +92,9 @@ protected function setUp(): void $avatar_helper = $this->getMockBuilder('\phpbb\avatar\helper') ->disableOriginalConstructor() ->getMock(); + $controller_helper = $this->getMockBuilder('\phpbb\controller\helper') + ->disableOriginalConstructor() + ->getMock(); $db = $this->db = $this->new_dbal(); $config = $this->config = new \phpbb\config\config([ 'allow_privmsg' => true, @@ -133,12 +136,14 @@ protected function setUp(): void $phpbb_container->set('language', $this->language); $phpbb_container->set('config', $this->config); $phpbb_container->set('dbal.conn', $this->db); + $phpbb_container->set('controller.helper', $this->createMock('\phpbb\controller\helper')); $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); $phpbb_container->set('log', $this->log); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('dispatcher', $this->phpbb_dispatcher); + $phpbb_container->set('event_dispatcher', $this->phpbb_dispatcher); $phpbb_container->setParameter('core.root_path', $phpbb_root_path); $phpbb_container->setParameter('core.php_ext', $phpEx); $phpbb_container->setParameter('tables.notifications', 'phpbb_notifications'); diff --git a/tests/notification/submit_post_base.php b/tests/notification/submit_post_base.php index d90a4736a84..3559f01a0db 100644 --- a/tests/notification/submit_post_base.php +++ b/tests/notification/submit_post_base.php @@ -135,6 +135,7 @@ protected function setUp(): void $phpbb_container->set('auth', $auth); $phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache', $cache); + $phpbb_container->set('controller.helper', $this->createMock('\phpbb\controller\helper')); $phpbb_container->set('log', new \phpbb\log\dummy()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set( diff --git a/tests/notification/user_list_trim_test.php b/tests/notification/user_list_trim_test.php index f895b895896..640adf6e4e4 100644 --- a/tests/notification/user_list_trim_test.php +++ b/tests/notification/user_list_trim_test.php @@ -66,7 +66,7 @@ protected function setUp(): void $user_loader->load_users(array(2, 3, 4, 5, 6)); $this->notification = new phpbb_mock_notification_type_post( - $user_loader, null, null, $lang, $user, null, null, $phpbb_root_path, $phpEx, null, null + $user_loader, null, null, null, $lang, $user, null, null, $phpbb_root_path, $phpEx, null, null ); } diff --git a/tests/ucp/controller_webpush_test.php b/tests/ucp/controller_webpush_test.php index 9539fa73dcc..4dade9862fc 100644 --- a/tests/ucp/controller_webpush_test.php +++ b/tests/ucp/controller_webpush_test.php @@ -188,6 +188,7 @@ public function test_get_user_notification() $this->notification_manager->method('get_item_type_class') ->willReturnCallback(function(string $type_name, array $row_data) { $notification_type = new quote( + $this->controller_helper, $this->db, $this->language, $this->user, @@ -242,6 +243,7 @@ public function test_get_user_notification_anonymous() $this->notification_manager->method('get_item_type_class') ->willReturnCallback(function(string $type_name, array $row_data) { $notification_type = new quote( + $this->controller_helper, $this->db, $this->language, $this->user, @@ -297,6 +299,7 @@ public function test_get_user_notification_anonymous_invalid_token() $this->notification_manager->method('get_item_type_class') ->willReturnCallback(function(string $type_name, array $row_data) { $notification_type = new quote( + $this->controller_helper, $this->db, $this->language, $this->user, @@ -345,6 +348,7 @@ public function test_get_user_notification_legacy() $this->notification_manager->method('get_item_type_class') ->willReturnCallback(function(string $type_name, array $row_data) { $notification_type = new quote( + $this->controller_helper, $this->db, $this->language, $this->user, From 25acb763d4758852cb01af24eba1fa07680dad2f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 11 Dec 2025 07:09:43 -0800 Subject: [PATCH 1016/1214] [ticket/17591] Update CodeSniffer to v4 PHPBB-17591 --- build/build.xml | 23 +++-- .../Sniffs/Namespaces/UnusedUseSniff.php | 86 +++++++++++++++---- phpBB/composer.json | 2 +- phpBB/composer.lock | 18 ++-- phpBB/includes/functions_convert.php | 2 + phpBB/includes/functions_module.php | 2 + phpBB/install/convert/convertor.php | 14 +++ 7 files changed, 111 insertions(+), 36 deletions(-) diff --git a/build/build.xml b/build/build.xml index a181092a235..a1c5511457d 100644 --- a/build/build.xml +++ b/build/build.xml @@ -106,13 +106,22 @@ --ignore=${sniffIgnoreList} phpBB" dir="." returnProperty="retval-php-legacy" passthru="true" /> - + + + + + + + + + + diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index 4e78d87ce8e..3fa18a63fec 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -27,6 +27,24 @@ class UnusedUseSniff implements Sniff T_WHITESPACE, ]; + private function getNameTokens() + { + $tokens = [T_NS_SEPARATOR, T_STRING]; + if (defined('T_NAME_QUALIFIED')) + { + $tokens[] = T_NAME_QUALIFIED; + } + if (defined('T_NAME_FULLY_QUALIFIED')) + { + $tokens[] = T_NAME_FULLY_QUALIFIED; + } + if (defined('T_NAME_RELATIVE')) + { + $tokens[] = T_NAME_RELATIVE; + } + return $tokens; + } + /** * {@inheritdoc} */ @@ -82,9 +100,25 @@ public function process(File $phpcsFile, $stackPtr) $tokens = $phpcsFile->getTokens(); - $class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), ($stackPtr + 1)); + $class_name_start = $phpcsFile->findNext($this->getNameTokens(), ($stackPtr + 1)); - $class_name_end = $phpcsFile->findNext(self::FIND, ($stackPtr + 1), null, true); + // Handle PHPCS v4 T_NAME_QUALIFIED tokens + if (isset($tokens[$class_name_start]) && + (defined('T_NAME_QUALIFIED') && $tokens[$class_name_start]['code'] === T_NAME_QUALIFIED || + defined('T_NAME_FULLY_QUALIFIED') && $tokens[$class_name_start]['code'] === T_NAME_FULLY_QUALIFIED || + defined('T_NAME_RELATIVE') && $tokens[$class_name_start]['code'] === T_NAME_RELATIVE)) + { + $name_full = $tokens[$class_name_start]['content']; + $class_name_end = $class_name_start + 1; + $parts = explode('\\', $name_full); + $name_short = end($parts); + } + else + { + $class_name_end = $phpcsFile->findNext(self::FIND, ($stackPtr + 1), null, true); + $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start)); + $name_short = $tokens[$class_name_end - 1]['content']; + } $aliasing_as_position = $phpcsFile->findNext(T_AS, $class_name_end, null, false, null, true); if ($aliasing_as_position !== false) @@ -93,11 +127,6 @@ public function process(File $phpcsFile, $stackPtr) $name_short = $tokens[$alias_position]['content']; $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start - 1)); } - else - { - $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start)); - $name_short = $tokens[$class_name_end - 1]['content']; - } if ($tokens[$class_name_start]['content'] === 'function' && $tokens[$class_name_start + 1]['code'] === T_WHITESPACE) @@ -138,9 +167,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name { $old_simple_statement = $simple_statement; - $simple_class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), ($simple_statement + 1)); + $simple_class_name_start = $phpcsFile->findNext($this->getNameTokens(), ($simple_statement + 1)); - if ($simple_class_name_start === false) { + if ($simple_class_name_start === false) + { continue; } @@ -148,7 +178,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name $simple_class_name = trim($phpcsFile->getTokensAsString($simple_class_name_start, ($simple_class_name_end - $simple_class_name_start))); - $ok = $this->check($phpcsFile, $simple_class_name, $class_name_full, $class_name_short, $simple_statement) || $ok; + if (!empty($simple_class_name)) + { + $ok = $this->check($phpcsFile, $simple_class_name, $class_name_full, $class_name_short, $simple_statement) || $ok; + } } } @@ -163,7 +196,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name $paamayim_nekudotayim_class_name = trim($phpcsFile->getTokensAsString($paamayim_nekudotayim_class_name_start + 1, ($paamayim_nekudotayim_class_name_end - $paamayim_nekudotayim_class_name_start))); - $ok = $this->check($phpcsFile, $paamayim_nekudotayim_class_name, $class_name_full, $class_name_short, $paamayim_nekudotayim) || $ok; + if (!empty($paamayim_nekudotayim_class_name)) + { + $ok = $this->check($phpcsFile, $paamayim_nekudotayim_class_name, $class_name_full, $class_name_short, $paamayim_nekudotayim) || $ok; + } } // Checks in implements @@ -177,12 +213,15 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name { $old_implemented_class = $implemented_class; - $implements_class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), ($implemented_class - 1)); + $implements_class_name_start = $phpcsFile->findNext($this->getNameTokens(), ($implemented_class - 1)); $implements_class_name_end = $phpcsFile->findNext(self::FIND, ($implemented_class - 1), null, true); $implements_class_name = trim($phpcsFile->getTokensAsString($implements_class_name_start, ($implements_class_name_end - $implements_class_name_start))); - $ok = $this->check($phpcsFile, $implements_class_name, $class_name_full, $class_name_short, $implements) || $ok; + if (!empty($implements_class_name)) + { + $ok = $this->check($phpcsFile, $implements_class_name, $class_name_full, $class_name_short, $implements) || $ok; + } } } @@ -203,7 +242,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name $params = $phpcsFile->getMethodParameters($function_declaration); foreach ($params as $param) { - $ok = $this->check($phpcsFile, $param['type_hint'], $class_name_full, $class_name_short, $function_declaration) || $ok; + if (!empty($param['type_hint'])) + { + $ok = $this->check($phpcsFile, $param['type_hint'], $class_name_full, $class_name_short, $function_declaration) || $ok; + } } $method_properties = $phpcsFile->getMethodProperties($function_declaration); @@ -216,12 +258,15 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name { $old_catch = $catch; - $caught_class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), $catch + 1); + $caught_class_name_start = $phpcsFile->findNext($this->getNameTokens(), $catch + 1); $caught_class_name_end = $phpcsFile->findNext(self::FIND, $caught_class_name_start + 1, null, true); $caught_class_name = trim($phpcsFile->getTokensAsString($caught_class_name_start, ($caught_class_name_end - $caught_class_name_start))); - $ok = $this->check($phpcsFile, $caught_class_name, $class_name_full, $class_name_short, $catch) || $ok; + if (!empty($caught_class_name)) + { + $ok = $this->check($phpcsFile, $caught_class_name, $class_name_full, $class_name_short, $catch) || $ok; + } } $old_use = $stackPtr; @@ -241,11 +286,14 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name continue; } - $class_name_start = $phpcsFile->findNext(array(T_NS_SEPARATOR, T_STRING), $use + 1, null, false, null, true); + $class_name_start = $phpcsFile->findNext($this->getNameTokens(), $use + 1, null, false, null, true); $class_name_end = $phpcsFile->findNext(self::FIND, $class_name_start + 1, null, true, null, true); $found_name = trim($phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start))); - $ok = $this->check($phpcsFile, $found_name, $class_name_full, $class_name_short, $use) || $ok; + if (!empty($found_name)) + { + $ok = $this->check($phpcsFile, $found_name, $class_name_full, $class_name_short, $use) || $ok; + } } return $ok; @@ -266,7 +314,7 @@ private function findFunctionUsage(File $phpcsFile, $stackPtr, $tokens, $name_fu ); $position = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $position + 1); - if ($found_start === null) + if ($found_start === null || $found_start === false) { continue; } diff --git a/phpBB/composer.json b/phpBB/composer.json index 3762817907c..594d9b9f16c 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -65,7 +65,7 @@ "misantron/dbunit": "~5.0", "phing/phing": "~2.4", "phpunit/phpunit": "^10.0", - "squizlabs/php_codesniffer": "~3.4", + "squizlabs/php_codesniffer": "^4.0", "symfony/browser-kit": "^7.4", "symfony/css-selector": "^7.4", "symfony/dom-crawler": "^7.4", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 600ce1dc6a5..19ca12ccb0f 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a100d158a331a9dc8b112c1c850e38b4", + "content-hash": "82ec75bff268307a263f6ff9851df677", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -10334,26 +10334,26 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.5", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, "bin": [ "bin/phpcbf", @@ -10378,7 +10378,7 @@ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", @@ -10409,7 +10409,7 @@ "type": "thanks_dev" } ], - "time": "2025-11-04T16:30:35+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { "name": "symfony/browser-kit", diff --git a/phpBB/includes/functions_convert.php b/phpBB/includes/functions_convert.php index 9292281c1f3..4abc5aab0cb 100644 --- a/phpBB/includes/functions_convert.php +++ b/phpBB/includes/functions_convert.php @@ -1125,7 +1125,9 @@ function restore_config($schema) $var = (empty($m[2]) || empty($convert_config[$m[2]])) ? "''" : "'" . addslashes($convert_config[$m[2]]) . "'"; $exec = '$config_value = ' . $m[1] . '(' . $var . ');'; // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($exec); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } else diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index d1f35107c50..e6adc4e7601 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -469,7 +469,9 @@ static function module_auth($module_auth, $forum_id) $is_auth = false; // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval('$is_auth = (int) (' . $module_auth . ');'); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd return $is_auth; diff --git a/phpBB/install/convert/convertor.php b/phpBB/install/convert/convertor.php index 1f09943b504..1e69a57b193 100644 --- a/phpBB/install/convert/convertor.php +++ b/phpBB/install/convert/convertor.php @@ -446,7 +446,9 @@ function convert_data($converter) if (!empty($convert->convertor['execute_first'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($convert->convertor['execute_first']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } @@ -518,7 +520,9 @@ function convert_data($converter) if (!empty($schema['execute_first'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($schema['execute_first']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } @@ -585,7 +589,9 @@ function convert_data($converter) if (!empty($schema['execute_always'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($schema['execute_always']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } @@ -1102,7 +1108,9 @@ function jump($converter, $jump, $last_statement) if (!is_array($convert->convertor['execute_last'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($convert->convertor['execute_last']); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } else @@ -1110,7 +1118,9 @@ function jump($converter, $jump, $last_statement) while ($last_statement < count($convert->convertor['execute_last'])) { // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($convert->convertor['execute_last'][$last_statement]); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd $this->template->assign_block_vars('checks', array( @@ -1478,7 +1488,9 @@ function process_row(&$schema, &$sql_data, &$insert_values) $execution = str_replace('{RESULT}', '$value', $execution); $execution = str_replace('{VALUE}', '$value', $execution); // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($execution); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } } @@ -1533,7 +1545,9 @@ function process_row(&$schema, &$sql_data, &$insert_values) $execution = str_replace('{RESULT}', '$value', $execution); $execution = str_replace('{VALUE}', '$value', $execution); // @codingStandardsIgnoreStart + // phpcs:disable Squiz.PHP.Eval eval($execution); + // phpcs:enable Squiz.PHP.Eval // @codingStandardsIgnoreEnd } } From 074a342fb8eebf54d42d6669047046a706e17e15 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 11 Dec 2025 07:50:24 -0800 Subject: [PATCH 1017/1214] [ticket/17591] Fix false positive sniffs on aliased namespaces PHPBB-17591 --- .../phpbb/Sniffs/Namespaces/UnusedUseSniff.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index 3fa18a63fec..fe72b67c897 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -69,7 +69,7 @@ protected function check(File $phpcsFile, $found_name, $full_name, $short_name, $phpcsFile->addError($error, $stack_pointer, 'FullName'); } - /* + /* * Check for possible union types (like string|MyType|null) * and question mark nullable type syntax (like ?MyType) */ @@ -125,7 +125,20 @@ public function process(File $phpcsFile, $stackPtr) { $alias_position = $phpcsFile->findNext(T_STRING, $aliasing_as_position, null, false, null, true); $name_short = $tokens[$alias_position]['content']; - $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start - 1)); + + // Only recalculate name_full if it wasn't a single token (T_NAME_QUALIFIED) + // to avoid calculating it as 0 length string. Prevents incorrectly calculating the + // "full name" of the aliased class (resolving it to an empty string in some cases). + if (!isset($tokens[$class_name_start]) || + ( + (!defined('T_NAME_QUALIFIED') || $tokens[$class_name_start]['code'] !== T_NAME_QUALIFIED) && + (!defined('T_NAME_FULLY_QUALIFIED') || $tokens[$class_name_start]['code'] !== T_NAME_FULLY_QUALIFIED) && + (!defined('T_NAME_RELATIVE') || $tokens[$class_name_start]['code'] !== T_NAME_RELATIVE) + ) + ) + { + $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start - 1)); + } } if ($tokens[$class_name_start]['content'] === 'function' From bc680bc06a5668cca06fe889f92306784f76f4ac Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 11 Dec 2025 08:02:15 -0800 Subject: [PATCH 1018/1214] [ticket/17591] Empty array offset fix PHPBB-17591 --- .../code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index fe72b67c897..42a0fc5288b 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -262,7 +262,10 @@ private function findClassUsage(File $phpcsFile, $stackPtr, $tokens, $class_name } $method_properties = $phpcsFile->getMethodProperties($function_declaration); - $ok = $this->check($phpcsFile, $method_properties['return_type'], $class_name_full, $class_name_short, $function_declaration) || $ok; + if (!empty($method_properties['return_type'])) + { + $ok = $this->check($phpcsFile, $method_properties['return_type'], $class_name_full, $class_name_short, $function_declaration) || $ok; + } } // Checks in catch blocks From 3490dfdb0538670dc276f832af1b50ae6f09fbe2 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Thu, 11 Dec 2025 07:54:01 -0800 Subject: [PATCH 1019/1214] [ticket/17591] Fix false positive sniffs on aliased namespaces PHPBB-17591 --- .../phpbb/Sniffs/Namespaces/UnusedUseSniff.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index 7c5a52e8f96..571d5058ae8 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -114,7 +114,20 @@ public function process(File $phpcsFile, $stackPtr) { $alias_position = $phpcsFile->findNext(T_STRING, $aliasing_as_position, null, false, null, true); $name_short = $tokens[$alias_position]['content']; - $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start - 1)); + + // Only recalculate name_full if it wasn't a single token (T_NAME_QUALIFIED) + // to avoid calculating it as 0 length string. Prevents incorrectly calculating the + // "full name" of the aliased class (resolving it to an empty string in some cases). + if (!isset($tokens[$class_name_start]) || + ( + (!defined('T_NAME_QUALIFIED') || $tokens[$class_name_start]['code'] !== T_NAME_QUALIFIED) && + (!defined('T_NAME_FULLY_QUALIFIED') || $tokens[$class_name_start]['code'] !== T_NAME_FULLY_QUALIFIED) && + (!defined('T_NAME_RELATIVE') || $tokens[$class_name_start]['code'] !== T_NAME_RELATIVE) + ) + ) + { + $name_full = $phpcsFile->getTokensAsString($class_name_start, ($class_name_end - $class_name_start - 1)); + } } if ($tokens[$class_name_start]['content'] === 'function' From e875d0975ae3245bcf3d8f8e5116fdcbc028d9f5 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Fri, 12 Dec 2025 10:18:14 -0800 Subject: [PATCH 1020/1214] [ticket/17593] Allow functional tests on local servers with SSL PHPBB-17593 --- tests/RUNNING_TESTS.md | 9 +++++++++ tests/test_framework/phpbb_functional_test_case.php | 7 +++++++ tests/test_framework/phpbb_test_case_helpers.php | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/tests/RUNNING_TESTS.md b/tests/RUNNING_TESTS.md index 81d7516c0bd..0470a09a148 100644 --- a/tests/RUNNING_TESTS.md +++ b/tests/RUNNING_TESTS.md @@ -175,6 +175,15 @@ on which to run tests. $phpbb_functional_url = 'http://localhost/phpBB3/'; +If your local server uses an SSL certificate with HTTPS, you may need to specify the +path to your security certificate for functional tests to run successfully. Set +`$path_to_ssl_cert` to the location of your certificate file (.pem or .crt). +If you can’t locate your certificate, or it still isn’t working, you can disable +SSL verification by setting this value to `false` (though this is less secure). +Note: You must keep `$phpbb_functional_url` set to `http`, since this option bypasses https. + + $path_to_ssl_cert = '/path/to/your/certificate.pem'; + Functional tests are automatically run, if '$phpbb_functional_url' is configured. If you only want the functional tests, run: diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index dd3b14e7152..957e035e9b1 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -89,6 +89,13 @@ protected function setUp(): void self::$cookieJar = new CookieJar; self::$client = new Goutte\Client(array(), null, self::$cookieJar); + // Disable SSL verification for local development with self-signed certificates + if (isset(self::$config['path_to_ssl_cert'])) + { + $guzzle_client = new \GuzzleHttp\Client(['verify' => self::$config['path_to_ssl_cert']]); + self::$client->setClient($guzzle_client); + } + // Clear the language array so that things // that were added in other tests are gone $this->lang = array(); diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index d22b1cb8fad..eb667e0336d 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -167,6 +167,11 @@ static public function get_test_config() $config['phpbb_functional_url'] = $phpbb_functional_url; } + if (isset($path_to_ssl_cert)) + { + $config['path_to_ssl_cert'] = $path_to_ssl_cert; + } + if (isset($phpbb_redis_host)) { $config['redis_host'] = $phpbb_redis_host; From 10aecb92644aedc83cecb1dcd962b83c02765b68 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 10 Oct 2025 22:02:45 +0200 Subject: [PATCH 1021/1214] [ticket/17566] Add default avatars based on username using css & html PHPBB-17566 --- phpBB/includes/ucp/ucp_pm_viewmessage.php | 1 + phpBB/memberlist.php | 1 + .../prosilver/template/memberlist_view.html | 23 ++++++++++++++----- .../template/ucp_pm_viewmessage.html | 10 +++++++- .../prosilver/template/viewtopic_body.html | 12 +++++++--- phpBB/styles/prosilver/theme/colours.css | 11 +++++++++ phpBB/styles/prosilver/theme/content.css | 17 +++++++++++++- phpBB/styles/prosilver/theme/responsive.css | 6 +++++ 8 files changed, 70 insertions(+), 11 deletions(-) diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index d418e498871..3aa4dc6ca75 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -214,6 +214,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) 'MESSAGE_AUTHOR_FULL' => get_username_string('full', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), 'MESSAGE_AUTHOR_COLOUR' => get_username_string('colour', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), 'MESSAGE_AUTHOR' => get_username_string('username', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), + 'MESSAGE_AUTHOR_ID' => $author_id, 'U_MESSAGE_AUTHOR' => get_username_string('profile', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), 'RANK_TITLE' => $user_info['rank_title'], diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 1170411f98e..4cf4cac950c 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -428,6 +428,7 @@ 'U_REMOVE_FOE' => ($foe && $foes_enabled) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=zebra&remove=1&mode=foes&usernames[]=' . $user_id) : '', 'U_CANONICAL' => generate_board_url() . '/' . append_sid("memberlist.$phpEx", 'mode=viewprofile&u=' . $user_id, true, ''), + 'USER_ID' => $user_id, ); /** diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index 2a6d13847af..958face98b5 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -8,14 +8,25 @@

      {PAGE_TITLE}

      - + {% if AVATAR_HTML %}
      -
      {AVATAR_HTML}
      - -
      {RANK_TITLE}
      - +
      {{ AVATAR_HTML }}
      + {% EVENT memberlist_view_rank_avatar_before %} + {% if RANK_TITLE %}
      {{ RANK_TITLE }}
      {% endif %} + {% EVENT memberlist_view_rank_avatar_after %}
      - + {% else %} + {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} + {% set color_class = color_classes[USER_ID % 5] %} +
      +
      +
      {{ USERNAME|striptags|slice(0, 2)|upper }}
      +
      + {% EVENT memberlist_view_rank_avatar_before %} + {% if RANK_TITLE %}
      {{ RANK_TITLE }}
      {% endif %} + {% EVENT memberlist_view_rank_avatar_after %} +
      + {% endif %}
      diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html index 9300eb285a2..8dc596bfc42 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html @@ -29,7 +29,15 @@
      - {AUTHOR_AVATAR_HTML} + {% if AUTHOR_AVATAR_HTML %} + {{ AUTHOR_AVATAR_HTML }} + {% else %} + {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} + {% set color_class = color_classes[MESSAGE_AUTHOR_ID % 5] %} + +
      {{ MESSAGE_AUTHOR|striptags|slice(0, 2)|upper }}
      +
      + {% endif %}
      {% apply spaceless %} diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 447e32524d6..fbfb9ecb829 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -143,9 +143,15 @@

      {POLL_
      - - {postrow.POSTER_AVATAR_HTML}{postrow.POSTER_AVATAR_HTML} - + {% if postrow.POSTER_AVATAR_HTML %} + {% if postrow.U_POST_AUTHOR %}{{ postrow.POSTER_AVATAR_HTML|raw }}{% else %}{{ postrow.POSTER_AVATAR_HTML|raw }}{% endif %} + {% else %} + {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} + {% set color_class = color_classes[postrow.POSTER_ID % 5] %} +
      +
      {{ postrow.POST_AUTHOR|striptags|slice(0, 1)|upper }}
      +
      + {% endif %}
      diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index d45583f3c27..38007b303ce 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -991,6 +991,17 @@ dl.mini dt { background-color: #ededed; } +/* avatar placeholder colours */ +.avatar-color-0 { background-color: #d32f2f; } +.avatar-color-1 { background-color: #c2185b; } +.avatar-color-2 { background-color: #7b1fa2; } +.avatar-color-3 { background-color: #512da8; } +.avatar-color-4 { background-color: #303f9f; } + +.avatar-initials { + color: #ffffff; +} + /* colours and backgrounds for forms.css */ /* general form styles */ diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index 07a4ddb325d..33a0d9b4c8b 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -805,7 +805,7 @@ fieldset.polls dd div { } /* Post-profile avatars */ -.postprofile .has-avatar .avatar-container { +.postprofile .avatar-container { overflow: hidden; margin-bottom: 3px; } @@ -822,6 +822,21 @@ fieldset.polls dd div { height: auto !important; } +.avatar-placeholder { + font-size: 36px; + font-weight: bold; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + width: 80px; + height: 80px; +} + +.avatar-initials { + line-height: 1; +} + .postprofile .profile-posts a { font-weight: normal; } diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index 0416598eaf8..a5f2ff207a7 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -574,6 +574,12 @@ max-height: 32px; } + .postprofile .avatar-placeholder { + font-size: 1rem; + width: 32px; + height: 32px; + } + .has-profile .postbody h3 { margin-right: 0 !important; margin-left: 0 !important; From d30f42bf529e6e63cae27287f1560272974cc549 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 10 Oct 2025 22:06:34 +0200 Subject: [PATCH 1022/1214] [ticket/17566] Unify number of characters for other pages as well PHPBB-17566 --- phpBB/styles/prosilver/template/memberlist_view.html | 2 +- phpBB/styles/prosilver/template/ucp_pm_viewmessage.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index 958face98b5..a3fec16c0ea 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -20,7 +20,7 @@

      {PAGE_TITLE}

      {% set color_class = color_classes[USER_ID % 5] %}
      -
      {{ USERNAME|striptags|slice(0, 2)|upper }}
      +
      {{ USERNAME|striptags|slice(0, 1)|upper }}
      {% EVENT memberlist_view_rank_avatar_before %} {% if RANK_TITLE %}
      {{ RANK_TITLE }}
      {% endif %} diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html index 8dc596bfc42..f7a86280661 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html @@ -35,7 +35,7 @@ {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} {% set color_class = color_classes[MESSAGE_AUTHOR_ID % 5] %} -
      {{ MESSAGE_AUTHOR|striptags|slice(0, 2)|upper }}
      +
      {{ MESSAGE_AUTHOR|striptags|slice(0, 1)|upper }}
      {% endif %} From 89869e043d2ad7621e5f854e127e02f37647d1a1 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Dec 2025 20:36:37 +0100 Subject: [PATCH 1023/1214] [ticket/17566] Adjust avatar handling via twig PHPBB-17566 --- phpBB/phpbb/avatar/manager.php | 5 ++ .../phpbb/template/twig/extension/avatar.php | 5 +- .../prosilver/template/viewtopic_body.html | 4 +- phpBB/viewtopic.php | 8 +--- tests/avatar/manager_test.php | 14 +++--- tests/template/extension_test.php | 48 +++++++++---------- tests/template/templates/avatar_group.html | 2 +- tests/template/templates/avatar_user.html | 2 +- 8 files changed, 43 insertions(+), 45 deletions(-) diff --git a/phpBB/phpbb/avatar/manager.php b/phpBB/phpbb/avatar/manager.php index f4fc16ab52c..ce47402def3 100644 --- a/phpBB/phpbb/avatar/manager.php +++ b/phpBB/phpbb/avatar/manager.php @@ -205,6 +205,11 @@ public static function clean_row($row, $prefix = '') $output = []; foreach ($row as $key => $value) { + if (!str_starts_with($key, $prefix ? "{$prefix}_avatar": 'avatar') && $key !== 'user_id' && $key !== 'group_id') + { + continue; + } + $key = preg_replace("#^(?:{$prefix}_)#", '', (string) $key); $output[$key] = $value; } diff --git a/phpBB/phpbb/template/twig/extension/avatar.php b/phpBB/phpbb/template/twig/extension/avatar.php index e10330301ef..2ae085194c8 100644 --- a/phpBB/phpbb/template/twig/extension/avatar.php +++ b/phpBB/phpbb/template/twig/extension/avatar.php @@ -63,19 +63,18 @@ public function getFunctions(): array * Get avatar for placing into templates. * * How to use in a template: - * - {{ avatar('mode', row, alt, ignore_config, lazy) }} + * - {{ avatar(row, alt, ignore_config, lazy) }} * * The mode and row (group_row or user_row) are required. * The other fields (alt|ignore_config|lazy) are optional. * * @return string The avatar HTML for the specified mode */ - public function get_avatar(environment $environment, string $mode, array $row, string|null $alt, bool|null $ignore_config, bool|null $lazy): string + public function get_avatar(environment $environment, array $row, string|null $alt = null, bool|null $ignore_config = null, bool|null $lazy = null): string { $alt = $alt ?? false; $ignore_config = $ignore_config ?? false; $lazy = $lazy ?? false; - $row = manager::clean_row($row, $mode); $avatar = $this->avatar_helper->get_avatar($row, $alt, $ignore_config, $lazy); try diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index fbfb9ecb829..949b1fad089 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -143,8 +143,8 @@

      {POLL_
      - {% if postrow.POSTER_AVATAR_HTML %} - {% if postrow.U_POST_AUTHOR %}{{ postrow.POSTER_AVATAR_HTML|raw }}{% else %}{{ postrow.POSTER_AVATAR_HTML|raw }}{% endif %} + {% if postrow.POSTER_AVATAR %} + {% if postrow.U_POST_AUTHOR %}{{ avatar(postrow.POSTER_AVATAR, lang('USER_AVATAR')) }}{% else %}{{ avatar(postrow.POSTER_AVATAR, lang('USER_AVATAR')) }}{% endif %} {% else %} {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} {% set color_class = color_classes[postrow.POSTER_ID % 5] %} diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 31812042922..4e980e224d0 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1479,7 +1479,7 @@ 'viewonline' => $row['user_allow_viewonline'], 'allow_pm' => $row['user_allow_pm'], - 'avatar' => ($user->optionget('viewavatars')) ? $avatar_helper->get_user_avatar($row) : [], + 'avatar' => ($user->optionget('viewavatars')) ? \phpbb\avatar\manager::clean_row($row, 'user') : [], 'age' => '', 'rank_title' => '', @@ -2032,6 +2032,7 @@ 'RANK_TITLE' => $user_cache[$poster_id]['rank_title'], 'RANK_IMG' => $user_cache[$poster_id]['rank_image'], 'RANK_IMG_SRC' => $user_cache[$poster_id]['rank_image_src'], + 'POSTER_AVATAR' => $user_cache[$poster_id]['avatar'], 'POSTER_JOINED' => $user_cache[$poster_id]['joined'], 'POSTER_POSTS' => $user_cache[$poster_id]['posts'], 'POSTER_WARNINGS' => $auth->acl_get('m_warn') ? $user_cache[$poster_id]['warnings'] : '', @@ -2105,11 +2106,6 @@ 'S_DELETE_PERMANENT' => $permanent_delete_allowed, ); - if ($user_cache[$poster_id]['avatar']) - { - $post_row += $avatar_helper->get_template_vars($user_cache[$poster_id]['avatar'], 'POSTER_'); - } - $user_poster_data = $user_cache[$poster_id]; $current_row_number = $i; diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php index caf2dc82e55..66cba2e8900 100644 --- a/tests/avatar/manager_test.php +++ b/tests/avatar/manager_test.php @@ -220,13 +220,7 @@ public static function database_row_data() 'user_avatar_height' => '', 'group_avatar' => '', ), - array( - 'user_avatar' => '', - 'user_avatar_type' => '', - 'user_avatar_width' => '', - 'user_avatar_height' => '', - 'group_avatar' => '', - ), + array(), 'foobar', ), array( @@ -245,7 +239,6 @@ public static function database_row_data() 'group_id' => 4, ), array( - 'user_avatar' => '', 'user_id' => 5, 'group_id' => 4, ), @@ -290,6 +283,11 @@ public function test_clean_row(array $input, array $output, $prefix = '') $this->assertArrayHasKey($key, $cleaned_row); $this->assertEquals($cleaned_row[$key], $value); } + + if (empty($output)) + { + $this->assertEquals($output, $cleaned_row); + } } public function test_clean_driver_name() diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index c40a58da53f..d1c03e9d8b7 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -148,10 +148,10 @@ public static function data_template_extensions() 'avatar_user.html', [ 'row' => [ - 'user_avatar' => 'great_avatar.png', - 'user_avatar_type' => 'avatar.driver.upload', - 'user_avatar_width' => 90, - 'user_avatar_height' => 90, + 'avatar' => 'great_avatar.png', + 'avatar_type' => 'avatar.driver.upload', + 'avatar_width' => 90, + 'avatar_height' => 90, ], 'alt' => 'foo' ], @@ -164,10 +164,10 @@ public static function data_template_extensions() 'avatar_user.html', [ 'row' => [ - 'user_avatar' => 'great_avatar.png', - 'user_avatar_type' => 'avatar.driver.upload', - 'user_avatar_width' => 90, - 'user_avatar_height' => 90, + 'avatar' => 'great_avatar.png', + 'avatar_type' => 'avatar.driver.upload', + 'avatar_width' => 90, + 'avatar_height' => 90, ], 'alt' => 'foo', 'ignore_config' => true, @@ -182,10 +182,10 @@ public static function data_template_extensions() 'avatar_user.html', [ 'row' => [ - 'user_avatar' => 'foo@bar.com', - 'user_avatar_type' => 'avatar.driver.gravatar', - 'user_avatar_width' => 90, - 'user_avatar_height' => 90, + 'avatar' => 'foo@bar.com', + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar_width' => 90, + 'avatar_height' => 90, ], 'alt' => 'foo' ], @@ -198,10 +198,10 @@ public static function data_template_extensions() 'avatar_group.html', [ 'row' => [ - 'group_avatar' => 'great_avatar.png', - 'group_avatar_type' => 'avatar.driver.upload', - 'group_avatar_width' => 90, - 'group_avatar_height' => 90, + 'avatar' => 'great_avatar.png', + 'avatar_type' => 'avatar.driver.upload', + 'avatar_width' => 90, + 'avatar_height' => 90, ], 'alt' => 'foo' ], @@ -214,10 +214,10 @@ public static function data_template_extensions() 'avatar_group.html', [ 'row' => [ - 'group_avatar' => 'great_avatar.png', - 'group_avatar_type' => 'avatar.driver.upload', - 'group_avatar_width' => 90, - 'group_avatar_height' => 90, + 'avatar' => 'great_avatar.png', + 'avatar_type' => 'avatar.driver.upload', + 'avatar_width' => 90, + 'avatar_height' => 90, ], 'alt' => 'foo', 'ignore_config' => true, @@ -232,10 +232,10 @@ public static function data_template_extensions() 'avatar_group.html', [ 'row' => [ - 'group_avatar' => 'foo@bar.com', - 'group_avatar_type' => 'avatar.driver.gravatar', - 'group_avatar_width' => 90, - 'group_avatar_height' => 90, + 'avatar' => 'foo@bar.com', + 'avatar_type' => 'avatar.driver.gravatar', + 'avatar_width' => 90, + 'avatar_height' => 90, ], 'alt' => 'foo' ], diff --git a/tests/template/templates/avatar_group.html b/tests/template/templates/avatar_group.html index 95ae9c08c0f..2532b86e8cb 100644 --- a/tests/template/templates/avatar_group.html +++ b/tests/template/templates/avatar_group.html @@ -1 +1 @@ -{{ avatar('group', row, alt, ignore_config, lazy) }} +{{ avatar(row, alt, ignore_config, lazy) }} diff --git a/tests/template/templates/avatar_user.html b/tests/template/templates/avatar_user.html index bd0b9606118..2532b86e8cb 100644 --- a/tests/template/templates/avatar_user.html +++ b/tests/template/templates/avatar_user.html @@ -1 +1 @@ -{{ avatar('user', row, alt, ignore_config, lazy) }} +{{ avatar(row, alt, ignore_config, lazy) }} From 6856c09f07704382dfdce36e5f444a9e0940c826 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sun, 14 Dec 2025 21:02:50 +0100 Subject: [PATCH 1024/1214] [ticket/17566] Ensure filename is output in pre-commit hook PHPBB-17566 --- git-tools/hooks/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-tools/hooks/pre-commit b/git-tools/hooks/pre-commit index 06ba15c7fa3..d7071f2c474 100755 --- a/git-tools/hooks/pre-commit +++ b/git-tools/hooks/pre-commit @@ -75,7 +75,7 @@ do if [ $? -ne 0 ] then # Swap back in correct filenames - errors=$(echo "$errors"; echo "$result" | grep ':' | sed -e "s@in - on@in $filename on@g") + errors=$(echo "$errors"; echo "$result" | grep ':' | sed -E "s@in (.+) on line@in $filename on@g") fi done unset IFS From 41c5b89ee9f7828b858ae341f3a0283e083d3b8e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 20 Dec 2025 08:23:25 +0100 Subject: [PATCH 1025/1214] [ticket/17566] Remove unused use statement PHPBB-17566 --- phpBB/phpbb/template/twig/extension/avatar.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpBB/phpbb/template/twig/extension/avatar.php b/phpBB/phpbb/template/twig/extension/avatar.php index 2ae085194c8..1675413ce70 100644 --- a/phpBB/phpbb/template/twig/extension/avatar.php +++ b/phpBB/phpbb/template/twig/extension/avatar.php @@ -14,7 +14,6 @@ namespace phpbb\template\twig\extension; use phpbb\avatar\helper; -use phpbb\avatar\manager; use phpbb\template\twig\environment; use Twig\Error\Error; use Twig\Extension\AbstractExtension; From 65105cc471a012724e97502ff859ec95d25f847a Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Sat, 20 Dec 2025 08:45:51 +0100 Subject: [PATCH 1026/1214] [ticket/17580] Replace while and remainder logic with simple loop PHPBB-17580 --- phpBB/includes/functions_download.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php index eb607f020bc..5b0ac2bea4c 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -267,16 +267,13 @@ function send_file_to_browser($attachment, $upload_dir, $category) header('Content-Range: bytes ' . $range['byte_pos_start'] . '-' . $range['byte_pos_end'] . '/' . $range['bytes_total']); header('Content-Length: ' . $range['bytes_requested']); - // First read chunks - while (!feof($fp) && ftell($fp) < $range['byte_pos_end'] - 8192) + // Read until we reach the end of the requested range + $bytes_to_read = $range['bytes_requested']; + while ($bytes_to_read > 0 && !feof($fp)) { - echo fread($fp, 8192); - } - // Then, read the remainder - $remainder = $range['bytes_requested'] % 8192; - if ($remainder > 0) - { - echo fread($fp, $remainder); + $chunk = ($bytes_to_read > 8192) ? 8192 : $bytes_to_read; + echo fread($fp, $chunk); + $bytes_to_read -= $chunk; } } else From 503fffc5726166a0a55383889ccacf04f7fe09a7 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Dec 2025 11:02:26 +0100 Subject: [PATCH 1027/1214] [ticket/17566] Remove underline when hovering over placeholder PHPBB-17566 --- phpBB/styles/prosilver/theme/links.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpBB/styles/prosilver/theme/links.css b/phpBB/styles/prosilver/theme/links.css index 2f7ac86236e..11cffbf73b2 100644 --- a/phpBB/styles/prosilver/theme/links.css +++ b/phpBB/styles/prosilver/theme/links.css @@ -140,6 +140,10 @@ a.lastsubject:hover { text-decoration: underline; } +.postprofile a.avatar:hover { + text-decoration: none; +} + /* Profile searchresults */ .search .postprofile a { font-weight: normal; From 9903f1da547d9c73f601e4e7e0c7529df52e4f59 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Dec 2025 11:03:18 +0100 Subject: [PATCH 1028/1214] [ticket/17566] Simplify avatar twig function and remove html PHPBB-17566 --- phpBB/phpbb/avatar/helper.php | 3 +-- .../phpbb/template/twig/extension/avatar.php | 19 +++++++------------ phpBB/viewtopic.php | 8 ++++++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/phpBB/phpbb/avatar/helper.php b/phpBB/phpbb/avatar/helper.php index 135bf22c88a..7187e0b6ca9 100644 --- a/phpBB/phpbb/avatar/helper.php +++ b/phpBB/phpbb/avatar/helper.php @@ -81,7 +81,7 @@ public function __construct( */ public function get_template_vars(array $avatar, string $prefix = ''): array { - $prefix = $prefix && substr($prefix, -1) !== '_' ? "{$prefix}_" : $prefix; + $prefix = $prefix && !str_ends_with($prefix, '_') ? "{$prefix}_" : $prefix; return [ "{$prefix}AVATAR" => $avatar, @@ -94,7 +94,6 @@ public function get_template_vars(array $avatar, string $prefix = ''): array "{$prefix}AVATAR_HEIGHT" => $avatar['height'], "{$prefix}AVATAR_LAZY" => $avatar['lazy'], - "{$prefix}AVATAR_HTML" => $avatar['html'], ]; } diff --git a/phpBB/phpbb/template/twig/extension/avatar.php b/phpBB/phpbb/template/twig/extension/avatar.php index 1675413ce70..5a1678cdc88 100644 --- a/phpBB/phpbb/template/twig/extension/avatar.php +++ b/phpBB/phpbb/template/twig/extension/avatar.php @@ -69,22 +69,17 @@ public function getFunctions(): array * * @return string The avatar HTML for the specified mode */ - public function get_avatar(environment $environment, array $row, string|null $alt = null, bool|null $ignore_config = null, bool|null $lazy = null): string + public function get_avatar(environment $environment, array $row): string { - $alt = $alt ?? false; - $ignore_config = $ignore_config ?? false; - $lazy = $lazy ?? false; - $avatar = $this->avatar_helper->get_avatar($row, $alt, $ignore_config, $lazy); - try { return $environment->render('macros/avatar.twig', [ - 'SRC' => $avatar['lazy'] ? $this->avatar_helper->get_no_avatar_source() : $avatar['src'], - 'DATA_SRC' => $avatar['lazy'] ? $avatar['src'] : '', - 'WIDTH' => $avatar['width'], - 'HEIGHT' => $avatar['height'], - 'TITLE' => $avatar['title'], - 'LAZY' => $avatar['lazy'], + 'SRC' => $row['lazy'] ? $this->avatar_helper->get_no_avatar_source() : $row['src'], + 'DATA_SRC' => $row['lazy'] ? $row['src'] : '', + 'WIDTH' => $row['width'], + 'HEIGHT' => $row['height'], + 'TITLE' => $row['title'], + 'LAZY' => $row['lazy'], ]); } catch (Error $e) diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 4e980e224d0..31812042922 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1479,7 +1479,7 @@ 'viewonline' => $row['user_allow_viewonline'], 'allow_pm' => $row['user_allow_pm'], - 'avatar' => ($user->optionget('viewavatars')) ? \phpbb\avatar\manager::clean_row($row, 'user') : [], + 'avatar' => ($user->optionget('viewavatars')) ? $avatar_helper->get_user_avatar($row) : [], 'age' => '', 'rank_title' => '', @@ -2032,7 +2032,6 @@ 'RANK_TITLE' => $user_cache[$poster_id]['rank_title'], 'RANK_IMG' => $user_cache[$poster_id]['rank_image'], 'RANK_IMG_SRC' => $user_cache[$poster_id]['rank_image_src'], - 'POSTER_AVATAR' => $user_cache[$poster_id]['avatar'], 'POSTER_JOINED' => $user_cache[$poster_id]['joined'], 'POSTER_POSTS' => $user_cache[$poster_id]['posts'], 'POSTER_WARNINGS' => $auth->acl_get('m_warn') ? $user_cache[$poster_id]['warnings'] : '', @@ -2106,6 +2105,11 @@ 'S_DELETE_PERMANENT' => $permanent_delete_allowed, ); + if ($user_cache[$poster_id]['avatar']) + { + $post_row += $avatar_helper->get_template_vars($user_cache[$poster_id]['avatar'], 'POSTER_'); + } + $user_poster_data = $user_cache[$poster_id]; $current_row_number = $i; From b7d6734c5358f79be0d1eb5b8519a4824b7445f9 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Dec 2025 11:06:44 +0100 Subject: [PATCH 1029/1214] [ticket/17566] Add user avatar template PHPBB-17566 --- phpBB/includes/mcp/mcp_notes.php | 3 ++- .../styles/prosilver/template/mcp_notes_user.html | 5 +++++ phpBB/styles/prosilver/template/user_avatar.html | 15 +++++++++++++++ .../styles/prosilver/template/viewtopic_body.html | 15 ++++++--------- 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 phpBB/styles/prosilver/template/user_avatar.html diff --git a/phpBB/includes/mcp/mcp_notes.php b/phpBB/includes/mcp/mcp_notes.php index 72e03c00f23..658ac007284 100644 --- a/phpBB/includes/mcp/mcp_notes.php +++ b/phpBB/includes/mcp/mcp_notes.php @@ -234,7 +234,7 @@ function mcp_notes_user_view($action) $avatar_helper = $phpbb_container->get('avatar.helper'); $avatar = $avatar_helper->get_user_avatar($userrow); - $template->assign_vars($avatar_helper->get_template_vars($avatar)); + $template->assign_vars($avatar_helper->get_template_vars($avatar, 'USER_')); $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, @@ -255,6 +255,7 @@ function mcp_notes_user_view($action) 'USERNAME_FULL' => get_username_string('full', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), 'USERNAME_COLOUR' => get_username_string('colour', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), 'USERNAME' => get_username_string('username', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), + 'USER_ID' => $userrow['user_id'], 'U_PROFILE' => get_username_string('profile', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), 'RANK_IMG' => $rank_data['img'], diff --git a/phpBB/styles/prosilver/template/mcp_notes_user.html b/phpBB/styles/prosilver/template/mcp_notes_user.html index 98f56cbb600..d061539fe21 100644 --- a/phpBB/styles/prosilver/template/mcp_notes_user.html +++ b/phpBB/styles/prosilver/template/mcp_notes_user.html @@ -11,6 +11,11 @@

      {USERNAME_FULL}

      + {% include 'user_avatar.html' with { + 'avatar_data': USER_AVATAR, + 'user_id': USER_ID, + 'username': USERNAME + } only %}
      {AVATAR_HTML}
      diff --git a/phpBB/styles/prosilver/template/user_avatar.html b/phpBB/styles/prosilver/template/user_avatar.html new file mode 100644 index 00000000000..5ca6d86f18e --- /dev/null +++ b/phpBB/styles/prosilver/template/user_avatar.html @@ -0,0 +1,15 @@ +{% if avatar_data.src %} + {% if user_link %} + {{ avatar(avatar_data, lang('USER_AVATAR')) }} + {% else %} + {{ avatar(avatar_data, lang('USER_AVATAR')) }} + {% endif %} +{% else %} + {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} + {% set color_class = color_classes[user_id % 5] %} + {% if user_link %}{% endif %} +
      +
      {{ username|striptags|slice(0, 1)|upper }}
      +
      + {% if user_link %}
      {% endif %} +{% endif %} diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 949b1fad089..9af52337b5a 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -143,15 +143,12 @@

      {POLL_
      - {% if postrow.POSTER_AVATAR %} - {% if postrow.U_POST_AUTHOR %}{{ avatar(postrow.POSTER_AVATAR, lang('USER_AVATAR')) }}{% else %}{{ avatar(postrow.POSTER_AVATAR, lang('USER_AVATAR')) }}{% endif %} - {% else %} - {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} - {% set color_class = color_classes[postrow.POSTER_ID % 5] %} -
      -
      {{ postrow.POST_AUTHOR|striptags|slice(0, 1)|upper }}
      -
      - {% endif %} + {% include 'user_avatar.html' with { + 'avatar_data': postrow.POSTER_AVATAR, + 'user_id': postrow.POSTER_ID, + 'username': postrow.POST_AUTHOR, + 'user_link': postrow.U_POST_AUTHOR + } only %}
      From 36e8fef5bb208692bf6d6c86c69f9fe2432ff526 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Dec 2025 13:01:22 +0100 Subject: [PATCH 1030/1214] [ticket/17566] Adapt navbar avatar to new template PHPBB-17566 --- phpBB/styles/prosilver/template/navbar_header.html | 4 ++-- phpBB/styles/prosilver/theme/common.css | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/phpBB/styles/prosilver/template/navbar_header.html b/phpBB/styles/prosilver/template/navbar_header.html index 7d6ee666eca..baab6954598 100644 --- a/phpBB/styles/prosilver/template/navbar_header.html +++ b/phpBB/styles/prosilver/template/navbar_header.html @@ -113,13 +113,13 @@ {% EVENT navbar_header_user_profile_prepend %} -
    • +
    • {% EVENT navbar_header_username_prepend %}

diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html index f7a86280661..53c3c811f12 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage.html @@ -29,15 +29,7 @@
- {% if AUTHOR_AVATAR_HTML %} - {{ AUTHOR_AVATAR_HTML }} - {% else %} - {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} - {% set color_class = color_classes[MESSAGE_AUTHOR_ID % 5] %} - -
{{ MESSAGE_AUTHOR|striptags|slice(0, 1)|upper }}
-
- {% endif %} + {% include 'user_avatar.html' with {'avatar_data': AUTHOR_AVATAR, 'username': MESSAGE_AUTHOR, 'avatar_link': U_MESSAGE_AUTHOR} only %}
{% apply spaceless %} diff --git a/phpBB/styles/prosilver/template/user_avatar.html b/phpBB/styles/prosilver/template/user_avatar.html index 5ca6d86f18e..baccab5513d 100644 --- a/phpBB/styles/prosilver/template/user_avatar.html +++ b/phpBB/styles/prosilver/template/user_avatar.html @@ -1,15 +1,15 @@ {% if avatar_data.src %} - {% if user_link %} - {{ avatar(avatar_data, lang('USER_AVATAR')) }} + {% if avatar_link %} + {{ avatar(avatar_data, lang('USER_AVATAR')) }} {% else %} {{ avatar(avatar_data, lang('USER_AVATAR')) }} {% endif %} -{% else %} +{% elseif avatar_data.id %} {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} - {% set color_class = color_classes[user_id % 5] %} - {% if user_link %}{% endif %} + {% set color_class = color_classes[avatar_data.id % 5] %} + {% if avatar_link %}{% endif %}
{{ username|striptags|slice(0, 1)|upper }}
- {% if user_link %}
{% endif %} + {% if avatar_link %}{% endif %} {% endif %} diff --git a/phpBB/styles/prosilver/template/viewtopic_body.html b/phpBB/styles/prosilver/template/viewtopic_body.html index 9af52337b5a..f6bdea3563c 100644 --- a/phpBB/styles/prosilver/template/viewtopic_body.html +++ b/phpBB/styles/prosilver/template/viewtopic_body.html @@ -145,9 +145,8 @@

{POLL_ {% include 'user_avatar.html' with { 'avatar_data': postrow.POSTER_AVATAR, - 'user_id': postrow.POSTER_ID, 'username': postrow.POST_AUTHOR, - 'user_link': postrow.U_POST_AUTHOR + 'avatar_link': postrow.U_POST_AUTHOR } only %} diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 8bc2ef5421f..2d5dd2601af 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -395,7 +395,7 @@ a.header-avatar .avatar-placeholder { } a.header-avatar .avatar-placeholder { - font-size: var(--ps-font-small); + font-size: var(--ps-font-tiny); display: inline-flex; width: 20px; } From 02271c2a990198ce3506e196886c0570d4fa2f9f Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 22 Dec 2025 16:52:54 +0100 Subject: [PATCH 1032/1214] [ticket/17566] Use avatar template for all avatars in prosilver PHPBB-17566 --- .../styles/prosilver/template/{user_avatar.html => avatar.html} | 2 +- phpBB/styles/prosilver/template/mcp_notes_user.html | 2 +- phpBB/styles/prosilver/template/memberlist_body.html | 1 + phpBB/styles/prosilver/template/memberlist_view.html | 2 +- phpBB/styles/prosilver/template/navbar_header.html | 2 +- phpBB/styles/prosilver/template/ucp_avatar_options.html | 2 +- phpBB/styles/prosilver/template/ucp_pm_viewmessage.html | 2 +- phpBB/styles/prosilver/template/viewtopic_body.html | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) rename phpBB/styles/prosilver/template/{user_avatar.html => avatar.html} (89%) diff --git a/phpBB/styles/prosilver/template/user_avatar.html b/phpBB/styles/prosilver/template/avatar.html similarity index 89% rename from phpBB/styles/prosilver/template/user_avatar.html rename to phpBB/styles/prosilver/template/avatar.html index baccab5513d..451b9102b49 100644 --- a/phpBB/styles/prosilver/template/user_avatar.html +++ b/phpBB/styles/prosilver/template/avatar.html @@ -4,7 +4,7 @@ {% else %} {{ avatar(avatar_data, lang('USER_AVATAR')) }} {% endif %} -{% elseif avatar_data.id %} +{% elseif avatar_data.id and not avatar_data.id starts with 'g' %} {% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %} {% set color_class = color_classes[avatar_data.id % 5] %} {% if avatar_link %}{% endif %} diff --git a/phpBB/styles/prosilver/template/mcp_notes_user.html b/phpBB/styles/prosilver/template/mcp_notes_user.html index 4f73cd98b7f..5e6b9ac97e9 100644 --- a/phpBB/styles/prosilver/template/mcp_notes_user.html +++ b/phpBB/styles/prosilver/template/mcp_notes_user.html @@ -11,7 +11,7 @@

{USERNAME_FULL}

- {% include 'user_avatar.html' with { + {% include 'avatar.html' with { 'avatar_data': USER_AVATAR, 'username': USERNAME } only %} diff --git a/phpBB/styles/prosilver/template/memberlist_body.html b/phpBB/styles/prosilver/template/memberlist_body.html index f908852ee3f..4a23619ab1e 100644 --- a/phpBB/styles/prosilver/template/memberlist_body.html +++ b/phpBB/styles/prosilver/template/memberlist_body.html @@ -26,6 +26,7 @@

style="color:#{GROUP_COLOR};"{AVATAR_HTML} {% EVENT memberlist_body_group_rank_before %} {% if RANK_IMG %}{{ RANK_IMG }}{% endif %} diff --git a/phpBB/styles/prosilver/template/memberlist_view.html b/phpBB/styles/prosilver/template/memberlist_view.html index 2d8b780e13c..f1fd43c4751 100644 --- a/phpBB/styles/prosilver/template/memberlist_view.html +++ b/phpBB/styles/prosilver/template/memberlist_view.html @@ -8,7 +8,7 @@

{PAGE_TITLE}

-
{% include 'user_avatar.html' with {'avatar_data': AVATAR, 'username': USERNAME} only %}
+
{% include 'avatar.html' with {'avatar_data': AVATAR, 'username': USERNAME} only %}
{% EVENT memberlist_view_rank_avatar_before %} {% if RANK_TITLE %}
{{ RANK_TITLE }}
{% endif %} {% EVENT memberlist_view_rank_avatar_after %} diff --git a/phpBB/styles/prosilver/template/navbar_header.html b/phpBB/styles/prosilver/template/navbar_header.html index baab6954598..b5bc775c07e 100644 --- a/phpBB/styles/prosilver/template/navbar_header.html +++ b/phpBB/styles/prosilver/template/navbar_header.html @@ -119,7 +119,7 @@