diff --git a/app/vendor/cakephp/bake/templates/bake/Template/login.twig b/app/vendor/cakephp/bake/templates/bake/Template/login.twig
index 32047213f..77f14d1cc 100644
--- a/app/vendor/cakephp/bake/templates/bake/Template/login.twig
+++ b/app/vendor/cakephp/bake/templates/bake/Template/login.twig
@@ -1,16 +1,16 @@
{#
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
#}
['primary' => ['type' => 'primary', 'columns' => ['id']]],
],
+ [
+ 'table' => 'todo_reminders',
+ 'columns' => [
+ 'id' => ['type' => 'integer', 'null' => false],
+ 'todo_item_id' => ['type' => 'integer', 'null' => false],
+ 'triggered_at' => ['type' => 'datetime'],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ 'unique_todo_item' => ['type' => 'unique', 'columns' => ['todo_item_id']],
+ ],
+ ],
[
'table' => 'todo_labels',
'columns' => [
@@ -511,4 +523,15 @@
],
],
],
+ [
+ 'table' => 'self_referencing_unique_keys',
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'parent_id' => ['type' => 'integer'],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ 'unique_self_referencing_parent' => ['type' => 'unique', 'columns' => ['parent_id']],
+ ],
+ ],
];
diff --git a/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml b/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml
index f3149de54..a9c5b62b7 100644
--- a/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml
+++ b/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml
@@ -54,9 +54,9 @@ jobs:
composer install
fi
- - name: Configure PHPUnit matcher
+ - name: Setup problem matchers for PHPUnit
if: matrix.php-version == '7.4'
- uses: mheap/phpunit-matcher-action@master
+ run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Run PHPUnit
run: |
diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml
index b85444eba..543481aa8 100644
--- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml
+++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml
@@ -23,6 +23,7 @@
*/config/Migrations/*
+ */config/Seeds/*
*/config/*
diff --git a/app/vendor/cakephp/cakephp-codesniffer/composer.json b/app/vendor/cakephp/cakephp-codesniffer/composer.json
index 2fd121bee..bb1e9380f 100644
--- a/app/vendor/cakephp/cakephp-codesniffer/composer.json
+++ b/app/vendor/cakephp/cakephp-codesniffer/composer.json
@@ -19,7 +19,7 @@
},
"require": {
"php": ">=7.2.0",
- "slevomat/coding-standard": "^6.3.6 || ^7.0",
+ "slevomat/coding-standard": "^6.3.6 || ^7.0 || ^8.0",
"squizlabs/php_codesniffer": "^3.6"
},
"require-dev": {
@@ -30,6 +30,11 @@
"CakePHP\\": "CakePHP/"
}
},
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ },
"scripts": {
"add-standard" : "phpcs --config-set installed_paths $(pwd)",
"test": [
diff --git a/app/vendor/cakephp/cakephp/README.md b/app/vendor/cakephp/cakephp/README.md
index 9cb846bb1..97ddeb14b 100644
--- a/app/vendor/cakephp/cakephp/README.md
+++ b/app/vendor/cakephp/cakephp/README.md
@@ -7,9 +7,6 @@
-
-
-
@@ -69,7 +66,7 @@ tests for CakePHP by doing the following:
## Get Support!
-* [Slack](https://cakesf.herokuapp.com/) - Join us on Slack.
+* [Slack](https://slack-invite.cakephp.org/) - Join us on Slack.
* [Discord](https://discord.gg/k4trEMPebj) - Join us on Discord.
* [#cakephp](https://webchat.freenode.net/?channels=#cakephp) on irc.freenode.net - Come chat with us, we have cake.
* [Forum](https://discourse.cakephp.org/) - Official CakePHP forum.
diff --git a/app/vendor/cakephp/cakephp/VERSION.txt b/app/vendor/cakephp/cakephp/VERSION.txt
index 181b6fa38..d69db1813 100644
--- a/app/vendor/cakephp/cakephp/VERSION.txt
+++ b/app/vendor/cakephp/cakephp/VERSION.txt
@@ -16,4 +16,4 @@
// @license https://opensource.org/licenses/mit-license.php MIT License
// +--------------------------------------------------------------------------------------------+ //
////////////////////////////////////////////////////////////////////////////////////////////////////
-4.3.8
+4.4.12
diff --git a/app/vendor/cakephp/cakephp/composer.json b/app/vendor/cakephp/cakephp/composer.json
index 9e5e15436..9a80cd6cb 100644
--- a/app/vendor/cakephp/cakephp/composer.json
+++ b/app/vendor/cakephp/cakephp/composer.json
@@ -22,14 +22,14 @@
}
],
"require": {
- "php": ">=7.2.0",
+ "php": ">=7.4.0",
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"cakephp/chronos": "^2.2",
"composer/ca-bundle": "^1.2",
"laminas/laminas-diactoros": "^2.2.2",
- "laminas/laminas-httphandlerrunner": "^1.1",
+ "laminas/laminas-httphandlerrunner": "^1.1 || ^2.0",
"league/container": "^4.2.0",
"psr/container": "^1.1 || ^2.0",
"psr/http-client": "^1.0",
@@ -67,6 +67,14 @@
"lib-ICU": "The intl PHP library, to use Text::transliterate() or Text::slug()",
"paragonie/csp-builder": "CSP builder, to use the CSP Middleware"
},
+ "provide": {
+ "psr/container-implementation": "^1.0 || ^2.0",
+ "psr/http-client-implementation": "^1.0",
+ "psr/http-server-handler-implementation": "^1.0",
+ "psr/http-server-middleware-implementation": "^1.0",
+ "psr/log-implementation": "^1.0 || ^2.0",
+ "psr/simple-cache-implementation": "^1.0 || ^2.0"
+ },
"config": {
"process-timeout": 900,
"sort-packages": true,
@@ -97,7 +105,7 @@
"TestPluginTwo\\": "tests/test_app/Plugin/TestPluginTwo/src/",
"Company\\TestPluginThree\\": "tests/test_app/Plugin/Company/TestPluginThree/src/",
"Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests/",
- "ParentPlugin\\": "tests/test_app/Plugin/ParentPlugin/src/"
+ "Named\\": "tests/test_app/Plugin/Named/src/"
}
},
"scripts": {
@@ -107,15 +115,15 @@
],
"cs-check": "phpcs --colors --parallel=16 -p src/ tests/",
"cs-fix": "phpcbf --colors --parallel=16 -p src/ tests/",
- "phpstan": "phpstan.phar analyse",
- "psalm": "psalm.phar --show-info=false",
+ "phpstan": "tools/phpstan analyse",
+ "psalm": "tools/psalm --show-info=false",
"stan": [
"@phpstan",
"@psalm"
],
"stan-tests": "phpstan.phar analyze -c tests/phpstan.neon",
"stan-baseline": "phpstan.phar --generate-baseline",
- "stan-setup": "cp composer.json composer.backup && composer require --dev symfony/polyfill-php81 phpstan/phpstan:~1.5.0 psalm/phar:~4.22.0 && mv composer.backup composer.json",
+ "stan-setup": "phive install",
"lowest": "validate-prefer-lowest",
"lowest-setup": "composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && cp composer.json composer.backup && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json",
"test": "phpunit",
diff --git a/app/vendor/cakephp/cakephp/config/bootstrap.php b/app/vendor/cakephp/cakephp/config/bootstrap.php
index 47b2d402d..da48e6bff 100644
--- a/app/vendor/cakephp/cakephp/config/bootstrap.php
+++ b/app/vendor/cakephp/cakephp/config/bootstrap.php
@@ -18,7 +18,7 @@
/**
* @var float
*/
-define('TIME_START', (float)microtime(true));
+define('TIME_START', microtime(true));
require CAKE . 'basics.php';
diff --git a/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php b/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php
index ed01098e3..da5bcc719 100644
--- a/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php
+++ b/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php
@@ -18,6 +18,7 @@
use Cake\Core\InstanceConfigTrait;
use DateInterval;
+use DateTime;
use Psr\SimpleCache\CacheInterface;
/**
@@ -383,7 +384,9 @@ protected function duration($ttl): int
return $ttl;
}
if ($ttl instanceof DateInterval) {
- return (int)$ttl->format('%s');
+ return (int)DateTime::createFromFormat('U', '0')
+ ->add($ttl)
+ ->format('U');
}
throw new InvalidArgumentException('TTL values must be one of null, int, \DateInterval');
diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php
index 9050537eb..4693c0242 100644
--- a/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php
+++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php
@@ -35,7 +35,7 @@ class ArrayEngine extends CacheEngine
*
* Structured as [key => [exp => expiration, val => value]]
*
- * @var array
+ * @var array
*/
protected $data = [];
diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php
index 46a7f187a..35252b98e 100644
--- a/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php
+++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php
@@ -17,7 +17,6 @@
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
-use Cake\Cache\InvalidArgumentException;
use CallbackFilterIterator;
use Exception;
use FilesystemIterator;
@@ -173,6 +172,7 @@ public function get($key, $default = null)
/** @psalm-suppress PossiblyNullReference */
$this->_File->rewind();
$time = time();
+ /** @psalm-suppress RiskyCast */
$cachetime = (int)$this->_File->current();
if ($cachetime < $time) {
@@ -441,14 +441,7 @@ protected function _key($key): string
{
$key = parent::_key($key);
- if (preg_match('/[\/\\<>?:|*"]/', $key)) {
- throw new InvalidArgumentException(
- "Cache key `{$key}` contains invalid characters. " .
- 'You cannot use /, \\, <, >, ?, :, |, *, or " in cache keys.'
- );
- }
-
- return $key;
+ return rawurlencode($key);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php
index b7f0fec4b..dc90ef72f 100644
--- a/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php
+++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php
@@ -47,6 +47,7 @@ class RedisEngine extends CacheEngine
* - `port` port number to the Redis server.
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
+ * - `scanCount` Number of keys to ask for each scan (default: 10)
* - `server` URL or IP to the Redis server host.
* - `timeout` timeout in seconds (float).
* - `unix_socket` Path to the unix socket file (default: false)
@@ -65,6 +66,7 @@ class RedisEngine extends CacheEngine
'server' => '127.0.0.1',
'timeout' => 0,
'unix_socket' => false,
+ 'scanCount' => 10,
];
/**
@@ -227,6 +229,21 @@ public function delete($key): bool
return $this->_Redis->del($key) > 0;
}
+ /**
+ * Delete a key from the cache asynchronously
+ *
+ * Just unlink a key from the cache. The actual removal will happen later asynchronously.
+ *
+ * @param string $key Identifier for the data
+ * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
+ */
+ public function deleteAsync(string $key): bool
+ {
+ $key = $this->_key($key);
+
+ return $this->_Redis->unlink($key) > 0;
+ }
+
/**
* Delete all keys from the cache
*
@@ -241,7 +258,7 @@ public function clear(): bool
$pattern = $this->_config['prefix'] . '*';
while (true) {
- $keys = $this->_Redis->scan($iterator, $pattern);
+ $keys = $this->_Redis->scan($iterator, $pattern, (int)$this->_config['scanCount']);
if ($keys === false) {
break;
@@ -256,6 +273,37 @@ public function clear(): bool
return $isAllDeleted;
}
+ /**
+ * Delete all keys from the cache by a blocking operation
+ *
+ * Faster than clear() using unlink method.
+ *
+ * @return bool True if the cache was successfully cleared, false otherwise
+ */
+ public function clearBlocking(): bool
+ {
+ $this->_Redis->setOption(Redis::OPT_SCAN, (string)Redis::SCAN_RETRY);
+
+ $isAllDeleted = true;
+ $iterator = null;
+ $pattern = $this->_config['prefix'] . '*';
+
+ while (true) {
+ $keys = $this->_Redis->scan($iterator, $pattern, (int)$this->_config['scanCount']);
+
+ if ($keys === false) {
+ break;
+ }
+
+ foreach ($keys as $key) {
+ $isDeleted = ($this->_Redis->unlink($key) > 0);
+ $isAllDeleted = $isAllDeleted && $isDeleted;
+ }
+ }
+
+ return $isAllDeleted;
+ }
+
/**
* Write data for key into cache if it doesn't exist already.
* If it already exists, it fails and returns false.
diff --git a/app/vendor/cakephp/cakephp/src/Cache/composer.json b/app/vendor/cakephp/cakephp/src/Cache/composer.json
index 001a8a9dc..7f20c1e9a 100644
--- a/app/vendor/cakephp/cakephp/src/Cache/composer.json
+++ b/app/vendor/cakephp/cakephp/src/Cache/composer.json
@@ -22,12 +22,12 @@
"source": "https://github.com/cakephp/cache"
},
"require": {
- "php": ">=7.2.0",
+ "php": ">=7.4.0",
"cakephp/core": "^4.0",
"psr/simple-cache": "^1.0 || ^2.0"
},
"provide": {
- "psr/simple-cache-implementation": "^1.0.0"
+ "psr/simple-cache-implementation": "^1.0 || ^2.0"
},
"autoload": {
"psr-4": {
diff --git a/app/vendor/cakephp/cakephp/src/Collection/Collection.php b/app/vendor/cakephp/cakephp/src/Collection/Collection.php
index d3af8c0f0..147d41302 100644
--- a/app/vendor/cakephp/cakephp/src/Collection/Collection.php
+++ b/app/vendor/cakephp/cakephp/src/Collection/Collection.php
@@ -17,6 +17,7 @@
namespace Cake\Collection;
use ArrayIterator;
+use Exception;
use IteratorIterator;
use Serializable;
@@ -120,8 +121,14 @@ public function countKeys(): int
*/
public function __debugInfo(): array
{
+ try {
+ $count = $this->count();
+ } catch (Exception $e) {
+ $count = 'An exception occurred while getting count';
+ }
+
return [
- 'count' => $this->count(),
+ 'count' => $count,
];
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Collection/composer.json b/app/vendor/cakephp/cakephp/src/Collection/composer.json
index 87c05665e..16ec80218 100644
--- a/app/vendor/cakephp/cakephp/src/Collection/composer.json
+++ b/app/vendor/cakephp/cakephp/src/Collection/composer.json
@@ -23,7 +23,7 @@
"source": "https://github.com/cakephp/collection"
},
"require": {
- "php": ">=7.2.0"
+ "php": ">=7.4.0"
},
"autoload": {
"psr-4": {
diff --git a/app/vendor/cakephp/cakephp/src/Command/Command.php b/app/vendor/cakephp/cakephp/src/Command/Command.php
index 5c6afc026..1ab0c3c49 100644
--- a/app/vendor/cakephp/cakephp/src/Command/Command.php
+++ b/app/vendor/cakephp/cakephp/src/Command/Command.php
@@ -30,6 +30,7 @@
* Includes traits that integrate logging
* and ORM models to console commands.
*/
+#[\AllowDynamicProperties]
class Command extends BaseCommand
{
use LocatorAwareTrait;
@@ -67,3 +68,10 @@ public function execute(Arguments $args, ConsoleIo $io)
{
}
}
+
+// phpcs:disable
+class_alias(
+ 'Cake\Command\Command',
+ 'Cake\Console\Command'
+);
+// phpcs:enable
diff --git a/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php b/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php
index 891f6d8e8..724783bcf 100644
--- a/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php
+++ b/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php
@@ -147,8 +147,6 @@ protected function _getPaths(ConsoleIo $io): void
if (strtoupper($response) === 'Q') {
$io->err('Extract Aborted');
$this->abort();
-
- return;
}
if (strtoupper($response) === 'D' && count($this->_paths)) {
$io->out();
@@ -832,7 +830,11 @@ protected function _searchFiles(): void
}
foreach ($this->_paths as $path) {
- $path = realpath($path) . DIRECTORY_SEPARATOR;
+ $path = realpath($path);
+ if ($path === false) {
+ continue;
+ }
+ $path .= DIRECTORY_SEPARATOR;
$fs = new Filesystem();
$files = $fs->findRecursive($path, '/\.php$/');
$files = array_keys(iterator_to_array($files));
diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php
index b4122832e..912855011 100644
--- a/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php
+++ b/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php
@@ -109,6 +109,14 @@ protected function modifyApplication(string $app, string $plugin): void
$this->abort();
}
+ // Check if plugin is already loaded
+ $regex = '/->addPlugin\(\'' . $plugin . '\'/mu';
+ if (preg_match($regex, $contents, $otherMatches, PREG_OFFSET_CAPTURE)) {
+ $this->io->info('The specified plugin is already loaded!');
+
+ return;
+ }
+
$append = "$indent \$this->addPlugin('%s');\n";
$insert = str_replace(', []', '', sprintf($append, $plugin));
diff --git a/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php b/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php
index b660e4d68..86245a5c7 100644
--- a/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php
+++ b/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php
@@ -58,7 +58,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
}
}
- unset($route['_matchedRoute']);
+ unset($route['_route'], $route['_matchedRoute']);
ksort($route);
$output = [
diff --git a/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php b/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php
index e3a20b9f1..66d9814de 100644
--- a/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php
+++ b/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php
@@ -40,10 +40,11 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
$header[] = 'Defaults';
}
- $output = [];
+ $availableRoutes = Router::routes();
+ $output = $duplicateRoutesCounter = [];
- foreach (Router::routes() as $route) {
- $methods = $route->defaults['_method'] ?? '';
+ foreach ($availableRoutes as $route) {
+ $methods = isset($route->defaults['_method']) ? (array)$route->defaults['_method'] : [''];
$item = [
$route->options['_name'] ?? $route->getName(),
@@ -52,7 +53,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
$route->defaults['prefix'] ?? '',
$route->defaults['controller'] ?? '',
$route->defaults['action'] ?? '',
- is_string($methods) ? $methods : implode(', ', $route->defaults['_method']),
+ implode(', ', $methods),
];
if ($args->getOption('verbose')) {
@@ -61,6 +62,14 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
}
$output[] = $item;
+
+ foreach ($methods as $method) {
+ if (!isset($duplicateRoutesCounter[$route->template][$method])) {
+ $duplicateRoutesCounter[$route->template][$method] = 0;
+ }
+
+ $duplicateRoutesCounter[$route->template][$method]++;
+ }
}
if ($args->getOption('sort')) {
@@ -74,6 +83,39 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
$io->helper('table')->output($output);
$io->out();
+ $duplicateRoutes = [];
+
+ foreach ($availableRoutes as $route) {
+ $methods = isset($route->defaults['_method']) ? (array)$route->defaults['_method'] : [''];
+
+ foreach ($methods as $method) {
+ if (
+ $duplicateRoutesCounter[$route->template][$method] > 1 ||
+ ($method === '' && count($duplicateRoutesCounter[$route->template]) > 1) ||
+ ($method !== '' && isset($duplicateRoutesCounter[$route->template]['']))
+ ) {
+ $duplicateRoutes[] = [
+ $route->options['_name'] ?? $route->getName(),
+ $route->template,
+ $route->defaults['plugin'] ?? '',
+ $route->defaults['prefix'] ?? '',
+ $route->defaults['controller'] ?? '',
+ $route->defaults['action'] ?? '',
+ implode(', ', $methods),
+ ];
+
+ break;
+ }
+ }
+ }
+
+ if ($duplicateRoutes) {
+ array_unshift($duplicateRoutes, $header);
+ $io->warning('The following possible route collisions were detected.');
+ $io->helper('table')->output($duplicateRoutes);
+ $io->out();
+ }
+
return static::CODE_SUCCESS;
}
diff --git a/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php b/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php
index ad4866bfe..c927ccf80 100644
--- a/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php
+++ b/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php
@@ -65,6 +65,16 @@ public function getName(): string
return $this->name;
}
+ /**
+ * Get the command description.
+ *
+ * @return string
+ */
+ public static function getDescription(): string
+ {
+ return '';
+ }
+
/**
* Get the root command name.
*
@@ -91,9 +101,8 @@ public static function defaultName(): string
$pos = strrpos(static::class, '\\');
/** @psalm-suppress PossiblyFalseOperand */
$name = substr(static::class, $pos + 1, -7);
- $name = Inflector::underscore($name);
- return $name;
+ return Inflector::underscore($name);
}
/**
@@ -109,6 +118,7 @@ public function getOptionParser(): ConsoleOptionParser
[$root, $name] = explode(' ', $this->name, 2);
$parser = new ConsoleOptionParser($name);
$parser->setRootName($root);
+ $parser->setDescription(static::getDescription());
$parser = $this->buildOptionParser($parser);
if ($parser->subcommands()) {
@@ -153,7 +163,7 @@ public function run(array $argv, ConsoleIo $io): ?int
$parser = $this->getOptionParser();
try {
- [$options, $arguments] = $parser->parse($argv);
+ [$options, $arguments] = $parser->parse($argv, $io);
$args = new Arguments(
$arguments,
$options,
@@ -233,6 +243,7 @@ abstract public function execute(Arguments $args, ConsoleIo $io);
* @param int $code The exit code to use.
* @throws \Cake\Console\Exception\StopException
* @return void
+ * @psalm-return never-return
*/
public function abort(int $code = self::CODE_ERROR): void
{
diff --git a/app/vendor/cakephp/cakephp/src/Console/Command.php b/app/vendor/cakephp/cakephp/src/Console/Command.php
index f593ff9bc..9494b024a 100644
--- a/app/vendor/cakephp/cakephp/src/Console/Command.php
+++ b/app/vendor/cakephp/cakephp/src/Console/Command.php
@@ -4,8 +4,4 @@
/**
* @deprecated 4.0.0 Use {@link \Cake\Command\Command} instead.
*/
-
-class_alias(
- 'Cake\Command\Command',
- 'Cake\Console\Command'
-);
+class_exists('Cake\Command\Command');
diff --git a/app/vendor/cakephp/cakephp/src/Console/Command/HelpCommand.php b/app/vendor/cakephp/cakephp/src/Console/Command/HelpCommand.php
index 9999518f7..8f96f1a86 100644
--- a/app/vendor/cakephp/cakephp/src/Console/Command/HelpCommand.php
+++ b/app/vendor/cakephp/cakephp/src/Console/Command/HelpCommand.php
@@ -112,7 +112,10 @@ protected function asText(ConsoleIo $io, iterable $commands): void
[, $shortestName] = explode('.', $shortestName, 2);
}
- $grouped[$prefix][] = $shortestName;
+ $grouped[$prefix][] = [
+ 'name' => $shortestName,
+ 'description' => is_subclass_of($class, BaseCommand::class) ? $class::getDescription() : '',
+ ];
}
ksort($grouped);
@@ -122,8 +125,11 @@ protected function asText(ConsoleIo $io, iterable $commands): void
foreach ($grouped as $prefix => $names) {
$io->out("{$prefix}:");
sort($names);
- foreach ($names as $name) {
- $io->out(' - ' . $name);
+ foreach ($names as $data) {
+ $io->out(' - ' . $data['name']);
+ if ($data['description']) {
+ $io->info(str_pad(" \u{2514}", 13, "\u{2500}") . ' ' . $data['description']);
+ }
}
$io->out('');
}
diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php b/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php
index a3ac801c5..47aa17db2 100644
--- a/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php
+++ b/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandCollectionAwareInterface.php b/app/vendor/cakephp/cakephp/src/Console/CommandCollectionAwareInterface.php
index 7892bc677..5c2e457c4 100644
--- a/app/vendor/cakephp/cakephp/src/Console/CommandCollectionAwareInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Console/CommandCollectionAwareInterface.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php b/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php
index 20c8367a2..364bb4b16 100644
--- a/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php
+++ b/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php
@@ -2,15 +2,15 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php b/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php
index 7be77a84f..67284a416 100644
--- a/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php
@@ -2,15 +2,15 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php b/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php
index 4d6fdfd43..8e84e7ea0 100644
--- a/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php
+++ b/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php b/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php
index a704e4112..d086679af 100644
--- a/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php
+++ b/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php
index 977aa7071..cac0d22f5 100644
--- a/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php
+++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php
@@ -1,10 +1,7 @@
usage() === $argument->usage();
+ return $this->name() === $argument->name() &&
+ $this->usage() === $argument->usage();
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php
index 21a253dd8..b6853726c 100644
--- a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php
+++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php
@@ -76,6 +76,13 @@ class ConsoleInputOption
*/
protected $_choices;
+ /**
+ * The prompt string
+ *
+ * @var string|null
+ */
+ protected $prompt;
+
/**
* Is the option required.
*
@@ -94,6 +101,7 @@ class ConsoleInputOption
* @param array $choices Valid choices for this option.
* @param bool $multiple Whether this option can accept multiple value definition.
* @param bool $required Whether this option is required or not.
+ * @param string|null $prompt The prompt string.
* @throws \Cake\Console\Exception\ConsoleException
*/
public function __construct(
@@ -104,7 +112,8 @@ public function __construct(
$default = null,
array $choices = [],
bool $multiple = false,
- bool $required = false
+ bool $required = false,
+ ?string $prompt = null
) {
$this->_name = $name;
$this->_short = $short;
@@ -113,6 +122,7 @@ public function __construct(
$this->_choices = $choices;
$this->_multiple = $multiple;
$this->required = $required;
+ $this->prompt = $prompt;
if ($isBoolean) {
$this->_default = (bool)$default;
@@ -125,6 +135,12 @@ public function __construct(
sprintf('Short option "%s" is invalid, short options must be one letter.', $this->_short)
);
}
+ if (isset($this->_default) && $this->prompt) {
+ throw new ConsoleException(
+ 'You cannot set both `prompt` and `default` options. ' .
+ 'Use either a static `default` or interactive `prompt`'
+ );
+ }
}
/**
@@ -266,6 +282,26 @@ public function validChoice($value): bool
return true;
}
+ /**
+ * Get the list of choices this option has.
+ *
+ * @return array
+ */
+ public function choices(): array
+ {
+ return $this->_choices;
+ }
+
+ /**
+ * Get the prompt string
+ *
+ * @return string
+ */
+ public function prompt(): string
+ {
+ return (string)$this->prompt;
+ }
+
/**
* Append the option's XML into the parent.
*
diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php
index 704f7951a..583873800 100644
--- a/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php
+++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php
@@ -298,6 +298,7 @@ public function success($message, int $newlines = 1, int $level = self::NORMAL):
* @param string $message Error message.
* @param int $code Error code.
* @return void
+ * @psalm-return never-return
* @throws \Cake\Console\Exception\StopException
*/
public function abort($message, $code = CommandInterface::CODE_ERROR): void
diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php
index 77044ffce..b20775a1d 100644
--- a/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php
+++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php
@@ -245,7 +245,7 @@ public static function buildFromArray(array $spec, bool $defaultOptions = true)
*/
public function toArray(): array
{
- $result = [
+ return [
'command' => $this->_command,
'arguments' => $this->_args,
'options' => $this->_options,
@@ -253,8 +253,6 @@ public function toArray(): array
'description' => $this->_description,
'epilog' => $this->_epilog,
];
-
- return $result;
}
/**
@@ -426,6 +424,7 @@ public function addOption($name, array $options = [])
'multiple' => false,
'choices' => [],
'required' => false,
+ 'prompt' => null,
];
$options += $defaults;
$option = new ConsoleInputOption(
@@ -436,7 +435,8 @@ public function addOption($name, array $options = [])
$options['default'],
$options['choices'],
$options['multiple'],
- $options['required']
+ $options['required'],
+ $options['prompt']
);
}
$this->_options[$name] = $option;
@@ -677,10 +677,11 @@ public function subcommands()
* to parse the $argv
*
* @param array $argv Array of args (argv) to parse.
+ * @param \Cake\Console\ConsoleIo|null $io A ConsoleIo instance or null. If null prompt options will error.
* @return array [$params, $args]
* @throws \Cake\Console\Exception\ConsoleException When an invalid parameter is encountered.
*/
- public function parse(array $argv): array
+ public function parse(array $argv, ?ConsoleIo $io = null): array
{
$command = isset($argv[0]) ? Inflector::underscore($argv[0]) : null;
if (isset($this->_subcommands[$command])) {
@@ -688,7 +689,7 @@ public function parse(array $argv): array
}
if (isset($this->_subcommands[$command]) && $this->_subcommands[$command]->parser()) {
/** @psalm-suppress PossiblyNullReference */
- return $this->_subcommands[$command]->parser()->parse($argv);
+ return $this->_subcommands[$command]->parser()->parse($argv, $io);
}
$params = $args = [];
$this->_tokens = $argv;
@@ -722,12 +723,29 @@ public function parse(array $argv): array
$isBoolean = $option->isBoolean();
$default = $option->defaultValue();
- if ($default !== null && !isset($params[$name]) && !$isBoolean) {
+ $useDefault = !isset($params[$name]);
+ if ($default !== null && $useDefault && !$isBoolean) {
$params[$name] = $default;
}
- if ($isBoolean && !isset($params[$name])) {
+ if ($isBoolean && $useDefault) {
$params[$name] = false;
}
+ $prompt = $option->prompt();
+ if (!isset($params[$name]) && $prompt) {
+ if (!$io) {
+ throw new ConsoleException(
+ 'Cannot use interactive option prompts without a ConsoleIo instance. ' .
+ 'Please provide a `$io` parameter to `parse()`.'
+ );
+ }
+ $choices = $option->choices();
+ if ($choices) {
+ $value = $io->askChoice($prompt, $choices);
+ } else {
+ $value = $io->ask($prompt);
+ }
+ $params[$name] = $value;
+ }
if ($option->isRequired() && !isset($params[$name])) {
throw new ConsoleException(
sprintf('Missing required option. The `%s` option is required and has no default value.', $name)
diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php
index d87fbb390..c11101875 100644
--- a/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php
+++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php
@@ -214,17 +214,25 @@ public function styleText(string $text): string
if ($this->_outputAs === static::RAW) {
return $text;
}
- if ($this->_outputAs === static::PLAIN) {
- $tags = implode('|', array_keys(static::$_styles));
+ if ($this->_outputAs !== static::PLAIN) {
+ $output = preg_replace_callback(
+ '/<(?P[a-z0-9-_]+)>(?P.*?)<\/(\1)>/ims',
+ [$this, '_replaceTags'],
+ $text
+ );
+ if ($output !== null) {
+ return $output;
+ }
+ }
- return preg_replace('#?(?:' . $tags . ')>#', '', $text);
+ $tags = implode('|', array_keys(static::$_styles));
+ $output = preg_replace('#?(?:' . $tags . ')>#', '', $text);
+
+ if ($output === null) {
+ return $text;
}
- return preg_replace_callback(
- '/<(?P[a-z0-9-_]+)>(?P.*?)<\/(\1)>/ims',
- [$this, '_replaceTags'],
- $text
- );
+ return $output;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Console/Shell.php b/app/vendor/cakephp/cakephp/src/Console/Shell.php
index 0fae3a508..636216766 100644
--- a/app/vendor/cakephp/cakephp/src/Console/Shell.php
+++ b/app/vendor/cakephp/cakephp/src/Console/Shell.php
@@ -40,6 +40,7 @@
* @deprecated 3.6.0 ShellDispatcher and Shell will be removed in 5.0
* @method int|bool|null|void main(...$args) Main entry method for the shell.
*/
+#[\AllowDynamicProperties]
class Shell
{
use LocatorAwareTrait;
@@ -136,7 +137,7 @@ class Shell
* Contains tasks to load and instantiate
*
* @var array|bool
- * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::$tasks
+ * @link https://book.cakephp.org/4/en/console-commands/shells.html#shell-tasks
*/
public $tasks = [];
@@ -180,7 +181,7 @@ class Shell
*
* @param \Cake\Console\ConsoleIo|null $io An io instance.
* @param \Cake\ORM\Locator\LocatorInterface|null $locator Table locator instance.
- * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell
+ * @link https://book.cakephp.org/4/en/console-commands/shells.html
*/
public function __construct(?ConsoleIo $io = null, ?LocatorInterface $locator = null)
{
@@ -468,7 +469,7 @@ public function runCommand(array $argv, bool $autoMethod = false, array $extra =
$command = isset($argv[0]) ? Inflector::underscore($argv[0]) : null;
$this->OptionParser = $this->getOptionParser();
try {
- [$this->params, $this->args] = $this->OptionParser->parse($argv);
+ [$this->params, $this->args] = $this->OptionParser->parse($argv, $this->_io);
} catch (ConsoleException $e) {
$this->err('Error: ' . $e->getMessage());
diff --git a/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php b/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php
index 79f4fcedf..2075784b0 100644
--- a/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php
+++ b/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php
@@ -179,9 +179,7 @@ public function dispatch(array $extra = []): int
try {
$result = $this->_dispatch($extra);
} catch (StopException $e) {
- $code = $e->getCode();
-
- return $code;
+ return $e->getCode();
}
if ($result === null || $result === true) {
/** @psalm-suppress DeprecatedClass */
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php
new file mode 100644
index 000000000..f5f756fc7
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php
@@ -0,0 +1,345 @@
+makeRunner();
+
+ if ($this->_out === null) {
+ $this->_out = new StubConsoleOutput();
+ }
+ if ($this->_err === null) {
+ $this->_err = new StubConsoleOutput();
+ }
+ if ($this->_in === null) {
+ $this->_in = new StubConsoleInput($input);
+ } elseif ($input) {
+ throw new RuntimeException('You can use `$input` only if `$_in` property is null and will be reset.');
+ }
+
+ $args = $this->commandStringToArgs("cake $command");
+ $io = new ConsoleIo($this->_out, $this->_err, $this->_in);
+
+ try {
+ $this->_exitCode = $runner->run($args, $io);
+ } catch (MissingConsoleInputException $e) {
+ $messages = $this->_out->messages();
+ if (count($messages)) {
+ $e->setQuestion($messages[count($messages) - 1]);
+ }
+ throw $e;
+ } catch (StopException $exception) {
+ $this->_exitCode = $exception->getCode();
+ }
+ }
+
+ /**
+ * Cleans state to get ready for the next test
+ *
+ * @after
+ * @return void
+ * @psalm-suppress PossiblyNullPropertyAssignmentValue
+ */
+ public function cleanupConsoleTrait(): void
+ {
+ $this->_exitCode = null;
+ $this->_out = null;
+ $this->_err = null;
+ $this->_in = null;
+ $this->_useCommandRunner = false;
+ }
+
+ /**
+ * Set this test case to use the CommandRunner rather than the legacy
+ * ShellDispatcher
+ *
+ * @return void
+ */
+ public function useCommandRunner(): void
+ {
+ $this->_useCommandRunner = true;
+ }
+
+ /**
+ * Asserts shell exited with the expected code
+ *
+ * @param int $expected Expected exit code
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertExitCode(int $expected, string $message = ''): void
+ {
+ $this->assertThat($expected, new ExitCode($this->_exitCode), $message);
+ }
+
+ /**
+ * Asserts shell exited with the Command::CODE_SUCCESS
+ *
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertExitSuccess($message = '')
+ {
+ $this->assertThat(Command::CODE_SUCCESS, new ExitCode($this->_exitCode), $message);
+ }
+
+ /**
+ * Asserts shell exited with Command::CODE_ERROR
+ *
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertExitError($message = '')
+ {
+ $this->assertThat(Command::CODE_ERROR, new ExitCode($this->_exitCode), $message);
+ }
+
+ /**
+ * Asserts that `stdout` is empty
+ *
+ * @param string $message The message to output when the assertion fails.
+ * @return void
+ */
+ public function assertOutputEmpty(string $message = ''): void
+ {
+ $this->assertThat(null, new ContentsEmpty($this->_out->messages(), 'output'), $message);
+ }
+
+ /**
+ * Asserts `stdout` contains expected output
+ *
+ * @param string $expected Expected output
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertOutputContains(string $expected, string $message = ''): void
+ {
+ $this->assertThat($expected, new ContentsContain($this->_out->messages(), 'output'), $message);
+ }
+
+ /**
+ * Asserts `stdout` does not contain expected output
+ *
+ * @param string $expected Expected output
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertOutputNotContains(string $expected, string $message = ''): void
+ {
+ $this->assertThat($expected, new ContentsNotContain($this->_out->messages(), 'output'), $message);
+ }
+
+ /**
+ * Asserts `stdout` contains expected regexp
+ *
+ * @param string $pattern Expected pattern
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertOutputRegExp(string $pattern, string $message = ''): void
+ {
+ $this->assertThat($pattern, new ContentsRegExp($this->_out->messages(), 'output'), $message);
+ }
+
+ /**
+ * Check that a row of cells exists in the output.
+ *
+ * @param array $row Row of cells to ensure exist in the output.
+ * @param string $message Failure message.
+ * @return void
+ */
+ protected function assertOutputContainsRow(array $row, string $message = ''): void
+ {
+ $this->assertThat($row, new ContentsContainRow($this->_out->messages(), 'output'), $message);
+ }
+
+ /**
+ * Asserts `stderr` contains expected output
+ *
+ * @param string $expected Expected output
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertErrorContains(string $expected, string $message = ''): void
+ {
+ $this->assertThat($expected, new ContentsContain($this->_err->messages(), 'error output'), $message);
+ }
+
+ /**
+ * Asserts `stderr` contains expected regexp
+ *
+ * @param string $pattern Expected pattern
+ * @param string $message Failure message
+ * @return void
+ */
+ public function assertErrorRegExp(string $pattern, string $message = ''): void
+ {
+ $this->assertThat($pattern, new ContentsRegExp($this->_err->messages(), 'error output'), $message);
+ }
+
+ /**
+ * Asserts that `stderr` is empty
+ *
+ * @param string $message The message to output when the assertion fails.
+ * @return void
+ */
+ public function assertErrorEmpty(string $message = ''): void
+ {
+ $this->assertThat(null, new ContentsEmpty($this->_err->messages(), 'error output'), $message);
+ }
+
+ /**
+ * Builds the appropriate command dispatcher
+ *
+ * @return \Cake\Console\CommandRunner|\Cake\Console\TestSuite\LegacyCommandRunner
+ */
+ protected function makeRunner()
+ {
+ if ($this->_useCommandRunner) {
+ /** @var \Cake\Core\ConsoleApplicationInterface $app */
+ $app = $this->createApp();
+
+ return new CommandRunner($app);
+ }
+
+ return new LegacyCommandRunner();
+ }
+
+ /**
+ * Creates an $argv array from a command string
+ *
+ * @param string $command Command string
+ * @return array
+ */
+ protected function commandStringToArgs(string $command): array
+ {
+ $charCount = strlen($command);
+ $argv = [];
+ $arg = '';
+ $inDQuote = false;
+ $inSQuote = false;
+ for ($i = 0; $i < $charCount; $i++) {
+ $char = substr($command, $i, 1);
+
+ // end of argument
+ if ($char === ' ' && !$inDQuote && !$inSQuote) {
+ if ($arg !== '') {
+ $argv[] = $arg;
+ }
+ $arg = '';
+ continue;
+ }
+
+ // exiting single quote
+ if ($inSQuote && $char === "'") {
+ $inSQuote = false;
+ continue;
+ }
+
+ // exiting double quote
+ if ($inDQuote && $char === '"') {
+ $inDQuote = false;
+ continue;
+ }
+
+ // entering double quote
+ if ($char === '"' && !$inSQuote) {
+ $inDQuote = true;
+ continue;
+ }
+
+ // entering single quote
+ if ($char === "'" && !$inDQuote) {
+ $inSQuote = true;
+ continue;
+ }
+
+ $arg .= $char;
+ }
+ $argv[] = $arg;
+
+ return $argv;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php
new file mode 100644
index 000000000..8ea9d2619
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php
@@ -0,0 +1,48 @@
+ $contents Contents
+ * @param string $output Output type
+ */
+ public function __construct(array $contents, string $output)
+ {
+ $this->contents = implode(PHP_EOL, $contents);
+ $this->output = $output;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php
new file mode 100644
index 000000000..5dc3942e4
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php
@@ -0,0 +1,45 @@
+contents, $other) !== false;
+ }
+
+ /**
+ * Assertion message
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return sprintf('is in %s,' . PHP_EOL . 'actual result:' . PHP_EOL, $this->output) . $this->contents;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php
new file mode 100644
index 000000000..a1b9edd08
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php
@@ -0,0 +1,61 @@
+contents) > 0;
+ }
+
+ /**
+ * Assertion message
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return sprintf('row was in %s', $this->output);
+ }
+
+ /**
+ * @param mixed $other Expected content
+ * @return string
+ */
+ public function failureDescription($other): string
+ {
+ return '`' . $this->exporter()->shortenedExport($other) . '` ' . $this->toString();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php
new file mode 100644
index 000000000..015d68bbc
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php
@@ -0,0 +1,56 @@
+contents === '';
+ }
+
+ /**
+ * Assertion message
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return sprintf('%s is empty', $this->output);
+ }
+
+ /**
+ * Overwrites the descriptions so we can remove the automatic "expected" message
+ *
+ * @param mixed $other Value
+ * @return string
+ */
+ protected function failureDescription($other): string
+ {
+ return $this->toString();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php
new file mode 100644
index 000000000..47855c520
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php
@@ -0,0 +1,45 @@
+contents, $other) === false;
+ }
+
+ /**
+ * Assertion message
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return sprintf('is not in %s', $this->output);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php
new file mode 100644
index 000000000..e3a3fb6c7
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php
@@ -0,0 +1,54 @@
+contents) > 0;
+ }
+
+ /**
+ * Assertion message
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return sprintf('PCRE pattern found in %s', $this->output);
+ }
+
+ /**
+ * @param mixed $other Expected
+ * @return string
+ */
+ public function failureDescription($other): string
+ {
+ return '`' . $other . '` ' . $this->toString();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php
new file mode 100644
index 000000000..4e7f01edb
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php
@@ -0,0 +1,62 @@
+exitCode = $exitCode;
+ }
+
+ /**
+ * Checks if event is in fired array
+ *
+ * @param mixed $other Constraint check
+ * @return bool
+ */
+ public function matches($other): bool
+ {
+ return $other === $this->exitCode;
+ }
+
+ /**
+ * Assertion message string
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return sprintf('matches exit code %s', $this->exitCode ?? 'null');
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyCommandRunner.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyCommandRunner.php
new file mode 100644
index 000000000..8cbb5542a
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyCommandRunner.php
@@ -0,0 +1,39 @@
+dispatch();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyShellDispatcher.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyShellDispatcher.php
new file mode 100644
index 000000000..59caba325
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyShellDispatcher.php
@@ -0,0 +1,64 @@
+_io = $io;
+ parent::__construct($args, $bootstrap);
+ }
+
+ /**
+ * Injects mock and stub io components into the shell
+ *
+ * @param string $className Class name
+ * @param string $shortName Short name
+ * @return \Cake\Console\Shell
+ */
+ protected function _createShell(string $className, string $shortName): Shell
+ {
+ [$plugin] = pluginSplit($shortName);
+ /** @var \Cake\Console\Shell $instance */
+ $instance = new $className($this->_io);
+ if ($plugin) {
+ $instance->plugin = trim($plugin, '.');
+ }
+
+ return $instance;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php
new file mode 100644
index 000000000..a76d36f63
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php
@@ -0,0 +1,39 @@
+message .= "\nThe question asked was: " . $question;
+ }
+}
+
+// phpcs:disable
+class_alias(MissingConsoleInputException::class, 'Cake\TestSuite\Stub\MissingConsoleInputException');
+// phpcs:enable
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php
new file mode 100644
index 000000000..96a83198f
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php
@@ -0,0 +1,88 @@
+
+ */
+ protected $replies = [];
+
+ /**
+ * Current message index
+ *
+ * @var int
+ */
+ protected $currentIndex = -1;
+
+ /**
+ * Constructor
+ *
+ * @param array $replies A list of replies for read()
+ */
+ public function __construct(array $replies)
+ {
+ parent::__construct();
+
+ $this->replies = $replies;
+ }
+
+ /**
+ * Read a reply
+ *
+ * @return string The value of the reply
+ */
+ public function read(): string
+ {
+ $this->currentIndex += 1;
+
+ if (!isset($this->replies[$this->currentIndex])) {
+ $total = count($this->replies);
+ $formatter = new NumberFormatter('en', NumberFormatter::ORDINAL);
+ $nth = $formatter->format($this->currentIndex + 1);
+
+ $replies = implode(', ', $this->replies);
+ $message = "There are no more input replies available. This is the {$nth} read operation, " .
+ "only {$total} replies were set.\nThe provided replies are: {$replies}";
+ throw new MissingConsoleInputException($message);
+ }
+
+ return $this->replies[$this->currentIndex];
+ }
+
+ /**
+ * Check if data is available on stdin
+ *
+ * @param int $timeout An optional time to wait for data
+ * @return bool True for data available, false otherwise
+ */
+ public function dataAvailable($timeout = 0): bool
+ {
+ return true;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php
new file mode 100644
index 000000000..280ea3e7d
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php
@@ -0,0 +1,84 @@
+
+ */
+ protected $_out = [];
+
+ /**
+ * Write output to the buffer.
+ *
+ * @param array|string $message A string or an array of strings to output
+ * @param int $newlines Number of newlines to append
+ * @return int
+ */
+ public function write($message, int $newlines = 1): int
+ {
+ foreach ((array)$message as $line) {
+ $this->_out[] = $line;
+ }
+
+ $newlines--;
+ while ($newlines > 0) {
+ $this->_out[] = '';
+ $newlines--;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get the buffered output.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return $this->_out;
+ }
+
+ /**
+ * Get the output as a string
+ *
+ * @return string
+ */
+ public function output(): string
+ {
+ return implode("\n", $this->_out);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Console/composer.json b/app/vendor/cakephp/cakephp/src/Console/composer.json
index 971d9a610..d7d392b50 100644
--- a/app/vendor/cakephp/cakephp/src/Console/composer.json
+++ b/app/vendor/cakephp/cakephp/src/Console/composer.json
@@ -23,7 +23,7 @@
"source": "https://github.com/cakephp/console"
},
"require": {
- "php": ">=7.2.0",
+ "php": ">=7.4.0",
"cakephp/core": "^4.0",
"cakephp/event": "^4.0",
"cakephp/filesystem": "^4.0",
diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component.php b/app/vendor/cakephp/cakephp/src/Controller/Component.php
index 52540c039..1ebafbd59 100644
--- a/app/vendor/cakephp/cakephp/src/Controller/Component.php
+++ b/app/vendor/cakephp/cakephp/src/Controller/Component.php
@@ -58,6 +58,7 @@
* @link https://book.cakephp.org/4/en/controllers/components.html
* @see \Cake\Controller\Controller::$components
*/
+#[\AllowDynamicProperties]
class Component implements EventListenerInterface
{
use InstanceConfigTrait;
diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php
new file mode 100644
index 000000000..e546b6e7c
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php
@@ -0,0 +1,54 @@
+getController();
+ $response = $controller->getResponse();
+ $request = $controller->getRequest();
+ if (!$response->isNotModified($request)) {
+ return;
+ }
+
+ $controller->setResponse($response->withNotModified());
+ $event->stopPropagation();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php
index 715ed486a..85e27729a 100644
--- a/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php
+++ b/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php
@@ -18,8 +18,8 @@
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
-use Cake\Datasource\Exception\PageOutOfBoundsException;
-use Cake\Datasource\Paginator;
+use Cake\Datasource\Paging\Exception\PageOutOfBoundsException;
+use Cake\Datasource\Paging\NumericPaginator;
use Cake\Datasource\ResultSetInterface;
use Cake\Http\Exception\NotFoundException;
use InvalidArgumentException;
@@ -34,14 +34,15 @@
* You configure pagination when calling paginate(). See that method for more details.
*
* @link https://book.cakephp.org/4/en/controllers/components/pagination.html
- * @mixin \Cake\Datasource\Paginator
+ * @mixin \Cake\Datasource\Paging\NumericPaginator
+ * @deprecated 4.4.0 Use Cake\Datasource\Paging\Paginator directly.
*/
class PaginatorComponent extends Component
{
/**
* Datasource paginator instance.
*
- * @var \Cake\Datasource\Paginator
+ * @var \Cake\Datasource\Paging\NumericPaginator
*/
protected $_paginator;
@@ -50,18 +51,30 @@ class PaginatorComponent extends Component
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
+ deprecationWarning(
+ 'PaginatorComponent is deprecated, use a Cake\Datasource\Pagination\NumericPaginator instance directly.'
+ );
+
if (!empty($this->_defaultConfig)) {
throw new UnexpectedValueException('Default configuration must be set using a custom Paginator class.');
}
if (isset($config['paginator'])) {
- if (!$config['paginator'] instanceof Paginator) {
- throw new InvalidArgumentException('Paginator must be an instance of ' . Paginator::class);
+ $config['className'] = $config['paginator'];
+ deprecationWarning(
+ '`paginator` option is deprecated,'
+ . ' use `className` instead a specify a paginator name/FQCN.'
+ );
+ }
+
+ if (isset($config['className'])) {
+ if (!$config['className'] instanceof NumericPaginator) {
+ throw new InvalidArgumentException('Paginator must be an instance of ' . NumericPaginator::class);
}
- $this->_paginator = $config['paginator'];
- unset($config['paginator']);
+ $this->_paginator = $config['className'];
+ unset($config['className']);
} else {
- $this->_paginator = new Paginator();
+ $this->_paginator = new NumericPaginator();
}
parent::__construct($registry, $config);
@@ -86,7 +99,7 @@ public function implementedEvents(): array
* These settings are used to build the queries made and control other pagination settings.
*
* If your settings contain a key with the current table's alias. The data inside that key will be used.
- * Otherwise the top level configuration will be used.
+ * Otherwise, the top level configuration will be used.
*
* ```
* $settings = [
@@ -225,10 +238,10 @@ public function mergeOptions(string $alias, array $settings): array
/**
* Set paginator instance.
*
- * @param \Cake\Datasource\Paginator $paginator Paginator instance.
+ * @param \Cake\Datasource\Paging\NumericPaginator $paginator Paginator instance.
* @return $this
*/
- public function setPaginator(Paginator $paginator)
+ public function setPaginator(NumericPaginator $paginator)
{
$this->_paginator = $paginator;
@@ -238,9 +251,9 @@ public function setPaginator(Paginator $paginator)
/**
* Get paginator instance.
*
- * @return \Cake\Datasource\Paginator
+ * @return \Cake\Datasource\Paging\NumericPaginator
*/
- public function getPaginator(): Paginator
+ public function getPaginator(): NumericPaginator
{
return $this->_paginator;
}
diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php
index f18c3cc64..84c5a26ab 100644
--- a/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php
+++ b/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php
@@ -22,6 +22,7 @@
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Event\EventInterface;
+use Cake\Http\ContentTypeNegotiation;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
@@ -38,6 +39,8 @@
* etc. and return a response accordingly.
*
* @link https://book.cakephp.org/4/en/controllers/components/request-handling.html
+ * @deprecated 4.4.0 See the 4.4 migration guide for how to upgrade.
+ * https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html#requesthandlercomponent
*/
class RequestHandlerComponent extends Component
{
@@ -121,7 +124,9 @@ public function implementedEvents(): array
*/
protected function _setExtension(ServerRequest $request, Response $response): void
{
- $accept = $request->parseAccept();
+ $content = new ContentTypeNegotiation();
+ $accept = $content->parseAccept($request);
+
if (empty($accept) || current($accept)[0] === 'text/html') {
return;
}
@@ -214,14 +219,10 @@ public function beforeRender(EventInterface $event): void
$response = $response->withCharset(Configure::read('App.encoding'));
}
- if (
- $this->_config['checkHttpCache'] &&
- $response->checkNotModified($controller->getRequest())
- ) {
- $controller->setResponse($response);
+ $request = $controller->getRequest();
+ if ($this->_config['checkHttpCache'] && $response->isNotModified($request)) {
+ $response = $response->withNotModified();
$event->stopPropagation();
-
- return;
}
$controller->setResponse($response);
@@ -252,6 +253,7 @@ public function beforeRender(EventInterface $event): void
* types the client accepts. If a string is passed, returns true
* if the client accepts it. If an array is passed, returns true
* if the client accepts one or more elements in the array.
+ * @deprecated 4.4.0 Use ContentTypeNegotiation::prefersChoice() or Controller::getViewClasses() instead.
*/
public function accepts($type = null)
{
@@ -339,12 +341,15 @@ public function requestedWith($type = null)
* a boolean will be returned if that type is preferred.
* If an array of types are provided then the first preferred type is returned.
* If no type is provided the first preferred type is returned.
+ * @deprecated 4.4.0 Use Controller::getViewClasses() instead.
*/
public function prefers($type = null)
{
$controller = $this->getController();
+ $request = $controller->getRequest();
+ $content = new ContentTypeNegotiation();
- $acceptRaw = $controller->getRequest()->parseAccept();
+ $acceptRaw = $content->parseAccept($request);
if (empty($acceptRaw)) {
return $type ? $type === $this->ext : $this->ext;
}
@@ -470,12 +475,7 @@ public function respondAs($type, array $options = []): bool
}
if (is_array($cType)) {
$cType = $cType[$options['index']] ?? $cType;
-
- if ($this->prefers($cType)) {
- $cType = $this->prefers($cType);
- } else {
- $cType = $cType[0];
- }
+ $cType = $this->prefers($cType) ?: $cType[0];
}
if (!$cType) {
diff --git a/app/vendor/cakephp/cakephp/src/Controller/Controller.php b/app/vendor/cakephp/cakephp/src/Controller/Controller.php
index a3300fbde..623214fd6 100644
--- a/app/vendor/cakephp/cakephp/src/Controller/Controller.php
+++ b/app/vendor/cakephp/cakephp/src/Controller/Controller.php
@@ -19,18 +19,25 @@
use Cake\Controller\Exception\MissingActionException;
use Cake\Core\App;
use Cake\Datasource\ModelAwareTrait;
+use Cake\Datasource\Paging\Exception\PageOutOfBoundsException;
+use Cake\Datasource\Paging\NumericPaginator;
+use Cake\Datasource\Paging\PaginatorInterface;
use Cake\Event\EventDispatcherInterface;
use Cake\Event\EventDispatcherTrait;
use Cake\Event\EventInterface;
use Cake\Event\EventListenerInterface;
use Cake\Event\EventManagerInterface;
+use Cake\Http\ContentTypeNegotiation;
+use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\Log\LogTrait;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Routing\Router;
+use Cake\View\View;
use Cake\View\ViewVarsTrait;
use Closure;
+use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use ReflectionClass;
use ReflectionException;
@@ -86,6 +93,7 @@
* @property \Cake\Controller\Component\AuthComponent $Auth
* @link https://book.cakephp.org/4/en/controllers.html
*/
+#[\AllowDynamicProperties]
class Controller implements EventListenerInterface, EventDispatcherInterface
{
use EventDispatcherTrait;
@@ -128,7 +136,7 @@ class Controller implements EventListenerInterface, EventDispatcherInterface
* tables your controller will be paginating.
*
* @var array
- * @see \Cake\Controller\Component\PaginatorComponent
+ * @see \Cake\Datasource\Paging\NumericPaginator
*/
public $paginate = [];
@@ -759,12 +767,78 @@ public function render(?string $template = null, ?string $layout = null): Respon
if ($builder->getTemplate() === null) {
$builder->setTemplate($this->request->getParam('action'));
}
+ $viewClass = $this->chooseViewClass();
+ $view = $this->createView($viewClass);
- $view = $this->createView();
$contents = $view->render();
- $this->setResponse($view->getResponse()->withStringBody($contents));
+ $response = $view->getResponse()->withStringBody($contents);
- return $this->response;
+ return $this->setResponse($response)->response;
+ }
+
+ /**
+ * Get the View classes this controller can perform content negotiation with.
+ *
+ * Each view class must implement the `getContentType()` hook method
+ * to participate in negotiation.
+ *
+ * @see Cake\Http\ContentTypeNegotiation
+ * @return array
+ */
+ public function viewClasses(): array
+ {
+ return [];
+ }
+
+ /**
+ * Use the view classes defined on this controller to view
+ * selection based on content-type negotiation.
+ *
+ * @return string|null The chosen view class or null for no decision.
+ */
+ protected function chooseViewClass(): ?string
+ {
+ $possibleViewClasses = $this->viewClasses();
+ if (empty($possibleViewClasses)) {
+ return null;
+ }
+ // Controller or component has already made a view class decision.
+ // That decision should overwrite the framework behavior.
+ if ($this->viewBuilder()->getClassName() !== null) {
+ return null;
+ }
+
+ $typeMap = [];
+ foreach ($possibleViewClasses as $class) {
+ $viewContentType = $class::contentType();
+ if ($viewContentType && !isset($typeMap[$viewContentType])) {
+ $typeMap[$viewContentType] = $class;
+ }
+ }
+ $request = $this->getRequest();
+
+ // Prefer the _ext route parameter if it is defined.
+ $ext = $request->getParam('_ext');
+ if ($ext) {
+ $extTypes = (array)($this->response->getMimeType($ext) ?: []);
+ foreach ($extTypes as $extType) {
+ if (isset($typeMap[$extType])) {
+ return $typeMap[$extType];
+ }
+ }
+
+ throw new NotFoundException();
+ }
+
+ // Use accept header based negotiation.
+ $contentType = new ContentTypeNegotiation();
+ $preferredType = $contentType->preferredType($request, array_keys($typeMap));
+ if ($preferredType) {
+ return $typeMap[$preferredType];
+ }
+
+ // Use the match-all view if available or null for no decision.
+ return $typeMap[View::TYPE_MATCH_ALL] ?? null;
}
/**
@@ -818,7 +892,7 @@ public function referer($default = '/', bool $local = true): string
/**
* Handles pagination of records in Table objects.
*
- * Will load the referenced Table object, and have the PaginatorComponent
+ * Will load the referenced Table object, and have the paginator
* paginate the query using the request date and settings defined in `$this->paginate`.
*
* This method will also make the PaginatorHelper available in the view.
@@ -847,13 +921,57 @@ public function paginate($object = null, array $settings = [])
}
}
- $this->loadComponent('Paginator');
if (empty($table)) {
throw new RuntimeException('Unable to locate an object compatible with paginate.');
}
+
$settings += $this->paginate;
- return $this->Paginator->paginate($table, $settings);
+ if (isset($this->Paginator)) {
+ return $this->Paginator->paginate($table, $settings);
+ }
+
+ if (isset($settings['paginator'])) {
+ $settings['className'] = $settings['paginator'];
+ deprecationWarning(
+ '`paginator` option is deprecated,'
+ . ' use `className` instead a specify a paginator name/FQCN.'
+ );
+ }
+
+ $paginator = $settings['className'] ?? NumericPaginator::class;
+ unset($settings['className']);
+ if (is_string($paginator)) {
+ $className = App::className($paginator, 'Datasource/Paging', 'Paginator');
+ if ($className === null) {
+ throw new InvalidArgumentException('Invalid paginator: ' . $paginator);
+ }
+ $paginator = new $className();
+ }
+ if (!$paginator instanceof PaginatorInterface) {
+ throw new InvalidArgumentException('Paginator must be an instance of ' . PaginatorInterface::class);
+ }
+
+ $results = null;
+ try {
+ $results = $paginator->paginate(
+ $table,
+ $this->request->getQueryParams(),
+ $settings
+ );
+ } catch (PageOutOfBoundsException $e) {
+ // Exception thrown below
+ } finally {
+ $paging = $paginator->getPagingParams() + (array)$this->request->getAttribute('paging', []);
+ $this->request = $this->request->withAttribute('paging', $paging);
+ }
+
+ if (isset($e)) {
+ throw new NotFoundException(null, null, $e);
+ }
+
+ /** @psalm-suppress NullableReturnStatement */
+ return $results;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php b/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php
index 65a09ca99..34b8c18a7 100644
--- a/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php
+++ b/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php
@@ -79,10 +79,9 @@ public function create(ServerRequestInterface $request): Controller
throw $this->missingController($request);
}
- // If the controller has a container definition
- // add the request as a service.
+ // Get the controller from the container if defined.
+ // The request is in the container by default.
if ($this->container->has($className)) {
- $this->container->add(ServerRequest::class, $request);
$controller = $this->container->get($className);
} else {
$controller = $reflection->newInstance($request);
@@ -269,7 +268,7 @@ protected function coerceStringToType(string $argument, ReflectionNamedType $typ
case 'float':
return is_numeric($argument) ? (float)$argument : null;
case 'int':
- return ctype_digit($argument) ? (int)$argument : null;
+ return filter_var($argument, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
case 'bool':
return $argument === '0' ? false : ($argument === '1' ? true : null);
case 'array':
@@ -347,10 +346,11 @@ function ($val) {
protected function missingController(ServerRequest $request)
{
return new MissingControllerException([
- 'class' => $request->getParam('controller'),
+ 'controller' => $request->getParam('controller'),
'plugin' => $request->getParam('plugin'),
'prefix' => $request->getParam('prefix'),
'_ext' => $request->getParam('_ext'),
+ 'class' => $request->getParam('controller'), // Deprecated: Will be removed in 4.5. Use `controller` instead.
]);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Core/Configure.php b/app/vendor/cakephp/cakephp/src/Core/Configure.php
index e4e8f10d8..99e82592a 100644
--- a/app/vendor/cakephp/cakephp/src/Core/Configure.php
+++ b/app/vendor/cakephp/cakephp/src/Core/Configure.php
@@ -37,7 +37,7 @@ class Configure
/**
* Array of values currently stored in Configure.
*
- * @var array
+ * @var array
*/
protected static $_values = [
'debug' => false,
@@ -78,7 +78,7 @@ class Configure
*
* @param array|string $config The key to write, can be a dot notation value.
* Alternatively can be an array containing key(s) and value(s).
- * @param mixed $value Value to set for var
+ * @param mixed $value Value to set for the given key.
* @return void
* @link https://book.cakephp.org/4/en/development/configuration.html#writing-configuration-data
*/
@@ -88,8 +88,8 @@ public static function write($config, $value = null): void
$config = [$config => $value];
}
- foreach ($config as $name => $value) {
- static::$_values = Hash::insert(static::$_values, $name, $value);
+ foreach ($config as $name => $valueToInsert) {
+ static::$_values = Hash::insert(static::$_values, $name, $valueToInsert);
}
if (isset($config['debug'])) {
diff --git a/app/vendor/cakephp/cakephp/src/Core/Container.php b/app/vendor/cakephp/cakephp/src/Core/Container.php
index 27ccafbb5..b5b56f2b0 100644
--- a/app/vendor/cakephp/cakephp/src/Core/Container.php
+++ b/app/vendor/cakephp/cakephp/src/Core/Container.php
@@ -22,9 +22,6 @@
* Dependency Injection container
*
* Based on the container out of League\Container
- *
- * @experimental This class' interface is not stable and may change
- * in future minor releases.
*/
class Container extends LeagueContainer implements ContainerInterface
{
diff --git a/app/vendor/cakephp/cakephp/src/Core/ContainerApplicationInterface.php b/app/vendor/cakephp/cakephp/src/Core/ContainerApplicationInterface.php
index 3caff77cb..bd5f31dc6 100644
--- a/app/vendor/cakephp/cakephp/src/Core/ContainerApplicationInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Core/ContainerApplicationInterface.php
@@ -18,9 +18,6 @@
/**
* Interface for applications that configure and use a dependency injection container.
- *
- * @experimental This interface is not final and can have additional
- * methods and parameters added in future minor releases.
*/
interface ContainerApplicationInterface
{
diff --git a/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php b/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php
index 244e45f7e..d8d39e1bc 100644
--- a/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php
@@ -26,9 +26,6 @@
*
* The methods defined in this interface use the conventions provided
* by league/container as that is the library that CakePHP uses.
- *
- * @experimental This interface is not final and can have additional
- * methods and parameters added in future minor releases.
*/
interface ContainerInterface extends DefinitionContainerInterface
{
diff --git a/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php b/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php
index 4c85e326c..1862d6e43 100644
--- a/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php
+++ b/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php
@@ -238,15 +238,28 @@ public function create(string $name, array $config = []): PluginInterface
}
$config += ['name' => $name];
- /** @var class-string<\Cake\Core\PluginInterface> $className */
- $className = str_replace('/', '\\', $name) . '\\' . 'Plugin';
+ $namespace = str_replace('/', '\\', $name);
+
+ $className = $namespace . '\\' . 'Plugin';
+ // Check for [Vendor/]Foo/Plugin class
if (!class_exists($className)) {
- $className = BasePlugin::class;
- if (empty($config['path'])) {
- $config['path'] = $this->findPath($name);
+ $pos = strpos($name, '/');
+ if ($pos === false) {
+ $className = $namespace . '\\' . $name . 'Plugin';
+ } else {
+ $className = $namespace . '\\' . substr($name, $pos + 1) . 'Plugin';
+ }
+
+ // Check for [Vendor/]Foo/FooPlugin
+ if (!class_exists($className)) {
+ $className = BasePlugin::class;
+ if (empty($config['path'])) {
+ $config['path'] = $this->findPath($name);
+ }
}
}
+ /** @var class-string<\Cake\Core\PluginInterface> $className */
return new $className($config);
}
diff --git a/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php b/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php
index b2ed8add9..8ff9b9cae 100644
--- a/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php
+++ b/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php
@@ -28,9 +28,6 @@
* to organize your application's dependencies. They also help
* improve performance of applications with many services by
* allowing service registration to be deferred until services are needed.
- *
- * @experimental This class' interface is not stable and may change
- * in future minor releases.
*/
abstract class ServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
{
diff --git a/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php b/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php
new file mode 100644
index 000000000..909ade79f
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php
@@ -0,0 +1,174 @@
+|class-string<\Cake\Core\ConsoleApplicationInterface>|null
+ * @var string|null
+ */
+ protected $_appClass;
+
+ /**
+ * The customized application constructor arguments.
+ *
+ * @var array|null
+ */
+ protected $_appArgs;
+
+ /**
+ * The collection of container services.
+ *
+ * @var array
+ */
+ private $containerServices = [];
+
+ /**
+ * Configure the application class to use in integration tests.
+ *
+ * @param string $class The application class name.
+ * @param array|null $constructorArgs The constructor arguments for your application class.
+ * @return void
+ * @psalm-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class
+ */
+ public function configApplication(string $class, ?array $constructorArgs): void
+ {
+ $this->_appClass = $class;
+ $this->_appArgs = $constructorArgs;
+ }
+
+ /**
+ * Create an application instance.
+ *
+ * Uses the configuration set in `configApplication()`.
+ *
+ * @return \Cake\Core\HttpApplicationInterface|\Cake\Core\ConsoleApplicationInterface
+ */
+ protected function createApp()
+ {
+ if ($this->_appClass) {
+ $appClass = $this->_appClass;
+ } else {
+ /** @psalm-var class-string<\Cake\Http\BaseApplication> */
+ $appClass = Configure::read('App.namespace') . '\Application';
+ }
+ if (!class_exists($appClass)) {
+ throw new LogicException("Cannot load `{$appClass}` for use in integration testing.");
+ }
+ $appArgs = $this->_appArgs ?: [CONFIG];
+
+ $app = new $appClass(...$appArgs);
+ if (!empty($this->containerServices) && method_exists($app, 'getEventManager')) {
+ $app->getEventManager()->on('Application.buildContainer', [$this, 'modifyContainer']);
+ }
+
+ return $app;
+ }
+
+ /**
+ * Add a mocked service to the container.
+ *
+ * When the container is created the provided classname
+ * will be mapped to the factory function. The factory
+ * function will be used to create mocked services.
+ *
+ * @param string $class The class or interface you want to define.
+ * @param \Closure $factory The factory function for mocked services.
+ * @return $this
+ */
+ public function mockService(string $class, Closure $factory)
+ {
+ $this->containerServices[$class] = $factory;
+
+ return $this;
+ }
+
+ /**
+ * Remove a mocked service to the container.
+ *
+ * @param string $class The class or interface you want to remove.
+ * @return $this
+ */
+ public function removeMockService(string $class)
+ {
+ unset($this->containerServices[$class]);
+
+ return $this;
+ }
+
+ /**
+ * Wrap the application's container with one containing mocks.
+ *
+ * If any mocked services are defined, the application's container
+ * will be replaced with one containing mocks. The original
+ * container will be set as a delegate to the mock container.
+ *
+ * @param \Cake\Event\EventInterface $event The event
+ * @param \Cake\Core\ContainerInterface $container The container to wrap.
+ * @return \Cake\Core\ContainerInterface|null
+ */
+ public function modifyContainer(EventInterface $event, ContainerInterface $container): ?ContainerInterface
+ {
+ if (empty($this->containerServices)) {
+ return null;
+ }
+ foreach ($this->containerServices as $key => $factory) {
+ if ($container->has($key)) {
+ try {
+ $container->extend($key)->setConcrete($factory);
+ } catch (NotFoundException $e) {
+ $container->add($key, $factory);
+ }
+ } else {
+ $container->add($key, $factory);
+ }
+ }
+
+ return $container;
+ }
+
+ /**
+ * Clears any mocks that were defined and cleans
+ * up application class configuration.
+ *
+ * @after
+ * @return void
+ */
+ public function cleanupContainer(): void
+ {
+ $this->_appArgs = null;
+ $this->_appClass = null;
+ $this->containerServices = [];
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Core/composer.json b/app/vendor/cakephp/cakephp/src/Core/composer.json
index ffd17ba7a..d5d4cf761 100644
--- a/app/vendor/cakephp/cakephp/src/Core/composer.json
+++ b/app/vendor/cakephp/cakephp/src/Core/composer.json
@@ -22,9 +22,12 @@
"source": "https://github.com/cakephp/core"
},
"require": {
- "php": ">=7.2.0",
+ "php": ">=7.4.0",
"cakephp/utility": "^4.0"
},
+ "provide": {
+ "psr/container-implementation": "^1.0 || ^2.0"
+ },
"suggest": {
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
"cakephp/cache": "To use Configure::store() and restore().",
diff --git a/app/vendor/cakephp/cakephp/src/Core/functions.php b/app/vendor/cakephp/cakephp/src/Core/functions.php
index 2ca8619e8..e74ac1736 100644
--- a/app/vendor/cakephp/cakephp/src/Core/functions.php
+++ b/app/vendor/cakephp/cakephp/src/Core/functions.php
@@ -30,7 +30,7 @@
*
* @param mixed $text Text to wrap through htmlspecialchars. Also works with arrays, and objects.
* Arrays will be mapped and have all their elements escaped. Objects will be string cast if they
- * implement a `__toString` method. Otherwise the class name will be used.
+ * implement a `__toString` method. Otherwise, the class name will be used.
* Other scalar types will be returned unchanged.
* @param bool $double Encode existing html entities.
* @param string|null $charset Character set to use when escaping.
@@ -206,8 +206,10 @@ function env(string $key, $default = null)
$key = 'SCRIPT_URL';
}
+ /** @var string|null $val */
$val = $_SERVER[$key] ?? $_ENV[$key] ?? null;
if ($val == null && getenv($key) !== false) {
+ /** @var string|false $val */
$val = getenv($key);
}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Connection.php b/app/vendor/cakephp/cakephp/src/Database/Connection.php
index 5153d0e81..92a54ae8d 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Connection.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Connection.php
@@ -31,6 +31,7 @@
use Cake\Database\Schema\Collection as SchemaCollection;
use Cake\Database\Schema\CollectionInterface as SchemaCollectionInterface;
use Cake\Datasource\ConnectionInterface;
+use Cake\Log\Engine\BaseLog;
use Cake\Log\Log;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
@@ -135,11 +136,14 @@ public function __construct(array $config)
{
$this->_config = $config;
- $driver = '';
- if (!empty($config['driver'])) {
- $driver = $config['driver'];
- }
- $this->setDriver($driver, $config);
+ $driverConfig = array_diff_key($config, array_flip([
+ 'name',
+ 'driver',
+ 'log',
+ 'cacheMetaData',
+ 'cacheKeyPrefix',
+ ]));
+ $this->_driver = $this->createDriver($config['driver'] ?? '', $driverConfig);
if (!empty($config['log'])) {
$this->enableQueryLogging((bool)$config['log']);
@@ -183,24 +187,43 @@ public function configName(): string
* @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing.
* @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing.
* @return $this
+ * @deprecated 4.4.0 Setting the driver is deprecated. Use the connection config instead.
*/
public function setDriver($driver, $config = [])
{
+ deprecationWarning('Setting the driver is deprecated. Use the connection config instead.');
+
+ $this->_driver = $this->createDriver($driver, $config);
+
+ return $this;
+ }
+
+ /**
+ * Creates driver from name, class name or instance.
+ *
+ * @param \Cake\Database\DriverInterface|string $name Driver name, class name or instance.
+ * @param array $config Driver config if $name is not an instance.
+ * @return \Cake\Database\DriverInterface
+ * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing.
+ * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing.
+ */
+ protected function createDriver($name, array $config): DriverInterface
+ {
+ $driver = $name;
if (is_string($driver)) {
/** @psalm-var class-string<\Cake\Database\DriverInterface>|null $className */
$className = App::className($driver, 'Database/Driver');
if ($className === null) {
- throw new MissingDriverException(['driver' => $driver]);
+ throw new MissingDriverException(['driver' => $driver, 'connection' => $this->configName()]);
}
$driver = new $className($config);
}
+
if (!$driver->enabled()) {
- throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $config['name']]);
+ throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $this->configName()]);
}
- $this->_driver = $driver;
-
- return $this;
+ return $driver;
}
/**
@@ -406,7 +429,7 @@ public function getSchemaCollection(): SchemaCollectionInterface
*
* @param string $table the table to insert values in
* @param array $values values to be inserted
- * @param array $types list of associative array containing the types to be used for casting
+ * @param array $types Array containing the types to be used for casting
* @return \Cake\Database\StatementInterface
*/
public function insert(string $table, array $values, array $types = []): StatementInterface
@@ -427,7 +450,7 @@ public function insert(string $table, array $values, array $types = []): Stateme
* @param string $table the table to update rows from
* @param array $values values to be updated
* @param array $conditions conditions to be set for update statement
- * @param array $types list of associative array containing the types to be used for casting
+ * @param array $types list of associative array containing the types to be used for casting
* @return \Cake\Database\StatementInterface
*/
public function update(string $table, array $values, array $conditions = [], array $types = []): StatementInterface
@@ -445,7 +468,7 @@ public function update(string $table, array $values, array $conditions = [], arr
*
* @param string $table the table to delete rows from
* @param array $conditions conditions to be set for delete statement
- * @param array $types list of associative array containing the types to be used for casting
+ * @param array $types list of associative array containing the types to be used for casting
* @return \Cake\Database\StatementInterface
*/
public function delete(string $table, array $conditions = [], array $types = []): StatementInterface
@@ -897,7 +920,7 @@ public function getLogger(): LoggerInterface
return $this->_logger;
}
- if (!class_exists(QueryLogger::class)) {
+ if (!class_exists(BaseLog::class)) {
throw new RuntimeException(
'For logging you must either set a logger using Connection::setLogger()' .
' or require the cakephp/log package in your composer config.'
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php
index 0b2cdb0c5..6ca955856 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php
@@ -29,6 +29,7 @@
use Cake\Database\StatementInterface;
use InvalidArgumentException;
use PDO;
+use RuntimeException;
/**
* Class Sqlite
@@ -52,6 +53,8 @@ class Sqlite extends Driver
'database' => ':memory:',
'encoding' => 'utf8',
'mask' => 0644,
+ 'cache' => null,
+ 'mode' => null,
'flags' => [],
'init' => [],
];
@@ -132,12 +135,30 @@ public function connect(): bool
);
}
- $databaseExists = file_exists($config['database']);
+ $chmodFile = false;
+ if ($config['database'] !== ':memory:' && $config['mode'] !== 'memory') {
+ $chmodFile = !file_exists($config['database']);
+ }
- $dsn = "sqlite:{$config['database']}";
- $this->_connect($dsn, $config);
+ $params = [];
+ if ($config['cache']) {
+ $params[] = 'cache=' . $config['cache'];
+ }
+ if ($config['mode']) {
+ $params[] = 'mode=' . $config['mode'];
+ }
- if (!$databaseExists && $config['database'] !== ':memory:') {
+ if ($params) {
+ if (PHP_VERSION_ID < 80100) {
+ throw new RuntimeException('SQLite URI support requires PHP 8.1.');
+ }
+ $dsn = 'sqlite:file:' . $config['database'] . '?' . implode('&', $params);
+ } else {
+ $dsn = 'sqlite:' . $config['database'];
+ }
+
+ $this->_connect($dsn, $config);
+ if ($chmodFile) {
// phpcs:disable
@chmod($config['database'], $config['mask']);
// phpcs:enable
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php
index 23e23dd2e..a0dc2ec58 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php
@@ -75,6 +75,8 @@ class Sqlserver extends Driver
'failoverPartner' => null,
'loginTimeout' => null,
'multiSubnetFailover' => null,
+ 'encrypt' => null,
+ 'trustServerCertificate' => null,
];
/**
@@ -151,6 +153,12 @@ public function connect(): bool
if ($config['multiSubnetFailover'] !== null) {
$dsn .= ";MultiSubnetFailover={$config['multiSubnetFailover']}";
}
+ if ($config['encrypt'] !== null) {
+ $dsn .= ";Encrypt={$config['encrypt']}";
+ }
+ if ($config['trustServerCertificate'] !== null) {
+ $dsn .= ";TrustServerCertificate={$config['trustServerCertificate']}";
+ }
$this->_connect($dsn, $config);
$connection = $this->getConnection();
diff --git a/app/vendor/cakephp/cakephp/src/Database/Exception/DatabaseException.php b/app/vendor/cakephp/cakephp/src/Database/Exception/DatabaseException.php
index 3852808d9..05e4cd0dd 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Exception/DatabaseException.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Exception/DatabaseException.php
@@ -23,6 +23,10 @@
*/
class DatabaseException extends CakeException
{
+ /**
+ * @inheritDoc
+ */
+ protected $_messageTemplate = '%s';
}
// phpcs:disable
diff --git a/app/vendor/cakephp/cakephp/src/Database/Exception/MissingDriverException.php b/app/vendor/cakephp/cakephp/src/Database/Exception/MissingDriverException.php
index d9f142264..77532a16c 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Exception/MissingDriverException.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Exception/MissingDriverException.php
@@ -26,5 +26,5 @@ class MissingDriverException extends CakeException
/**
* @inheritDoc
*/
- protected $_messageTemplate = 'Database driver %s could not be found.';
+ protected $_messageTemplate = 'Could not find driver `%s` for connection `%s`.';
}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php
index e486a193f..8d1728b9e 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php
@@ -20,6 +20,7 @@
use Cake\Chronos\MutableDate;
use Cake\Database\ExpressionInterface;
use Cake\Database\Query;
+use Cake\Database\TypedResultInterface;
use Cake\Database\ValueBinder;
use DateTimeInterface;
@@ -67,6 +68,8 @@ protected function inferType($value): ?string
$value instanceof IdentifierExpression
) {
$type = $this->_typeMap->type($value->getIdentifier());
+ } elseif ($value instanceof TypedResultInterface) {
+ $type = $value->getReturnType();
}
return $type;
diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php
index 2ab13d219..edb67399e 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php
@@ -51,7 +51,7 @@ class QueryExpression implements ExpressionInterface, Countable
/**
* Constructor. A new expression object can be created without any params and
- * be built dynamically. Otherwise it is possible to pass an array of conditions
+ * be built dynamically. Otherwise, it is possible to pass an array of conditions
* containing either a tree-like array structure to be parsed and/or other
* expression objects. Optionally, you can set the conjunction keyword to be used
* for joining each part of this level of the expression tree.
@@ -111,7 +111,7 @@ public function getConjunction(): string
* be added. When using an array and the key is 'OR' or 'AND' a new expression
* object will be created with that conjunction and internal array value passed
* as conditions.
- * @param array $types Associative array of fields pointing to the type of the
+ * @param array $types Associative array of fields pointing to the type of the
* values that are being passed. Used for correctly binding values to statements.
* @see \Cake\Database\Query::where() for examples on conditions
* @return $this
@@ -702,7 +702,7 @@ public function hasNestedExpression(): bool
* representation is wrapped around an adequate instance or of this class.
*
* @param array $conditions list of conditions to be stored in this object
- * @param array $types list of types associated on fields referenced in $conditions
+ * @param array $types list of types associated on fields referenced in $conditions
* @return void
*/
protected function _addConditions(array $conditions, array $types): void
diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php
index 3604be4b0..383e652cf 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php
@@ -310,13 +310,11 @@ protected function buildOffsetSql(ValueBinder $binder, $offset, string $directio
$offset = $offset->sql($binder);
}
- $sql = sprintf(
+ return sprintf(
'%s %s',
$offset ?? 'UNBOUNDED',
$direction
);
-
- return $sql;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php b/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php
index ff7f39f67..3448aa1ef 100644
--- a/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php
+++ b/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php
@@ -118,7 +118,7 @@ public function __construct(TypeMap $typeMap, DriverInterface $driver)
* using the corresponding Type class.
*
* @param array $row The array with the fields to be casted
- * @return array
+ * @return array
*/
public function __invoke(array $row): array
{
diff --git a/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php b/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php
index 6b21a5ad6..b3a93e262 100644
--- a/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php
+++ b/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php
@@ -128,8 +128,8 @@ protected function _quoteParts(Query $query): void
/**
* A generic identifier quoting function used for various parts of the query
*
- * @param array $part the part of the query to quote
- * @return array
+ * @param array $part the part of the query to quote
+ * @return array
*/
protected function _basicQuoter(array $part): array
{
@@ -148,7 +148,7 @@ protected function _basicQuoter(array $part): array
* object
*
* @param array $joins The joins to quote.
- * @return array
+ * @return array
*/
protected function _quoteJoins(array $joins): array
{
diff --git a/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php b/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php
index 22814e394..ada3d381d 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php
@@ -16,6 +16,8 @@
*/
namespace Cake\Database\Log;
+use Cake\Core\Configure;
+use Cake\Database\Exception\DatabaseException;
use Cake\Database\Statement\StatementDecorator;
use Exception;
use Psr\Log\LoggerInterface;
@@ -75,10 +77,31 @@ public function execute(?array $params = null): bool
$result = parent::execute($params);
$this->loggedQuery->took = (int)round((microtime(true) - $this->startTime) * 1000, 0);
} catch (Exception $e) {
- /** @psalm-suppress UndefinedPropertyAssignment */
- $e->queryString = $this->queryString;
$this->loggedQuery->error = $e;
$this->_log();
+
+ if (Configure::read('Error.convertStatementToDatabaseException', false) === true) {
+ $code = $e->getCode();
+ if (!is_int($code)) {
+ $code = null;
+ }
+
+ throw new DatabaseException([
+ 'message' => $e->getMessage(),
+ 'queryString' => $this->queryString,
+ ], $code, $e);
+ }
+
+ if (version_compare(PHP_VERSION, '8.2.0', '<')) {
+ deprecationWarning(
+ '4.4.12 - Having queryString set on exceptions is deprecated.' .
+ 'If you are not using this attribute there is no action to take.' .
+ 'Otherwise, enable Error.convertStatementToDatabaseException.'
+ );
+ /** @psalm-suppress UndefinedPropertyAssignment */
+ $e->queryString = $this->queryString;
+ }
+
throw $e;
}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php b/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php
index b6957d1a5..a7f27b489 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php
@@ -52,6 +52,6 @@ public function log($level, $message, array $context = [])
$context = $context['query']->getContext() + $context;
$message = 'connection={connection} duration={took} rows={numRows} ' . $message;
}
- Log::write('debug', $message, $context);
+ Log::write('debug', (string)$message, $context);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Query.php b/app/vendor/cakephp/cakephp/src/Database/Query.php
index 3df66cd51..fc66f6e80 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Query.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Query.php
@@ -101,6 +101,7 @@ class Query implements ExpressionInterface, IteratorAggregate
* The list of query clauses to traverse for generating a SELECT statement
*
* @var array
+ * @deprecated 4.4.3 This property is unused.
*/
protected $_selectParts = [
'with', 'select', 'from', 'join', 'where', 'group', 'having', 'order', 'limit',
@@ -111,6 +112,7 @@ class Query implements ExpressionInterface, IteratorAggregate
* The list of query clauses to traverse for generating an UPDATE statement
*
* @var array
+ * @deprecated 4.4.3 This property is unused.
*/
protected $_updateParts = ['with', 'update', 'set', 'where', 'epilog'];
@@ -118,6 +120,7 @@ class Query implements ExpressionInterface, IteratorAggregate
* The list of query clauses to traverse for generating a DELETE statement
*
* @var array
+ * @deprecated 4.4.3 This property is unused.
*/
protected $_deleteParts = ['with', 'delete', 'modifier', 'from', 'where', 'epilog'];
@@ -125,6 +128,7 @@ class Query implements ExpressionInterface, IteratorAggregate
* The list of query clauses to traverse for generating an INSERT statement
*
* @var array
+ * @deprecated 4.4.3 This property is unused.
*/
protected $_insertParts = ['with', 'insert', 'values', 'epilog'];
@@ -996,6 +1000,19 @@ protected function _makeJoin($table, $conditions, $type): array
* If you use string conditions make sure that your values are correctly quoted.
* The safest thing you can do is to never use string conditions.
*
+ * ### Using null-able values
+ *
+ * When using values that can be null you can use the 'IS' keyword to let the ORM generate the correct SQL based on the value's type
+ *
+ * ```
+ * $query->where([
+ * 'posted >=' => new DateTime('3 days ago'),
+ * 'category_id IS' => $category,
+ * ]);
+ * ```
+ *
+ * If $category is `null` - it will actually convert that into `category_id IS NULL` - if it's `4` it will convert it into `category_id = 4`
+ *
* @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions The conditions to filter on.
* @param array $types Associative array of type names used to bind values to query
* @param bool $overwrite whether to reset conditions with passed list or not
@@ -1530,6 +1547,9 @@ public function page(int $num, ?int $limit = null)
*/
public function limit($limit)
{
+ if (is_string($limit) && !is_numeric($limit)) {
+ throw new InvalidArgumentException('Invalid value for `limit()`');
+ }
$this->_dirty();
$this->_parts['limit'] = $limit;
@@ -1556,6 +1576,9 @@ public function limit($limit)
*/
public function offset($offset)
{
+ if (is_string($offset) && !is_numeric($offset)) {
+ throw new InvalidArgumentException('Invalid value for `offset()`');
+ }
$this->_dirty();
$this->_parts['offset'] = $offset;
@@ -1642,7 +1665,7 @@ public function unionAll($query, $overwrite = false)
* with Query::values().
*
* @param array $columns The columns to insert into.
- * @param array $types A map between columns & their datatypes.
+ * @param array $types A map between columns & their datatypes.
* @return $this
* @throws \RuntimeException When there are 0 columns.
*/
@@ -1886,14 +1909,36 @@ public function type(): string
* any format accepted by \Cake\Database\Expression\QueryExpression:
*
* ```
- * $expression = $query->newExpr(); // Returns an empty expression object
- * $expression = $query->newExpr('Table.column = Table2.column'); // Return a raw SQL expression
+ * $expression = $query->expr(); // Returns an empty expression object
+ * $expression = $query->expr('Table.column = Table2.column'); // Return a raw SQL expression
* ```
*
* @param \Cake\Database\ExpressionInterface|array|string|null $rawExpression A string, array or anything you want wrapped in an expression object
* @return \Cake\Database\Expression\QueryExpression
*/
public function newExpr($rawExpression = null): QueryExpression
+ {
+ return $this->expr($rawExpression);
+ }
+
+ /**
+ * Returns a new QueryExpression object. This is a handy function when
+ * building complex queries using a fluent interface. You can also override
+ * this function in subclasses to use a more specialized QueryExpression class
+ * if required.
+ *
+ * You can optionally pass a single raw SQL string or an array or expressions in
+ * any format accepted by \Cake\Database\Expression\QueryExpression:
+ *
+ * ```
+ * $expression = $query->expr(); // Returns an empty expression object
+ * $expression = $query->expr('Table.column = Table2.column'); // Return a raw SQL expression
+ * ```
+ *
+ * @param \Cake\Database\ExpressionInterface|array|string|null $rawExpression A string, array or anything you want wrapped in an expression object
+ * @return \Cake\Database\Expression\QueryExpression
+ */
+ public function expr($rawExpression = null): QueryExpression
{
$expression = new QueryExpression([], $this->getTypeMap());
diff --git a/app/vendor/cakephp/cakephp/src/Database/README.md b/app/vendor/cakephp/cakephp/src/Database/README.md
index 877c7b68d..d7bbe8eb1 100644
--- a/app/vendor/cakephp/cakephp/src/Database/README.md
+++ b/app/vendor/cakephp/cakephp/src/Database/README.md
@@ -35,35 +35,29 @@ to use:
```php
use Cake\Database\Connection;
use Cake\Database\Driver\Mysql;
+use Cake\Database\Driver\Sqlite;
-$driver = new Mysql([
+$connection = new Connection([
+ 'driver' => Mysql::class,
'database' => 'test',
'username' => 'root',
- 'password' => 'secret'
-]);
-$connection = new Connection([
- 'driver' => $driver
+ 'password' => 'secret',
]);
-```
-
-Drivers are classes responsible for actually executing the commands to the database and
-correctly building the SQL according to the database specific dialect. Drivers can also
-be specified by passing a class name. In that case, include all the connection details
-directly in the options array:
-```php
-use Cake\Database\Connection;
-
-$connection = new Connection([
- 'driver' => Cake\Database\Driver\Sqlite::class,
+$connection2 = new Connection([
+ 'driver' => Sqlite::class,
'database' => '/path/to/file.db'
]);
```
+Drivers are classes responsible for actually executing the commands to the database and
+correctly building the SQL according to the database specific dialect.
+
### Connection options
This is a list of possible options that can be passed when creating a connection:
+* `driver`: Driver class name
* `persistent`: Creates a persistent connection
* `host`: The server host
* `database`: The database name
diff --git a/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php b/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php
index 2cdd0980e..7d76cc01e 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php
@@ -110,7 +110,9 @@ protected function reconnect(): bool
try {
$this->connection->connect();
- $this->connection->log('[RECONNECT]');
+ if ($this->connection->isQueryLoggingEnabled()) {
+ $this->connection->log('[RECONNECT]');
+ }
return true;
} catch (Exception $e) {
diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchemaDialect.php b/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchemaDialect.php
index d8dddef82..6142eb800 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchemaDialect.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchemaDialect.php
@@ -238,7 +238,9 @@ public function convertIndexDescription(TableSchema $schema, array $row): void
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
}
- $columns[] = $row['Column_name'];
+ if (!empty($row['Column_name'])) {
+ $columns[] = $row['Column_name'];
+ }
if ($row['Index_type'] === 'FULLTEXT') {
$type = TableSchema::INDEX_FULLTEXT;
diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/SqlGeneratorInterface.php b/app/vendor/cakephp/cakephp/src/Database/Schema/SqlGeneratorInterface.php
index 3acaf27b9..9fbd5919f 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Schema/SqlGeneratorInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Schema/SqlGeneratorInterface.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php
index f9ad3dcb0..3dac32330 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php
@@ -71,7 +71,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface
/**
* Options for the table.
*
- * @var array
+ * @var array
*/
protected $_options = [];
diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php
index f08e363ba..d4045f9d2 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaInterface.php b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaInterface.php
index 19e942de6..c3ec9d183 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaInterface.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Schema;
diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/BatchCastingInterface.php b/app/vendor/cakephp/cakephp/src/Database/Type/BatchCastingInterface.php
index 35f046bcb..8761f1c88 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Type/BatchCastingInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Type/BatchCastingInterface.php
@@ -31,7 +31,7 @@ interface BatchCastingInterface
* @param array $values The original array of values containing the fields to be casted
* @param array $fields The field keys to cast
* @param \Cake\Database\DriverInterface $driver Object from which database preferences and configuration will be extracted.
- * @return array
+ * @return array
*/
public function manyToPHP(array $values, array $fields, DriverInterface $driver): array;
}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php b/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php
index eeb1c8854..7bfa769bf 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php
@@ -59,7 +59,7 @@ public function toDatabase($value, DriverInterface $driver): ?bool
*/
public function toPHP($value, DriverInterface $driver): ?bool
{
- if ($value === null || $value === true || $value === false) {
+ if ($value === null || is_bool($value)) {
return $value;
}
@@ -76,21 +76,11 @@ public function toPHP($value, DriverInterface $driver): ?bool
public function manyToPHP(array $values, array $fields, DriverInterface $driver): array
{
foreach ($fields as $field) {
- if (!isset($values[$field]) || $values[$field] === true || $values[$field] === false) {
+ $value = $values[$field] ?? null;
+ if ($value === null || is_bool($value)) {
continue;
}
- if ($values[$field] === '1') {
- $values[$field] = true;
- continue;
- }
-
- if ($values[$field] === '0') {
- $values[$field] = false;
- continue;
- }
-
- $value = $values[$field];
if (!is_numeric($value)) {
$values[$field] = strtolower($value) === 'true';
continue;
diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php
index e87fdeaf8..655c78f3c 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php
@@ -328,7 +328,7 @@ public function marshal($value): ?DateTimeInterface
return $value->setTimezone($this->defaultTimezone);
}
- /** @var class-string<\DatetimeInterface> $class */
+ /** @var class-string<\DateTimeInterface> $class */
$class = $this->_className;
try {
if ($value === '' || $value === null || is_bool($value)) {
@@ -336,7 +336,7 @@ public function marshal($value): ?DateTimeInterface
}
if (is_int($value) || (is_string($value) && ctype_digit($value))) {
- /** @var \Datetime|\DateTimeImmutable $dateTime */
+ /** @var \DateTime|\DateTimeImmutable $dateTime */
$dateTime = new $class('@' . $value);
return $dateTime->setTimezone($this->defaultTimezone);
@@ -349,7 +349,7 @@ public function marshal($value): ?DateTimeInterface
$dateTime = $this->_parseValue($value);
}
- /** @var \Datetime|\DateTimeImmutable $dateTime */
+ /** @var \DateTime|\DateTimeImmutable $dateTime */
if ($dateTime !== null) {
$dateTime = $dateTime->setTimezone($this->defaultTimezone);
}
@@ -392,7 +392,7 @@ public function marshal($value): ?DateTimeInterface
$value['microsecond']
);
- /** @var \Datetime|\DateTimeImmutable $dateTime */
+ /** @var \DateTime|\DateTimeImmutable $dateTime */
$dateTime = new $class($format, $value['timezone'] ?? $this->userTimezone);
return $dateTime->setTimezone($this->defaultTimezone);
diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php b/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php
index 212b1aaba..4a84c59e3 100644
--- a/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php
+++ b/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php
@@ -109,7 +109,7 @@ public function toStatement($value, DriverInterface $driver): int
}
/**
- * Marshals request data into PHP floats.
+ * Marshals request data into PHP integers.
*
* @param mixed $value The value to convert.
* @return int|null Converted value.
diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeMap.php b/app/vendor/cakephp/cakephp/src/Database/TypeMap.php
index ad3482cb8..ac3f8eb8b 100644
--- a/app/vendor/cakephp/cakephp/src/Database/TypeMap.php
+++ b/app/vendor/cakephp/cakephp/src/Database/TypeMap.php
@@ -22,29 +22,29 @@
class TypeMap
{
/**
- * Associative array with the default fields and the related types this query might contain.
+ * Array with the default fields and the related types this query might contain.
*
* Used to avoid repetition when calling multiple functions inside this class that
* may require a custom type for a specific field.
*
- * @var array
+ * @var array
*/
protected $_defaults = [];
/**
- * Associative array with the fields and the related types that override defaults this query might contain
+ * Array with the fields and the related types that override defaults this query might contain
*
* Used to avoid repetition when calling multiple functions inside this class that
* may require a custom type for a specific field.
*
- * @var array
+ * @var array
*/
protected $_types = [];
/**
* Creates an instance with the given defaults
*
- * @param array $defaults The defaults to use.
+ * @param array $defaults The defaults to use.
*/
public function __construct(array $defaults = [])
{
@@ -69,7 +69,7 @@ public function __construct(array $defaults = [])
* This method will replace all the existing default mappings with the ones provided.
* To add into the mappings use `addDefaults()`.
*
- * @param array $defaults Associative array where keys are field names and values
+ * @param array $defaults Array where keys are field names / positions and values
* are the correspondent type.
* @return $this
*/
@@ -83,7 +83,7 @@ public function setDefaults(array $defaults)
/**
* Returns the currently configured types.
*
- * @return array
+ * @return array
*/
public function getDefaults(): array
{
@@ -95,7 +95,7 @@ public function getDefaults(): array
*
* If a key already exists it will not be overwritten.
*
- * @param array $types The additional types to add.
+ * @param array $types The additional types to add.
* @return void
*/
public function addDefaults(array $types): void
@@ -114,7 +114,7 @@ public function addDefaults(array $types): void
*
* This method will replace all the existing type maps with the ones provided.
*
- * @param array $types Associative array where keys are field names and values
+ * @param array $types Array where keys are field names / positions and values
* are the correspondent type.
* @return $this
*/
@@ -128,7 +128,7 @@ public function setTypes(array $types)
/**
* Gets a map of fields and their associated types for single-use.
*
- * @return array
+ * @return array
*/
public function getTypes(): array
{
@@ -151,7 +151,7 @@ public function type($column): ?string
/**
* Returns an array of all types mapped types
*
- * @return array
+ * @return array
*/
public function toArray(): array
{
diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php b/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php
index d33070937..402a9b5fe 100644
--- a/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php
@@ -66,7 +66,7 @@ public function getTypeMap(): TypeMap
* To add a default without overwriting existing ones
* use `getTypeMap()->addDefaults()`
*
- * @param array $types The array of types to set.
+ * @param array $types The array of types to set.
* @return $this
* @see \Cake\Database\TypeMap::setDefaults()
*/
@@ -80,7 +80,7 @@ public function setDefaultTypes(array $types)
/**
* Gets default types of current type map.
*
- * @return array
+ * @return array
*/
public function getDefaultTypes(): array
{
diff --git a/app/vendor/cakephp/cakephp/src/Database/composer.json b/app/vendor/cakephp/cakephp/src/Database/composer.json
index 93251172c..64c22e80b 100644
--- a/app/vendor/cakephp/cakephp/src/Database/composer.json
+++ b/app/vendor/cakephp/cakephp/src/Database/composer.json
@@ -24,12 +24,13 @@
"source": "https://github.com/cakephp/database"
},
"require": {
- "php": ">=7.2.0",
+ "php": ">=7.4.0",
"cakephp/core": "^4.0",
"cakephp/datasource": "^4.0"
},
"suggest": {
- "cakephp/i18n": "If you are using locale-aware datetime formats or Chronos types."
+ "cakephp/i18n": "If you are using locale-aware datetime formats or Chronos types.",
+ "cakephp/log": "If you want to use query logging without providing a logger yourself."
},
"autoload": {
"psr-4": {
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php
index fcf96b65d..01a7d5330 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php
@@ -74,7 +74,7 @@ public function configName(): string;
/**
* Get the configuration data used to create the connection.
*
- * @return array
+ * @return array
*/
public function config(): array;
@@ -82,7 +82,7 @@ public function config(): array;
* Executes a callable function inside a transaction, if any exception occurs
* while executing the passed callable, the transaction will be rolled back
* If the result of the callable function is `false`, the transaction will
- * also be rolled back. Otherwise the transaction is committed after executing
+ * also be rolled back. Otherwise, the transaction is committed after executing
* the callback.
*
* The callback will receive the connection instance as its first argument.
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php
index 2f7b27337..c0991064b 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php
@@ -216,7 +216,7 @@ public function getOriginalValues(): array;
/**
* Returns whether this entity contains a field named $field
- * regardless of if it is empty.
+ * and is not set to null.
*
* @param array|string $field The field to check.
* @return bool
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php b/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php
index a18b4cdea..b8ef4a9dd 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php
@@ -107,7 +107,7 @@ trait EntityTrait
* not defined in the map will take its value. For example, `'*' => true`
* means that any field not defined in the map will be accessible by default
*
- * @var array
+ * @var array
*/
protected $_accessible = ['*' => true];
@@ -279,12 +279,12 @@ public function &get(string $field)
}
$value = null;
- $method = static::_accessor($field, 'get');
if (isset($this->_fields[$field])) {
$value = &$this->_fields[$field];
}
+ $method = static::_accessor($field, 'get');
if ($method) {
$result = $this->{$method}($value);
@@ -1071,7 +1071,7 @@ protected function _readError($object, $path = null): array
/**
* Get a list of invalid fields and their data for errors upon validation/patching
*
- * @return array
+ * @return array
*/
public function getInvalid(): array
{
@@ -1096,7 +1096,7 @@ public function getInvalidField(string $field)
* This value could not be patched into the entity and is simply copied into the _invalid property for debugging
* purposes or to be able to log it away.
*
- * @param array $fields The values to set.
+ * @param array $fields The values to set.
* @param bool $overwrite Whether to overwrite pre-existing values for $field.
* @return $this
*/
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Exception/PageOutOfBoundsException.php b/app/vendor/cakephp/cakephp/src/Datasource/Exception/PageOutOfBoundsException.php
index 787365b14..be75b2d76 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/Exception/PageOutOfBoundsException.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/Exception/PageOutOfBoundsException.php
@@ -1,28 +1,7 @@
$fields The values to set.
* @param bool $overwrite Whether to overwrite pre-existing values for $field.
* @return $this
*/
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php b/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php
index 4bf00a814..1d925c09d 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php
@@ -53,7 +53,7 @@ public function get(string $alias, array $options = [])
unset($storeOptions['allowFallbackClass']);
if (isset($this->instances[$alias])) {
- if (!empty($storeOptions) && $this->options[$alias] !== $storeOptions) {
+ if (!empty($storeOptions) && isset($this->options[$alias]) && $this->options[$alias] !== $storeOptions) {
throw new RuntimeException(sprintf(
'You cannot configure "%s", it already exists in the registry.',
$alias
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php b/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php
index d842cd90a..a87c8ea62 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php
@@ -1,679 +1,7 @@
- */
- protected $_defaultConfig = [
- 'page' => 1,
- 'limit' => 20,
- 'maxLimit' => 100,
- 'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
- ];
-
- /**
- * Paging params after pagination operation is done.
- *
- * @var array
- */
- protected $_pagingParams = [];
-
- /**
- * Handles automatic pagination of model records.
- *
- * ### Configuring pagination
- *
- * When calling `paginate()` you can use the $settings parameter to pass in
- * pagination settings. These settings are used to build the queries made
- * and control other pagination settings.
- *
- * If your settings contain a key with the current table's alias. The data
- * inside that key will be used. Otherwise the top level configuration will
- * be used.
- *
- * ```
- * $settings = [
- * 'limit' => 20,
- * 'maxLimit' => 100
- * ];
- * $results = $paginator->paginate($table, $settings);
- * ```
- *
- * The above settings will be used to paginate any repository. You can configure
- * repository specific settings by keying the settings with the repository alias.
- *
- * ```
- * $settings = [
- * 'Articles' => [
- * 'limit' => 20,
- * 'maxLimit' => 100
- * ],
- * 'Comments' => [ ... ]
- * ];
- * $results = $paginator->paginate($table, $settings);
- * ```
- *
- * This would allow you to have different pagination settings for
- * `Articles` and `Comments` repositories.
- *
- * ### Controlling sort fields
- *
- * By default CakePHP will automatically allow sorting on any column on the
- * repository object being paginated. Often times you will want to allow
- * sorting on either associated columns or calculated fields. In these cases
- * you will need to define an allowed list of all the columns you wish to allow
- * sorting on. You can define the allowed sort fields in the `$settings` parameter:
- *
- * ```
- * $settings = [
- * 'Articles' => [
- * 'finder' => 'custom',
- * 'sortableFields' => ['title', 'author_id', 'comment_count'],
- * ]
- * ];
- * ```
- *
- * Passing an empty array as sortableFields disallows sorting altogether.
- *
- * ### Paginating with custom finders
- *
- * You can paginate with any find type defined on your table using the
- * `finder` option.
- *
- * ```
- * $settings = [
- * 'Articles' => [
- * 'finder' => 'popular'
- * ]
- * ];
- * $results = $paginator->paginate($table, $settings);
- * ```
- *
- * Would paginate using the `find('popular')` method.
- *
- * You can also pass an already created instance of a query to this method:
- *
- * ```
- * $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
- * return $q->where(['name' => 'CakePHP'])
- * });
- * $results = $paginator->paginate($query);
- * ```
- *
- * ### Scoping Request parameters
- *
- * By using request parameter scopes you can paginate multiple queries in
- * the same controller action:
- *
- * ```
- * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
- * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
- * ```
- *
- * Each of the above queries will use different query string parameter sets
- * for pagination data. An example URL paginating both results would be:
- *
- * ```
- * /dashboard?articles[page]=1&tags[page]=2
- * ```
- *
- * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The repository or query
- * to paginate.
- * @param array $params Request params
- * @param array $settings The settings/configuration used for pagination.
- * @return \Cake\Datasource\ResultSetInterface Query results
- * @throws \Cake\Datasource\Exception\PageOutOfBoundsException
- */
- public function paginate(object $object, array $params = [], array $settings = []): ResultSetInterface
- {
- $query = null;
- if ($object instanceof QueryInterface) {
- $query = $object;
- $object = $query->getRepository();
- if ($object === null) {
- throw new CakeException('No repository set for query.');
- }
- }
-
- $data = $this->extractData($object, $params, $settings);
- $query = $this->getQuery($object, $query, $data);
-
- $cleanQuery = clone $query;
- $results = $query->all();
- $data['numResults'] = count($results);
- $data['count'] = $this->getCount($cleanQuery, $data);
-
- $pagingParams = $this->buildParams($data);
- $alias = $object->getAlias();
- $this->_pagingParams = [$alias => $pagingParams];
- if ($pagingParams['requestedPage'] > $pagingParams['page']) {
- throw new PageOutOfBoundsException([
- 'requestedPage' => $pagingParams['requestedPage'],
- 'pagingParams' => $this->_pagingParams,
- ]);
- }
-
- return $results;
- }
-
- /**
- * Get query for fetching paginated results.
- *
- * @param \Cake\Datasource\RepositoryInterface $object Repository instance.
- * @param \Cake\Datasource\QueryInterface|null $query Query Instance.
- * @param array $data Pagination data.
- * @return \Cake\Datasource\QueryInterface
- */
- protected function getQuery(RepositoryInterface $object, ?QueryInterface $query, array $data): QueryInterface
- {
- if ($query === null) {
- $query = $object->find($data['finder'], $data['options']);
- } else {
- $query->applyOptions($data['options']);
- }
-
- return $query;
- }
-
- /**
- * Get total count of records.
- *
- * @param \Cake\Datasource\QueryInterface $query Query instance.
- * @param array $data Pagination data.
- * @return int|null
- */
- protected function getCount(QueryInterface $query, array $data): ?int
- {
- return $query->count();
- }
-
- /**
- * Extract pagination data needed
- *
- * @param \Cake\Datasource\RepositoryInterface $object The repository object.
- * @param array $params Request params
- * @param array $settings The settings/configuration used for pagination.
- * @return array Array with keys 'defaults', 'options' and 'finder'
- */
- protected function extractData(RepositoryInterface $object, array $params, array $settings): array
- {
- $alias = $object->getAlias();
- $defaults = $this->getDefaults($alias, $settings);
- $options = $this->mergeOptions($params, $defaults);
- $options = $this->validateSort($object, $options);
- $options = $this->checkLimit($options);
-
- $options += ['page' => 1, 'scope' => null];
- $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page'];
- [$finder, $options] = $this->_extractFinder($options);
-
- return compact('defaults', 'options', 'finder');
- }
-
- /**
- * Build pagination params.
- *
- * @param array $data Paginator data containing keys 'options',
- * 'count', 'defaults', 'finder', 'numResults'.
- * @return array Paging params.
- */
- protected function buildParams(array $data): array
- {
- $limit = $data['options']['limit'];
-
- $paging = [
- 'count' => $data['count'],
- 'current' => $data['numResults'],
- 'perPage' => $limit,
- 'page' => $data['options']['page'],
- 'requestedPage' => $data['options']['page'],
- ];
-
- $paging = $this->addPageCountParams($paging, $data);
- $paging = $this->addStartEndParams($paging, $data);
- $paging = $this->addPrevNextParams($paging, $data);
- $paging = $this->addSortingParams($paging, $data);
-
- $paging += [
- 'limit' => $data['defaults']['limit'] != $limit ? $limit : null,
- 'scope' => $data['options']['scope'],
- 'finder' => $data['finder'],
- ];
-
- return $paging;
- }
-
- /**
- * Add "page" and "pageCount" params.
- *
- * @param array $params Paging params.
- * @param array $data Paginator data.
- * @return array Updated params.
- */
- protected function addPageCountParams(array $params, array $data): array
- {
- $page = $params['page'];
- $pageCount = 0;
-
- if ($params['count'] !== null) {
- $pageCount = max((int)ceil($params['count'] / $params['perPage']), 1);
- $page = min($page, $pageCount);
- } elseif ($params['current'] === 0 && $params['requestedPage'] > 1) {
- $page = 1;
- }
-
- $params['page'] = $page;
- $params['pageCount'] = $pageCount;
-
- return $params;
- }
-
- /**
- * Add "start" and "end" params.
- *
- * @param array $params Paging params.
- * @param array $data Paginator data.
- * @return array Updated params.
- */
- protected function addStartEndParams(array $params, array $data): array
- {
- $start = $end = 0;
-
- if ($params['current'] > 0) {
- $start = (($params['page'] - 1) * $params['perPage']) + 1;
- $end = $start + $params['current'] - 1;
- }
-
- $params['start'] = $start;
- $params['end'] = $end;
-
- return $params;
- }
-
- /**
- * Add "prevPage" and "nextPage" params.
- *
- * @param array $params Paginator params.
- * @param array $data Paging data.
- * @return array Updated params.
- */
- protected function addPrevNextParams(array $params, array $data): array
- {
- $params['prevPage'] = $params['page'] > 1;
- if ($params['count'] === null) {
- $params['nextPage'] = true;
- } else {
- $params['nextPage'] = $params['count'] > $params['page'] * $params['perPage'];
- }
-
- return $params;
- }
-
- /**
- * Add sorting / ordering params.
- *
- * @param array $params Paginator params.
- * @param array $data Paging data.
- * @return array Updated params.
- */
- protected function addSortingParams(array $params, array $data): array
- {
- $defaults = $data['defaults'];
- $order = (array)$data['options']['order'];
- $sortDefault = $directionDefault = false;
-
- if (!empty($defaults['order']) && count($defaults['order']) === 1) {
- $sortDefault = key($defaults['order']);
- $directionDefault = current($defaults['order']);
- }
-
- $params += [
- 'sort' => $data['options']['sort'],
- 'direction' => isset($data['options']['sort']) && count($order) ? current($order) : null,
- 'sortDefault' => $sortDefault,
- 'directionDefault' => $directionDefault,
- 'completeSort' => $order,
- ];
-
- return $params;
- }
-
- /**
- * Extracts the finder name and options out of the provided pagination options.
- *
- * @param array $options the pagination options.
- * @return array An array containing in the first position the finder name
- * and in the second the options to be passed to it.
- */
- protected function _extractFinder(array $options): array
- {
- $type = !empty($options['finder']) ? $options['finder'] : 'all';
- unset($options['finder'], $options['maxLimit']);
-
- if (is_array($type)) {
- $options = (array)current($type) + $options;
- $type = key($type);
- }
-
- return [$type, $options];
- }
-
- /**
- * Get paging params after pagination operation.
- *
- * @return array
- */
- public function getPagingParams(): array
- {
- return $this->_pagingParams;
- }
-
- /**
- * Shim method for reading the deprecated whitelist or allowedParameters options
- *
- * @return array
- */
- protected function getAllowedParameters(): array
- {
- $allowed = $this->getConfig('allowedParameters');
- if (!$allowed) {
- $allowed = [];
- }
- $whitelist = $this->getConfig('whitelist');
- if ($whitelist) {
- deprecationWarning('The `whitelist` option is deprecated. Use the `allowedParameters` option instead.');
-
- return array_merge($allowed, $whitelist);
- }
-
- return $allowed;
- }
-
- /**
- * Shim method for reading the deprecated sortWhitelist or sortableFields options.
- *
- * @param array $config The configuration data to coalesce and emit warnings on.
- * @return array|null
- */
- protected function getSortableFields(array $config): ?array
- {
- $allowed = $config['sortableFields'] ?? null;
- if ($allowed !== null) {
- return $allowed;
- }
- $deprecated = $config['sortWhitelist'] ?? null;
- if ($deprecated !== null) {
- deprecationWarning('The `sortWhitelist` option is deprecated. Use `sortableFields` instead.');
- }
-
- return $deprecated;
- }
-
- /**
- * Merges the various options that Paginator uses.
- * Pulls settings together from the following places:
- *
- * - General pagination settings
- * - Model specific settings.
- * - Request parameters
- *
- * The result of this method is the aggregate of all the option sets
- * combined together. You can change config value `allowedParameters` to modify
- * which options/values can be set using request parameters.
- *
- * @param array $params Request params.
- * @param array $settings The settings to merge with the request data.
- * @return array Array of merged options.
- */
- public function mergeOptions(array $params, array $settings): array
- {
- if (!empty($settings['scope'])) {
- $scope = $settings['scope'];
- $params = !empty($params[$scope]) ? (array)$params[$scope] : [];
- }
-
- $allowed = $this->getAllowedParameters();
- $params = array_intersect_key($params, array_flip($allowed));
-
- return array_merge($settings, $params);
- }
-
- /**
- * Get the settings for a $model. If there are no settings for a specific
- * repository, the general settings will be used.
- *
- * @param string $alias Model name to get settings for.
- * @param array $settings The settings which is used for combining.
- * @return array An array of pagination settings for a model,
- * or the general settings.
- */
- public function getDefaults(string $alias, array $settings): array
- {
- if (isset($settings[$alias])) {
- $settings = $settings[$alias];
- }
-
- $defaults = $this->getConfig();
- $defaults['whitelist'] = $defaults['allowedParameters'] = $this->getAllowedParameters();
-
- $maxLimit = $settings['maxLimit'] ?? $defaults['maxLimit'];
- $limit = $settings['limit'] ?? $defaults['limit'];
-
- if ($limit > $maxLimit) {
- $limit = $maxLimit;
- }
-
- $settings['maxLimit'] = $maxLimit;
- $settings['limit'] = $limit;
-
- return $settings + $defaults;
- }
-
- /**
- * Validate that the desired sorting can be performed on the $object.
- *
- * Only fields or virtualFields can be sorted on. The direction param will
- * also be sanitized. Lastly sort + direction keys will be converted into
- * the model friendly order key.
- *
- * You can use the allowedParameters option to control which columns/fields are
- * available for sorting via URL parameters. This helps prevent users from ordering large
- * result sets on un-indexed values.
- *
- * If you need to sort on associated columns or synthetic properties you
- * will need to use the `sortableFields` option.
- *
- * Any columns listed in the allowed sort fields will be implicitly trusted.
- * You can use this to sort on synthetic columns, or columns added in custom
- * find operations that may not exist in the schema.
- *
- * The default order options provided to paginate() will be merged with the user's
- * requested sorting field/direction.
- *
- * @param \Cake\Datasource\RepositoryInterface $object Repository object.
- * @param array $options The pagination options being used for this request.
- * @return array An array of options with sort + direction removed and
- * replaced with order if possible.
- */
- public function validateSort(RepositoryInterface $object, array $options): array
- {
- if (isset($options['sort'])) {
- $direction = null;
- if (isset($options['direction'])) {
- $direction = strtolower($options['direction']);
- }
- if (!in_array($direction, ['asc', 'desc'], true)) {
- $direction = 'asc';
- }
-
- $order = isset($options['order']) && is_array($options['order']) ? $options['order'] : [];
- if ($order && $options['sort'] && strpos($options['sort'], '.') === false) {
- $order = $this->_removeAliases($order, $object->getAlias());
- }
-
- $options['order'] = [$options['sort'] => $direction] + $order;
- } else {
- $options['sort'] = null;
- }
- unset($options['direction']);
-
- if (empty($options['order'])) {
- $options['order'] = [];
- }
- if (!is_array($options['order'])) {
- return $options;
- }
-
- $sortAllowed = false;
- $allowed = $this->getSortableFields($options);
- if ($allowed !== null) {
- $options['sortableFields'] = $options['sortWhitelist'] = $allowed;
-
- $field = key($options['order']);
- $sortAllowed = in_array($field, $allowed, true);
- if (!$sortAllowed) {
- $options['order'] = [];
- $options['sort'] = null;
-
- return $options;
- }
- }
-
- if (
- $options['sort'] === null
- && count($options['order']) === 1
- && !is_numeric(key($options['order']))
- ) {
- $options['sort'] = key($options['order']);
- }
-
- $options['order'] = $this->_prefix($object, $options['order'], $sortAllowed);
-
- return $options;
- }
-
- /**
- * Remove alias if needed.
- *
- * @param array $fields Current fields
- * @param string $model Current model alias
- * @return array $fields Unaliased fields where applicable
- */
- protected function _removeAliases(array $fields, string $model): array
- {
- $result = [];
- foreach ($fields as $field => $sort) {
- if (strpos($field, '.') === false) {
- $result[$field] = $sort;
- continue;
- }
-
- [$alias, $currentField] = explode('.', $field);
-
- if ($alias === $model) {
- $result[$currentField] = $sort;
- continue;
- }
-
- $result[$field] = $sort;
- }
-
- return $result;
- }
-
- /**
- * Prefixes the field with the table alias if possible.
- *
- * @param \Cake\Datasource\RepositoryInterface $object Repository object.
- * @param array $order Order array.
- * @param bool $allowed Whether the field was allowed.
- * @return array Final order array.
- */
- protected function _prefix(RepositoryInterface $object, array $order, bool $allowed = false): array
- {
- $tableAlias = $object->getAlias();
- $tableOrder = [];
- foreach ($order as $key => $value) {
- if (is_numeric($key)) {
- $tableOrder[] = $value;
- continue;
- }
- $field = $key;
- $alias = $tableAlias;
-
- if (strpos($key, '.') !== false) {
- [$alias, $field] = explode('.', $key);
- }
- $correctAlias = ($tableAlias === $alias);
-
- if ($correctAlias && $allowed) {
- // Disambiguate fields in schema. As id is quite common.
- if ($object->hasField($field)) {
- $field = $alias . '.' . $field;
- }
- $tableOrder[$field] = $value;
- } elseif ($correctAlias && $object->hasField($field)) {
- $tableOrder[$tableAlias . '.' . $field] = $value;
- } elseif (!$correctAlias && $allowed) {
- $tableOrder[$alias . '.' . $field] = $value;
- }
- }
-
- return $tableOrder;
- }
-
- /**
- * Check the limit parameter and ensure it's within the maxLimit bounds.
- *
- * @param array $options An array of options with a limit key to be checked.
- * @return array An array of options for pagination.
- */
- public function checkLimit(array $options): array
- {
- $options['limit'] = (int)$options['limit'];
- if ($options['limit'] < 1) {
- $options['limit'] = 1;
- }
- $options['limit'] = max(min($options['limit'], $options['maxLimit']), 1);
-
- return $options;
- }
-}
+class_exists('Cake\Datasource\Paging\NumericPaginator');
+deprecationWarning(
+ 'Use Cake\Datasource\Paging\NumericPaginator instead of Cake\Datasource\Paginator.'
+);
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/PaginatorInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/PaginatorInterface.php
index c2bff1a17..eb8b3b79f 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/PaginatorInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/PaginatorInterface.php
@@ -1,41 +1,7 @@
+ */
+ protected $_defaultConfig = [
+ 'page' => 1,
+ 'limit' => 20,
+ 'maxLimit' => 100,
+ 'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+ ];
+
+ /**
+ * Paging params after pagination operation is done.
+ *
+ * @var array
+ */
+ protected $_pagingParams = [];
+
+ /**
+ * Handles automatic pagination of model records.
+ *
+ * ### Configuring pagination
+ *
+ * When calling `paginate()` you can use the $settings parameter to pass in
+ * pagination settings. These settings are used to build the queries made
+ * and control other pagination settings.
+ *
+ * If your settings contain a key with the current table's alias. The data
+ * inside that key will be used. Otherwise, the top level configuration will
+ * be used.
+ *
+ * ```
+ * $settings = [
+ * 'limit' => 20,
+ * 'maxLimit' => 100
+ * ];
+ * $results = $paginator->paginate($table, $settings);
+ * ```
+ *
+ * The above settings will be used to paginate any repository. You can configure
+ * repository specific settings by keying the settings with the repository alias.
+ *
+ * ```
+ * $settings = [
+ * 'Articles' => [
+ * 'limit' => 20,
+ * 'maxLimit' => 100
+ * ],
+ * 'Comments' => [ ... ]
+ * ];
+ * $results = $paginator->paginate($table, $settings);
+ * ```
+ *
+ * This would allow you to have different pagination settings for
+ * `Articles` and `Comments` repositories.
+ *
+ * ### Controlling sort fields
+ *
+ * By default CakePHP will automatically allow sorting on any column on the
+ * repository object being paginated. Often times you will want to allow
+ * sorting on either associated columns or calculated fields. In these cases
+ * you will need to define an allowed list of all the columns you wish to allow
+ * sorting on. You can define the allowed sort fields in the `$settings` parameter:
+ *
+ * ```
+ * $settings = [
+ * 'Articles' => [
+ * 'finder' => 'custom',
+ * 'sortableFields' => ['title', 'author_id', 'comment_count'],
+ * ]
+ * ];
+ * ```
+ *
+ * Passing an empty array as sortableFields disallows sorting altogether.
+ *
+ * ### Paginating with custom finders
+ *
+ * You can paginate with any find type defined on your table using the
+ * `finder` option.
+ *
+ * ```
+ * $settings = [
+ * 'Articles' => [
+ * 'finder' => 'popular'
+ * ]
+ * ];
+ * $results = $paginator->paginate($table, $settings);
+ * ```
+ *
+ * Would paginate using the `find('popular')` method.
+ *
+ * You can also pass an already created instance of a query to this method:
+ *
+ * ```
+ * $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
+ * return $q->where(['name' => 'CakePHP'])
+ * });
+ * $results = $paginator->paginate($query);
+ * ```
+ *
+ * ### Scoping Request parameters
+ *
+ * By using request parameter scopes you can paginate multiple queries in
+ * the same controller action:
+ *
+ * ```
+ * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
+ * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
+ * ```
+ *
+ * Each of the above queries will use different query string parameter sets
+ * for pagination data. An example URL paginating both results would be:
+ *
+ * ```
+ * /dashboard?articles[page]=1&tags[page]=2
+ * ```
+ *
+ * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The repository or query
+ * to paginate.
+ * @param array $params Request params
+ * @param array $settings The settings/configuration used for pagination.
+ * @return \Cake\Datasource\ResultSetInterface Query results
+ * @throws \Cake\Datasource\Paging\Exception\PageOutOfBoundsException
+ */
+ public function paginate(object $object, array $params = [], array $settings = []): ResultSetInterface
+ {
+ $query = null;
+ if ($object instanceof QueryInterface) {
+ $query = $object;
+ $object = $query->getRepository();
+ if ($object === null) {
+ throw new CakeException('No repository set for query.');
+ }
+ }
+
+ $data = $this->extractData($object, $params, $settings);
+ $query = $this->getQuery($object, $query, $data);
+
+ $cleanQuery = clone $query;
+ $results = $query->all();
+ $data['numResults'] = count($results);
+ $data['count'] = $this->getCount($cleanQuery, $data);
+
+ $pagingParams = $this->buildParams($data);
+ $alias = $object->getAlias();
+ $this->_pagingParams = [$alias => $pagingParams];
+ if ($pagingParams['requestedPage'] > $pagingParams['page']) {
+ throw new PageOutOfBoundsException([
+ 'requestedPage' => $pagingParams['requestedPage'],
+ 'pagingParams' => $this->_pagingParams,
+ ]);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get query for fetching paginated results.
+ *
+ * @param \Cake\Datasource\RepositoryInterface $object Repository instance.
+ * @param \Cake\Datasource\QueryInterface|null $query Query Instance.
+ * @param array $data Pagination data.
+ * @return \Cake\Datasource\QueryInterface
+ */
+ protected function getQuery(RepositoryInterface $object, ?QueryInterface $query, array $data): QueryInterface
+ {
+ if ($query === null) {
+ $query = $object->find($data['finder'], $data['options']);
+ } else {
+ $query->applyOptions($data['options']);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Get total count of records.
+ *
+ * @param \Cake\Datasource\QueryInterface $query Query instance.
+ * @param array $data Pagination data.
+ * @return int|null
+ */
+ protected function getCount(QueryInterface $query, array $data): ?int
+ {
+ return $query->count();
+ }
+
+ /**
+ * Extract pagination data needed
+ *
+ * @param \Cake\Datasource\RepositoryInterface $object The repository object.
+ * @param array $params Request params
+ * @param array $settings The settings/configuration used for pagination.
+ * @return array Array with keys 'defaults', 'options' and 'finder'
+ */
+ protected function extractData(RepositoryInterface $object, array $params, array $settings): array
+ {
+ $alias = $object->getAlias();
+ $defaults = $this->getDefaults($alias, $settings);
+ $options = $this->mergeOptions($params, $defaults);
+ $options = $this->validateSort($object, $options);
+ $options = $this->checkLimit($options);
+
+ $options += ['page' => 1, 'scope' => null];
+ $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page'];
+ [$finder, $options] = $this->_extractFinder($options);
+
+ return compact('defaults', 'options', 'finder');
+ }
+
+ /**
+ * Build pagination params.
+ *
+ * @param array $data Paginator data containing keys 'options',
+ * 'count', 'defaults', 'finder', 'numResults'.
+ * @return array Paging params.
+ */
+ protected function buildParams(array $data): array
+ {
+ $limit = $data['options']['limit'];
+
+ $paging = [
+ 'count' => $data['count'],
+ 'current' => $data['numResults'],
+ 'perPage' => $limit,
+ 'page' => $data['options']['page'],
+ 'requestedPage' => $data['options']['page'],
+ ];
+
+ $paging = $this->addPageCountParams($paging, $data);
+ $paging = $this->addStartEndParams($paging, $data);
+ $paging = $this->addPrevNextParams($paging, $data);
+ $paging = $this->addSortingParams($paging, $data);
+
+ $paging += [
+ 'limit' => $data['defaults']['limit'] != $limit ? $limit : null,
+ 'scope' => $data['options']['scope'],
+ 'finder' => $data['finder'],
+ ];
+
+ return $paging;
+ }
+
+ /**
+ * Add "page" and "pageCount" params.
+ *
+ * @param array $params Paging params.
+ * @param array $data Paginator data.
+ * @return array Updated params.
+ */
+ protected function addPageCountParams(array $params, array $data): array
+ {
+ $page = $params['page'];
+ $pageCount = 0;
+
+ if ($params['count'] !== null) {
+ $pageCount = max((int)ceil($params['count'] / $params['perPage']), 1);
+ $page = min($page, $pageCount);
+ } elseif ($params['current'] === 0 && $params['requestedPage'] > 1) {
+ $page = 1;
+ }
+
+ $params['page'] = $page;
+ $params['pageCount'] = $pageCount;
+
+ return $params;
+ }
+
+ /**
+ * Add "start" and "end" params.
+ *
+ * @param array $params Paging params.
+ * @param array $data Paginator data.
+ * @return array Updated params.
+ */
+ protected function addStartEndParams(array $params, array $data): array
+ {
+ $start = $end = 0;
+
+ if ($params['current'] > 0) {
+ $start = (($params['page'] - 1) * $params['perPage']) + 1;
+ $end = $start + $params['current'] - 1;
+ }
+
+ $params['start'] = $start;
+ $params['end'] = $end;
+
+ return $params;
+ }
+
+ /**
+ * Add "prevPage" and "nextPage" params.
+ *
+ * @param array $params Paginator params.
+ * @param array $data Paging data.
+ * @return array Updated params.
+ */
+ protected function addPrevNextParams(array $params, array $data): array
+ {
+ $params['prevPage'] = $params['page'] > 1;
+ if ($params['count'] === null) {
+ $params['nextPage'] = true;
+ } else {
+ $params['nextPage'] = $params['count'] > $params['page'] * $params['perPage'];
+ }
+
+ return $params;
+ }
+
+ /**
+ * Add sorting / ordering params.
+ *
+ * @param array $params Paginator params.
+ * @param array $data Paging data.
+ * @return array Updated params.
+ */
+ protected function addSortingParams(array $params, array $data): array
+ {
+ $defaults = $data['defaults'];
+ $order = (array)$data['options']['order'];
+ $sortDefault = $directionDefault = false;
+
+ if (!empty($defaults['order']) && count($defaults['order']) >= 1) {
+ $sortDefault = key($defaults['order']);
+ $directionDefault = current($defaults['order']);
+ }
+
+ $params += [
+ 'sort' => $data['options']['sort'],
+ 'direction' => isset($data['options']['sort']) && count($order) ? current($order) : null,
+ 'sortDefault' => $sortDefault,
+ 'directionDefault' => $directionDefault,
+ 'completeSort' => $order,
+ ];
+
+ return $params;
+ }
+
+ /**
+ * Extracts the finder name and options out of the provided pagination options.
+ *
+ * @param array $options the pagination options.
+ * @return array An array containing in the first position the finder name
+ * and in the second the options to be passed to it.
+ */
+ protected function _extractFinder(array $options): array
+ {
+ $type = !empty($options['finder']) ? $options['finder'] : 'all';
+ unset($options['finder'], $options['maxLimit']);
+
+ if (is_array($type)) {
+ $options = (array)current($type) + $options;
+ $type = key($type);
+ }
+
+ return [$type, $options];
+ }
+
+ /**
+ * Get paging params after pagination operation.
+ *
+ * @return array
+ */
+ public function getPagingParams(): array
+ {
+ return $this->_pagingParams;
+ }
+
+ /**
+ * Shim method for reading the deprecated whitelist or allowedParameters options
+ *
+ * @return array
+ */
+ protected function getAllowedParameters(): array
+ {
+ $allowed = $this->getConfig('allowedParameters');
+ if (!$allowed) {
+ $allowed = [];
+ }
+ $whitelist = $this->getConfig('whitelist');
+ if ($whitelist) {
+ deprecationWarning('The `whitelist` option is deprecated. Use the `allowedParameters` option instead.');
+
+ return array_merge($allowed, $whitelist);
+ }
+
+ return $allowed;
+ }
+
+ /**
+ * Shim method for reading the deprecated sortWhitelist or sortableFields options.
+ *
+ * @param array $config The configuration data to coalesce and emit warnings on.
+ * @return array|null
+ */
+ protected function getSortableFields(array $config): ?array
+ {
+ $allowed = $config['sortableFields'] ?? null;
+ if ($allowed !== null) {
+ return $allowed;
+ }
+ $deprecated = $config['sortWhitelist'] ?? null;
+ if ($deprecated !== null) {
+ deprecationWarning('The `sortWhitelist` option is deprecated. Use `sortableFields` instead.');
+ }
+
+ return $deprecated;
+ }
+
+ /**
+ * Merges the various options that Paginator uses.
+ * Pulls settings together from the following places:
+ *
+ * - General pagination settings
+ * - Model specific settings.
+ * - Request parameters
+ *
+ * The result of this method is the aggregate of all the option sets
+ * combined together. You can change config value `allowedParameters` to modify
+ * which options/values can be set using request parameters.
+ *
+ * @param array $params Request params.
+ * @param array $settings The settings to merge with the request data.
+ * @return array Array of merged options.
+ */
+ public function mergeOptions(array $params, array $settings): array
+ {
+ if (!empty($settings['scope'])) {
+ $scope = $settings['scope'];
+ $params = !empty($params[$scope]) ? (array)$params[$scope] : [];
+ }
+
+ $allowed = $this->getAllowedParameters();
+ $params = array_intersect_key($params, array_flip($allowed));
+
+ return array_merge($settings, $params);
+ }
+
+ /**
+ * Get the settings for a $model. If there are no settings for a specific
+ * repository, the general settings will be used.
+ *
+ * @param string $alias Model name to get settings for.
+ * @param array $settings The settings which is used for combining.
+ * @return array An array of pagination settings for a model,
+ * or the general settings.
+ */
+ public function getDefaults(string $alias, array $settings): array
+ {
+ if (isset($settings[$alias])) {
+ $settings = $settings[$alias];
+ }
+
+ $defaults = $this->getConfig();
+ $defaults['whitelist'] = $defaults['allowedParameters'] = $this->getAllowedParameters();
+
+ $maxLimit = $settings['maxLimit'] ?? $defaults['maxLimit'];
+ $limit = $settings['limit'] ?? $defaults['limit'];
+
+ if ($limit > $maxLimit) {
+ $limit = $maxLimit;
+ }
+
+ $settings['maxLimit'] = $maxLimit;
+ $settings['limit'] = $limit;
+
+ return $settings + $defaults;
+ }
+
+ /**
+ * Validate that the desired sorting can be performed on the $object.
+ *
+ * Only fields or virtualFields can be sorted on. The direction param will
+ * also be sanitized. Lastly sort + direction keys will be converted into
+ * the model friendly order key.
+ *
+ * You can use the allowedParameters option to control which columns/fields are
+ * available for sorting via URL parameters. This helps prevent users from ordering large
+ * result sets on un-indexed values.
+ *
+ * If you need to sort on associated columns or synthetic properties you
+ * will need to use the `sortableFields` option.
+ *
+ * Any columns listed in the allowed sort fields will be implicitly trusted.
+ * You can use this to sort on synthetic columns, or columns added in custom
+ * find operations that may not exist in the schema.
+ *
+ * The default order options provided to paginate() will be merged with the user's
+ * requested sorting field/direction.
+ *
+ * @param \Cake\Datasource\RepositoryInterface $object Repository object.
+ * @param array $options The pagination options being used for this request.
+ * @return array An array of options with sort + direction removed and
+ * replaced with order if possible.
+ */
+ public function validateSort(RepositoryInterface $object, array $options): array
+ {
+ if (isset($options['sort'])) {
+ $direction = null;
+ if (isset($options['direction'])) {
+ $direction = strtolower($options['direction']);
+ }
+ if (!in_array($direction, ['asc', 'desc'], true)) {
+ $direction = 'asc';
+ }
+
+ $order = isset($options['order']) && is_array($options['order']) ? $options['order'] : [];
+ if ($order && $options['sort'] && strpos($options['sort'], '.') === false) {
+ $order = $this->_removeAliases($order, $object->getAlias());
+ }
+
+ $options['order'] = [$options['sort'] => $direction] + $order;
+ } else {
+ $options['sort'] = null;
+ }
+ unset($options['direction']);
+
+ if (empty($options['order'])) {
+ $options['order'] = [];
+ }
+ if (!is_array($options['order'])) {
+ return $options;
+ }
+
+ $sortAllowed = false;
+ $allowed = $this->getSortableFields($options);
+ if ($allowed !== null) {
+ $options['sortableFields'] = $options['sortWhitelist'] = $allowed;
+
+ $field = key($options['order']);
+ $sortAllowed = in_array($field, $allowed, true);
+ if (!$sortAllowed) {
+ $options['order'] = [];
+ $options['sort'] = null;
+
+ return $options;
+ }
+ }
+
+ if (
+ $options['sort'] === null
+ && count($options['order']) >= 1
+ && !is_numeric(key($options['order']))
+ ) {
+ $options['sort'] = key($options['order']);
+ }
+
+ $options['order'] = $this->_prefix($object, $options['order'], $sortAllowed);
+
+ return $options;
+ }
+
+ /**
+ * Remove alias if needed.
+ *
+ * @param array $fields Current fields
+ * @param string $model Current model alias
+ * @return array $fields Unaliased fields where applicable
+ */
+ protected function _removeAliases(array $fields, string $model): array
+ {
+ $result = [];
+ foreach ($fields as $field => $sort) {
+ if (strpos($field, '.') === false) {
+ $result[$field] = $sort;
+ continue;
+ }
+
+ [$alias, $currentField] = explode('.', $field);
+
+ if ($alias === $model) {
+ $result[$currentField] = $sort;
+ continue;
+ }
+
+ $result[$field] = $sort;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Prefixes the field with the table alias if possible.
+ *
+ * @param \Cake\Datasource\RepositoryInterface $object Repository object.
+ * @param array $order Order array.
+ * @param bool $allowed Whether the field was allowed.
+ * @return array Final order array.
+ */
+ protected function _prefix(RepositoryInterface $object, array $order, bool $allowed = false): array
+ {
+ $tableAlias = $object->getAlias();
+ $tableOrder = [];
+ foreach ($order as $key => $value) {
+ if (is_numeric($key)) {
+ $tableOrder[] = $value;
+ continue;
+ }
+ $field = $key;
+ $alias = $tableAlias;
+
+ if (strpos($key, '.') !== false) {
+ [$alias, $field] = explode('.', $key);
+ }
+ $correctAlias = ($tableAlias === $alias);
+
+ if ($correctAlias && $allowed) {
+ // Disambiguate fields in schema. As id is quite common.
+ if ($object->hasField($field)) {
+ $field = $alias . '.' . $field;
+ }
+ $tableOrder[$field] = $value;
+ } elseif ($correctAlias && $object->hasField($field)) {
+ $tableOrder[$tableAlias . '.' . $field] = $value;
+ } elseif (!$correctAlias && $allowed) {
+ $tableOrder[$alias . '.' . $field] = $value;
+ }
+ }
+
+ return $tableOrder;
+ }
+
+ /**
+ * Check the limit parameter and ensure it's within the maxLimit bounds.
+ *
+ * @param array $options An array of options with a limit key to be checked.
+ * @return array An array of options for pagination.
+ */
+ public function checkLimit(array $options): array
+ {
+ $options['limit'] = (int)$options['limit'];
+ if ($options['limit'] < 1) {
+ $options['limit'] = 1;
+ }
+ $options['limit'] = max(min($options['limit'], $options['maxLimit']), 1);
+
+ return $options;
+ }
+}
+
+// phpcs:disable
+class_alias(
+ 'Cake\Datasource\Paging\NumericPaginator',
+ 'Cake\Datasource\Paginator'
+);
+// phpcs:enable
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php
new file mode 100644
index 000000000..4d30597b2
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php
@@ -0,0 +1,50 @@
+
*/
public function aliasField(string $field, ?string $alias = null): array;
@@ -62,7 +62,7 @@ public function aliasField(string $field, ?string $alias = null): array;
*
* @param array $fields The fields to alias
* @param string|null $defaultAlias The default alias
- * @return array
+ * @return array
*/
public function aliasFields(array $fields, ?string $defaultAlias = null): array;
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php b/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php
index dd950dd74..6d70a77b5 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php
@@ -224,7 +224,7 @@ public function eagerLoaded(bool $value)
*
* @param string $field The field to alias
* @param string|null $alias the alias used to prefix the field
- * @return array
+ * @return array
*/
public function aliasField(string $field, ?string $alias = null): array
{
@@ -247,7 +247,7 @@ public function aliasField(string $field, ?string $alias = null): array
*
* @param array $fields The fields to alias
* @param string|null $defaultAlias The default alias
- * @return array
+ * @return array
*/
public function aliasFields(array $fields, ?string $defaultAlias = null): array
{
@@ -549,9 +549,11 @@ public function __call(string $method, array $arguments)
$resultSetClass = $this->_decoratorClass();
if (in_array($method, get_class_methods($resultSetClass), true)) {
deprecationWarning(sprintf(
- 'Calling result set method `%s()` directly on query instance is deprecated. ' .
- 'You must call `all()` to retrieve the results first.',
- $method
+ 'Calling `%s` methods, such as `%s()`, on queries is deprecated. ' .
+ 'You must call `all()` first (for example, `all()->%s()`).',
+ ResultSetInterface::class,
+ $method,
+ $method,
), 2);
$results = $this->all();
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php
index df4ed7d6c..93a00416c 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php
@@ -242,7 +242,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options
* $article = $this->Articles->patchEntities($articles, $this->request->getData());
* ```
*
- * @param \Traversable|array<\Cake\Datasource\EntityInterface> $entities the entities that will get the
+ * @param iterable<\Cake\Datasource\EntityInterface> $entities the entities that will get the
* data merged in
* @param array $data list of arrays to be merged into the entities
* @param array $options A list of options for the objects hydration.
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php b/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php
index 3f9fe54a6..8a80efc8e 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php
@@ -37,7 +37,7 @@ class RuleInvoker
/**
* Rule options
*
- * @var array
+ * @var array
*/
protected $options = [];
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php
index eef98d1bb..08ab8be25 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
+ * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @license https://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Datasource;
diff --git a/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php b/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php
index 705be9a2a..95499fb6d 100644
--- a/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php
+++ b/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php
@@ -1,40 +1,7 @@
=7.2.0",
+ "php": ">=7.4.0",
"cakephp/core": "^4.0",
"psr/log": "^1.0 || ^2.0",
"psr/simple-cache": "^1.0 || ^2.0"
diff --git a/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php b/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php
index c4b3f1d14..74963b2ab 100644
--- a/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php
+++ b/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php
@@ -88,6 +88,12 @@ abstract protected function _displayException(Throwable $exception): void;
*/
public function register(): void
{
+ deprecationWarning(
+ 'Use of `BaseErrorHandler` and subclasses are deprecated. ' .
+ 'Upgrade to the new `ErrorTrap` and `ExceptionTrap` subsystem. ' .
+ 'See https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html'
+ );
+
$level = $this->_config['errorLevel'] ?? -1;
error_reporting($level);
set_error_handler([$this, 'handleError'], $level);
@@ -330,6 +336,11 @@ public function logException(Throwable $exception, ?ServerRequestInterface $requ
if (empty($this->_config['log'])) {
return false;
}
+ foreach ($this->_config['skipLog'] as $class) {
+ if ($exception instanceof $class) {
+ return false;
+ }
+ }
return $this->getLogger()->log($exception, $request ?? Router::getRequest());
}
diff --git a/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php b/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php
index fa12c763c..78353d0ca 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php
@@ -130,3 +130,10 @@ protected function _stop(int $code): void
exit($code);
}
}
+
+// phpcs:disable
+class_alias(
+ 'Cake\Error\ConsoleErrorHandler',
+ 'Cake\Console\ConsoleErrorHandler'
+);
+// phpcs:enable
diff --git a/app/vendor/cakephp/cakephp/src/Error/Debugger.php b/app/vendor/cakephp/cakephp/src/Error/Debugger.php
index ad4395dae..e8465bb1c 100644
--- a/app/vendor/cakephp/cakephp/src/Error/Debugger.php
+++ b/app/vendor/cakephp/cakephp/src/Error/Debugger.php
@@ -31,6 +31,8 @@
use Cake\Error\Debug\ScalarNode;
use Cake\Error\Debug\SpecialNode;
use Cake\Error\Debug\TextFormatter;
+use Cake\Error\Renderer\HtmlErrorRenderer;
+use Cake\Error\Renderer\TextErrorRenderer;
use Cake\Log\Log;
use Cake\Utility\Hash;
use Cake\Utility\Security;
@@ -81,6 +83,7 @@ class Debugger
*/
protected $_templates = [
'log' => [
+ // These templates are not actually used, as Debugger::log() is called instead.
'trace' => '{:reference} - {:path}, line {:line}',
'error' => '{:error} ({:code}): {:description} in [{:file}, line {:line}]',
],
@@ -110,6 +113,21 @@ class Debugger
],
];
+ /**
+ * Mapping for error renderers.
+ *
+ * Error renderers are replacing output formatting with
+ * an object based system. Having Debugger handle and render errors
+ * will be deprecated and the new ErrorTrap system should be used instead.
+ *
+ * @var array
+ */
+ protected $renderers = [
+ 'txt' => TextErrorRenderer::class,
+ // The html alias currently uses no JS and will be deprecated.
+ 'js' => HtmlErrorRenderer::class,
+ ];
+
/**
* A map of editors to their link templates.
*
@@ -442,9 +460,12 @@ public static function formatTrace($backtrace, array $options = [])
if (in_array($signature, $options['exclude'], true)) {
continue;
}
- if ($options['format'] === 'points' && $trace['file'] !== '[internal]') {
- $back[] = ['file' => $trace['file'], 'line' => $trace['line']];
+ if ($options['format'] === 'points') {
+ $back[] = ['file' => $trace['file'], 'line' => $trace['line'], 'reference' => $reference];
} elseif ($options['format'] === 'array') {
+ if (!$options['args']) {
+ unset($trace['args']);
+ }
$back[] = $trace;
} else {
if (isset($self->_templates[$options['format']]['traceLine'])) {
@@ -463,7 +484,10 @@ public static function formatTrace($backtrace, array $options = [])
return $back;
}
- /** @psalm-suppress InvalidArgument */
+ /**
+ * @psalm-suppress InvalidArgument
+ * @phpstan-ignore-next-line
+ */
return implode("\n", $back);
}
@@ -758,7 +782,7 @@ protected static function exportObject(object $var, DebugContext $context): Node
if ($remaining > 0) {
if (method_exists($var, '__debugInfo')) {
try {
- foreach ($var->__debugInfo() as $key => $val) {
+ foreach ((array)$var->__debugInfo() as $key => $val) {
$node->addProperty(new PropertyNode("'{$key}'", null, static::export($val, $context)));
}
@@ -817,9 +841,12 @@ protected static function exportObject(object $var, DebugContext $context): Node
* Get the output format for Debugger error rendering.
*
* @return string Returns the current format when getting.
+ * @deprecated 4.4.0 Update your application so use ErrorTrap instead.
*/
public static function getOutputFormat(): string
{
+ deprecationWarning('Debugger::getOutputFormat() is deprecated.');
+
return Debugger::getInstance()->_outputFormat;
}
@@ -829,9 +856,11 @@ public static function getOutputFormat(): string
* @param string $format The format you want errors to be output as.
* @return void
* @throws \InvalidArgumentException When choosing a format that doesn't exist.
+ * @deprecated 4.4.0 Update your application so use ErrorTrap instead.
*/
public static function setOutputFormat(string $format): void
{
+ deprecationWarning('Debugger::setOutputFormat() is deprecated.');
$self = Debugger::getInstance();
if (!isset($self->_templates[$format])) {
@@ -882,9 +911,11 @@ public static function setOutputFormat(string $format): void
* straight HTML output, or 'txt' for unformatted text.
* @param array $strings Template strings, or a callback to be used for the output format.
* @return array The resulting format string set.
+ * @deprecated 4.4.0 Update your application so use ErrorTrap instead.
*/
public static function addFormat(string $format, array $strings): array
{
+ deprecationWarning('Debugger::addFormat() is deprecated.');
$self = Debugger::getInstance();
if (isset($self->_templates[$format])) {
if (isset($strings['links'])) {
@@ -898,15 +929,37 @@ public static function addFormat(string $format, array $strings): array
} else {
$self->_templates[$format] = $strings;
}
+ unset($self->renderers[$format]);
return $self->_templates[$format];
}
+ /**
+ * Add a renderer to the current instance.
+ *
+ * @param string $name The alias for the the renderer.
+ * @param class-string<\Cake\Error\ErrorRendererInterface> $class The classname of the renderer to use.
+ * @return void
+ * @deprecated 4.4.0 Update your application so use ErrorTrap instead.
+ */
+ public static function addRenderer(string $name, string $class): void
+ {
+ deprecationWarning('Debugger::addRenderer() is deprecated.');
+ if (!in_array(ErrorRendererInterface::class, class_implements($class))) {
+ throw new InvalidArgumentException(
+ 'Invalid renderer class. $class must implement ' . ErrorRendererInterface::class
+ );
+ }
+ $self = Debugger::getInstance();
+ $self->renderers[$name] = $class;
+ }
+
/**
* Takes a processed array of data from an error and displays it in the chosen format.
*
* @param array $data Data to output.
* @return void
+ * @deprecated 4.4.0 Update your application so use ErrorTrap instead.
*/
public function outputError(array $data): void
{
@@ -922,6 +975,17 @@ public function outputError(array $data): void
];
$data += $defaults;
+ $outputFormat = $this->_outputFormat;
+ if (isset($this->renderers[$outputFormat])) {
+ /** @var array $trace */
+ $trace = static::trace(['start' => $data['start'], 'format' => 'points']);
+ $error = new PhpError($data['code'], $data['description'], $data['file'], $data['line'], $trace);
+ $renderer = new $this->renderers[$outputFormat]();
+ echo $renderer->render($error, Configure::read('debug'));
+
+ return;
+ }
+
$files = static::trace(['start' => $data['start'], 'format' => 'points']);
$code = '';
$file = null;
@@ -956,7 +1020,7 @@ public function outputError(array $data): void
$data['trace'] = $trace;
$data['id'] = 'cakeErr' . uniqid();
- $tpl = $this->_templates[$this->_outputFormat] + $this->_templates['base'];
+ $tpl = $this->_templates[$outputFormat] + $this->_templates['base'];
if (isset($tpl['links'])) {
foreach ($tpl['links'] as $key => $val) {
@@ -1064,9 +1128,8 @@ public static function formatHtmlMessage(string $message): string
{
$message = h($message);
$message = preg_replace('/`([^`]+)`/', '$1
', $message);
- $message = nl2br($message);
- return $message;
+ return nl2br($message);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php b/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php
index 540f02a87..f69193f3f 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php
@@ -199,7 +199,7 @@ protected function _logInternalError(Throwable $exception): void
/**
* Method that can be easily stubbed in testing.
*
- * @param \Cake\Http\Response|string $response Either the message or response object.
+ * @param \Psr\Http\Message\ResponseInterface|string $response Either the message or response object.
* @return void
*/
protected function _sendResponse($response): void
diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php b/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php
index 91b81db0a..4e91d8b90 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php
@@ -33,14 +33,11 @@ class ErrorLogger implements ErrorLoggerInterface
/**
* Default configuration values.
*
- * - `skipLog` List of exceptions to skip logging. Exceptions that
- * extend one of the listed exceptions will also not be logged.
* - `trace` Should error logs include stack traces?
*
* @var array
*/
protected $_defaultConfig = [
- 'skipLog' => [],
'trace' => false,
];
@@ -55,7 +52,59 @@ public function __construct(array $config = [])
}
/**
- * @inheritDoc
+ * Log an error to Cake's Log subsystem
+ *
+ * @param \Cake\Error\PhpError $error The error to log
+ * @param ?\Psr\Http\Message\ServerRequestInterface $request The request if in an HTTP context.
+ * @param bool $includeTrace Should the log message include a stacktrace
+ * @return void
+ */
+ public function logError(PhpError $error, ?ServerRequestInterface $request = null, bool $includeTrace = false): void
+ {
+ $message = $error->getMessage();
+ if ($request) {
+ $message .= $this->getRequestContext($request);
+ }
+ if ($includeTrace) {
+ $message .= "\nTrace:\n" . $error->getTraceAsString() . "\n";
+ }
+ $logMap = [
+ 'strict' => LOG_NOTICE,
+ 'deprecated' => LOG_NOTICE,
+ ];
+ $level = $error->getLabel();
+ $level = $logMap[$level] ?? $level;
+
+ Log::write($level, $message);
+ }
+
+ /**
+ * Log an exception to Cake's Log subsystem
+ *
+ * @param \Throwable $exception The exception to log a message for.
+ * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available.
+ * @param bool $includeTrace Whether or not a stack trace should be logged.
+ * @return void
+ */
+ public function logException(
+ Throwable $exception,
+ ?ServerRequestInterface $request = null,
+ bool $includeTrace = false
+ ): void {
+ $message = $this->getMessage($exception, false, $includeTrace);
+
+ if ($request !== null) {
+ $message .= $this->getRequestContext($request);
+ }
+ Log::error($message);
+ }
+
+ /**
+ * @param string|int $level The logging level
+ * @param string $message The message to be logged.
+ * @param array $context Context.
+ * @return bool
+ * @deprecated 4.4.0 Use logError instead.
*/
public function logMessage($level, string $message, array $context = []): bool
{
@@ -65,22 +114,24 @@ public function logMessage($level, string $message, array $context = []): bool
if (!empty($context['trace'])) {
$message .= "\nTrace:\n" . $context['trace'] . "\n";
}
+ $logMap = [
+ 'strict' => LOG_NOTICE,
+ 'deprecated' => LOG_NOTICE,
+ ];
+ $level = $logMap[$level] ?? $level;
return Log::write($level, $message);
}
/**
- * @inheritDoc
+ * @param \Throwable $exception The exception to log a message for.
+ * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available.
+ * @return bool
+ * @deprecated 4.4.0 Use logException instead.
*/
public function log(Throwable $exception, ?ServerRequestInterface $request = null): bool
{
- foreach ($this->getConfig('skipLog') as $class) {
- if ($exception instanceof $class) {
- return false;
- }
- }
-
- $message = $this->getMessage($exception);
+ $message = $this->getMessage($exception, false, $this->getConfig('trace'));
if ($request !== null) {
$message .= $this->getRequestContext($request);
@@ -96,9 +147,10 @@ public function log(Throwable $exception, ?ServerRequestInterface $request = nul
*
* @param \Throwable $exception The exception to log a message for.
* @param bool $isPrevious False for original exception, true for previous
+ * @param bool $includeTrace Whether or not to include a stack trace.
* @return string Error message
*/
- protected function getMessage(Throwable $exception, bool $isPrevious = false): string
+ protected function getMessage(Throwable $exception, bool $isPrevious = false, bool $includeTrace = false): string
{
$message = sprintf(
'%s[%s] %s in %s on line %s',
@@ -117,7 +169,7 @@ protected function getMessage(Throwable $exception, bool $isPrevious = false): s
}
}
- if ($this->getConfig('trace')) {
+ if ($includeTrace) {
/** @var array $trace */
$trace = Debugger::formatTrace($exception, ['format' => 'points']);
$message .= "\nStack Trace:\n";
@@ -132,7 +184,7 @@ protected function getMessage(Throwable $exception, bool $isPrevious = false): s
$previous = $exception->getPrevious();
if ($previous) {
- $message .= $this->getMessage($previous, true);
+ $message .= $this->getMessage($previous, true, $includeTrace);
}
return $message;
diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php b/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php
index b8ee57c23..5a2626b24 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php
@@ -24,6 +24,11 @@
*
* Used by the ErrorHandlerMiddleware and global
* error handlers to log exceptions and errors.
+ *
+ * @method void logException(\Throwable $exception, ?\Psr\Http\Message\ServerRequestInterface $request = null, bool $includeTrace = false)
+ * Log an exception with an optional HTTP request.
+ * @method void logError(\Cake\Error\PhpError $error, ?\Psr\Http\Message\ServerRequestInterface $request = null, bool $includeTrace = false)
+ * Log an error with an optional HTTP request.
*/
interface ErrorLoggerInterface
{
@@ -33,6 +38,7 @@ interface ErrorLoggerInterface
* @param \Throwable $exception The exception to log a message for.
* @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available.
* @return bool
+ * @deprecated 4.4.0 Implement `logException` instead.
*/
public function log(
Throwable $exception,
@@ -46,6 +52,7 @@ public function log(
* @param string $message The message to be logged.
* @param array $context Context.
* @return bool
+ * @deprecated 4.4.0 Implement `logError` instead.
*/
public function logMessage($level, string $message, array $context = []): bool;
}
diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php b/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php
new file mode 100644
index 000000000..c0829126b
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php
@@ -0,0 +1,43 @@
+
+ */
+ protected $_defaultConfig = [
+ 'errorLevel' => E_ALL,
+ 'errorRenderer' => null,
+ 'log' => true,
+ 'logger' => ErrorLogger::class,
+ 'trace' => false,
+ ];
+
+ /**
+ * Constructor
+ *
+ * @param array $options An options array. See $_defaultConfig.
+ */
+ public function __construct(array $options = [])
+ {
+ $this->setConfig($options);
+ }
+
+ /**
+ * Choose an error renderer based on config or the SAPI
+ *
+ * @return class-string<\Cake\Error\ErrorRendererInterface>
+ */
+ protected function chooseErrorRenderer(): string
+ {
+ $config = $this->getConfig('errorRenderer');
+ if ($config !== null) {
+ return $config;
+ }
+
+ /** @var class-string<\Cake\Error\ErrorRendererInterface> */
+ return PHP_SAPI === 'cli' ? ConsoleErrorRenderer::class : HtmlErrorRenderer::class;
+ }
+
+ /**
+ * Attach this ErrorTrap to PHP's default error handler.
+ *
+ * This will replace the existing error handler, and the
+ * previous error handler will be discarded.
+ *
+ * This method will also set the global error level
+ * via error_reporting().
+ *
+ * @return void
+ */
+ public function register(): void
+ {
+ $level = $this->_config['errorLevel'] ?? -1;
+ error_reporting($level);
+ set_error_handler([$this, 'handleError'], $level);
+ }
+
+ /**
+ * Handle an error from PHP set_error_handler
+ *
+ * Will use the configured renderer to generate output
+ * and output it.
+ *
+ * This method will dispatch the `Error.beforeRender` event which can be listened
+ * to on the global event manager.
+ *
+ * @param int $code Code of error
+ * @param string $description Error description
+ * @param string|null $file File on which error occurred
+ * @param int|null $line Line that triggered the error
+ * @return bool True if error was handled
+ */
+ public function handleError(
+ int $code,
+ string $description,
+ ?string $file = null,
+ ?int $line = null
+ ): bool {
+ if (!(error_reporting() & $code)) {
+ return false;
+ }
+ if ($code === E_USER_ERROR || $code === E_ERROR || $code === E_PARSE) {
+ throw new FatalErrorException($description, $code, $file, $line);
+ }
+
+ /** @var array $trace */
+ $trace = Debugger::trace(['start' => 1, 'format' => 'points']);
+ $error = new PhpError($code, $description, $file, $line, $trace);
+
+ $debug = Configure::read('debug');
+ $renderer = $this->renderer();
+
+ try {
+ // Log first incase rendering or event listeners fail
+ $this->logError($error);
+ $event = $this->dispatchEvent('Error.beforeRender', ['error' => $error]);
+ if ($event->isStopped()) {
+ return true;
+ }
+ $renderer->write($renderer->render($error, $debug));
+ } catch (Exception $e) {
+ // Fatal errors always log.
+ $this->logger()->logMessage('error', 'Could not render error. Got: ' . $e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Logging helper method.
+ *
+ * @param \Cake\Error\PhpError $error The error object to log.
+ * @return void
+ */
+ protected function logError(PhpError $error): void
+ {
+ if (!$this->_config['log']) {
+ return;
+ }
+ $logger = $this->logger();
+ if (method_exists($logger, 'logError')) {
+ $logger->logError($error, Router::getRequest(), $this->_config['trace']);
+ } else {
+ $loggerClass = get_class($logger);
+ deprecationWarning(
+ "The configured logger `{$loggerClass}` does not implement `logError()` " .
+ 'which will be required in future versions of CakePHP.'
+ );
+ $context = [];
+ if ($this->_config['trace']) {
+ $context = [
+ 'trace' => $error->getTraceAsString(),
+ 'request' => Router::getRequest(),
+ ];
+ }
+ $logger->logMessage($error->getLabel(), $error->getMessage(), $context);
+ }
+ }
+
+ /**
+ * Get an instance of the renderer.
+ *
+ * @return \Cake\Error\ErrorRendererInterface
+ */
+ public function renderer(): ErrorRendererInterface
+ {
+ /** @var class-string<\Cake\Error\ErrorRendererInterface> $class */
+ $class = $this->getConfig('errorRenderer') ?: $this->chooseErrorRenderer();
+
+ return new $class($this->_config);
+ }
+
+ /**
+ * Get an instance of the logger.
+ *
+ * @return \Cake\Error\ErrorLoggerInterface
+ */
+ public function logger(): ErrorLoggerInterface
+ {
+ $oldConfig = $this->getConfig('errorLogger');
+ if ($oldConfig !== null) {
+ deprecationWarning('The `errorLogger` configuration key is deprecated. Use `logger` instead.');
+ $this->setConfig(['logger' => $oldConfig, 'errorLogger' => null]);
+ }
+
+ /** @var class-string<\Cake\Error\ErrorLoggerInterface> $class */
+ $class = $this->getConfig('logger', $this->_defaultConfig['logger']);
+
+ return new $class($this->_config);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php
index 9c0e6fad3..c4dab58a6 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php
@@ -16,460 +16,13 @@
*/
namespace Cake\Error;
-use Cake\Controller\Controller;
-use Cake\Controller\ControllerFactory;
-use Cake\Controller\Exception\InvalidParameterException;
-use Cake\Controller\Exception\MissingActionException;
-use Cake\Core\App;
-use Cake\Core\Configure;
-use Cake\Core\Container;
-use Cake\Core\Exception\CakeException;
-use Cake\Core\Exception\MissingPluginException;
-use Cake\Datasource\Exception\PageOutOfBoundsException;
-use Cake\Datasource\Exception\RecordNotFoundException;
-use Cake\Event\Event;
-use Cake\Http\Exception\HttpException;
-use Cake\Http\Exception\MissingControllerException;
-use Cake\Http\Response;
-use Cake\Http\ServerRequest;
-use Cake\Http\ServerRequestFactory;
-use Cake\Routing\Exception\MissingRouteException;
-use Cake\Routing\Router;
-use Cake\Utility\Inflector;
-use Cake\View\Exception\MissingLayoutException;
-use Cake\View\Exception\MissingTemplateException;
-use PDOException;
-use Psr\Http\Message\ResponseInterface;
-use Throwable;
+use Cake\Error\Renderer\WebExceptionRenderer;
/**
- * Exception Renderer.
+ * Backwards compatible Exception Renderer.
*
- * Captures and handles all unhandled exceptions. Displays helpful framework errors when debug is true.
- * When debug is false a ExceptionRenderer will render 404 or 500 errors. If an uncaught exception is thrown
- * and it is a type that ExceptionHandler does not know about it will be treated as a 500 error.
- *
- * ### Implementing application specific exception rendering
- *
- * You can implement application specific exception handling by creating a subclass of
- * ExceptionRenderer and configure it to be the `exceptionRenderer` in config/error.php
- *
- * #### Using a subclass of ExceptionRenderer
- *
- * Using a subclass of ExceptionRenderer gives you full control over how Exceptions are rendered, you
- * can configure your class in your config/app.php.
+ * @deprecated 4.4.0 Use `Cake\Error\Renderer\WebExceptionRenderer` instead.
*/
-class ExceptionRenderer implements ExceptionRendererInterface
+class ExceptionRenderer extends WebExceptionRenderer
{
- /**
- * The exception being handled.
- *
- * @var \Throwable
- */
- protected $error;
-
- /**
- * Controller instance.
- *
- * @var \Cake\Controller\Controller
- */
- protected $controller;
-
- /**
- * Template to render for {@link \Cake\Core\Exception\CakeException}
- *
- * @var string
- */
- protected $template = '';
-
- /**
- * The method corresponding to the Exception this object is for.
- *
- * @var string
- */
- protected $method = '';
-
- /**
- * If set, this will be request used to create the controller that will render
- * the error.
- *
- * @var \Cake\Http\ServerRequest|null
- */
- protected $request;
-
- /**
- * Map of exceptions to http status codes.
- *
- * This can be customized for users that don't want specific exceptions to throw 404 errors
- * or want their application exceptions to be automatically converted.
- *
- * @var array
- * @psalm-var array, int>
- */
- protected $exceptionHttpCodes = [
- // Controller exceptions
- InvalidParameterException::class => 404,
- MissingActionException::class => 404,
- // Datasource exceptions
- PageOutOfBoundsException::class => 404,
- RecordNotFoundException::class => 404,
- // Http exceptions
- MissingControllerException::class => 404,
- // Routing exceptions
- MissingRouteException::class => 404,
- ];
-
- /**
- * Creates the controller to perform rendering on the error response.
- *
- * @param \Throwable $exception Exception.
- * @param \Cake\Http\ServerRequest|null $request The request if this is set it will be used
- * instead of creating a new one.
- */
- public function __construct(Throwable $exception, ?ServerRequest $request = null)
- {
- $this->error = $exception;
- $this->request = $request;
- $this->controller = $this->_getController();
- }
-
- /**
- * Get the controller instance to handle the exception.
- * Override this method in subclasses to customize the controller used.
- * This method returns the built in `ErrorController` normally, or if an error is repeated
- * a bare controller will be used.
- *
- * @return \Cake\Controller\Controller
- * @triggers Controller.startup $controller
- */
- protected function _getController(): Controller
- {
- $request = $this->request;
- $routerRequest = Router::getRequest();
- // Fallback to the request in the router or make a new one from
- // $_SERVER
- if ($request === null) {
- $request = $routerRequest ?: ServerRequestFactory::fromGlobals();
- }
-
- // If the current request doesn't have routing data, but we
- // found a request in the router context copy the params over
- if ($request->getParam('controller') === null && $routerRequest !== null) {
- $request = $request->withAttribute('params', $routerRequest->getAttribute('params'));
- }
-
- $errorOccured = false;
- try {
- $params = $request->getAttribute('params');
- $params['controller'] = 'Error';
-
- $factory = new ControllerFactory(new Container());
- $class = $factory->getControllerClass($request->withAttribute('params', $params));
-
- if (!$class) {
- /** @var string $class */
- $class = App::className('Error', 'Controller', 'Controller');
- }
-
- /** @var \Cake\Controller\Controller $controller */
- $controller = new $class($request);
- $controller->startupProcess();
- } catch (Throwable $e) {
- $errorOccured = true;
- }
-
- if (!isset($controller)) {
- return new Controller($request);
- }
-
- // Retry RequestHandler, as another aspect of startupProcess()
- // could have failed. Ignore any exceptions out of startup, as
- // there could be userland input data parsers.
- if ($errorOccured && isset($controller->RequestHandler)) {
- try {
- $event = new Event('Controller.startup', $controller);
- $controller->RequestHandler->startup($event);
- } catch (Throwable $e) {
- }
- }
-
- return $controller;
- }
-
- /**
- * Clear output buffers so error pages display properly.
- *
- * @return void
- */
- protected function clearOutput(): void
- {
- if (in_array(PHP_SAPI, ['cli', 'phpdbg'])) {
- return;
- }
- while (ob_get_level()) {
- ob_end_clean();
- }
- }
-
- /**
- * Renders the response for the exception.
- *
- * @return \Cake\Http\Response The response to be sent.
- */
- public function render(): ResponseInterface
- {
- $exception = $this->error;
- $code = $this->getHttpCode($exception);
- $method = $this->_method($exception);
- $template = $this->_template($exception, $method, $code);
- $this->clearOutput();
-
- if (method_exists($this, $method)) {
- return $this->_customMethod($method, $exception);
- }
-
- $message = $this->_message($exception, $code);
- $url = $this->controller->getRequest()->getRequestTarget();
- $response = $this->controller->getResponse();
-
- if ($exception instanceof CakeException) {
- /** @psalm-suppress DeprecatedMethod */
- foreach ((array)$exception->responseHeader() as $key => $value) {
- $response = $response->withHeader($key, $value);
- }
- }
- if ($exception instanceof HttpException) {
- foreach ($exception->getHeaders() as $name => $value) {
- $response = $response->withHeader($name, $value);
- }
- }
- $response = $response->withStatus($code);
-
- $viewVars = [
- 'message' => $message,
- 'url' => h($url),
- 'error' => $exception,
- 'code' => $code,
- ];
- $serialize = ['message', 'url', 'code'];
-
- $isDebug = Configure::read('debug');
- if ($isDebug) {
- $trace = (array)Debugger::formatTrace($exception->getTrace(), [
- 'format' => 'array',
- 'args' => false,
- ]);
- $origin = [
- 'file' => $exception->getFile() ?: 'null',
- 'line' => $exception->getLine() ?: 'null',
- ];
- // Traces don't include the origin file/line.
- array_unshift($trace, $origin);
- $viewVars['trace'] = $trace;
- $viewVars += $origin;
- $serialize[] = 'file';
- $serialize[] = 'line';
- }
- $this->controller->set($viewVars);
- $this->controller->viewBuilder()->setOption('serialize', $serialize);
-
- if ($exception instanceof CakeException && $isDebug) {
- $this->controller->set($exception->getAttributes());
- }
- $this->controller->setResponse($response);
-
- return $this->_outputMessage($template);
- }
-
- /**
- * Render a custom error method/template.
- *
- * @param string $method The method name to invoke.
- * @param \Throwable $exception The exception to render.
- * @return \Cake\Http\Response The response to send.
- */
- protected function _customMethod(string $method, Throwable $exception): Response
- {
- $result = $this->{$method}($exception);
- $this->_shutdown();
- if (is_string($result)) {
- $result = $this->controller->getResponse()->withStringBody($result);
- }
-
- return $result;
- }
-
- /**
- * Get method name
- *
- * @param \Throwable $exception Exception instance.
- * @return string
- */
- protected function _method(Throwable $exception): string
- {
- [, $baseClass] = namespaceSplit(get_class($exception));
-
- if (substr($baseClass, -9) === 'Exception') {
- $baseClass = substr($baseClass, 0, -9);
- }
-
- // $baseClass would be an empty string if the exception class is \Exception.
- $method = $baseClass === '' ? 'error500' : Inflector::variable($baseClass);
-
- return $this->method = $method;
- }
-
- /**
- * Get error message.
- *
- * @param \Throwable $exception Exception.
- * @param int $code Error code.
- * @return string Error message
- */
- protected function _message(Throwable $exception, int $code): string
- {
- $message = $exception->getMessage();
-
- if (
- !Configure::read('debug') &&
- !($exception instanceof HttpException)
- ) {
- if ($code < 500) {
- $message = __d('cake', 'Not Found');
- } else {
- $message = __d('cake', 'An Internal Error Has Occurred.');
- }
- }
-
- return $message;
- }
-
- /**
- * Get template for rendering exception info.
- *
- * @param \Throwable $exception Exception instance.
- * @param string $method Method name.
- * @param int $code Error code.
- * @return string Template name
- */
- protected function _template(Throwable $exception, string $method, int $code): string
- {
- if ($exception instanceof HttpException || !Configure::read('debug')) {
- return $this->template = $code < 500 ? 'error400' : 'error500';
- }
-
- if ($exception instanceof PDOException) {
- return $this->template = 'pdo_error';
- }
-
- return $this->template = $method;
- }
-
- /**
- * Gets the appropriate http status code for exception.
- *
- * @param \Throwable $exception Exception.
- * @return int A valid HTTP status code.
- */
- protected function getHttpCode(Throwable $exception): int
- {
- if ($exception instanceof HttpException) {
- return $exception->getCode();
- }
-
- return $this->exceptionHttpCodes[get_class($exception)] ?? 500;
- }
-
- /**
- * Generate the response using the controller object.
- *
- * @param string $template The template to render.
- * @return \Cake\Http\Response A response object that can be sent.
- */
- protected function _outputMessage(string $template): Response
- {
- try {
- $this->controller->render($template);
-
- return $this->_shutdown();
- } catch (MissingTemplateException $e) {
- $attributes = $e->getAttributes();
- if (
- $e instanceof MissingLayoutException ||
- strpos($attributes['file'], 'error500') !== false
- ) {
- return $this->_outputMessageSafe('error500');
- }
-
- return $this->_outputMessage('error500');
- } catch (MissingPluginException $e) {
- $attributes = $e->getAttributes();
- if (isset($attributes['plugin']) && $attributes['plugin'] === $this->controller->getPlugin()) {
- $this->controller->setPlugin(null);
- }
-
- return $this->_outputMessageSafe('error500');
- } catch (Throwable $outer) {
- try {
- return $this->_outputMessageSafe('error500');
- } catch (Throwable $inner) {
- throw $outer;
- }
- }
- }
-
- /**
- * A safer way to render error messages, replaces all helpers, with basics
- * and doesn't call component methods.
- *
- * @param string $template The template to render.
- * @return \Cake\Http\Response A response object that can be sent.
- */
- protected function _outputMessageSafe(string $template): Response
- {
- $builder = $this->controller->viewBuilder();
- $builder
- ->setHelpers([], false)
- ->setLayoutPath('')
- ->setTemplatePath('Error');
- $view = $this->controller->createView('View');
-
- $response = $this->controller->getResponse()
- ->withType('html')
- ->withStringBody($view->render($template, 'error'));
- $this->controller->setResponse($response);
-
- return $response;
- }
-
- /**
- * Run the shutdown events.
- *
- * Triggers the afterFilter and afterDispatch events.
- *
- * @return \Cake\Http\Response The response to serve.
- */
- protected function _shutdown(): Response
- {
- $this->controller->dispatchEvent('Controller.shutdown');
-
- return $this->controller->getResponse();
- }
-
- /**
- * Returns an array that can be used to describe the internal state of this
- * object.
- *
- * @return array
- */
- public function __debugInfo(): array
- {
- return [
- 'error' => $this->error,
- 'request' => $this->request,
- 'controller' => $this->controller,
- 'template' => $this->template,
- 'method' => $this->method,
- ];
- }
}
diff --git a/app/vendor/cakephp/cakephp/src/Error/ExceptionRendererInterface.php b/app/vendor/cakephp/cakephp/src/Error/ExceptionRendererInterface.php
index 103e244ac..e6bc4f7a4 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ExceptionRendererInterface.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ExceptionRendererInterface.php
@@ -20,13 +20,17 @@
/**
* Interface ExceptionRendererInterface
+ *
+ * @method \Psr\Http\Message\ResponseInterface|string render() Render the exception to a string or Http Response.
+ * @method void write(\Psr\Http\Message\ResponseInterface|string $output) Write the output to the output stream.
+ * This method is only called when exceptions are handled by a global default exception handler.
*/
interface ExceptionRendererInterface
{
/**
* Renders the response for the exception.
*
- * @return \Cake\Http\Response The response to be sent.
+ * @return \Psr\Http\Message\ResponseInterface The response to be sent.
*/
public function render(): ResponseInterface;
}
diff --git a/app/vendor/cakephp/cakephp/src/Error/ExceptionTrap.php b/app/vendor/cakephp/cakephp/src/Error/ExceptionTrap.php
new file mode 100644
index 000000000..f86155762
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/ExceptionTrap.php
@@ -0,0 +1,392 @@
+ ['Cake\Http\Exception\NotFoundException', 'Cake\Http\Exception\UnauthorizedException']
+ * ```
+ * This option is forwarded to the configured `logger`
+ * - `extraFatalErrorMemory` - int - The number of megabytes to increase the memory limit by when a fatal error is
+ * encountered. This allows breathing room to complete logging or error handling.
+ * - `stderr` Used in console environments so that renderers have access to the current console output stream.
+ *
+ * @var array
+ */
+ protected $_defaultConfig = [
+ 'exceptionRenderer' => null,
+ 'logger' => ErrorLogger::class,
+ 'stderr' => null,
+ 'log' => true,
+ 'skipLog' => [],
+ 'trace' => false,
+ 'extraFatalErrorMemory' => 4,
+ ];
+
+ /**
+ * A list of handling callbacks.
+ *
+ * Callbacks are invoked for each error that is handled.
+ * Callbacks are invoked in the order they are attached.
+ *
+ * @var array<\Closure>
+ */
+ protected $callbacks = [];
+
+ /**
+ * The currently registered global exception handler
+ *
+ * This is best effort as we can't know if/when another
+ * exception handler is registered.
+ *
+ * @var \Cake\Error\ExceptionTrap|null
+ */
+ protected static $registeredTrap = null;
+
+ /**
+ * Track if this trap was removed from the global handler.
+ *
+ * @var bool
+ */
+ protected $disabled = false;
+
+ /**
+ * Constructor
+ *
+ * @param array $options An options array. See $_defaultConfig.
+ */
+ public function __construct(array $options = [])
+ {
+ $this->setConfig($options);
+ }
+
+ /**
+ * Get an instance of the renderer.
+ *
+ * @param \Throwable $exception Exception to render
+ * @param \Psr\Http\Message\ServerRequestInterface|null $request The request if possible.
+ * @return \Cake\Error\ExceptionRendererInterface
+ */
+ public function renderer(Throwable $exception, $request = null)
+ {
+ $request = $request ?? Router::getRequest();
+
+ /** @var class-string|callable $class */
+ $class = $this->getConfig('exceptionRenderer');
+ $deprecatedConfig = ($class === ExceptionRenderer::class && PHP_SAPI === 'cli');
+ if ($deprecatedConfig) {
+ deprecationWarning(
+ 'Your application is using a deprecated `Error.exceptionRenderer`. ' .
+ 'You can either remove the `Error.exceptionRenderer` config key to have CakePHP choose ' .
+ 'one of the default exception renderers, or define a class that is not `Cake\Error\ExceptionRenderer`.'
+ );
+ }
+ if (!$class || $deprecatedConfig) {
+ // Default to detecting the exception renderer if we're
+ // in a CLI context and the Web renderer is currently selected.
+ // This indicates old configuration or user error, in both scenarios
+ // it is preferrable to use the Console renderer instead.
+ $class = $this->chooseRenderer();
+ }
+
+ if (is_string($class)) {
+ /** @psalm-suppress ArgumentTypeCoercion */
+ if (!(method_exists($class, 'render') && method_exists($class, 'write'))) {
+ throw new InvalidArgumentException(
+ "Cannot use {$class} as an `exceptionRenderer`. " .
+ 'It must implement render() and write() methods.'
+ );
+ }
+
+ /** @var class-string<\Cake\Error\ExceptionRendererInterface> $class */
+ return new $class($exception, $request, $this->_config);
+ }
+
+ return $class($exception, $request);
+ }
+
+ /**
+ * Choose an exception renderer based on config or the SAPI
+ *
+ * @return class-string<\Cake\Error\ExceptionRendererInterface>
+ */
+ protected function chooseRenderer(): string
+ {
+ /** @var class-string<\Cake\Error\ExceptionRendererInterface> */
+ return PHP_SAPI === 'cli' ? ConsoleExceptionRenderer::class : ExceptionRenderer::class;
+ }
+
+ /**
+ * Get an instance of the logger.
+ *
+ * @return \Cake\Error\ErrorLoggerInterface
+ */
+ public function logger(): ErrorLoggerInterface
+ {
+ /** @var class-string<\Cake\Error\ErrorLoggerInterface> $class */
+ $class = $this->getConfig('logger', $this->_defaultConfig['logger']);
+
+ return new $class($this->_config);
+ }
+
+ /**
+ * Attach this ExceptionTrap to PHP's default exception handler.
+ *
+ * This will replace the existing exception handler, and the
+ * previous exception handler will be discarded.
+ *
+ * @return void
+ */
+ public function register(): void
+ {
+ set_exception_handler([$this, 'handleException']);
+ register_shutdown_function([$this, 'handleShutdown']);
+ static::$registeredTrap = $this;
+ }
+
+ /**
+ * Remove this instance from the singleton
+ *
+ * If this instance is not currently the registered singleton
+ * nothing happens.
+ *
+ * @return void
+ */
+ public function unregister(): void
+ {
+ if (static::$registeredTrap == $this) {
+ $this->disabled = true;
+ static::$registeredTrap = null;
+ }
+ }
+
+ /**
+ * Get the registered global instance if set.
+ *
+ * Keep in mind that the global state contained here
+ * is mutable and the object returned by this method
+ * could be a stale value.
+ *
+ * @return \Cake\Error\ExceptionTrap|null The global instance or null.
+ */
+ public static function instance(): ?self
+ {
+ return static::$registeredTrap;
+ }
+
+ /**
+ * Handle uncaught exceptions.
+ *
+ * Uses a template method provided by subclasses to display errors in an
+ * environment appropriate way.
+ *
+ * @param \Throwable $exception Exception instance.
+ * @return void
+ * @throws \Exception When renderer class not found
+ * @see https://secure.php.net/manual/en/function.set-exception-handler.php
+ */
+ public function handleException(Throwable $exception): void
+ {
+ if ($this->disabled) {
+ return;
+ }
+ $request = Router::getRequest();
+
+ $this->logException($exception, $request);
+
+ try {
+ $renderer = $this->renderer($exception);
+ $renderer->write($renderer->render());
+ } catch (Throwable $exception) {
+ $this->logInternalError($exception);
+ }
+ // Use this constant as a proxy for cakephp tests.
+ if (PHP_SAPI == 'cli' && !env('FIXTURE_SCHEMA_METADATA')) {
+ exit(1);
+ }
+ }
+
+ /**
+ * Shutdown handler
+ *
+ * Convert fatal errors into exceptions that we can render.
+ *
+ * @return void
+ */
+ public function handleShutdown(): void
+ {
+ if ($this->disabled) {
+ return;
+ }
+ $megabytes = $this->_config['extraFatalErrorMemory'] ?? 4;
+ if ($megabytes > 0) {
+ $this->increaseMemoryLimit($megabytes * 1024);
+ }
+ $error = error_get_last();
+ if (!is_array($error)) {
+ return;
+ }
+ $fatals = [
+ E_USER_ERROR,
+ E_ERROR,
+ E_PARSE,
+ ];
+ if (!in_array($error['type'], $fatals, true)) {
+ return;
+ }
+ $this->handleFatalError(
+ $error['type'],
+ $error['message'],
+ $error['file'],
+ $error['line']
+ );
+ }
+
+ /**
+ * Increases the PHP "memory_limit" ini setting by the specified amount
+ * in kilobytes
+ *
+ * @param int $additionalKb Number in kilobytes
+ * @return void
+ */
+ public function increaseMemoryLimit(int $additionalKb): void
+ {
+ $limit = ini_get('memory_limit');
+ if ($limit === false || $limit === '' || $limit === '-1') {
+ return;
+ }
+ $limit = trim($limit);
+ $units = strtoupper(substr($limit, -1));
+ $current = (int)substr($limit, 0, -1);
+ if ($units === 'M') {
+ $current *= 1024;
+ $units = 'K';
+ }
+ if ($units === 'G') {
+ $current = $current * 1024 * 1024;
+ $units = 'K';
+ }
+
+ if ($units === 'K') {
+ ini_set('memory_limit', ceil($current + $additionalKb) . 'K');
+ }
+ }
+
+ /**
+ * Display/Log a fatal error.
+ *
+ * @param int $code Code of error
+ * @param string $description Error description
+ * @param string $file File on which error occurred
+ * @param int $line Line that triggered the error
+ * @return void
+ */
+ public function handleFatalError(int $code, string $description, string $file, int $line): void
+ {
+ $this->handleException(new FatalErrorException('Fatal Error: ' . $description, 500, $file, $line));
+ }
+
+ /**
+ * Log an exception.
+ *
+ * Primarily a public function to ensure consistency between global exception handling
+ * and the ErrorHandlerMiddleware. This method will apply the `skipLog` filter
+ * skipping logging if the exception should not be logged.
+ *
+ * After logging is attempted the `Exception.beforeRender` event is triggered.
+ *
+ * @param \Throwable $exception The exception to log
+ * @param \Psr\Http\Message\ServerRequestInterface|null $request The optional request
+ * @return void
+ */
+ public function logException(Throwable $exception, ?ServerRequestInterface $request = null): void
+ {
+ $shouldLog = $this->_config['log'];
+ if ($shouldLog) {
+ foreach ($this->getConfig('skipLog') as $class) {
+ if ($exception instanceof $class) {
+ $shouldLog = false;
+ break;
+ }
+ }
+ }
+ if ($shouldLog) {
+ $logger = $this->logger();
+ if (method_exists($logger, 'logException')) {
+ $logger->logException($exception, $request, $this->_config['trace']);
+ } else {
+ $loggerClass = get_class($logger);
+ deprecationWarning(
+ "The configured logger `{$loggerClass}` should implement `logException()` " .
+ 'to be compatible with future versions of CakePHP.'
+ );
+ $this->logger()->log($exception, $request);
+ }
+ }
+ $this->dispatchEvent('Exception.beforeRender', ['exception' => $exception]);
+ }
+
+ /**
+ * Trigger an error that occurred during rendering an exception.
+ *
+ * By triggering an E_USER_ERROR we can end up in the default
+ * exception handling which will log the rendering failure,
+ * and hopefully render an error page.
+ *
+ * @param \Throwable $exception Exception to log
+ * @return void
+ */
+ public function logInternalError(Throwable $exception): void
+ {
+ $message = sprintf(
+ '[%s] %s (%s:%s)', // Keeping same message format
+ get_class($exception),
+ $exception->getMessage(),
+ $exception->getFile(),
+ $exception->getLine(),
+ );
+ trigger_error($message, E_USER_ERROR);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php b/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php
index 565935446..d1e7efae7 100644
--- a/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php
@@ -20,7 +20,8 @@
use Cake\Core\Configure;
use Cake\Core\InstanceConfigTrait;
use Cake\Error\ErrorHandler;
-use Cake\Error\ExceptionRenderer;
+use Cake\Error\ExceptionTrap;
+use Cake\Error\Renderer\WebExceptionRenderer;
use Cake\Http\Exception\RedirectException;
use Cake\Http\Response;
use InvalidArgumentException;
@@ -44,28 +45,17 @@ class ErrorHandlerMiddleware implements MiddlewareInterface
/**
* Default configuration values.
*
- * Ignored if contructor is passed an ErrorHandler instance.
+ * Ignored if contructor is passed an ExceptionTrap instance.
*
- * - `log` Enable logging of exceptions.
- * - `skipLog` List of exceptions to skip logging. Exceptions that
- * extend one of the listed exceptions will also not be logged. Example:
- *
- * ```
- * 'skipLog' => ['Cake\Error\NotFoundException', 'Cake\Error\UnauthorizedException']
- * ```
- *
- * - `trace` Should error logs include stack traces?
- * - `exceptionRenderer` The renderer instance or class name to use or a callable factory
- * which returns a \Cake\Error\ExceptionRendererInterface instance.
- * Defaults to \Cake\Error\ExceptionRenderer
+ * Configuration keys and values are shared with `ExceptionTrap`.
+ * This class will pass its configuration onto the ExceptionTrap
+ * class if you are using the array style constructor.
*
* @var array
+ * @see \Cake\Error\ExceptionTrap
*/
protected $_defaultConfig = [
- 'skipLog' => [],
- 'log' => true,
- 'trace' => false,
- 'exceptionRenderer' => ExceptionRenderer::class,
+ 'exceptionRenderer' => WebExceptionRenderer::class,
];
/**
@@ -73,12 +63,19 @@ class ErrorHandlerMiddleware implements MiddlewareInterface
*
* @var \Cake\Error\ErrorHandler|null
*/
- protected $errorHandler;
+ protected $errorHandler = null;
+
+ /**
+ * ExceptionTrap instance
+ *
+ * @var \Cake\Error\ExceptionTrap|null
+ */
+ protected $exceptionTrap = null;
/**
* Constructor
*
- * @param \Cake\Error\ErrorHandler|array $errorHandler The error handler instance
+ * @param \Cake\Error\ErrorHandler|\Cake\Error\ExceptionTrap|array $errorHandler The error handler instance
* or config array.
* @throws \InvalidArgumentException
*/
@@ -102,15 +99,23 @@ public function __construct($errorHandler = [])
return;
}
+ if ($errorHandler instanceof ErrorHandler) {
+ deprecationWarning(
+ 'Using an `ErrorHandler` is deprecated. You should migate to the `ExceptionTrap` sub-system instead.'
+ );
+ $this->errorHandler = $errorHandler;
- if (!$errorHandler instanceof ErrorHandler) {
- throw new InvalidArgumentException(sprintf(
- '$errorHandler argument must be a config array or ErrorHandler instance. Got `%s` instead.',
- getTypeName($errorHandler)
- ));
+ return;
}
+ if ($errorHandler instanceof ExceptionTrap) {
+ $this->exceptionTrap = $errorHandler;
- $this->errorHandler = $errorHandler;
+ return;
+ }
+ throw new InvalidArgumentException(sprintf(
+ '$errorHandler argument must be a config array or ExceptionTrap instance. Got `%s` instead.',
+ getTypeName($errorHandler)
+ ));
}
/**
@@ -118,7 +123,7 @@ public function __construct($errorHandler = [])
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler.
- * @return \Psr\Http\Message\ResponseInterface A response.
+ * @return \Psr\Http\Message\ResponseInterface A response
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
@@ -136,22 +141,35 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
*
* @param \Throwable $exception The exception to handle.
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
- * @return \Psr\Http\Message\ResponseInterface A response
+ * @return \Psr\Http\Message\ResponseInterface A response.
*/
public function handleException(Throwable $exception, ServerRequestInterface $request): ResponseInterface
{
- $errorHandler = $this->getErrorHandler();
- $renderer = $errorHandler->getRenderer($exception, $request);
+ if ($this->errorHandler === null) {
+ $handler = $this->getExceptionTrap();
+ $handler->logException($exception, $request);
+
+ $renderer = $handler->renderer($exception, $request);
+ } else {
+ $handler = $this->getErrorHandler();
+ $handler->logException($exception, $request);
+
+ $renderer = $handler->getRenderer($exception, $request);
+ }
try {
- $errorHandler->logException($exception, $request);
+ /** @var \Psr\Http\Message\ResponseInterface|string $response */
$response = $renderer->render();
+ if (is_string($response)) {
+ return new Response(['body' => $response, 'status' => 500]);
+ }
+
+ return $response;
} catch (Throwable $internalException) {
- $errorHandler->logException($internalException, $request);
- $response = $this->handleInternalError();
- }
+ $handler->logException($internalException, $request);
- return $response;
+ return $this->handleInternalError();
+ }
}
/**
@@ -176,9 +194,10 @@ public function handleRedirect(RedirectException $exception): ResponseInterface
*/
protected function handleInternalError(): ResponseInterface
{
- $response = new Response(['body' => 'An Internal Server Error Occurred']);
-
- return $response->withStatus(500);
+ return new Response([
+ 'body' => 'An Internal Server Error Occurred',
+ 'status' => 500,
+ ]);
}
/**
@@ -196,4 +215,20 @@ protected function getErrorHandler(): ErrorHandler
return $this->errorHandler;
}
+
+ /**
+ * Get a exception trap instance
+ *
+ * @return \Cake\Error\ExceptionTrap The exception trap.
+ */
+ protected function getExceptionTrap(): ExceptionTrap
+ {
+ if ($this->exceptionTrap === null) {
+ /** @var class-string<\Cake\Error\ExceptionTrap> $className */
+ $className = App::className('ExceptionTrap', 'Error');
+ $this->exceptionTrap = new $className($this->getConfig());
+ }
+
+ return $this->exceptionTrap;
+ }
}
diff --git a/app/vendor/cakephp/cakephp/src/Error/PhpError.php b/app/vendor/cakephp/cakephp/src/Error/PhpError.php
new file mode 100644
index 000000000..a0a5396fe
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/PhpError.php
@@ -0,0 +1,191 @@
+>
+ */
+ private $trace;
+
+ /**
+ * @var array
+ */
+ private $levelMap = [
+ E_PARSE => 'error',
+ E_ERROR => 'error',
+ E_CORE_ERROR => 'error',
+ E_COMPILE_ERROR => 'error',
+ E_USER_ERROR => 'error',
+ E_WARNING => 'warning',
+ E_USER_WARNING => 'warning',
+ E_COMPILE_WARNING => 'warning',
+ E_RECOVERABLE_ERROR => 'warning',
+ E_NOTICE => 'notice',
+ E_USER_NOTICE => 'notice',
+ E_STRICT => 'strict',
+ E_DEPRECATED => 'deprecated',
+ E_USER_DEPRECATED => 'deprecated',
+ ];
+
+ /**
+ * @var array
+ */
+ private $logMap = [
+ 'error' => LOG_ERR,
+ 'warning' => LOG_WARNING,
+ 'notice' => LOG_NOTICE,
+ 'strict' => LOG_NOTICE,
+ 'deprecated' => LOG_NOTICE,
+ ];
+
+ /**
+ * Constructor
+ *
+ * @param int $code The PHP error code constant
+ * @param string $message The error message.
+ * @param string|null $file The filename of the error.
+ * @param int|null $line The line number for the error.
+ * @param array $trace The backtrace for the error.
+ */
+ public function __construct(
+ int $code,
+ string $message,
+ ?string $file = null,
+ ?int $line = null,
+ array $trace = []
+ ) {
+ $this->code = $code;
+ $this->message = $message;
+ $this->file = $file;
+ $this->line = $line;
+ $this->trace = $trace;
+ }
+
+ /**
+ * Get the PHP error constant.
+ *
+ * @return int
+ */
+ public function getCode(): int
+ {
+ return $this->code;
+ }
+
+ /**
+ * Get the mapped LOG_ constant.
+ *
+ * @return int
+ */
+ public function getLogLevel(): int
+ {
+ $label = $this->getLabel();
+
+ return $this->logMap[$label] ?? LOG_ERR;
+ }
+
+ /**
+ * Get the error code label
+ *
+ * @return string
+ */
+ public function getLabel(): string
+ {
+ return $this->levelMap[$this->code] ?? 'error';
+ }
+
+ /**
+ * Get the error message.
+ *
+ * @return string
+ */
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+
+ /**
+ * Get the error file
+ *
+ * @return string|null
+ */
+ public function getFile(): ?string
+ {
+ return $this->file;
+ }
+
+ /**
+ * Get the error line number.
+ *
+ * @return int|null
+ */
+ public function getLine(): ?int
+ {
+ return $this->line;
+ }
+
+ /**
+ * Get the stacktrace as an array.
+ *
+ * @return array
+ */
+ public function getTrace(): array
+ {
+ return $this->trace;
+ }
+
+ /**
+ * Get the stacktrace as a string.
+ *
+ * @return string
+ */
+ public function getTraceAsString(): string
+ {
+ $out = [];
+ foreach ($this->trace as $frame) {
+ $out[] = "{$frame['reference']} {$frame['file']}, line {$frame['line']}";
+ }
+
+ return implode("\n", $out);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php
new file mode 100644
index 000000000..eb1a2efac
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php
@@ -0,0 +1,84 @@
+output = $config['stderr'] ?? new ConsoleOutput('php://stderr');
+ $this->trace = (bool)($config['trace'] ?? false);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function write(string $out): void
+ {
+ $this->output->write($out);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function render(PhpError $error, bool $debug): string
+ {
+ $trace = '';
+ if ($this->trace) {
+ $trace = "\nStack Trace:\n\n" . $error->getTraceAsString();
+ }
+
+ return sprintf(
+ '%s: %s :: %s on line %s of %s%s',
+ $error->getLabel(),
+ $error->getCode(),
+ $error->getMessage(),
+ $error->getLine() ?? '',
+ $error->getFile() ?? '',
+ $trace
+ );
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php
new file mode 100644
index 000000000..988dcfa44
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php
@@ -0,0 +1,137 @@
+error = $error;
+ $this->output = $config['stderr'] ?? new ConsoleOutput('php://stderr');
+ $this->trace = $config['trace'] ?? true;
+ }
+
+ /**
+ * Render an exception into a plain text message.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|string
+ */
+ public function render()
+ {
+ $exceptions = [$this->error];
+ $previous = $this->error->getPrevious();
+ while ($previous !== null) {
+ $exceptions[] = $previous;
+ $previous = $previous->getPrevious();
+ }
+ $out = [];
+ foreach ($exceptions as $i => $error) {
+ $out = array_merge($out, $this->renderException($error, $i));
+ }
+
+ return join("\n", $out);
+ }
+
+ /**
+ * Render an individual exception
+ *
+ * @param \Throwable $exception The exception to render.
+ * @param int $index Exception index in the chain
+ * @return array
+ */
+ protected function renderException(Throwable $exception, int $index): array
+ {
+ $out = [
+ sprintf(
+ '%s[%s] %s in %s on line %s',
+ $index > 0 ? 'Caused by ' : '',
+ get_class($exception),
+ $exception->getMessage(),
+ $exception->getFile(),
+ $exception->getLine()
+ ),
+ ];
+
+ $debug = Configure::read('debug');
+ if ($debug && $exception instanceof CakeException) {
+ $attributes = $exception->getAttributes();
+ if ($attributes) {
+ $out[] = '';
+ $out[] = 'Exception Attributes';
+ $out[] = '';
+ $out[] = var_export($exception->getAttributes(), true);
+ }
+ }
+
+ if ($this->trace) {
+ $out[] = '';
+ $out[] = 'Stack Trace:';
+ $out[] = '';
+ $out[] = $exception->getTraceAsString();
+ $out[] = '';
+ }
+
+ return $out;
+ }
+
+ /**
+ * Write output to the output stream
+ *
+ * @param string $output The output to print.
+ * @return void
+ */
+ public function write($output): void
+ {
+ $this->output->write($output);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php
new file mode 100644
index 000000000..569445e0b
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php
@@ -0,0 +1,104 @@
+getFile();
+
+ // Some of the error data is not HTML safe so we escape everything.
+ $description = h($error->getMessage());
+ $path = h($file);
+ $trace = h($error->getTraceAsString());
+ $line = $error->getLine();
+
+ $errorMessage = sprintf(
+ '%s (%s)',
+ h(ucfirst($error->getLabel())),
+ h($error->getCode())
+ );
+ $toggle = $this->renderToggle($errorMessage, $id, 'trace');
+ $codeToggle = $this->renderToggle('Code', $id, 'code');
+
+ $excerpt = [];
+ if ($file && $line) {
+ $excerpt = Debugger::excerpt($file, $line, 1);
+ }
+ $code = implode("\n", $excerpt);
+
+ return <<
+ {$toggle}: {$description} [in {$path}, line {$line}]
+
+ {$codeToggle}
+
{$code}
+
{$trace}
+
+
+HTML;
+ }
+
+ /**
+ * Render a toggle link in the error content.
+ *
+ * @param string $text The text to insert. Assumed to be HTML safe.
+ * @param string $id The error id scope.
+ * @param string $suffix The element selector.
+ * @return string
+ */
+ private function renderToggle(string $text, string $id, string $suffix): string
+ {
+ $selector = $id . '-' . $suffix;
+
+ // phpcs:disable
+ return <<
+ {$text}
+
+HTML;
+ // phpcs:enable
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/TextErrorRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/TextErrorRenderer.php
new file mode 100644
index 000000000..ed98b15f0
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/TextErrorRenderer.php
@@ -0,0 +1,56 @@
+getLabel(),
+ $error->getCode(),
+ $error->getMessage(),
+ $error->getLine() ?? '',
+ $error->getFile() ?? '',
+ $error->getTraceAsString(),
+ );
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php
new file mode 100644
index 000000000..42fbda2bf
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php
@@ -0,0 +1,73 @@
+error = $error;
+ }
+
+ /**
+ * Render an exception into a plain text message.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|string
+ */
+ public function render()
+ {
+ return sprintf(
+ "%s : %s on line %s of %s\nTrace:\n%s",
+ $this->error->getCode(),
+ $this->error->getMessage(),
+ $this->error->getLine(),
+ $this->error->getFile(),
+ $this->error->getTraceAsString(),
+ );
+ }
+
+ /**
+ * Write output to stdout.
+ *
+ * @param string $output The output to print.
+ * @return void
+ */
+ public function write($output): void
+ {
+ echo $output;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php
new file mode 100644
index 000000000..5c7143c1b
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php
@@ -0,0 +1,504 @@
+
+ * @psalm-var array