From 0be997f8b46b15bcaf747d6aee199552f8082987 Mon Sep 17 00:00:00 2001 From: Chris Gilligan <49878588+UTCGilligan@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:23:16 -0400 Subject: [PATCH 1/5] UTCT-260: Upgrade QR Code generator package. Signed-off-by: Chris Gilligan <49878588+UTCGilligan@users.noreply.github.com> --- user/plugins/seans-qrcode/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/plugins/seans-qrcode/composer.json b/user/plugins/seans-qrcode/composer.json index 1771a62..3d7de91 100644 --- a/user/plugins/seans-qrcode/composer.json +++ b/user/plugins/seans-qrcode/composer.json @@ -4,7 +4,7 @@ "description": "QR Code plugin for YOURLS", "require": { "php": "^8.0", - "chillerlan/php-qrcode": "^4.4.0", + "chillerlan/php-qrcode": "^5.0.5", "ext-mbstring": "*", "ext-gd": "*" } From fb59e5ff063c1cc3ae002d8f9c25dc5c4310cb5e Mon Sep 17 00:00:00 2001 From: Chris Gilligan <49878588+UTCGilligan@users.noreply.github.com> Date: Tue, 16 Jun 2026 19:18:39 -0400 Subject: [PATCH 2/5] UTCT-260: Composer update and YOURLS 1.10.4. Signed-off-by: Chris Gilligan <49878588+UTCGilligan@users.noreply.github.com> Lock file operations: 0 installs, 9 updates, 0 removals - Upgrading chillerlan/php-settings-container (3.2.1 => 3.3.0) - Upgrading composer/ca-bundle (1.5.10 => 1.5.12) - Upgrading onelogin/php-saml (4.3.1 => 4.3.2) - Upgrading rmccue/requests (v2.0.17 => v2.0.18) - Upgrading robrichards/xmlseclibs (3.1.4 => 3.1.5) - Upgrading symfony/polyfill-intl-idn (v1.33.0 => v1.38.1) - Upgrading symfony/polyfill-intl-normalizer (v1.33.0 => v1.38.0) - Upgrading symfony/polyfill-mbstring (v1.33.0 => v1.38.2) - Upgrading yourls/seans-qrcode (dev-develop d162de5 => dev-develop 1eac726) --- composer.lock | 107 +++++++------- css/style.css | 3 +- includes/Config/Config.php | 41 +++-- includes/Database/Logger.php | 5 + includes/Database/YDB.php | 230 ++++++++++++++++++++++++++--- includes/class-mysql.php | 12 +- includes/functions-auth.php | 11 +- includes/functions-debug.php | 21 +-- includes/functions-geo.php | 12 +- includes/functions-html.php | 15 +- includes/functions-http.php | 14 +- includes/functions-infos.php | 60 +++++--- includes/functions-install.php | 15 +- includes/functions-options.php | 8 +- includes/functions-plugins.php | 11 ++ includes/functions-shorturls.php | 30 ++-- includes/functions.php | 31 ++-- includes/geo/GeoLite2-Country.mmdb | Bin 9651078 -> 9297332 bytes includes/geo/LICENSE.txt | 2 +- includes/load-yourls.php | 13 +- includes/version.php | 2 +- yourls-infos.php | 4 +- 22 files changed, 449 insertions(+), 198 deletions(-) diff --git a/composer.lock b/composer.lock index 988a797..ce0b26b 100644 --- a/composer.lock +++ b/composer.lock @@ -159,16 +159,16 @@ }, { "name": "chillerlan/php-settings-container", - "version": "3.2.1", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681" + "reference": "a0a487cbf5344f721eb504bf0f59bada40c381b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681", - "reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/a0a487cbf5344f721eb504bf0f59bada40c381b7", + "reference": "a0a487cbf5344f721eb504bf0f59bada40c381b7", "shasum": "" }, "require": { @@ -176,11 +176,13 @@ "php": "^8.1" }, "require-dev": { + "phan/phan": "^5.5.2", "phpmd/phpmd": "^2.15", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-deprecation-rules": "^1.2", + "phpstan/phpstan": "^2.1.31", + "phpstan/phpstan-deprecation-rules": "^2.0.3", "phpunit/phpunit": "^10.5", - "squizlabs/php_codesniffer": "^3.10" + "slevomat/coding-standard": "^8.22", + "squizlabs/php_codesniffer": "^4.0" }, "type": "library", "autoload": { @@ -205,7 +207,8 @@ "Settings", "configuration", "container", - "helper" + "helper", + "property hook" ], "support": { "issues": "https://github.com/chillerlan/php-settings-container/issues", @@ -221,20 +224,20 @@ "type": "ko_fi" } ], - "time": "2024-07-16T11:13:48+00:00" + "time": "2026-03-20T21:10:52+00:00" }, { "name": "composer/ca-bundle", - "version": "1.5.10", + "version": "1.5.12", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", - "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", "shasum": "" }, "require": { @@ -281,7 +284,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.10" + "source": "https://github.com/composer/ca-bundle/tree/1.5.12" }, "funding": [ { @@ -293,7 +296,7 @@ "type": "github" } ], - "time": "2025-12-08T15:06:51+00:00" + "time": "2026-05-19T11:26:22+00:00" }, { "name": "geoip2/geoip2", @@ -507,21 +510,21 @@ }, { "name": "onelogin/php-saml", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "b009f160e4ac11f49366a45e0d45706b48429353" + "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/b009f160e4ac11f49366a45e0d45706b48429353", - "reference": "b009f160e4ac11f49366a45e0d45706b48429353", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/26b3a47349415e5b7aa300ba4ab7fc316c65f19e", + "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e", "shasum": "" }, "require": { "php": ">=7.3", - "robrichards/xmlseclibs": ">=3.1.4" + "robrichards/xmlseclibs": "^3.1.5" }, "require-dev": { "pdepend/pdepend": "^2.8.0", @@ -567,7 +570,7 @@ "type": "github" } ], - "time": "2025-12-09T10:50:49+00:00" + "time": "2026-05-07T22:38:04+00:00" }, { "name": "ozh/bookmarkletgen", @@ -822,16 +825,16 @@ }, { "name": "rmccue/requests", - "version": "v2.0.17", + "version": "v2.0.18", "source": { "type": "git", "url": "https://github.com/WordPress/Requests.git", - "reference": "74d1648cc34e16a42ea25d548fc73ec107a90421" + "reference": "2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/Requests/zipball/74d1648cc34e16a42ea25d548fc73ec107a90421", - "reference": "74d1648cc34e16a42ea25d548fc73ec107a90421", + "url": "https://api.github.com/repos/WordPress/Requests/zipball/2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773", + "reference": "2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773", "shasum": "" }, "require": { @@ -904,20 +907,20 @@ "issues": "https://github.com/WordPress/Requests/issues", "source": "https://github.com/WordPress/Requests" }, - "time": "2025-12-12T17:47:19+00:00" + "time": "2026-04-30T00:41:23+00:00" }, { "name": "robrichards/xmlseclibs", - "version": "3.1.4", + "version": "3.1.5", "source": { "type": "git", "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd" + "reference": "03062be78178cbb5e8f605cd255dc32a14981f92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/bc87389224c6de95802b505e5265b0ec2c5bcdbd", - "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92", + "reference": "03062be78178cbb5e8f605cd255dc32a14981f92", "shasum": "" }, "require": { @@ -944,9 +947,9 @@ ], "support": { "issues": "https://github.com/robrichards/xmlseclibs/issues", - "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.4" + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5" }, - "time": "2025-12-08T11:57:53+00:00" + "time": "2026-03-13T10:31:56+00:00" }, { "name": "spatie/array-to-xml", @@ -1018,16 +1021,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "reference": "dc21118016c039a66235cf93d96b435ffb282412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", "shasum": "" }, "require": { @@ -1081,7 +1084,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" }, "funding": [ { @@ -1101,20 +1104,20 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-05-25T15:22:23+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -1166,7 +1169,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -1186,20 +1189,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -1251,7 +1254,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -1271,7 +1274,7 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "yourls/seans-qrcode", @@ -1279,10 +1282,10 @@ "dist": { "type": "path", "url": "user/plugins/seans-qrcode", - "reference": "d162de53f08a19e0b27335362a74788712f1d20e" + "reference": "1eac726423697695cc4f2445eefb4920b597632d" }, "require": { - "chillerlan/php-qrcode": "^4.4.0 || ^5.0.3", + "chillerlan/php-qrcode": "^5.0.5", "ext-gd": "*", "ext-mbstring": "*", "php": "^8.0" diff --git a/css/style.css b/css/style.css index 1f10841..d475bca 100644 --- a/css/style.css +++ b/css/style.css @@ -32,7 +32,7 @@ a, a:link, a:active, a:visited { a:hover { text-decoration: underline; } -h1 {height:50px;margin:0;float:right;max-width:500px;} +h1 {margin:0;float:right;max-width:500px;} h1 a {text-align:right;font-size:20px;float:right;} h1 a, h1 a:link, h1 a:active, h1 a:visited {color:#2a85b3} h1 a:hover{text-decoration:none;} @@ -287,6 +287,7 @@ a.bookmarklet:hover { margin-left:15%; padding-left:10px; margin-bottom:5px; + clear: both; } .jquery-notify-bar { diff --git a/includes/Config/Config.php b/includes/Config/Config.php index cef031b..2f726d2 100644 --- a/includes/Config/Config.php +++ b/includes/Config/Config.php @@ -13,48 +13,48 @@ class Config { /** * @var string */ - protected $root; + protected string $root; /** * @var string */ - protected $config; + protected string $config; /** * @since 1.7.3 - * @param string $config Optional user defined config path + * @param string $config Optional user defined config path */ - public function __construct($config = '') { - $this->set_root( $this->fix_win32_path( dirname( dirname( __DIR__ ) ) ) ); + public function __construct(string $config = '') { + $this->set_root( $this->fix_win32_path(dirname(__DIR__, 2)) ); $this->set_config($config); } /** - * Convert antislashes to slashes + * Convert backslashes to slashes * * @since 1.7.3 - * @param string $path + * @param string $path * @return string path with \ converted to / */ - public function fix_win32_path($path) { + public function fix_win32_path(string $path): string { return str_replace('\\', '/', $path); } /** * @since 1.7.3 - * @param string $config path to config file + * @param string $config path to config file * @return void */ - public function set_config($config) { + public function set_config(string $config): void { $this->config = $config; } /** * @since 1.7.3 - * @param string $root path to YOURLS root directory + * @param string $root path to YOURLS root directory * @return void */ - public function set_root($root) { + public function set_root(string $root): void { $this->root = $root; } @@ -65,7 +65,7 @@ public function set_root($root) { * @return string path to found config file * @throws ConfigException */ - public function find_config() { + public function find_config(): string { $config = $this->fix_win32_path($this->config); @@ -99,7 +99,7 @@ public function find_config() { * @return void * @throws ConfigException */ - public function define_core_constants() { + public function define_core_constants(): void { // Check minimal config job has been properly done $must_haves = array('YOURLS_DB_USER', 'YOURLS_DB_PASS', 'YOURLS_DB_NAME', 'YOURLS_DB_HOST', 'YOURLS_DB_PREFIX', 'YOURLS_SITE'); foreach($must_haves as $must_have) { @@ -201,16 +201,11 @@ public function define_core_constants() { if (!defined( 'YOURLS_ADMIN_SSL' )) define( 'YOURLS_ADMIN_SSL', false ); - // if set to true, verbose debug infos. Will break things. Don't enable. - if (!defined( 'YOURLS_DEBUG' )) - define( 'YOURLS_DEBUG', false ); - - // Error reporting - if (defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG == true ) { - error_reporting( -1 ); - } else { - error_reporting( E_ERROR | E_PARSE ); + // if set to true, verbose debug infos + if (!defined( 'YOURLS_DEBUG' )) { + define('YOURLS_DEBUG', false); } + } } diff --git a/includes/Database/Logger.php b/includes/Database/Logger.php index 3c256db..56bc087 100644 --- a/includes/Database/Logger.php +++ b/includes/Database/Logger.php @@ -52,6 +52,11 @@ class Logger extends AbstractLogger { public function log($level, string|\Stringable $message, array $context = []): void { // if it's an internal SQL query, format the message, otherwise store a string if($level === 'query') { + // Get the real function name that called the query (not just "perform") + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6); + // We should have at index 5 "fetchSomething" (eg fetchAll, fetchOne, etc), otherwise make it "perform" + $function = $backtrace[5]['function'] ?? 'perform'; + $context['function'] = str_starts_with($function, 'fetch') ? $function : 'perform'; $this->messages[] = sprintf( 'SQL %s: %s (%s s)', $context['function'], diff --git a/includes/Database/YDB.php b/includes/Database/YDB.php index 803f345..6be405e 100644 --- a/includes/Database/YDB.php +++ b/includes/Database/YDB.php @@ -13,10 +13,9 @@ namespace YOURLS\Database; -use \Aura\Sql\ExtendedPdo; -use \YOURLS\Database\Profiler; -use \YOURLS\Database\Logger; +use Aura\Sql\ExtendedPdo; use PDO; +use PDOStatement; class YDB extends ExtendedPdo { @@ -24,13 +23,13 @@ class YDB extends ExtendedPdo { * Debug mode, default false * @var bool */ - protected $debug = false; + protected bool $debug = false; /** * Page context (ie "infos", "bookmark", "plugins"...) * @var string */ - protected $context = ''; + protected string $context = ''; /** * Information related to a short URL keyword (e.g. timestamp, long URL, ...) @@ -38,45 +37,50 @@ class YDB extends ExtendedPdo { * @var array * */ - protected $infos = []; + protected array $infos = []; /** * Is YOURLS installed and ready to run? * @var bool */ - protected $installed = false; + protected bool $installed = false; /** * Options * @var array */ - protected $option = []; + protected array $option = []; /** * Plugin admin pages information * @var array */ - protected $plugin_pages = []; + protected array $plugin_pages = []; /** * Plugin information * @var array */ - protected $plugins = []; + protected array $plugins = []; /** * Are we emulating prepare statements ? * @var bool */ - protected $is_emulate_prepare; + protected bool $is_emulate_prepare; + + /** + * Bypass shunt filter? See fetch_wrapper() + * @var bool + */ + private bool $bypass_shunt_filter = false; /** * @since 1.7.3 - * @param string $dsn The data source name - * @param string $user The username - * @param string $pass The password - * @param array $options Driver-specific options - * @param array $attributes Attributes to set after a connection + * @param string $dsn The data source name + * @param string $user The username + * @param string $pass The password + * @param array $options Driver-specific options */ public function __construct($dsn, $user, $pass, $options) { parent::__construct($dsn, $user, $pass, $options); @@ -176,8 +180,8 @@ public function dead_or_error(\Exception $exception) { * Start a Message Logger * * @since 1.7.3 - * @see \Aura\Sql\Profiler\Profiler - * @see \Aura\Sql\Profiler\MemoryLogger + * @see includes/Database/Logger.php + * @see includes/Database/Profiler.php * @return void */ public function start_profiler() { @@ -187,8 +191,8 @@ public function start_profiler() { $this->setProfiler($profiler); /* By default, make "query" the log level. This way, each internal logging triggered - * by Aura SQL will be a "query", and logging triggered by yourls_debug() will be - * a "message". See includes/functions-debug.php:yourls_debug() + * by Aura SQL will be a "query", and logging triggered by yourls_debug_log() will be + * a "debug". See includes/functions-debug.php:yourls_debug_log() */ $profiler->setLoglevel('query'); } @@ -370,7 +374,6 @@ public function remove_plugin_page( $slug ) { unset( $this->plugin_pages[ $slug ] ); } - /** * Return count of SQL queries performed * @@ -427,4 +430,189 @@ public function mysql_version() { return $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION); } + /** + * Fetch the number of affected rows + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return int Number of affected rows + */ + public function fetchAffected(string $statement, array $values = []): int { + return $this->fetch_wrapper('fetchAffected', $statement, $values); + } + + /** + * Fetch all rows + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return array All rows returned by the query + */ + public function fetchAll(string $statement, array $values = []): array { + return $this->fetch_wrapper('fetchAll', $statement, $values); + } + + /** + * Fetch all rows as associative arrays + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return array All rows as associative arrays + */ + public function fetchAssoc(string $statement, array $values = []): array { + return $this->fetch_wrapper('fetchAssoc', $statement, $values); + } + + /** + * Fetch a single column from all rows + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return array First column values from all rows + */ + public function fetchCol(string $statement, array $values = []): array { + return $this->fetch_wrapper('fetchCol', $statement, $values); + } + + /** + * Fetch rows grouped by the first column + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @param int $style Optional. PDO fetch style constant. Default PDO::FETCH_COLUMN. + * @return array Rows grouped by the first column value + */ + public function fetchGroup(string $statement, array $values = [], int $style = PDO::FETCH_COLUMN): array { + return $this->fetch_wrapper('fetchGroup', $statement, $values, $style); + } + + /** + * Fetch a single row as an object + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @param string $class Optional. Class name for the returned object. Default 'stdClass'. + * @param array $args Optional. Constructor arguments for the class. Default empty array. + * @return object|false Object representing the row, or false if no rows found + */ + public function fetchObject(string $statement, array $values = [], string $class = 'stdClass', array $args = []): object|false { + return $this->fetch_wrapper('fetchObject', $statement, $values, $class, $args); + } + + /** + * Fetch all rows as objects + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @param string $class Optional. Class name for the returned objects. Default 'stdClass'. + * @param array $args Optional. Constructor arguments for the class. Default empty array. + * @return array All rows as objects + */ + public function fetchObjects(string $statement, array $values = [], string $class = 'stdClass', array $args = []): array { + return $this->fetch_wrapper('fetchObjects', $statement, $values, $class, $args); + } + + /** + * Fetch a single row as an array + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return array|false Associative array representing the row, or false if no rows found + */ + public function fetchOne(string $statement, array $values = []): array|false { + return $this->fetch_wrapper('fetchOne', $statement, $values); + } + + /** + * Fetch key-value pairs + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return array Associative array of key-value pairs + */ + public function fetchPairs(string $statement, array $values = []): array { + return $this->fetch_wrapper('fetchPairs', $statement, $values); + } + + /** + * Fetch a single value + * + * @since 1.10.4 + * @param string $statement SQL statement to execute + * @param array $values Optional. Values to bind to the statement. Default empty array. + * @return mixed Single value from the query result + */ + public function fetchValue(string $statement, array $values = []): mixed { + return $this->fetch_wrapper('fetchValue', $statement, $values); + } + + /** + * Performs a query with bound values and returns the resulting PDOStatement + * You most likely should not use this method directly. Use the fetch_* methods instead. + * + * @since 1.10.4 + * @param string $statement The SQL statement to perform. + * @param array $values Values to bind to the query + * @return PDOStatement + */ + public function perform(string $statement, array $values = []): PDOStatement { + return $this->fetch_wrapper('perform', $statement, $values); + } + + /** + * Wrapper for all fetch methods, allowing plugins to intercept and modify query results. + * + * @since 1.10.4 + * @param string $method The parent fetch method name to call (e.g., 'fetchAll', 'fetchValue') + * @param mixed ...$args Variable number of arguments to pass to the parent method + * @return mixed The cached result if available, otherwise the fresh query result + */ + public function fetch_wrapper(string $method, ...$args): mixed { + // Allow plugins to short-circuit the whole function if we're not in bypass mode + if (!$this->bypass_shunt_filter) { + $pre = yourls_apply_filter('shunt_fetch_wrapper', yourls_shunt_default(), $method, ...$args); + if (yourls_shunt_default() !== $pre) { + return $pre; + } + } + + // Filter the query statement + $args[0] = yourls_apply_filter('fetch_wrapper_statement', $args[0], $method, $args); + + return parent::$method( ...$args); + } + + /** + * Execute a callback with filters temporarily disabled + * + * This method allows bypassing the plugin filter system for the duration of the callback execution. Useful to + * prevent infinite loops when a filter needs to call the original method without re-triggering itself. + * + * Example usage: + * $ydb = yourls_get_db('write-get_from_cache'); + * $result = $ydb->withoutFilters(function($db) use ($method, $args) { + * return $db->fetch_wrapper($method, ...$args); + * }); + * + * @since 1.10.4 + * @param callable $callback + * @return mixed + */ + public function without_filters(callable $callback): mixed { + $this->bypass_shunt_filter = true; + try { + return $callback($this); + } finally { + $this->bypass_shunt_filter = false; + } + } } diff --git a/includes/class-mysql.php b/includes/class-mysql.php index 603c87c..6443636 100644 --- a/includes/class-mysql.php +++ b/includes/class-mysql.php @@ -4,7 +4,7 @@ * Connect to DB * * @since 1.0 - * @param string $context Optional context. Default: ''. + * @param string $context Optional context. Default: ''. See yourls_get_db() * @return \YOURLS\Database\YDB */ function yourls_db_connect($context = '') { @@ -60,11 +60,7 @@ function yourls_db_connect($context = '') { $ydb->init(); // Past this point, we're connected - $msg = 'Connected to ' . $dsn; - if ($context !== '') { - $msg .= ', context: ' . $context; - } - yourls_debug_log( $msg ); + yourls_debug_log( 'Connected to ' . $dsn ); yourls_debug_mode( YOURLS_DEBUG ); @@ -96,8 +92,8 @@ function yourls_db_connect($context = '') { */ function yourls_get_db($context = '') { // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_get_db', false, $context ); - if ( false !== $pre ) { + $pre = yourls_apply_filter( 'shunt_get_db', yourls_shunt_default(), $context ); + if ( yourls_shunt_default() !== $pre ) { return $pre; } diff --git a/includes/functions-auth.php b/includes/functions-auth.php index cadf538..8a702b8 100644 --- a/includes/functions-auth.php +++ b/includes/functions-auth.php @@ -25,8 +25,8 @@ function yourls_maybe_require_auth() { */ function yourls_is_valid_user() { // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_is_valid_user', null ); - if ( null !== $pre ) { + $pre = yourls_apply_filter( 'shunt_is_valid_user', yourls_shunt_default() ); + if ( yourls_shunt_default() !== $pre ) { return $pre; } @@ -702,14 +702,13 @@ function yourls_verify_nonce($action, $nonce = false, $user = false, $return = ' } /** - * Check if YOURLS_USER comes from environment variables + * Check if user credentials comes from environment variables * * @since 1.8.2 - * @return bool true if YOURLS_USER and YOURLS_PASSWORD are defined as environment variables + * @return bool true if credentials are defined as environment variables */ function yourls_is_user_from_env() { - return yourls_apply_filter('is_user_from_env', getenv('YOURLS_USER') && getenv('YOURLS_PASSWORD')); - + return yourls_apply_filter('is_user_from_env', getenv('YOURLS_PASSWORD') || getenv('YOURLS_PASS') || getenv('YOURLS_PASS_FILE')); } /** diff --git a/includes/functions-debug.php b/includes/functions-debug.php index cac4f2d..20f5178 100644 --- a/includes/functions-debug.php +++ b/includes/functions-debug.php @@ -6,16 +6,19 @@ /** * Add a message to the debug log * - * When in debug mode (YOURLS_DEBUG == true) the debug log is echoed in yourls_html_footer() + * When in debug mode (YOURLS_DEBUG == true at startup or yourls_debug_mode() set to true later on), the debug log is + * echoed in yourls_html_footer(). * Log messages are appended to $ydb->debug_log array, which is instantiated within class Database\YDB * * @since 1.7 * @param string $msg Message to add to the debug log * @return string The message itself */ -function yourls_debug_log( $msg ) { - yourls_do_action( 'debug_log', $msg ); - yourls_get_db('read-debug_log')->getProfiler()->getLogger()->log('debug', $msg); +function yourls_debug_log(string $msg): string { + if (yourls_get_debug_mode()) { + yourls_do_action('debug_log', $msg); + yourls_get_db('read-debug_log')->getProfiler()->getLogger()->log('debug', $msg); + } return $msg; } @@ -25,7 +28,7 @@ function yourls_debug_log( $msg ) { * @since 1.7.3 * @return array */ -function yourls_get_debug_log() { +function yourls_get_debug_log(): array { return yourls_get_db('read-get_debug_log')->getProfiler()->getLogger()->getMessages(); } @@ -34,7 +37,7 @@ function yourls_get_debug_log() { * * @return int */ -function yourls_get_num_queries() { +function yourls_get_num_queries(): int { return yourls_apply_filter( 'get_num_queries', yourls_get_db('read-get_num_queries')->get_num_queries() ); } @@ -45,7 +48,7 @@ function yourls_get_num_queries() { * @param bool $bool Debug on or off * @return void */ -function yourls_debug_mode( $bool ) { +function yourls_debug_mode(bool $bool): void { // log queries if true yourls_get_db('read-debug_mode')->getProfiler()->setActive( (bool)$bool ); @@ -60,6 +63,6 @@ function yourls_debug_mode( $bool ) { * @since 1.7.7 * @return bool */ -function yourls_get_debug_mode() { - return defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG; +function yourls_get_debug_mode(): bool { + return yourls_get_db('read-debug_mode')->getProfiler()->isActive(); } diff --git a/includes/functions-geo.php b/includes/functions-geo.php index 2dd7898..e93e959 100644 --- a/includes/functions-geo.php +++ b/includes/functions-geo.php @@ -14,11 +14,13 @@ */ function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) { // Allow plugins to short-circuit the Geo IP API - $location = yourls_apply_filter( 'shunt_geo_ip_to_countrycode', false, $ip, $default ); // at this point $ip can be '', check if your plugin hooks in here - if ( false !== $location ) { + $location = yourls_apply_filter( 'shunt_geo_ip_to_countrycode', yourls_shunt_default(), $ip, $default ); // at this point $ip can be '', check if your plugin hooks in here + if ( yourls_shunt_default() !== $location ) { return $location; } + $location = false; + if ( yourls_apply_filter( 'geo_use_cloudflare', true ) && !empty( $_SERVER[ 'HTTP_CF_IPCOUNTRY' ] ) && preg_match( '/^[A-Z]{2}$/', $_SERVER[ 'HTTP_CF_IPCOUNTRY' ] ) ) { return $_SERVER[ 'HTTP_CF_IPCOUNTRY' ]; @@ -74,11 +76,13 @@ function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) { */ function yourls_geo_countrycode_to_countryname( $code ) { // Allow plugins to short-circuit the function - $country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code ); - if ( false !== $country ) { + $country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', yourls_shunt_default(), $code ); + if ( yourls_shunt_default() !== $country ) { return $country; } + $country = false; + // Weeeeeeeeeeee $countries = [ 'A1' => 'Anonymous Proxy', 'A2' => 'Satellite Provider', 'AD' => 'Andorra', 'AE' => 'United Arab Emirates', 'AF' => 'Afghanistan', diff --git a/includes/functions-html.php b/includes/functions-html.php index aadf6f6..4fe0a05 100644 --- a/includes/functions-html.php +++ b/includes/functions-html.php @@ -181,8 +181,8 @@ function yourls_html_footer($can_query = true) { * @return void */ function yourls_html_addnew( $url = '', $keyword = '' ) { - $pre = yourls_apply_filter( 'shunt_html_addnew', false, $url, $keyword ); - if ( false !== $pre ) { + $pre = yourls_apply_filter( 'shunt_html_addnew', yourls_shunt_default(), $url, $keyword ); + if ( yourls_shunt_default() !== $pre ) { return $pre; } ?> @@ -439,9 +439,10 @@ function yourls_share_box( $longurl, $shorturl, $title = '', $text='', $shortlin $share_title = '
Iq zd}TEBk+r`2Xlz4cTXom;ovkRG?`Z5m<3buc(m0gHPBiwXv9sL2)7XW^J~Vcvv72hv z=M^%p-D&JWW3Q~gn6zg${lIMcai(f-`D+zM>`P-meZn9v6}PL;53q&@(m0sLLHZ;? zpI6B656L}d28-sCXdF)Ccp690=s@F08b^r(v*}%Fv{wUkzK*7$e-yO&_rV&+(l}1F zY1BoAC&ZUNm}VGhWVSe3Z@jQH0H17t!cL;~pA)X m%M9h6mvE*Cwi-^OafLCbD^qJ{7|t}* zfTS^7P~>SyP+e_ZUuFEc`T^-onQ@t;c{H@Sj#M!p#e5n!(U4HT!3KLhjfFH8(^!-X zD5?{dSQF86W6m+ 7(BvPY@o+v1HVQH0Q5sL0{9`m8r}0EKb9gp$N;Wfh%Tw9R zTg?FR|1&h+r130`S7 onfT zyB0HOyhY jsBLjjqaGK zUjLKID_}|cAM5MgbUD)H^}VWO0LgEXj?%WY*?@FK+N?{u5^WOFl}R^~-bq&>U6piW z($z@UBwd|!jcisSo7Fd)H8PtmX0JuMQ8s%S(zQ)~9ny7;SkJJXpb_hnZeYZQ%9*{U z;bz(FT}d}FzT|)5A7b?8q&t#sLAnj;md4qNbZZk>n$3PRoBb;3wxrvWZl_H7UD?gV zb}(9;yc6jjq&t)DM!E~>t~uXO+%DtVJ-hM>Eh@by>0U sRGe=x;cs6H%(E~`2B<( (pQOXCy|~>dNOGz(o;w~ zlAfAfb%fQ5Ri|ZF-DC7Uq^FafLE1T+yRmZS%B*$C=I&&)IHW6S57M(p&nE4r( x4Auajjmk#sWY zr0lx!#u-m~SvKDSZ@xtG6w(=_qVNjRX&O}0YPO7bdNzM^rRPf!%GAszos}&Jl(Qfp zy)s)c*l02SD$?gk=aSw@dNt`1(rZW;kX}oA9jVAqFiEj Gh 5Zf}lirjql)o<|eCgtrY~f8t-$Z&F>FuOTvxN^D=Rwjt zvV|`g{Q~J-q%w-TNgp7UkhqWZ-fZFfR{K8b{n;Y(|040fG<=x!p={AJTD#~O(nqqz zeUx4-bNU#ma2_Xpiu4K6CyoDgj-*d#i~llOk@Q*8=dvYb GC68e(;_&jq&6b zbot49Qk9Y*U4Eg {6m+&y(ecGXBH0L z@E%GJ?}4)*&XS^ea2CQ@SXGeHix@77vzQT!<1C?G!)F(Wvy}0dHe5!T8fsa?<#3kw zhQFrF;jiJWh!fy+!Ex;7mJ@JXoU%9kQ=AfxCxrLZX@(!+_})`LdL!f}ial}U6(IUQ z!4dsm#i>|unU40vso~h7K(2zhazk%~B+ 9`cD5vTG~7whf=h&ZSmt-d*-fIwk@>$a zE%i+;cjh>f#9oCHr#sHxIC5$8vN$~q_rcL$0N~jCUy1#3^!^`X9*Cp&{}_ERj@|#` z9NOX7EpT=*HI7~ZgmakT;f8&1jxgd#LtD=}M+ HHJHIbOZw4_S^>i}Vw* zw6v>2oPN5Q;7IG7j57e|6r3}0PQ~eua~jU+8md^8$Wleju^id|xn0c|WJ2Pcg>yEJ z9R3#P!UHVkb8&9MIS*$Tj>un$GZ5!eoC|O+vMRk$+f=LWVw_6~TJMR{#u|iU$G*;G zoen8Xy#h#A<<4M}8G qn>&(P?3FjG{IXHIst738skl?c_IpTMmx#paQ^E}RTIP<-cOL!y8-pJfW+zU7_ zYW!FJ=8ZJVJ?CXl_I$*|*LlV8RUA=Puj`~*40Pp`Tub^UjtsrGaNfmv8|NKOtEgv9 z`aO+7YEOsf2kNEF4-G|?eT*Y*@d?hSYK%8>8x `OX%|9?thTKj@-+WIx#!!1)PxNt~ZGC#ooZ!TA;E51ij{e)mS6 z>y7GZk3{t+?h@W8X`{bz{`N-wBM*+D3+@8Ai{LJ(Om`u}g-vFF(Tf@`W*iY%yyMl= zd-^`yrEr(gJu!D_Z}bt~=u?fBCBVYo=nHX|R~rd;1zZPrMcghr9;JKO3?J9UwaLHa z|LW_t_S~`#aM#Cu2RFdo5I4l_iW}ipabvBz(O2V&38?P8;nr|-+&Zoq`MU{jLr1II z#7)(PR+>+40+)7_cY*9kxGhcH_7ZVd!d(Mb>^~pIT?Kblv1PTcyC?2y+Vfp~1*jTv z*Th{1cP-qtHLT=8^sU?q<*th>e|}k3Rjw=hKVo7bh26j#eWy42aomk?hvIIGdpPbU zxVzzQin|@|X1H7EzJ$9uZZ~bsb5}9vmbhCRv6V7MPr%& |(yVcI0=*JrGx_bZ^`}&1)~*?(I d(aSy>g)EiS)dClz~7Ni$$Z`{L-Gr@2!ZXevUagV_5hkGRM z@wi9f9%J%HYd|gOvAD-sNhO}5Hu{=?H1Uarj5j8i%-~8@_s2aM_cYv7a8K1##;j*) zNj#?)!3A;8z&*3zTM}n=GI RxDodz+?zXvD(YE^cB_@>HVbvT1rg?*xDVsrg?q2@?>4;0(iPwPa391K z{r^BGKhi+Lf5?Jsinx#9O22*7=>Oq9_J8TB?-RJgai3IqDY9l*jDI9x5q2c*EZkAJ zlW?EL9gjO2cP#Fh|HzChGGk$j7$*M5cQWoY+$jYIS8m_cO;p_JxX<9uz@6C+UUW#y z2JUR!mvEoOosT;QSDL1Xc^>X_|2B;`W*aNi^SCeIzNqC=j`W=FxG&>MOTB{oI_|5u zuL;u|)7yNd$G(C4rZ?t1Z_JR6{M$yDQ|Da+aNi?-ANPHH=_(&k_Hj-5yC32Hfcvp( z40$8CdJ2I1so`h1pW}XQ^cT2x5a8PWfAQ{fzcDY_|Cg{*E%FKwlp_FpGrB)g+6VV1 z+`mNs;QoyJi)qeZbz@&u({F~q8~$PVr|zGt*WZ*xo&G~<5lT>6h|&U-7L;QS)nKBE zMVXiEipXNpRa%tNa+DUMv?Qg)DJ`LAE5**U6sNQlB|BOwElo*(1XKK}R^t)9T3Vh` ziP8#`RupxuXA^SPQ0hX-q2zjFZqsf5F>>SOEJ_}wkWyK-cgd#|sP0uoEurr7#>l;w z*Helq<$4@bs!*y@YEY`_TccF>#>g6Q6s4pfjM+3y4Ksc3sAP-M-juph+DvznODj=Y znbNwHR?)p;<*Z6+H6vCxT%*HZ)0}G=u1#qj<&SyYoa<2%JAVngzTpOj8+v0tHDY5y zBjghRk=eAv-<*;dnG2_z;TDEl8g6B{wc$2`5`@xrly;`Hy)k!CriR^-(oThdId?JK zm6A+F#CJDJyQ{C#dlcy!y%(kK%C{WqX;i6)RE|y|O8Zh8Kxscphf~_0(jk-%pmZ>$ z11TL;+(NAFBCl_$PYrk|rCt_L?Em}9LXuK%N_z6E=K>-l%_mEtQXfi3=%*s3BPkuF zZ%?I<)*fDxBLGSFSi|E4jX0iCUrJ|D($=SRBBfI(^)vb;N+)|`e)h&L;*E8TbE@HK zl+K`ZI;H;J*oFx>-q^J$ohiO5+>ZHd6Fx^}H0E>hB*OFXYLw2W^eUx+lpdvY0i~f* z3Q8AJx`@&hMqf-x&wnZD;V-2@f<|0M$ 0zVg2)G@Jl1PrH^cba4=JhzGCyaQ~&};xoPpQ1dIl^${ ze{%RVrO_SVF_gxdz&Pc!YiR ~& zrZk_@vqtM7Bc-{9^9-L8)aQopDLrqT7YYug7tQ$+rI#taVlp3e^7a~~pUn4lN^ejS zjr1m^x7y|zrMD@Gl9YtrF?`q1RHEkU192MXLqjtGDt%1p6LWsr@%>CpC5`h1rSFXR z((o(8uMNL3{8q4&^!LX3!SF|~u&ycnjE8Z4q4X=Izl{FP@OMK!1*Y_8A#e2GhW`j^ zO?nI9by1GDAfEgZcu}JlHe5ty#%^KsVup+3ErF-AXgs+*ZYd*{He5!qZCB NLyrO!y6er$RhFcqMqXKQ2 z?ToX%;SPp78tx?6Hc$0-G0v`d`{3<{x4U{3W`5otczc@fUdq&jy5sF_L=WW@qte^g zIQuC_W%kEAphKJek9Uyq)z(NY!MsE9j>YR~%wBl5)!_BUJIpwT8}?CQjqM2Orb_>7 zUF;o=cTC|ceGKooj^y!peLM6CLg1Zf*iTG-j5rzZG9ymGlO<;n|7pfK9j`y$`9_~% zcqZOi9htN7&gswt%vW9k=PAbwM2$1h@B+gN4KFgh*zgiO)BoNeakdAhcRAiicvs+! z!MhUgF1*2bH{lJz8-{n41stmKT9&JefOyw*IM?D`*P-nd;Mps{yU`?7rQqF+XD +-)_gd@t(xH2k%kyy%+C3BkorjjqL%v2Rrmbcn=%%5#{_}>xkH6 z#(%sc`9vpv)BoO6=Bvuzh>>{qvhbena7K6J$KuVy8;3UyZ@dXl=*UdOn`E5HhEqEH zsY Leqs2f z;a7%V<9#DU+d|Cyj`E^--{bvlzCYmoXv9x=rs2Jx&G`%7uSWbPs872ynEv YxSeWu6N*9ro7o)r!<;6R`OPI`(l$YvomZrQ+ht{`)1f<;2 z|K$}ZuV_KK7&;wE*PJCok8-)->vsg@fbt~DA?1B3N4g m%pN@1)avZu1=fB9`B7koAUaUH?c%FFx-&xMwB1 z|1#1gliO3?p+oOTc_+$JGdok>jq)y(ch%oR$W7z&KZq;upu9WfJ-ngodt)Eee@0v~ zhO+2?iKM&6Fm{ywhoaoWGP93vcout@<^3q1OnHCG2U#%>pnRYn$LktFD*Rx|eJCG7 zxfkU_jnh+$DUS%q>a5&bPgTmY3$!fd!*&1dx>G41LHSt9M^ZkT@=@9@gXNzxi&8#D zTU;jRvUVsRN4YQM hy%mJDe*hU)iAtoA40Bs|<%4UM=X2lf{~(bq(cfDc?x> zI%O)yHbTocs8f2Ha-@xJrhKc>w-ijHZ|ew1LqACQPGjCh`CiI)SWv!4I2{>V+G}k0 zo7e*#{zH@>rTnllANh~G42{v0AEW#P<;VZSe^P0UXSm^0h9fAC?C?h!=V=`&DkfcS zJms++-*HN`%Qb=W#DXsz%9HV3%F@=KQl3irWnIyir%|3x`FYASD9@ujlkzOe&v@hH zFy#arDYGfhu@-+;Iob#27KAaMqdZ@Gm-O~x-Ktr7f%1!#U-AaMtD7DYO7?KduTXwN z_fpEQTDD)K{Cc}f>j-_5^4roaDZf?Rb#F57P=1&4Cq} 1B%qP~)}tmU<$_QYQte@T2%{ eyi RDZ=nB% z@YmNk Ge!WAMA-Z->7H{?_ *sz$b73f;9LA{ z@V6~Kv{a53XM6b}5&jPPTOyT|7 MkG4;K4p`~&dq{s-+RGXMX#=?}p_6#rQKp7_1= zC{W&~e(yp6|1kW+HA~XB{XY0d7;&WGQHDng8gYy_Ud_?*kJER7Fy$@j_r*U0Us5~` z|3v(g@%!PQBpa9-e{r9Ke+vGoT0k*i&^N4|^82S-@c#Pxkf>xh_-Epug+CDgZ2a@_ z^#~Y$fZ@6LVhts>viANeiYBiu{{nnd{%T|^gXMbsiwUHeF2Ns$f2k!g2>)t)J^aPL z+#A1&Jor}_UWq@r!x>_ns|<%KQ_P{|v`ahuHTd`8Uu!KW=f66F@FlGqjF7`$amp1C z1^;Gz`9N4Ww{|%46|gv^fOi;iXNP~6Iq$}oU%-gKy~-cIvC;SAPr`pd_rClG@gK*3 zNPV@!AI4YxkN>Dv>D3}2t@)UaeR;-D#D4;RJpPmTPf2y*i~iSHkOdimKhokHg+B)W zY5dV<)+=93FYb+>hd&m7oN{ESN F>gX-}kMU)szl;Bl^|zj# zUiTh;p$|VWt@xqgN9sGFs~kS!e}XUl 2a6LdOVAl>!ICDh6v5I)EYp^h|9NS^ z %-cOzCNSc_m4g4GFDHI5$sc6`@RTIJWY z+UrTMHo
vZ_*{wJU7o1|I+5o}1Xkr6WP#JP!~*#8u NN{I@T{`?-jk6nptb+>4J&Yq;_aeC$ zf!zOdy*J@7bM_!OfnXn A3&DZ _qECW@38WqlAu!_~ zQ35(q7k)2-z68Aqjv_dW;0S`l3Hr$5$h5?FIzj{0|K5a?^qZaFXo6#nd5q4YC!9}k z9KrD_FtodIPU@t1BEjhd{RmFdIh#!EgOdqPC6NC=imBBD8bq{5pi2OKgJ@>@6P#hy zd#2X8GNl#HCY(iZ4x#j!0R-<5oJ%mC;5>qB2qXg+5)34e$-m_1DsRH=dR`V>L~tp= z#rjv!;1WBr(sL_ 0pA+0paECYHH xPGvf0O=Zp4!Mevn&_=#V66aOUmhTunnZwbD)PV${L`lO|-LhKcwI`AiR{!H+T zH!0AJ$up@d!EXeA6Z}r_hekL_G?L_8;`~d8>hV9w$2A215H3uJTB?K#5H6^P+DC_a zQB}B*cidEOk|~^U5nY;vi?)|+;o^i2;Sz+)5-v%&4B=8{inAx7z5?`5j)i4TxE$e% zgv(1M6Rx0DZQCY
)OoG6hJUO<=;9!QuI?oQYuT$`{f;p&7d5w1$OGT|y_ z5B9k?Nv FCESm2e~DSurhEe^m(K_-qa!?s@F>EA2@fMYgz!)eUQE5ho`k&! z)tF5!+{Di9s$qnO6CP<<>Lb02@CfZ=@(J6dXLRKr9!=Pn@EF4536IrToMuCo0JrOg zRXauL3?~ta{ikHCpMDgkCZ;pYc{1TC`q4^ws^MvbLZ7bRt%S1qBY1}4nTBFIE>`+y z8;aLC#vEXHuA!^~gfDjff&&FD&I<`;bX-Ju4dKOvLkKS+lvcZx@N&XIgqLZY@&Vv^ zVqz@;uOPhAn ItM${raF|};GeNwrCA^35I>NgN zMfgrang46W5=xvm*{^S o4hlj5TkTOFRRGO2gonH2}cu-u~8$18mrAJ z=Goym9jEe7#&81R48ny|GO zzO4Dt>9o$|318LszP#MS*Sw3QZiRS*Q1s86gufELMfeWk+Zv&TB7B!nl(}R>YEs9D z(P{!p_#xp(9gdm+5y~q-@Ka?@zRQ~|^(n6a;e0{(CE@o*s|gU{*Mw&O6Uyd~IK}u! zWA-LbBK*NPKNcLqp9p{M&{Bt!l$Pv?(M9+>;a`M*nEapGxCc*El&$jcZ^D0w GIWjFu+yh?XH* zK}UMDEYWgA%j-Abvi)mG6Rk+(X#bDo2*B1CkxL}@pE3+>5KVrLs7w?S`9u*>KoshG zcJkZ$>)6R(dXxXqkF_PXDN7JliH;|#5%nRe6ZIxah RZ5qSc7jBwC$l4R6Xx-jwt87oStE zB3g@RZEwo$-jx4&Q^tE!q_eF{v m&VPqcwI -LkZHIXj`IP zh_)lzk!X9O9g3V6c_!M4Xy+p5MQ(|9CE9~%H=^B(oELc}k|y4($a#@ZqP>X@B ;5Y!FHRIa2vb98GkL_EqVS(XlcCAhP@ar`4p1iTV wn zn6|n%O{!7m{}OypZ<^H4X+&pRB&QSgCpyy-)$?CHON!3&rX8p!yn_2LX8m~pk<|RT z8eICh=(p&6qJi4sb;TsRqmt(fiN+9JL^O=(VxlXEE+HBuzC@R*29UQ{bQ#g*nu^3D z`y@IRq&U)B1``cYnQ2x=9YI5huD0Cv?~JKyh;Adgmgq(cc%AqX*%b%T4O&dud5&%( zy4jm{i3ZeaxkWS;k*or2nHfnnjv%^&=qcT)i|!=4i%9i3(cOmk5Z!CUeTMo|03#kC zday&w|G(7So#+w6M-BgHsF(j6@i@^FMyUNi(QxJ4R{&OaX{M1xqpe~`5k0NBoqm|2 zJX(`uiKG?A5zQnTPc(&S0?}lmiPmJ3bk|g7;Ica;^4b%o63wtWnPy!@&VO&0ca1K- zw1%G{nnyH?XfDxgZ@Tmf(f_jfbG?emw$k+T^fe@Fw@6gVi$rn+AfcWodO;_3cKV =_D-qfGZ}bk)yP8|siPg^h z9+5QhKSUo8{Y>;B(YHh&5q&}QG0|s4pAdbj^0MtPw72wFp^0=uMpr}8mqcG1=PTQJ z=t-oTKl;cnP4t~N{Z^vyiGC!q$-jj%d0hf%5EYQ}{z~)*(QoEuM*x!0^hbzv|A*)= zqQAZAV>O7p?}Q&i><}+Nyd?30#ETIxL~PqWv8rgzssxM|?GXBMA#U&gXwKuMh-F(q zm`fAe{*Pv6Ht}-AD-thHY@0vRXKRYZmP_14U$`pc64!`J!~wA^uOyN(v0wa0-U=l$ zA#qHsn?If472>Ko^_4A;gsKzg#0haq+#qhM@ATITc@xN_uogswTf|+Jmds4pR$Eq@ zFJ6UsP2yFF*C3YtpNEN8|G(L$=m$BejMpMwTf4mm5iPek@w&vauW}#pdaADD^@%so z!^(I=;@yciBHo^OW8zKp5Aw2QElMEXl-MT!N^fqQZp32zDNdXJ$2$Kv=hnnF`B$%P ziFNYdUWLUw5bs93qvc^I;$4Y%Cf-G7t<&Xb>3;2#qQPV|Nl(`I`{Lg8Ux}scVyL_q z@fhOn!~== !By{k;J`>?oHf>_%PzbEv&2sWT7N7M`(~4`t=#{QN+g*AFUj9iV3I? z(~Le&|8uR(zQp~BPar;-_(T -TX=Qy8pF(_^al`~vA}=y J zLVT$%tY+M5HFFv9VB=p-d ydNBEE 4aWg34#P1@x{YSMxqQ57XDJ?&%SPpC?MJ~f%osEi=~oXRf5Ul5zoPW&bDSE^X# zbs2w6{EZRcb~xV=e{Y;0I-DPge=?34|HP)0%yx_TH!7 qOg$*7RUxk$uPzjYT%)lx!mBjcJ zD%Flmjf$KvtGt+lQfYMfO(CeHRI&~yr_wTJ*A8bTDk~SX&JruDQdyVEY8~IzsjOl0 zYZ|U)xVGUsf}Py1M`dFw>nlf7+`w={DjOA?PL*zA{7tEBMrAuHn^W1wINc1lFx=8` zD=J$Hp^uhG?0*XS11j5_zz$S)G~W-*DKijBcjy1J+^g(LWj899QrVqKKPr1r=}ERQ99NL!(mXK2-KC=(g!vWq&FMbbJpiOi4}hV8cVG9IC!DuWrZC zi%M@Qb`V@SjEXJ+sPr*B!q6uFW>IF&qp2KY#IaQRQnB-24RXBR>p0_6Dzf|(=ZS*i zH2Nee=TSMCip}sVrx^28!_y2;ry>*VqC{tOD*r6=I@|D^j_&{}=PLbw`8nSN1{z*K z<-(5SMaH?<@RCmD45D%;mCLAHV|-l&P`SeJO2fg1LkzDn9BO#A;V?n9daYbb_X`&0 z+~$1H@F6M>D_;d3QDSV-8^!rphyOU0Cpz?#REC@IQ_8W{r!tc2_EbhuU4Y8dRNkR7 zn#v?9V@y)^e~S3WQ5oN%Cs3JK(2{v7lc~(5GNt1?mC7_Kv#Ct)aApWWWhRwpsLWDk zVX9gvy~?xZJIDA^d-JHgPUSgc&ZqJ+mFLw %1=~G|5r@^tNe5-zjb_nr}Br1{n_FCMdj}f zZLfeT>J&XwU686vbs?3nE=+Y Ra@O>T*<< zZ#R%exB}G`O{R;=D92Gk>abd(x)N27YL#l)y!?(#Ks7W@WEdM()VFAQji+YJI@N?~ zO0}WB%5N4jRI?5zH-5{ot2aYyjH>E?s;f|4m+GoUtNy3Dy5SmzYf@dC>RLj#zuKs- z^B;OWs+&<=UpX3Z1H%oeZbWqxqc>JBdB0RQHR5|K_2yK!rrON}Y`9fT`B%46nSbA~ z)on~*Tf^9Us3RqaGXHp$P^(?BU+pA|&J*V)cI>3a_H9XJcrHuxf^8%`as9tFFMTQp}USfEu zc7x*ko$6)6F{bJN>J?P4>~IFR=PgxJ{?(x-cD3O!m1&QZYpFhB% 5smgiH5>#K&b-k>eWtFXIy{6|c)z{_zNws6FzDe~Ps&DDSy!y7`J5;}* z`mWLMX{ec^ByHth{eY?-{ZReL@MFVI3_mse% D4Pmxf;{M+1JX#0crS-%|aB z>UUJX*DVX#8LR$4^(U%7>Y7m=xusJ!K&n4$K4m>F>yhfORCWJHcei9KzWN8X#i;&C zO|thFwMD4@ZRPz(v4#ezEub;i7StG&Scuxf1?|n$&m=`sA2HUdiSQD-Cog*@wI$m} zFtw$0)1$UDwPmO+M@=Kqtzg-It1Yj= 3(krT#v( zm8kWwf~-tUj)0|Tt5RE!+G@I`Q(K*yDt{ALlbU|{Yn8LMHqCW+P+P})25RdH$}cmh zt*^(ywGF84Ky5>68)?C%DQk90RojGGH)@;OZj485GisX`wD*j(nsBzDCe^#0(OXg5 z+6Y; }1GxGWqlnn1fZC|MbE5&})o}soswQH#zK 0$ zK 8!h?ScQuJVfnbbLws?wMVEuYQ+Bx9}_g z(2harm#Do-O(J>2O7sdf+5Z_qO*VxDUl;T)J6%!#za|mBMeSp1Z&Q<@^p0`#2!Psq z8t3GljsC#!L&J}hZ+p~MoKL9f5dgK%3~lp&wr*up`;yvs)V?zMYii%LL#fxd!nEMu zE1{ep41c7ioBz~)HvC1fQ&+!H`-_@vJ&ErhhJUtw2WdS}``hpz>QG-my`(P{sni#u zo>E_!x<`Ex>Pu2zl=|Y-7gK>^bk~ dR1Hk-8{7y<)rg+dR~CZ$Dg|E-1*mI2jk&tv8r0XMeh2lns2@&!ZR*=m zUx)g})Ymlu+5DFn)~CLq5qdCZ@oZEO#@vK@HzSH1ni{EZ-jUpb`qtF9v`|~=!Gs*U zOdm^q8>KZ%+ZLxb)VHU;AN3um?@fJ2>bp_jiTW >ibha#P|nLKal#tMjuq%UFOZU6gAYL)TJ7Gn{O{2 zDRQ})=J~KrZ|Ot*Z0bi)?@Rqi>UthZ{U}4#|0aB_IaT?a^Z24H)K8$^pZbZ^Po>_E z`pMK!Dr6Kjo>TsHQrA}ib?rzN$r cdR Gu{(yPiNB#b`d~vQ 6?-wK>KZN|laNj*VieRq7L{Pckok1$2r( z*$OzN<5c}meLD4Lsn4MPjQP&gd@6qybvaeEGfM0E)aOutp88zs&zZnHZ9ehRN}Jz_ zQ}sXf7pcFb$A9Llktp*O!&j-lM*S_LUpIV%`kO-7BVGM%>hCB$AuwNi1=Q^oAaB_E z2gS^j`iIm%(#dE2W5Z86{7 c3L|jr#AJLz(@~mLBzopf}r2i0Xe) z|69JEqyCR#0?C3T3+P7~@=2XcCX wI7$FN5x!zx<&B9C#GZHznl(S3u zHSL|=?59Y&lB`Fv63MzG`u(r|#v+jqa|GoIXhFH~Q&4Uy7L@yM1=l2zOQ(fcThNGg zyxDTe?F=( }7AkQ`2OC`oUUo+Q1r-Niyf)+zFSkri}um^Vkh0eqjN4@qB=BS?-XIg;cU z66t0~tA(UwX->r(v^mMKB*%$apf_h#!;RF=Ut3Tr_GFS1wW}xnNKW$RY_Ar2bN2D( zNVKP@y>4*OLe?9qZ>Y;Q~{?4=~4{7=wPqlLYl O>=v;Rh8i=; zD8r`(^-%#yXpF|M#5j`iT5s}|XflChk~eo6$wWc@tXrFVvW}FwFOW u LuU2Xn&Iof2LLwqO=oH6kmFCza{yi zeamR_70K7$T*<~DwR#mB+C+SRC;5& 9QtXK&uJ-n`hGw~9AU z#>lTEzv&nh8Offe_y>(eN&cj<2+3bG79#nZ#6H}Us*uWQKw|+K3wrZ*^ycmB%{x-3 z;-cpp3u|o*TvU6wyw@6w(O6cix3M^lC1@ 3Ge0~#Bun0U>*$nvl;jZF$# zQR`$g8r_7Zv3VgzL#}|{(wldU $(vY^>o5o%=y6g7>7M10&2aSE&PKi?sxSz({rVpTT zu;unZ8gd0(=kANfAv6xBaVU*mH01I(s|_twZyJZSGxmQO?nC1Um66=4%uzIs6FW>A zN7FclhJFQBOdGV*7W0QjUmB;=urEVL45DGLfJQ&7K RsJDH>3E`-P)uoK{#^ z8Qq`8RW#&NYpMJ*X`EHuTt~wUL>sdIuX|!N2GBUSL!U=O4hV!fkj5o6E-+g5|Aiy_ z|Ao990myCAQiqq)xWc?7W3vA*GPdC+zGJ0YWdC0{gAIoW>Z75C(zubv)vA9Q!wj!c z&7jU}4X>kdy%9Gkf1cU!sIS>9HEyPHi}ICotKn^GfGuL8`Wko8xKoYOl)j6GGL626 zhS`5M%>J{{vHxs5V7?F1c&H=!Fb%W+Y^eRG`I-r6Lrp+w*!|CqCuo@cXT$72mH!lt z5hh^wBsWGCVZB~U(HPAtuhJO9DiUfet4OyW$4ZjV@vJ0$W&*AGG$ztKj>aUK(k&;` z>_%e>&1Gp!wK&yuoW^vT(3nBv4|C3>@eGY$Xv{L_Y#Pt%1-~je$8au8g ziiQmBPiTD5k@-*v8XwX4_+Lrmd}{ca;pa5Ipz&pqgo tXsG2GU0JDS@IVYGb*ap)wPJDGFm4u2PO?rOLj&E1W# z*-=vs$i3%ugwpIz(=Nto?oG3YarFKln)@2=XIND40YaPeKqa0VZ}h=559#m^HBL{O zy}ZM^((G-{!)P91#Njmi6p~)AVKk4Vc?`{?XdbQ1BYxCJ(>+$5N1bQ%@igC}*_Y;x zG*6&;hA~ex>}PlqP5T_6d5SoVbE@HKG*745Uzvj+@_Jol%rj{YrFj<3OKF}>bD%NL zF&seiTq87HBhLR%@4dh{7t$=^x!9bS6q22652AUQ@h>;L!thF(LyQ E!0ECUzUm+iBiI^Nvmo zchbCzrX2nbZ4dv0x1f11P1E+x`)J-@Na~lf%?D{dZ1Ftg^?%WbM`%8(-&RO|3O%Du zWBR{o`oC$P05t6t&>T+lsdj7!iyAlm-yB7ACe5d5PM|ru@TED1=2(k=9L@1M*Od>E z+NG0#Q)y1>NXl54qO{!Hu1TBzZ%*&{&iIelGc;BIo7il_XAS3QStNc9KF{zun)7MC zLi2fZz94ACi!@*A&@U^|u7p?3_cfZY8}WwWn<~)mQE!uOK=U2aCe3$ger^Kq8QOQ~ z&7z?{r1_CCKc@Mq5uXTp+9IDRF<6o27o-c&{F3JH=KPB0*G7D!GAi&b&F_r(zQg&! zoIle1sl)l%IKR;RwZr+XP;tipgXW(`{AKvJ;XkCb^}oF%l$kDQXqBHXOu7iEFc&5D zNEb7SNDrU7mDFqfP&(OABpIeFcy%Yq*>;yYElBf)dJK(Xfl5W9X8WgwUtZ$!VF? z^nV%@eA19KGU2$xsgUZ6! {AXuA_v!I^_kbV(S^MZ_M7L8`7Fdx)JF?q#Kj= zAl-y?C(=z#Y%|jBNH MFT?JJdkeNZcDfI#>VMi^ia~C9m!s#QvAb74{OW!5oe!{?-8U&lD B|Xj} zJpSLQ3+V}@{YX#L7^EsRwv$LtF4V6wm2)cTS)`|t_O}W=UA2c?5s;oidZtEYPK)_$ z(gCEZ{I%Ymn?rgo>3PKzNSUG)(}AQ{kX}GKi1b3zi%BnP=SSQ964FamvKTUjno2Jt zRsC<#O1f8)UL`c?U{ZY%bofI_uOYpfbeMh@StwX-@oP!1GZ`zU*6 y_fVR z(mP0RCcTaH7E;yx-u$h+{`Xs9ZervEh3J4x>*y{pa94BVr%JX)^%NFOD=pY$Qp z2S^{(QcFO|iAp|9`iOP|oeC2?=k-kDIiv3m6H%MP2eZ8F%DT}tzo1|}PR^`PiF%*-Q z^c@?L?`np%4&NvJl=K7Ak4Zly{YbNF)hj_rKT$EQUa2=J+LxrClYXI@m!433f0urx z&NCJ;`EN-7BK?;1XVULTZ7q;~UsM;V6zV6h&v)&zYy18}`a9{bq`!HC`)bk_GwC0s zf40X dQ{}HRlDDIz9@ZjTTN_dOc2Serx?~%Z ztw**Y+4^J~wCh}_6dgdbt<0?qw i z1)uCNvcokto90 @+QrINKH2pX^MsGn7*dK#lXPLV)ZXvKz?;kX=T0F4=`-=aCI0 zJ6{E)611*#@^8*DWEYWLN_H{XC7P~e(khB @Kof$ZjXQmFza{ zmNLt=VNP}j*_|4{s18Bx&v%pEM|Ka{y~>e9rN4<5%I+t7Kzr{Y-4tbQl08KB1lhx6 z|08>Z>``5(NaUhdwa-6B_PEAySXa~M(l?$IC)sear?j>^`jBiS*_ULa$mWwhO*VyW zG}(AEslc(tc(H)vl-3%aKsK3dBH1L3--dj<1Bf=urjpGfn?^Q+Y`O}x$IDEzXUu8( zO>}!Un`|!Gvt)CWZv#`Rg=`+#bIK_?m6rW^vUkZ|AbXwcMKT$cFOk{IU+PB!YN%Jq zUeiJqy
jy-D_##%2v_{rMernw}(kkL(k&_sKpY`+)326BFfcZ& z &PY_F$<)f(7 P#*K@L?Nsk( -J2v^& W*kw^MNzBBo*M(m=J8s~20 zySLL7j`W9oPx2$l_aZ--ygT`RFnZ1^0Ucp@?X?jJM6jS=N0i-Dg((!lV3o78~KIgSCLEg4kEvp{1WXm?NFC0 zp=G&@{7Ul6$**YFwv rG~N+9J|Vt|z~m{2B>MK1@q-NH_9p$**fq0Ho%% zrEVa 3YTirfEw@>|Gn{kJVFmD|Z5BEN(DUh+H1? ZE zA0WTqB3#Hi?SpMvv|Ro$`IF?2kUvKLDEa^7%5L?Q9BSCd$)C{o)RFdDRQciLBgvm4 zAE7eR?(%4%Mv*_QsY_Uy8)(lI;g`tAlFuL?M?Q&syh% x5?ile~0{CjX^SB zymmFY_d9WZNd7tbN93Q7e{7Ky 3{F;`GyKiWv Pl-B zS}W07`QPGLx~tM!O>-h6PU4r{_0}4+)}pnh7FjB`T}8GFByXbDy0kW?wH__m@L8YM z2JP3e)PR=MCjSzZ%u!pL(Au2VrnENGsEXof{dA*cl_OoESX8#QqO}{Xt!eE vo-u(ss%grD%}>lm}7Xj`G0Lkn1&f$g(P_S=*P! z>tvw3BjwX652ieV@=lcZro1!d-6`)vSw?MFEhQF?U)+R;Xi^qVc@N5aQQnjC&~9&c zjT}aKxZb3T+1(=hP(G0IzLYKa*RuOlK0tfbRGHd?C?87sV9JM-#;?-#jj|t=4^tVN zu9Od_d?Mu|C?8AtNXkc3w&36Thq7$}LhDRBj`9hV&Hu~X(;7#r9=UXUPqG@N!%s2d zWbIxpbt>i4RHj%sI=*L6zK!yklrN`z7Uc^lkD`1o<+CZD^S`Y=kFuQqNc6r=&)N$q zUqbmJ$`>nNXF@TEmr}k=yq``CaWZPp6_l^1d?n>;C|^bS>h8IUct0JTYbjsXHOX$f zDBnQ&X395GzDeh^G+A#M#M)aZ->Q4D-4s~a+bKUn`3}nWQNEM19{W(ft7s$Tdnk+l zH^(BD$evXmP5D8}_fvjA4VEz2#)$GmlpikIrPZmDM=3u|`7z2*QhuEB6W!jB-rG?Y z#Z#2Ws1q~c(x~4kKSTLB%Fk-m-QIql@(ZQ$o=z2WK9ubiK$X92RlH*ODrLI`Q2toT zud4>7-zfTo@_4GNP@X_VrtU;4;=3kMR_9Dv?*b@G55Hx^6vMX#jd+LhR3q#bKqcR` zD<#DssOY_W9Nwg4^lbN9JkS+@Yn(v5!_@jK-iy;6Vl`r$7svrxA9 zUyj}6kFZ3oEL%e=vr(Ck%Iw`z6 u$SdYIrl_gBrZUMAZNweN~h|1DbnpBpdvYH7jYq%Vh zbqk=fB9)b>tU^WTmDLhiDe??aW_V>)l`NjHR8}`(J^weNOvU1V)lj8UGiIGiLj}4A zU)xY=QE6M2Lq+{R6;B0}@Tmkwgj8Z9BFz%dP)Q0xRu+|%%4SqDD(h3pO{_y@9V)g3 zs2+{~smON$wo)qVT2j}t5&8(AM{i&py$hhCj{vA_Ol1=)o2r0J67$$p`cm1NiaCFk z*@DWJCcKqlKNT)kWu?Dyq?y|ru`QJWR0dMnu3M?>O_f0^Upz0a?4Y#j+|h7wkH0gO z7pUw) biyH2hEUmu%I>{VdswMGsSKqu%;>!|9ynZ<;czN@t6v(wj^2V5 zi>0zJ6`PP+XI5S5pxip?)md6&v7CiyCrDOA+|QyEKT zoDr`ZzR}$^D&s9VLC}bahLfmF?s23QZxwWDe6etJ3!w6j;Z!QqR8_GDRo*k^`-MD} z=~O fAWKg z{b=}K!><3g+42k3S*ZNllmE>Gem9(9_=lnG3zff&{+lX76!j{nI;-JqhPnk%orCI} zM$9E>#N1Tp>Cy9cZLZF5Sqm60sJU|Yr^Oehx-8X2l%vT-4Hq+9oT~Z%>XKBKqH4}x z#N;l%x-`{gw8+fft}drZ)v!F(6^vNXaHSq!vQ{o=-9f6WQr(*BYE+w4S2xKu49kY< z|EX3DYld|}BN|GK-;!#}l1`8BQuU~+pQq|m4U7njT(OgCOm%ar3DrJS+f>)4no{jh z&8X%&+b#4Gq`D^6wUi_7Mdr5L@m1Hc V{o;iR(0X U6hC3LV z^REszdMCr31&!FHd$Um8jp`Xxhfp0sRsBCzbN {-u_@% XwNyW)dL7kiRIjJ{4AmQ`-c9w!()e*y zZ!)~u@Rrhcmr}iz>YY??GtTYCxkFH|@#BnnmlBqwdXI7LHN21NXd~`7d_d5M2dO?} z#KVS<^!PGkA2ZJ5hEMePPg0eAUu+xG<2+p&|E_VKr8?G#=cqnU^<}DZ3m}p&8onf` zm$u>+!&ePoo5?q^*A3q=l(OTEm>_7xM5>dFm~8lFkN+0cDLwjas_*pZsYZN7^ _fFS=8Aox`tG)%o|9`3!2}xc;EF2Gu{Q%~g6WrTQ1u zzp2ehjneoTr3v#;n}yn}r3oc!vr(HvO|8vdny{K$KEX4br!??=YI9Rttu$~1H7PQ$ z5%W=7%82<57ofJF5epeEENH|chKm|5X1KWF5{63}mISryYD+7jwJbwz*&e-|ah5mR zRxn(V+DdAhTKj+0R_@WOP+L`LQKDlZJ+QhMuMMx2skziD)M}=!+H2if)auk4+T?)~ zs5Pmzq&Fl<&G|>wguc{#Y8z4us72I5v+aEmpcWTm)DmiK
N)sh+J)H+72*~^tm z*Y45lP+PZ0_o23)Nv?0WLBY3LHlijrY)ox4YMW5oR4dgH^u%oaa!SW&b1Sul$!}?# zt*G_Wris{uzNHDzvHTC~S+ZIsUftvb%;|!*z z{@-Zb0;ug`xGS~Ydi){X8Cctc+TPU6|JR068%9n2zg~KS|2?(grJ+w)@qMTrKu!HW zwf(5=ZwD0eQ#nQMf!$KY(O=E{f9()zhnl?le`+HP)&Em7=U+4DUo+=lJKFfiXvAAP z*6=t(J=vgkf}jy24Ns(Y5;YC}m2XJR{D19K>Wi4F)2N+J?KNs=P`j1dnbfYNc9z_S zQX8drqe`4@cn&r7|5nPjfSPRqwF{_SNbO>3>i_l9B9~B8M^A?E*Ums3;c-(y}y z?Ph9MQ@fGcHCF0cYBx~3&gkn^Oip;@epW6av29e&KHJXQv0gl zYtN|sH`Km0;ydb4lQkl(_@R)Y_M;LL#J&7P?GI`{Q~S+?f1&oPX;{|A=yzkzC`y$k zUTR6%0{$vE)c)4qlUTPt3-$Rlw>~TN*{IJ!eRh>8`nEnN^|`6frJPC2l_oAreIDxb zszR~0E2GUc4GU0TutzUcRH`yEzV$`bs`_Hoo7C;_U!I)Sm!Q6+@k@qFQD24n(nc?1 zsBJM~IqJ*z=oO5!qTx!^ZRl0`%6e#~#Hu~X)u>ziU)T8Gvg{V1UZGyKq{aXBy3vh7 zMx9c WL5<2Fv+>L1>XoPgGP%PQ9bFJVevv zTGY3tzP8o7j^VmR?bQ2FxA?!levh*O^$ksCBg2giH|gbWN_{iq^zCsrr>^lo^(_sz zGVIsO-I{uT<7`uK^p2@MfVvFFcGO2uA4q*q>Vv57M16beJ5t|4zqFDL)DN{}o)0c) z>N`{4mHIB-EcvP1x-@wYlNq9)s?~Sb@5p*(sSl-oAoabd?@fIe_2Dyh3PF7z>K6Q) zJoWvkYy40B02Q;d2 Ka~2x)HU+gFPbM^XfpEWUtJ5SA5Q%g>PJvNf%=iukE4DR zb+JwTe`(SU)Q=JD%FFkIb^8QZipzg8)<;tR5A_qNTkt>921VPY@xNZ$eDVKhQrGyO z`sviqDE?DGjZw~7)X%0qYNos~^*d7|&ZB-K_4BD;Mg0Qmmr%cu`bAx1w77ma*;92X z^~ Niopllsln zZ!;PF(=_V0YC$D#FEmiULqF4dadDHmi~8M0+*61t%I#JCKI)^D*1GPe{s8q?jDFBi z=F-DPJR)esqtqYk(T`Kt-Q4(3Qh%yPk1@{EhR+y2Yxo@X=Y=r(1;ZB&U!wl9@{31Q zb@l($Uo#wQ_`2a6hT{y!8%{8sXgJAmvf-P8x{;}#DWnTif1AcH)Zd}82lc5mX0@zo z)Ze8pZuvdxpHY8bOKFkmhQ$znNc|&Ae%#Cbg!-oiZT|Li%l)F4`=uqnqHghjUE_c1 z8vJ*Uq3YlFB!9519}WL&_!IS?jrc{-h+nB|{7?P&9%qIn|DgV-5r6ggf73uYjadqg zEHN6h(OA=%v(uP^M#bnk4d i>;ihKB8BjpZ!a^Z$(%EV&|$m3orhzB2wQhO5$8 z%?R`VjWvu`=ilwsMwLcj%$lKX0gZ;yO&TqgRC^p6t`T03;};r?8PbS){Ma}NjdqWd z8YiQX8__BFJp njl+eOs%ac)%%e2 ^H>_k6?_`U8|MVW zk;?3veG-lT7*qW}jZ+LyH8lU V;aP^G1m&)r#yK=zq;W2dCup2U<6au) z)3}bt1txZ(;YEfQYZ0yJ62nUkFEhNH#uY|fDQLu1G_E${8pCUq-!=Ps =P2({dkN=DRq|h{;G8|+0bdgKr85-*Rjrkmn=X &AJbS9Y8w$I~$X- ~ZFzIk$1 #$ z3QaZGRIRLpMAyw#dz{sbzk0#%Mhndf%}`%IXjW;~XgW0OCf2aB`U()umS9neruqM- zXMFtxfM)Qo+{nttG}ok=7~Q5R%2T~BXl8mZAXe!efh27UP+~2UT$^Sen(J7W9RB}X za=oGy%?)U7WWw?kAU9%TeG90$iP4+V>_>Amqx;g_lIG^tv@Hr@MQ!O;B4C91|7L%h z(!=r|nq+OOGL~z}?Rt`fXdXs$dzy#R+=1pEG %~P2mP<4ZUM@%=ZQ2& z&^* !l-Uo `V;)UY=GZa%-f#2R;-RpjCXZJ=N{lqg6KS4I^CaWQJDGnM zxt2VoCwUr8bN+H0+dM;`0X5I0c_B@6{>@P|g|DxG(mdy1v*kSFoKN$DE>r4N8T0?m zi)mg)^Ah7++7-~;%Z+md&40cBsr;*H$*R1D=7%(|rTI9`>uBCV^LlH=4K#0|d7~Pv zBcfXX&6_o;t5Ti6am@cW)&KW=!ksklqWJ*LyG`I8nxkpntIwXA_Z1JAtivUFzm`(I z`TyoaG=+ZHIFJ0V0-}(iIku?F)V@J;9L=|Bj<@6lnv-cxGy~)ZBDyo5ro4kBYvC=L zQ@TuTmvG*(Qd4QZM^k?PK{)Sr1(cRoKn17Mls7+3RkzefG=HP{v6VId-~5#3cQijU z&gV40qWOiD{qkShzBbM`G{5~{ZQom|A87tWQ$7Tf8hc;sC@sJLAovT-U;nq%@3iLB z7k!&EX#PQK7Mg#Wp1*7^{jJ#IpE=et|KFO;__G_%F*8>R>ZwR;Zdx^3^UzvGRkh}& zrT(AR{InLMrT(ARg0vQ)C7%F@y!-{E8n38Hy>~ETag$tv)>5>VG)_rn{*hd|ORK_V zbs@KwqqRJ(RcWnYWmlxNGOd*?%ifievU2#NlB;N0CCvG^R;N{=wT5xZD)WzIwI^Ap zwK1&*t&M0kX?19|XhpOftIMSo(6YmymLC3SsluPP!h)t1n`A;Oqh*IhE%pEZlC =;gw_tU z#PS_=I<*Fye)a#fc2=L?+Qm>l{_3??w$7cU&v&P_hY%*Yr{Pdq`_MA~-x_A6 s;;=! zX|zVs(k+128D^V)>PYLXe_4CB<(^}FJr$&N-oJ7$Fg+L2x{Q|ke-pl#mim9AHU77H zFYo1EL94q3(7LLZdkwAUXkAO|KAQ>G(YjtH2CW-t-A+sWKdqZ+-AYTof)V~L-Dx9- zCz@3MZ^RuYc_*!VXx(L;yZ=}6-k#)WTF=r_=TGYaT94B*|KECu)}yq<|4aXf|F=1> zyR#%8)3QoDVWpm=^)xN{1VA`r{#WvuLQ-FuQ2oyvzCi00BVMHSQg2edtdhFYHU950 z$I|+f*6XxnZofh6U0UO;;CNcoXicE?7A=kcX-%?~HQDgZnd^&|#{abB698G8?-)*< znOiszTJO>Nl9t8)t?9Hrp{4OZtq*l?ZRsaqv_3BOQ=={ZZ+&L;=Z0U*%(a4F(fXR! z5466aWe&e(w*am0x-QFFYLY(+TC0De^{a7yHY4pRu-fn&tr=E@{R)t-@jtXk@i?jV z7tSnJ>Teuo@|EM*7U0N>(CRL5=D^tkXHJ|p&RjSv;LMF9A^JQxOPavEhV$XfkFyAl z`G03YUHi^Lh6@*iVzfGcoW+b@+;EARxw?rvCEdQI&z+@lmcv;FXIbrb@z}+OZ}LLE zv%J!(e?^>Ca8}aQ;fUImRh7Ivqwmk;IIHTr`tnfES 7=lzHx%CfaXTViE)xHvs)^~Ssy3ESsO>h*2L*_1?1g4 zXRTt5oBh&+K32uLg6fkU_5a
)w?N9U!2W!ok~Z^lF^>j zEx`0_i8CB$E1d0c`r&MYv$d7#ua=0s%u;7t(>y?n$fYeBh_egMAe Wj5mE|g1kU0A(r_fsF*rxz z9Iek~WLAi^s`*%z5svou@i^b$oPhH%&PbeVaZbcJ9p@yRQ*b1xkT>?lhLhC>`5ehP zwa|}qTG2T;XW(3cBg1+b&RIC4R6vB)p0jbz!MPCUTuWN~@0@Sr-n#|RIjsI4=VIeq z{O|k||67x73($VM66b0YzG`Nl;9R4$w&gmU+i Hy;M|3CrwQlQt9RqvqiwXQVrB2cc>rfL&i&oRD?=>dhw~uLLptKBS> KunN0cX4l6z{7$6LBV~8O7|? zCG#fEhd6KHOvRaEdfvu)M>R_Wi~9~|8qWJT@8Z0tn#C9k*l?!fsPiw9R^vxFpW}Rt z^BK-3IO_b(xyfQt2PH|IFL1uX`BJkqw_tv)w9e;maaYFq4tIW>?{UHT0p~ZIA8~%h zk+J(p`&s6*nj!MP;QXrnugPsp|L-_|;>^JLLt9!Hp!NQRBf-C|O<~GNxwGKTg*z+m z?6|Z2V~jfo?wm6V>Jo70#+?^;9%ZURv)P?bX&Fs-0o)~U7sOpeIJgVpF5HuR6n9bF z#nn5xi)mNPe7Dn;^PlB#OSntpE~PP;bhyN0?lQRY=U*0*6(3%?%j2$uy8`Zt-P)xG z%o1GdUimV`T?IG8T@}~GT@AO6yE<+KcMV*1{@OXZ%WQ*N#jO>6W&GW68@Mf@ahuAK zw eu;6I>rR!VPdk<*P#TNN%jO_!BMOHcY#pPz_lYSBh_f+reEAcTHUB ztF>^~R)Jy?YN>T``)If5E~b7JcYWNAa5uo+P&JFB3X7c^Yh6W%=x&O;74BxZTj2J^ z-CSEFlf0Pc?v^IGg&2w34_9< 2}sp#1uZyDRQ)y>G|l{gdNycgG!yy9e%`DkCOID|7 vv)1J%qqLvM>@? DxaSpH$-i20f$8}h z_afX&aWBTbL^(sBu!SJ?%AbEdQA9zwSKwZYdnK; }y>$orBzKZ)Yt{(oVVe BTL!TBx@G;s$Xi uQqu}1 z?t8eO;l7XiG46ES4=vZe|CK;g2jZjd($G?$Sg!2?+W(*9evA7B?$@|q;(n#=?JY^% zZ?uZShr8e5{(!5u1ICmVS@17CuO9Lz+`n*t#{C`l7pp>V2aNcw=tHAt;QrC$|EYYP zGk@dFhDVq0&4Q=Fe|M#Lv*XQ$H-{GYYzr`x@aD#w7tj2E;p@El@aEToGe^x^5N{~n zLU=LW!gy=o$@%{Zcyj)~6y9QZOW-ZuEu#9D#4Bl1dO(I!Z1a}JTNZB_?fhbg^_Ii4 z$X_Bcxx~+VE8?w+w-VmUctz*@-F152DymIo?EJ@DU6Z0&Vgs*?SI1MojaN0SX|7x{ z%3cG{!)xNXcr84q7= nnKDK;YIu>(TbVOng0~f38*hEQ6i;l(@Yci= z TmbL1b)Q gYu$x-7h8=Nb=9hj z?E*SuFEioG4X?nv7Vk>DtMSbF7h9Fe%NB5=x*hL2le`{J{`~Po?Tx}2b|>o1c(+Ld z@ovGpwY$@G`$3p@;LEhX6Mt5`yYL>zyBqI5Q+SWzy*>Xh8t(z)+;6X)%N%$R?-9I* z@Z_H#&sEKG$q0(O^B%=}OoVkzwM9?hy^HrG-m7@hz0a7QF^2LNkXjb+S-cnUp2K_o zpEW27
7kI?pnl^uylHr2Z9eED1|!}u9EUgFhzW)h1&x@5H`xd~ z|M%qgp9}sJJoz@Kpx-fpshX5->{|F9-Urefc< 6VZ2KEeC1 zY53Goe*ancpBvgE08bwQ;C+SnwGs0B&yuu907`s^_q}k8`Get)n$)%MQ;% =A&L`VH^*Uarj2KdhiRf6e_1Pxkj5pRSBQOX(>2JuBhNhW~%~v*Ry`Z~ot(Q@dAs z-k%G9ZX@O~oYzo(YeKT-H(Wr_hz0Q%>d_11FJjC^dz{7a7dOrl$|;6V=CH530RB?= zOB=Ba{)$E{i@%%^%NwpBr~_e$zfwWtuZ&;8U&Ulr)m+tPTY$eh{u(;>eEIWV4biRk zDt_H$YK0m24dXX8DPr0i4sG4t@Lhb5 q z)G!k)<^z5QzmGB3#9s@4ZOu}v*1=y_E0t-Z!T*258ZKKw{ftA|OaHFn2_cy`c z$~c?iZ)QYad^`5{^|yfZ{bb+bf18c?{qUc}-x~iK{QmeC;%|dL5`SC#UGWFt>roH> zcK8EDn<>=2!0g!pf3R_O>~VH7;hoh;HF%evRlDIIgg*p-IR5VVd*bh*MT+s#whYDJ z3*QcZbXIFDjlVbk{`mV?75n0w|Ca?-?74o?CkGZ)sDJPe#y=AO5d0DNhnfM0X)R{0 zDLEYf2utcp#6JpOeLnut_{V5hOaBP!)IARWczp5v|5bUhM?2v}{Il^-!aogP%sW}L ztO@w1n82yM 6bMeo&8qd?JMWzt5dhsv9zZCys ztw^o9q{zj;4F5{}%ki(!QvaB775>#-n+wn5UyFY?{&o1b;$M${6aEeOH|nGkfo?4~ z tiqRC2k zTlyCMhxk+Qr&`wA`0r?Lw}+?UzmNYe{(IUv67uPANFPqe|DfA@CXD})W~oUZ<9|}n zHg7*8xC{St0w4bi{2BOP;{SmE75;blUw6YfUvB~MztuMC;xhj)^w1~pf5f+OIl2$N z`2R;u@@K