_templates['js']['context'] = '_templates['js']['context'] .= 'style="display: none;">{:context}';
$this->_templates['js']['code'] = '_templates['html']['error'] = $e;
- $this->_templates['html']['context'] = 'Context ';
+ $this->_templates['html']['context'] = 'Context ';
$this->_templates['html']['context'] .= '{:context}
';
}
@@ -182,7 +218,7 @@ public static function getInstance(?string $class = null)
* @param mixed|null $value The value to set.
* @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
* @return mixed Config value being read, or the object itself on write operations.
- * @throws \Cake\Core\Exception\Exception When trying to set a key that is invalid.
+ * @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid.
*/
public static function configInstance($key = null, $value = null, bool $merge = true)
{
@@ -223,18 +259,78 @@ public static function setOutputMask(array $value, bool $merge = true): void
static::configInstance('outputMask', $value, $merge);
}
+ /**
+ * Add an editor link format
+ *
+ * Template strings can use the `{file}` and `{line}` placeholders.
+ * Closures templates must return a string, and accept two parameters:
+ * The file and line.
+ *
+ * @param string $name The name of the editor.
+ * @param string|\Closure $template The string template or closure
+ * @return void
+ */
+ public static function addEditor(string $name, $template): void
+ {
+ $instance = static::getInstance();
+ if (!is_string($template) && !($template instanceof Closure)) {
+ $type = getTypeName($template);
+ throw new RuntimeException("Invalid editor type of `{$type}`. Expected string or Closure.");
+ }
+ $instance->editors[$name] = $template;
+ }
+
+ /**
+ * Choose the editor link style you want to use.
+ *
+ * @param string $name The editor name.
+ * @return void
+ */
+ public static function setEditor(string $name): void
+ {
+ $instance = static::getInstance();
+ if (!isset($instance->editors[$name])) {
+ $known = implode(', ', array_keys($instance->editors));
+ throw new RuntimeException("Unknown editor `{$name}`. Known editors are {$known}");
+ }
+ $instance->setConfig('editor', $name);
+ }
+
+ /**
+ * Get a formatted URL for the active editor.
+ *
+ * @param string $file The file to create a link for.
+ * @param int $line The line number to create a link for.
+ * @return string The formatted URL.
+ */
+ public static function editorUrl(string $file, int $line): string
+ {
+ $instance = static::getInstance();
+ $editor = $instance->getConfig('editor');
+ if (!isset($instance->editors[$editor])) {
+ throw new RuntimeException("Cannot format editor URL `{$editor}` is not a known editor.");
+ }
+
+ $template = $instance->editors[$editor];
+ if (is_string($template)) {
+ return str_replace(['{file}', '{line}'], [$file, (string)$line], $template);
+ }
+
+ return $template($file, $line);
+ }
+
/**
* Recursively formats and outputs the contents of the supplied variable.
*
* @param mixed $var The variable to dump.
- * @param int $depth The depth to output to. Defaults to 3.
+ * @param int $maxDepth The depth to output to. Defaults to 3.
* @return void
* @see \Cake\Error\Debugger::exportVar()
* @link https://book.cakephp.org/4/en/development/debugging.html#outputting-values
*/
- public static function dump($var, int $depth = 3): void
+ public static function dump($var, int $maxDepth = 3): void
{
- pr(static::exportVar($var, $depth));
+ pr(static::exportVar($var, $maxDepth));
}
/**
@@ -243,16 +339,16 @@ public static function dump($var, int $depth = 3): void
*
* @param mixed $var Variable or content to log.
* @param int|string $level Type of log to use. Defaults to 'debug'.
- * @param int $depth The depth to output to. Defaults to 3.
+ * @param int $maxDepth The depth to output to. Defaults to 3.
* @return void
*/
- public static function log($var, $level = 'debug', int $depth = 3): void
+ public static function log($var, $level = 'debug', int $maxDepth = 3): void
{
/** @var string $source */
$source = static::trace(['start' => 1]);
$source .= "\n";
- Log::write($level, "\n" . $source . static::exportVar($var, $depth));
+ Log::write($level, "\n" . $source . static::exportVar($var, $maxDepth));
}
/**
@@ -364,6 +460,7 @@ public static function formatTrace($backtrace, array $options = [])
return $back;
}
+ /** @psalm-suppress InvalidArgument */
return implode("\n", $back);
}
@@ -471,6 +568,36 @@ protected static function _highlight(string $str): string
return $highlight;
}
+ /**
+ * Get the configured export formatter or infer one based on the environment.
+ *
+ * @return \Cake\Error\Debug\FormatterInterface
+ * @unstable This method is not stable and may change in the future.
+ * @since 4.1.0
+ */
+ public function getExportFormatter(): FormatterInterface
+ {
+ $instance = static::getInstance();
+ $class = $instance->getConfig('exportFormatter');
+ if (!$class) {
+ if (ConsoleFormatter::environmentMatches()) {
+ $class = ConsoleFormatter::class;
+ } elseif (HtmlFormatter::environmentMatches()) {
+ $class = HtmlFormatter::class;
+ } else {
+ $class = TextFormatter::class;
+ }
+ }
+ $instance = new $class();
+ if (!$instance instanceof FormatterInterface) {
+ throw new RuntimeException(
+ "The `{$class}` formatter does not implement " . FormatterInterface::class
+ );
+ }
+
+ return $instance;
+ }
+
/**
* Converts a variable to a string for debug output.
*
@@ -489,47 +616,59 @@ protected static function _highlight(string $str): string
* shown in an error message if CakePHP is deployed in development mode.
*
* @param mixed $var Variable to convert.
- * @param int $depth The depth to output to. Defaults to 3.
+ * @param int $maxDepth The depth to output to. Defaults to 3.
* @return string Variable as a formatted string
*/
- public static function exportVar($var, int $depth = 3): string
+ public static function exportVar($var, int $maxDepth = 3): string
{
- return static::_export($var, $depth, 0);
+ $context = new DebugContext($maxDepth);
+ $node = static::export($var, $context);
+
+ return static::getInstance()->getExportFormatter()->dump($node);
+ }
+
+ /**
+ * Convert the variable to the internal node tree.
+ *
+ * The node tree can be manipulated and serialized more easily
+ * than many object graphs can.
+ *
+ * @param mixed $var Variable to convert.
+ * @param int $maxDepth The depth to generate nodes to. Defaults to 3.
+ * @return \Cake\Error\Debug\NodeInterface The root node of the tree.
+ */
+ public static function exportVarAsNodes($var, int $maxDepth = 3): NodeInterface
+ {
+ return static::export($var, new DebugContext($maxDepth));
}
/**
* Protected export function used to keep track of indentation and recursion.
*
* @param mixed $var The variable to dump.
- * @param int $depth The remaining depth.
- * @param int $indent The current indentation level.
- * @return string The dumped variable.
+ * @param \Cake\Error\Debug\DebugContext $context Dump context
+ * @return \Cake\Error\Debug\NodeInterface The dumped variable.
*/
- protected static function _export($var, int $depth, int $indent): string
+ protected static function export($var, DebugContext $context): NodeInterface
{
- switch (static::getType($var)) {
- case 'boolean':
- return $var ? 'true' : 'false';
- case 'integer':
- return '(int) ' . $var;
+ $type = static::getType($var);
+ switch ($type) {
case 'float':
- return '(float) ' . $var;
case 'string':
- if (trim($var) === '' && ctype_space($var) === false) {
- return "''";
- }
-
- return "'" . $var . "'";
- case 'array':
- return static::_array($var, $depth - 1, $indent + 1);
case 'resource':
- return strtolower(gettype($var));
+ case 'resource (closed)':
case 'null':
- return 'null';
+ return new ScalarNode($type, $var);
+ case 'boolean':
+ return new ScalarNode('bool', $var);
+ case 'integer':
+ return new ScalarNode('int', $var);
+ case 'array':
+ return static::exportArray($var, $context->withAddedDepth());
case 'unknown':
- return 'unknown';
+ return new SpecialNode('(unknown)');
default:
- return static::_object($var, $depth - 1, $indent + 1);
+ return static::exportObject($var, $context->withAddedDepth());
}
}
@@ -547,81 +686,81 @@ protected static function _export($var, int $depth, int $indent): string
* - schema
*
* @param array $var The array to export.
- * @param int $depth The current depth, used for recursion tracking.
- * @param int $indent The current indentation level.
- * @return string Exported array.
+ * @param \Cake\Error\Debug\DebugContext $context The current dump context.
+ * @return \Cake\Error\Debug\ArrayNode Exported array.
*/
- protected static function _array(array $var, int $depth, int $indent): string
+ protected static function exportArray(array $var, DebugContext $context): ArrayNode
{
- $out = '[';
- $break = $end = '';
- if (!empty($var)) {
- $break = "\n" . str_repeat("\t", $indent);
- $end = "\n" . str_repeat("\t", $indent - 1);
- }
- $vars = [];
+ $items = [];
- if ($depth >= 0) {
- $outputMask = (array)static::outputMask();
+ $remaining = $context->remainingDepth();
+ if ($remaining >= 0) {
+ $outputMask = static::outputMask();
foreach ($var as $key => $val) {
- // Sniff for globals as !== explodes in < 5.4
- if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
- $val = '[recursion]';
- } elseif (array_key_exists($key, $outputMask)) {
- $val = (string)$outputMask[$key];
+ if (array_key_exists($key, $outputMask)) {
+ $node = new ScalarNode('string', $outputMask[$key]);
} elseif ($val !== $var) {
- $val = static::_export($val, $depth, $indent);
+ // Dump all the items without increasing depth.
+ $node = static::export($val, $context);
+ } else {
+ // Likely recursion, so we increase depth.
+ $node = static::export($val, $context->withAddedDepth());
}
- $vars[] = $break . static::exportVar($key) .
- ' => ' .
- $val;
+ $items[] = new ArrayItemNode(static::export($key, $context), $node);
}
} else {
- $vars[] = $break . '[maximum depth reached]';
+ $items[] = new ArrayItemNode(
+ new ScalarNode('string', ''),
+ new SpecialNode('[maximum depth reached]')
+ );
}
- return $out . implode(',', $vars) . $end . ']';
+ return new ArrayNode($items);
}
/**
- * Handles object to string conversion.
+ * Handles object to node conversion.
*
* @param object $var Object to convert.
- * @param int $depth The current depth, used for tracking recursion.
- * @param int $indent The current indentation level.
- * @return string
+ * @param \Cake\Error\Debug\DebugContext $context The dump context.
+ * @return \Cake\Error\Debug\NodeInterface
* @see \Cake\Error\Debugger::exportVar()
*/
- protected static function _object(object $var, int $depth, int $indent): string
+ protected static function exportObject(object $var, DebugContext $context): NodeInterface
{
- $out = '';
- $props = [];
+ $isRef = $context->hasReference($var);
+ $refNum = $context->getReferenceId($var);
$className = get_class($var);
- $out .= 'object(' . $className . ') {';
- $break = "\n" . str_repeat("\t", $indent);
- $end = "\n" . str_repeat("\t", $indent - 1);
-
- if ($depth > 0 && method_exists($var, '__debugInfo')) {
- try {
- return $out . "\n" .
- substr(static::_array($var->__debugInfo(), $depth - 1, $indent), 1, -1) .
- $end . '}';
- } catch (Exception $e) {
- $message = $e->getMessage();
-
- return $out . "\n(unable to export object: $message)\n }";
- }
+ if ($isRef) {
+ return new ReferenceNode($className, $refNum);
}
+ $node = new ClassNode($className, $refNum);
+
+ $remaining = $context->remainingDepth();
+ if ($remaining > 0) {
+ if (method_exists($var, '__debugInfo')) {
+ try {
+ foreach ($var->__debugInfo() as $key => $val) {
+ $node->addProperty(new PropertyNode("'{$key}'", null, static::export($val, $context)));
+ }
- if ($depth > 0) {
- $outputMask = (array)static::outputMask();
+ return $node;
+ } catch (Exception $e) {
+ return new SpecialNode("(unable to export object: {$e->getMessage()})");
+ }
+ }
+
+ $outputMask = static::outputMask();
$objectVars = get_object_vars($var);
foreach ($objectVars as $key => $value) {
- $value = array_key_exists($key, $outputMask)
- ? $outputMask[$key]
- : static::_export($value, $depth - 1, $indent);
- $props[] = "$key => " . $value;
+ if (array_key_exists($key, $outputMask)) {
+ $value = $outputMask[$key];
+ }
+ /** @psalm-suppress RedundantCast */
+ $node->addProperty(
+ new PropertyNode((string)$key, 'public', static::export($value, $context->withAddedDepth()))
+ );
}
$ref = new ReflectionObject($var);
@@ -633,36 +772,28 @@ protected static function _object(object $var, int $depth, int $indent): string
foreach ($filters as $filter => $visibility) {
$reflectionProperties = $ref->getProperties($filter);
foreach ($reflectionProperties as $reflectionProperty) {
- $value = null;
$reflectionProperty->setAccessible(true);
if (
method_exists($reflectionProperty, 'isInitialized') &&
!$reflectionProperty->isInitialized($var)
) {
- $value = '[uninitialized]';
+ $value = new SpecialNode('[uninitialized]');
+ } else {
+ $value = static::export($reflectionProperty->getValue($var), $context->withAddedDepth());
}
-
- if ($value === null) {
- $property = $reflectionProperty->getValue($var);
- $value = static::_export($property, $depth - 1, $indent);
- }
-
- $key = $reflectionProperty->name;
- $props[] = sprintf(
- '[%s] %s => %s',
- $visibility,
- $key,
- array_key_exists($key, $outputMask) ? $outputMask[$key] : $value
+ $node->addProperty(
+ new PropertyNode(
+ $reflectionProperty->getName(),
+ $visibility,
+ $value
+ )
);
}
}
-
- $out .= $break . implode($break, $props) . $end;
}
- $out .= '}';
- return $out;
+ return $node;
}
/**
@@ -713,7 +844,7 @@ public static function setOutputFormat(string $format): void
* Gets the following templates: `id`, `context`
* - 'links' - An array of HTML links that are used for creating links to other resources.
* Typically this is used to create javascript links to open other sections.
- * Link keys, are: `code`, `context`, `help`. See the js output format for an
+ * Link keys, are: `code`, `context`, `help`. See the JS output format for an
* example.
* - 'traceLine' - Used for creating lines in the stacktrace. Gets the following
* template variables: `reference`, `path`, `line`
@@ -817,7 +948,6 @@ public function outputError(array $data): void
}
if (!empty($tpl['escapeContext'])) {
- $context = h($context);
$data['description'] = h($data['description']);
}
@@ -834,7 +964,7 @@ public function outputError(array $data): void
$links = implode(' ', $links);
if (isset($tpl['callback']) && is_callable($tpl['callback'])) {
- call_user_func($tpl['callback'], $data, compact('links', 'info'));
+ $tpl['callback']($data, compact('links', 'info'));
return;
}
@@ -850,32 +980,21 @@ public function outputError(array $data): void
*/
public static function getType($var): string
{
- if (is_object($var)) {
- return get_class($var);
- }
- if ($var === null) {
+ $type = getTypeName($var);
+
+ if ($type === 'NULL') {
return 'null';
}
- if (is_string($var)) {
- return 'string';
- }
- if (is_array($var)) {
- return 'array';
- }
- if (is_int($var)) {
- return 'integer';
- }
- if (is_bool($var)) {
- return 'boolean';
- }
- if (is_float($var)) {
+
+ if ($type === 'double') {
return 'float';
}
- if (is_resource($var)) {
- return 'resource';
+
+ if ($type === 'unknown type') {
+ return 'unknown';
}
- return 'unknown';
+ return $type;
}
/**
@@ -885,59 +1004,31 @@ public static function getType($var): string
* @param array $location If contains keys "file" and "line" their values will
* be used to show location info.
* @param bool|null $showHtml If set to true, the method prints the debug
- * data in a browser-friendly way.
+ * data encoded as HTML. If false, plain text formatting will be used.
+ * If null, the format will be chosen based on the configured exportFormatter, or
+ * environment conditions.
* @return void
*/
public static function printVar($var, array $location = [], ?bool $showHtml = null): void
{
$location += ['file' => null, 'line' => null];
- $file = $location['file'];
- $line = $location['line'];
- $lineInfo = '';
- if ($file) {
- $search = [];
- if (defined('ROOT')) {
- $search = [ROOT];
- }
- if (defined('CAKE_CORE_INCLUDE_PATH')) {
- array_unshift($search, CAKE_CORE_INCLUDE_PATH);
- }
- $file = str_replace($search, '', $file);
- }
- $html = <<
-%s
-
-%s
-
-
-HTML;
- $text = <<getConfig('exportFormatter');
+ $debugger->setConfig('exportFormatter', $showHtml ? HtmlFormatter::class : TextFormatter::class);
}
- $var = Debugger::exportVar($var, 25);
- if ($showHtml) {
- $template = $html;
- $var = h($var);
- if ($file && $line) {
- $lineInfo = sprintf('%s (line %s)', $file, $line);
- }
+ $contents = static::exportVar($var, 25);
+ $formatter = $debugger->getExportFormatter();
+
+ if ($restore) {
+ $debugger->setConfig('exportFormatter', $restore);
}
- printf($template, $lineInfo, $var);
+ echo $formatter->formatWrapper($contents, $location);
}
/**
@@ -968,12 +1059,13 @@ public static function formatHtmlMessage(string $message): string
*/
public static function checkSecurityKeys(): void
{
- if (Security::getSalt() === '__SALT__') {
- trigger_error(sprintf(
- 'Please change the value of %s in %s to a salt value specific to your application.',
- '\'Security.salt\'',
- 'ROOT/config/app.php'
- ), E_USER_NOTICE);
+ $salt = Security::getSalt();
+ if ($salt === '__SALT__' || strlen($salt) < 32) {
+ trigger_error(
+ 'Please change the value of `Security.salt` in `ROOT/config/app_local.php` ' .
+ 'to a random value of at least 32 characters.',
+ E_USER_NOTICE
+ );
}
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php b/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php
index 06380ab03..21285bd5d 100644
--- a/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php
+++ b/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php
@@ -17,7 +17,7 @@
namespace Cake\Error;
use Cake\Core\Configure;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\InstanceConfigTrait;
use Cake\Log\Log;
use Psr\Http\Message\ServerRequestInterface;
@@ -26,7 +26,7 @@
/**
* Log errors and unhandled exceptions to `Cake\Log\Log`
*/
-class ErrorLogger
+class ErrorLogger implements ErrorLoggerInterface
{
use InstanceConfigTrait;
@@ -55,11 +55,22 @@ public function __construct(array $config = [])
}
/**
- * Generate the error log message.
- *
- * @param \Throwable $exception The exception to log a message for.
- * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available.
- * @return bool
+ * @inheritDoc
+ */
+ public function logMessage($level, string $message, array $context = []): bool
+ {
+ if (!empty($context['request'])) {
+ $message .= $this->getRequestContext($context['request']);
+ }
+ if (!empty($context['trace'])) {
+ $message .= "\nTrace:\n" . $context['trace'] . "\n";
+ }
+
+ return Log::write($level, $message);
+ }
+
+ /**
+ * @inheritDoc
*/
public function log(Throwable $exception, ?ServerRequestInterface $request = null): bool
{
@@ -99,7 +110,7 @@ protected function getMessage(Throwable $exception, bool $isPrevious = false): s
);
$debug = Configure::read('debug');
- if ($debug && $exception instanceof Exception) {
+ if ($debug && $exception instanceof CakeException) {
$attributes = $exception->getAttributes();
if ($attributes) {
$message .= "\nException Attributes: " . var_export($exception->getAttributes(), true);
diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php b/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php
new file mode 100644
index 000000000..b8ee57c23
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php
@@ -0,0 +1,51 @@
+, int>
+ */
+ protected $exceptionHttpCodes = [
+ // Controller exceptions
+ 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.
- * If the error is a Cake\Core\Exception\Exception it will be converted to either a 400 or a 500
- * code error depending on the code used to construct the error.
*
* @param \Throwable $exception Exception.
* @param \Cake\Http\ServerRequest $request The request if this is set it will be used
@@ -136,7 +161,7 @@ protected function _getController(): Controller
$params = $request->getAttribute('params');
$params['controller'] = 'Error';
- $factory = new ControllerFactory();
+ $factory = new ControllerFactory(new Container());
$class = $factory->getControllerClass($request->withAttribute('params', $params));
if (!$class) {
@@ -192,7 +217,7 @@ protected function clearOutput(): void
public function render(): ResponseInterface
{
$exception = $this->error;
- $code = $this->_code($exception);
+ $code = $this->getHttpCode($exception);
$method = $this->_method($exception);
$template = $this->_template($exception, $method, $code);
$this->clearOutput();
@@ -206,10 +231,16 @@ public function render(): ResponseInterface
$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 = [
@@ -257,7 +288,7 @@ public function render(): ResponseInterface
*/
protected function _customMethod(string $method, Throwable $exception): Response
{
- $result = call_user_func([$this, $method], $exception);
+ $result = $this->{$method}($exception);
$this->_shutdown();
if (is_string($result)) {
$result = $this->controller->getResponse()->withStringBody($result);
@@ -280,7 +311,8 @@ protected function _method(Throwable $exception): string
$baseClass = substr($baseClass, 0, -9);
}
- $method = Inflector::variable($baseClass) ?: 'error500';
+ // $baseClass would be an empty string if the exception class is \Exception.
+ $method = $baseClass === '' ? 'error500' : Inflector::variable($baseClass);
return $this->method = $method;
}
@@ -320,41 +352,30 @@ protected function _message(Throwable $exception, int $code): string
*/
protected function _template(Throwable $exception, string $method, int $code): string
{
- $isHttpException = $exception instanceof HttpException;
-
- if (!Configure::read('debug') && !$isHttpException || $isHttpException) {
- $template = 'error500';
- if ($code < 500) {
- $template = 'error400';
- }
-
- return $this->template = $template;
+ if ($exception instanceof HttpException || !Configure::read('debug')) {
+ return $this->template = $code < 500 ? 'error400' : 'error500';
}
- $template = $method ?: 'error500';
-
if ($exception instanceof PDOException) {
- $template = 'pdo_error';
+ return $this->template = 'pdo_error';
}
- return $this->template = $template;
+ return $this->template = $method;
}
/**
- * Get HTTP status code.
+ * Gets the appropriate http status code for exception.
*
* @param \Throwable $exception Exception.
- * @return int A valid HTTP error status code.
+ * @return int A valid HTTP status code.
*/
- protected function _code(Throwable $exception): int
+ protected function getHttpCode(Throwable $exception): int
{
- $code = 500;
- $errorCode = (int)$exception->getCode();
- if ($errorCode >= 400 && $errorCode < 600) {
- $code = $errorCode;
+ if ($exception instanceof HttpException) {
+ return $exception->getCode();
}
- return $code;
+ return $this->exceptionHttpCodes[get_class($exception)] ?? 500;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php b/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php
index 396317086..42f751850 100644
--- a/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php
+++ b/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php
@@ -14,13 +14,13 @@
*/
namespace Cake\Error;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Throwable;
/**
* Represents a fatal error
*/
-class FatalErrorException extends Exception
+class FatalErrorException extends CakeException
{
/**
* Constructor
diff --git a/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php b/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php
index c24867214..8f2ea7967 100644
--- a/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php
@@ -17,11 +17,14 @@
namespace Cake\Error\Middleware;
use Cake\Core\App;
+use Cake\Core\Configure;
use Cake\Core\InstanceConfigTrait;
use Cake\Error\ErrorHandler;
use Cake\Error\ExceptionRenderer;
+use Cake\Http\Exception\RedirectException;
use Cake\Http\Response;
use InvalidArgumentException;
+use Laminas\Diactoros\Response\RedirectResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
@@ -90,6 +93,10 @@ public function __construct($errorHandler = [])
$errorHandler = func_get_arg(1);
}
+ if (PHP_VERSION_ID >= 70400 && Configure::read('debug')) {
+ ini_set('zend.exception_ignore_args', '0');
+ }
+
if (is_array($errorHandler)) {
$this->setConfig($errorHandler);
@@ -117,6 +124,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
{
try {
return $handler->handle($request);
+ } catch (RedirectException $exception) {
+ return $this->handleRedirect($exception);
} catch (Throwable $exception) {
return $this->handleException($exception, $request);
}
@@ -135,8 +144,8 @@ public function handleException(Throwable $exception, ServerRequestInterface $re
$renderer = $errorHandler->getRenderer($exception, $request);
try {
- $response = $renderer->render();
$errorHandler->logException($exception, $request);
+ $response = $renderer->render();
} catch (Throwable $internalException) {
$errorHandler->logException($internalException, $request);
$response = $this->handleInternalError();
@@ -145,6 +154,21 @@ public function handleException(Throwable $exception, ServerRequestInterface $re
return $response;
}
+ /**
+ * Convert a redirect exception into a response.
+ *
+ * @param \Cake\Http\Exception\RedirectException $exception The exception to handle
+ * @return \Psr\Http\Message\ResponseInterface Response created from the redirect.
+ */
+ public function handleRedirect(RedirectException $exception): ResponseInterface
+ {
+ return new RedirectResponse(
+ $exception->getMessage(),
+ $exception->getCode(),
+ $exception->getHeaders()
+ );
+ }
+
/**
* Handle internal errors.
*
diff --git a/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php b/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php
index a591e483e..b802ed1ab 100644
--- a/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php
+++ b/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Event\Decorator;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Event\EventInterface;
use RuntimeException;
@@ -61,7 +61,7 @@ public function canTrigger(EventInterface $event): bool
try {
$subject = $event->getSubject();
- } catch (Exception $e) {
+ } catch (CakeException $e) {
return false;
}
diff --git a/app/vendor/cakephp/cakephp/src/Event/Event.php b/app/vendor/cakephp/cakephp/src/Event/Event.php
index 36c34a99e..75084ca31 100644
--- a/app/vendor/cakephp/cakephp/src/Event/Event.php
+++ b/app/vendor/cakephp/cakephp/src/Event/Event.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Event;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Class Event
@@ -103,14 +103,14 @@ public function getName(): string
* If the event has no subject an exception will be raised.
*
* @return object
- * @throws \Cake\Core\Exception\Exception
+ * @throws \Cake\Core\Exception\CakeException
* @psalm-return TSubject
* @psalm-suppress LessSpecificImplementedReturnType
*/
public function getSubject()
{
if ($this->_subject === null) {
- throw new Exception('No subject set for this event');
+ throw new CakeException('No subject set for this event');
}
return $this->_subject;
@@ -172,6 +172,7 @@ public function getData(?string $key = null)
return $this->_data[$key] ?? null;
}
+ /** @psalm-suppress RedundantCastGivenDocblockType */
return (array)$this->_data;
}
diff --git a/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php b/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php
index 4c314cac6..e9b4dd963 100644
--- a/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php
@@ -25,7 +25,7 @@ trait EventDispatcherTrait
* Instance of the Cake\Event\EventManager this object is using
* to dispatch inner events.
*
- * @var \Cake\Event\EventManagerInterface
+ * @var \Cake\Event\EventManagerInterface|null
*/
protected $_eventManager;
diff --git a/app/vendor/cakephp/cakephp/src/Event/EventManager.php b/app/vendor/cakephp/cakephp/src/Event/EventManager.php
index f7dd1d14e..e65ab1378 100644
--- a/app/vendor/cakephp/cakephp/src/Event/EventManager.php
+++ b/app/vendor/cakephp/cakephp/src/Event/EventManager.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Event;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* The event manager is responsible for keeping track of event listeners, passing the correct
@@ -36,7 +36,7 @@ class EventManager implements EventManagerInterface
/**
* The globally available instance, used for dispatching events attached from any scope
*
- * @var \Cake\Event\EventManager
+ * @var \Cake\Event\EventManager|null
*/
protected static $_generalManager;
@@ -130,7 +130,7 @@ public function on($eventKey, $options = [], ?callable $callable = null)
*/
protected function _attachSubscriber(EventListenerInterface $subscriber): void
{
- foreach ((array)$subscriber->implementedEvents() as $eventKey => $function) {
+ foreach ($subscriber->implementedEvents() as $eventKey => $function) {
$options = [];
$method = $function;
if (is_array($function) && isset($function['callable'])) {
@@ -184,7 +184,7 @@ public function off($eventKey, $callable = null)
if (!is_string($eventKey)) {
if (!is_callable($eventKey)) {
- throw new Exception(
+ throw new CakeException(
'First argument of EventManager::off() must be ' .
' string or EventListenerInterface instance or callable.'
);
@@ -234,7 +234,7 @@ public function off($eventKey, $callable = null)
*/
protected function _detachSubscriber(EventListenerInterface $subscriber, ?string $eventKey = null): void
{
- $events = (array)$subscriber->implementedEvents();
+ $events = $subscriber->implementedEvents();
if (!empty($eventKey) && empty($events[$eventKey])) {
return;
}
@@ -406,7 +406,7 @@ public function addEventToList(EventInterface $event)
*/
public function trackEvents(bool $enabled)
{
- $this->_trackEvents = (bool)$enabled;
+ $this->_trackEvents = $enabled;
return $this;
}
@@ -472,7 +472,7 @@ public function __debugInfo(): array
try {
$subject = $event->getSubject();
$properties['_dispatchedEvents'][] = $event->getName() . ' with subject ' . get_class($subject);
- } catch (Exception $e) {
+ } catch (CakeException $e) {
$properties['_dispatchedEvents'][] = $event->getName() . ' with no subject';
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Event/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Event/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Event/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Event/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php b/app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php
index 90ef244e6..d1ecebc3b 100644
--- a/app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php
+++ b/app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Filesystem;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use CallbackFilterIterator;
use FilesystemIterator;
use Iterator;
@@ -128,7 +128,7 @@ protected function filterIterator(Iterator $iterator, $filter): Iterator
* @param string $filename File path.
* @param string $content Content to dump.
* @return void
- * @throws \Cake\Core\Exception\Exception When dumping fails.
+ * @throws \Cake\Core\Exception\CakeException When dumping fails.
*/
public function dumpFile(string $filename, string $content): void
{
@@ -148,7 +148,7 @@ public function dumpFile(string $filename, string $content): void
}
if ($success === false) {
- throw new Exception(sprintf('Failed dumping content to file `%s`', $dir));
+ throw new CakeException(sprintf('Failed dumping content to file `%s`', $dir));
}
if (!$exists) {
@@ -162,7 +162,7 @@ public function dumpFile(string $filename, string $content): void
* @param string $dir Directory path.
* @param int $mode Octal mode passed to mkdir(). Defaults to 0755.
* @return void
- * @throws \Cake\Core\Exception\Exception When directory creation fails.
+ * @throws \Cake\Core\Exception\CakeException When directory creation fails.
*/
public function mkdir(string $dir, int $mode = 0755): void
{
@@ -174,7 +174,7 @@ public function mkdir(string $dir, int $mode = 0755): void
// phpcs:ignore
if (@mkdir($dir, $mode, true) === false) {
umask($old);
- throw new Exception(sprintf('Failed to create directory "%s"', $dir));
+ throw new CakeException(sprintf('Failed to create directory "%s"', $dir));
}
umask($old);
@@ -185,7 +185,7 @@ public function mkdir(string $dir, int $mode = 0755): void
*
* @param string $path Directory path.
* @return bool
- * @throws \Cake\Core\Exception\Exception If path is not a directory.
+ * @throws \Cake\Core\Exception\CakeException If path is not a directory.
*/
public function deleteDir(string $path): bool
{
@@ -194,7 +194,7 @@ public function deleteDir(string $path): bool
}
if (!is_dir($path)) {
- throw new Exception(sprintf('"%s" is not a directory', $path));
+ throw new CakeException(sprintf('"%s" is not a directory', $path));
}
$iterator = new RecursiveIteratorIterator(
@@ -204,16 +204,24 @@ public function deleteDir(string $path): bool
$result = true;
foreach ($iterator as $fileInfo) {
- if ($fileInfo->getType() === self::TYPE_DIR) {
+ $isWindowsLink = DIRECTORY_SEPARATOR === '\\' && $fileInfo->getType() === 'link';
+ if ($fileInfo->getType() === self::TYPE_DIR || $isWindowsLink) {
// phpcs:ignore
$result = $result && @rmdir($fileInfo->getPathname());
+ unset($fileInfo);
continue;
}
// phpcs:ignore
$result = $result && @unlink($fileInfo->getPathname());
+ // possible inner iterators need to be unset too in order for locks on parents to be released
+ unset($fileInfo);
}
+ // unsetting iterators helps releasing possible locks in certain environments,
+ // which could otherwise make `rmdir()` fail
+ unset($iterator);
+
// phpcs:ignore
$result = $result && @rmdir($path);
diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php b/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php
index a8cdac495..83da3d71c 100644
--- a/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php
+++ b/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php
@@ -558,6 +558,8 @@ public function tree(?string $path = null, $exceptions = false, ?string $type =
);
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
} catch (Exception $e) {
+ unset($directory, $iterator);
+
if ($type === null) {
return [[], []];
}
@@ -573,12 +575,14 @@ public function tree(?string $path = null, $exceptions = false, ?string $type =
if ($skipHidden) {
$subPathName = $fsIterator->getSubPathname();
if ($subPathName[0] === '.' || strpos($subPathName, DIRECTORY_SEPARATOR . '.') !== false) {
+ unset($fsIterator);
continue;
}
}
/** @var \FilesystemIterator $item */
$item = $fsIterator->current();
if (!empty($exceptions) && isset($exceptions[$item->getFilename()])) {
+ unset($fsIterator, $item);
continue;
}
@@ -587,7 +591,15 @@ public function tree(?string $path = null, $exceptions = false, ?string $type =
} elseif ($item->isDir() && !$item->isDot()) {
$directories[] = $itemPath;
}
+
+ // inner iterators need to be unset too in order for locks on parents to be released
+ unset($fsIterator, $item);
}
+
+ // unsetting iterators helps releasing possible locks in certain environments,
+ // which could otherwise make `rmdir()` fail
+ unset($directory, $iterator);
+
if ($type === null) {
return [$directories, $files];
}
@@ -707,6 +719,8 @@ public function delete(?string $path = null): bool
$directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::CURRENT_AS_SELF);
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST);
} catch (Exception $e) {
+ unset($directory, $iterator);
+
return false;
}
@@ -728,11 +742,20 @@ public function delete(?string $path = null): bool
} else {
$this->_errors[] = sprintf('%s NOT removed', $filePath);
+ unset($directory, $iterator, $item);
+
return false;
}
}
+
+ // inner iterators need to be unset too in order for locks on parents to be released
+ unset($item);
}
+ // unsetting iterators helps releasing possible locks in certain environments,
+ // which could otherwise make `rmdir()` fail
+ unset($directory, $iterator);
+
$path = rtrim($path, DIRECTORY_SEPARATOR);
// phpcs:disable
if (@rmdir($path)) {
diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/README.md b/app/vendor/cakephp/cakephp/src/Filesystem/README.md
index df1862ebb..ce2fa5c15 100644
--- a/app/vendor/cakephp/cakephp/src/Filesystem/README.md
+++ b/app/vendor/cakephp/cakephp/src/Filesystem/README.md
@@ -1,7 +1,7 @@
[](https://packagist.org/packages/cakephp/filesystem)
[](LICENSE.txt)
-# This package is deprecated.
+# This package has been deprecated.
## CakePHP Filesystem Library
diff --git a/app/vendor/cakephp/cakephp/src/Form/Form.php b/app/vendor/cakephp/cakephp/src/Form/Form.php
index 530651b5e..41563e253 100644
--- a/app/vendor/cakephp/cakephp/src/Form/Form.php
+++ b/app/vendor/cakephp/cakephp/src/Form/Form.php
@@ -75,7 +75,7 @@ class Form implements EventListenerInterface, EventDispatcherInterface, Validato
/**
* The schema used by this form.
*
- * @var \Cake\Form\Schema
+ * @var \Cake\Form\Schema|null
*/
protected $_schema;
@@ -136,6 +136,39 @@ public function implementedEvents(): array
return [];
}
+ /**
+ * Set the schema for this form.
+ *
+ * @since 4.1.0
+ * @param \Cake\Form\Schema $schema The schema to set
+ * @return $this
+ */
+ public function setSchema(Schema $schema)
+ {
+ $this->_schema = $schema;
+
+ return $this;
+ }
+
+ /**
+ * Get the schema for this form.
+ *
+ * This method will call `_buildSchema()` when the schema
+ * is first built. This hook method lets you configure the
+ * schema or load a pre-defined one.
+ *
+ * @since 4.1.0
+ * @return \Cake\Form\Schema the schema instance.
+ */
+ public function getSchema(): Schema
+ {
+ if ($this->_schema === null) {
+ $this->_schema = $this->_buildSchema(new $this->_schemaClass());
+ }
+
+ return $this->_schema;
+ }
+
/**
* Get/Set the schema for this form.
*
@@ -143,19 +176,18 @@ public function implementedEvents(): array
* is first built. This hook method lets you configure the
* schema or load a pre-defined one.
*
+ * @deprecated 4.1.0 Use {@link setSchema()}/{@link getSchema()} instead.
* @param \Cake\Form\Schema|null $schema The schema to set, or null.
* @return \Cake\Form\Schema the schema instance.
*/
public function schema(?Schema $schema = null): Schema
{
- if ($schema === null && empty($this->_schema)) {
- $schema = $this->_buildSchema(new $this->_schemaClass());
- }
- if ($schema) {
- $this->_schema = $schema;
+ deprecationWarning('Form::schema() is deprecated. Use setSchema() and getSchema() instead.');
+ if ($schema !== null) {
+ $this->setSchema($schema);
}
- return $this->_schema;
+ return $this->getSchema();
}
/**
@@ -235,6 +267,8 @@ public function setErrors(array $errors)
*/
public function execute(array $data): bool
{
+ $this->_data = $data;
+
if (!$this->validate($data)) {
return false;
}
@@ -271,6 +305,29 @@ public function getData(?string $field = null)
return Hash::get($this->_data, $field);
}
+ /**
+ * Saves a variable or an associative array of variables for use inside form data.
+ *
+ * @param string|array $name 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
+ * @return $this
+ */
+ public function set($name, $value = null)
+ {
+ $write = $name;
+ if (!is_array($name)) {
+ $write = [$name => $value];
+ }
+
+ /** @psalm-suppress PossiblyInvalidIterator */
+ foreach ($write as $key => $val) {
+ $this->_data = Hash::insert($this->_data, $key, $val);
+ }
+
+ return $this;
+ }
+
/**
* Set form data.
*
@@ -292,7 +349,7 @@ public function setData(array $data)
public function __debugInfo(): array
{
$special = [
- '_schema' => $this->schema()->__debugInfo(),
+ '_schema' => $this->getSchema()->__debugInfo(),
'_errors' => $this->getErrors(),
'_validator' => $this->getValidator()->__debugInfo(),
];
diff --git a/app/vendor/cakephp/cakephp/src/Form/FormProtector.php b/app/vendor/cakephp/cakephp/src/Form/FormProtector.php
index e981f096d..e0776ce88 100644
--- a/app/vendor/cakephp/cakephp/src/Form/FormProtector.php
+++ b/app/vendor/cakephp/cakephp/src/Form/FormProtector.php
@@ -533,7 +533,7 @@ protected function matchExistingFields(
$messages = [];
foreach ($dataFields as $key => $value) {
if (is_int($key)) {
- $foundKey = array_search($value, (array)$expectedFields, true);
+ $foundKey = array_search($value, $expectedFields, true);
if ($foundKey === false) {
$messages[] = sprintf($intKeyMessage, $value);
} else {
@@ -564,7 +564,7 @@ protected function debugExpectedFields(array $expectedFields = [], string $missi
}
$expectedFieldNames = [];
- foreach ((array)$expectedFields as $key => $expectedField) {
+ foreach ($expectedFields as $key => $expectedField) {
if (is_int($key)) {
$expectedFieldNames[] = $expectedField;
} else {
diff --git a/app/vendor/cakephp/cakephp/src/Form/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Form/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Form/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Form/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php b/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php
index 02109f9b6..f6c9867a0 100644
--- a/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php
+++ b/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php
@@ -14,11 +14,16 @@
* @since 3.3.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
+
namespace Cake\Http;
use Cake\Console\CommandCollection;
use Cake\Controller\ControllerFactory;
use Cake\Core\ConsoleApplicationInterface;
+use Cake\Core\Container;
+use Cake\Core\ContainerApplicationInterface;
+use Cake\Core\ContainerInterface;
+use Cake\Core\Exception\MissingPluginException;
use Cake\Core\HttpApplicationInterface;
use Cake\Core\Plugin;
use Cake\Core\PluginApplicationInterface;
@@ -45,6 +50,7 @@
*/
abstract class BaseApplication implements
ConsoleApplicationInterface,
+ ContainerApplicationInterface,
HttpApplicationInterface,
PluginApplicationInterface,
RoutingApplicationInterface
@@ -70,6 +76,13 @@ abstract class BaseApplication implements
*/
protected $controllerFactory;
+ /**
+ * Container
+ *
+ * @var \Cake\Core\ContainerInterface|null
+ */
+ protected $container;
+
/**
* Constructor
*
@@ -121,6 +134,26 @@ public function addPlugin($name, array $config = [])
return $this;
}
+ /**
+ * Add an optional plugin
+ *
+ * If it isn't available, ignore it.
+ *
+ * @param string|\Cake\Core\PluginInterface $name The plugin name or plugin object.
+ * @param array $config The configuration data for the plugin if using a string for $name
+ * @return $this
+ */
+ public function addOptionalPlugin($name, array $config = [])
+ {
+ try {
+ $this->addPlugin($name, $config);
+ } catch (MissingPluginException $e) {
+ // Do not halt if the plugin is missing
+ }
+
+ return $this;
+ }
+
/**
* Get the plugin collection in use.
*
@@ -203,6 +236,57 @@ public function pluginConsole(CommandCollection $commands): CommandCollection
return $commands;
}
+ /**
+ * Get the dependency injection container for the application.
+ *
+ * The first time the container is fetched it will be constructed
+ * and stored for future calls.
+ *
+ * @return \Cake\Core\ContainerInterface
+ */
+ public function getContainer(): ContainerInterface
+ {
+ if ($this->container === null) {
+ $this->container = $this->buildContainer();
+ }
+
+ return $this->container;
+ }
+
+ /**
+ * Build the service container
+ *
+ * Override this method if you need to use a custom container or
+ * want to change how the container is built.
+ *
+ * @return \Cake\Core\ContainerInterface
+ */
+ protected function buildContainer(): ContainerInterface
+ {
+ $container = new Container();
+ $this->services($container);
+ foreach ($this->plugins->with('services') as $plugin) {
+ $plugin->services($container);
+ }
+
+ $event = $this->dispatchEvent('Application.buildContainer', ['container' => $container]);
+ if ($event->getResult() instanceof ContainerInterface) {
+ return $event->getResult();
+ }
+
+ return $container;
+ }
+
+ /**
+ * Register application container services.
+ *
+ * @param \Cake\Core\ContainerInterface $container The Container to update.
+ * @return void
+ */
+ public function services(ContainerInterface $container): void
+ {
+ }
+
/**
* Invoke the application.
*
@@ -217,7 +301,7 @@ public function handle(
ServerRequestInterface $request
): ResponseInterface {
if ($this->controllerFactory === null) {
- $this->controllerFactory = new ControllerFactory();
+ $this->controllerFactory = new ControllerFactory($this->getContainer());
}
if (Router::getRequest() !== $request) {
diff --git a/app/vendor/cakephp/cakephp/src/Http/CallbackStream.php b/app/vendor/cakephp/cakephp/src/Http/CallbackStream.php
index 4f930e276..4ceed68e4 100644
--- a/app/vendor/cakephp/cakephp/src/Http/CallbackStream.php
+++ b/app/vendor/cakephp/cakephp/src/Http/CallbackStream.php
@@ -40,7 +40,8 @@ public function getContents(): string
{
$callback = $this->detach();
$result = '';
- if (is_callable($callback)) {
+ /** @psalm-suppress TypeDoesNotContainType */
+ if ($callback !== null) {
$result = $callback();
}
if (!is_string($result)) {
diff --git a/app/vendor/cakephp/cakephp/src/Http/Client.php b/app/vendor/cakephp/cakephp/src/Http/Client.php
index 3a1e2d83d..277911405 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Client.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Client.php
@@ -16,7 +16,7 @@
namespace Cake\Http;
use Cake\Core\App;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\InstanceConfigTrait;
use Cake\Http\Client\Adapter\Curl;
use Cake\Http\Client\Adapter\Stream;
@@ -114,6 +114,7 @@ class Client implements ClientInterface
'host' => null,
'port' => null,
'scheme' => 'http',
+ 'basePath' => '',
'timeout' => 30,
'ssl_verify_peer' => true,
'ssl_verify_peer_name' => true,
@@ -150,6 +151,7 @@ class Client implements ClientInterface
* - host - The hostname to do requests on.
* - port - The port to use.
* - scheme - The default scheme/protocol to use. Defaults to http.
+ * - basePath - A path to append to the domain to use. (/api/v1/)
* - timeout - The timeout in seconds. Defaults to 30
* - ssl_verify_peer - Whether or not SSL certificates should be validated.
* Defaults to true.
@@ -200,6 +202,38 @@ public function __construct(array $config = [])
}
}
+ /**
+ * Client instance returned is scoped to the domain, port, and scheme parsed from the passed URL string. The passed
+ * string must have a scheme and a domain. Optionally, if a port is included in the string, the port will be scoped
+ * too. If a path is included in the URL, the client instance will build urls with it prepended.
+ * Other parts of the url string are ignored.
+ *
+ * @param string $url A string URL e.g. https://example.com
+ * @return static
+ * @throws \InvalidArgumentException
+ */
+ public static function createFromUrl(string $url)
+ {
+ $parts = parse_url($url);
+
+ if ($parts === false) {
+ throw new InvalidArgumentException('String ' . $url . ' did not parse');
+ }
+
+ $config = array_intersect_key($parts, ['scheme' => '', 'port' => '', 'host' => '', 'path' => '']);
+
+ if (empty($config['scheme']) || empty($config['host'])) {
+ throw new InvalidArgumentException('The URL was parsed but did not contain a scheme or host');
+ }
+
+ if (isset($config['path'])) {
+ $config['basePath'] = $config['path'];
+ unset($config['path']);
+ }
+
+ return new static($config);
+ }
+
/**
* Get the cookies stored in the Client.
*
@@ -493,6 +527,7 @@ public function buildUrl(string $url, $query = [], array $options = []): string
'host' => null,
'port' => null,
'scheme' => 'http',
+ 'basePath' => '',
'protocolRelative' => false,
];
$options += $defaults;
@@ -512,6 +547,9 @@ public function buildUrl(string $url, $query = [], array $options = []): string
if ($options['port'] && (int)$options['port'] !== $defaultPorts[$options['scheme']]) {
$out .= ':' . $options['port'];
}
+ if (!empty($options['basePath'])) {
+ $out .= '/' . trim($options['basePath'], '/');
+ }
$out .= '/' . ltrim($url, '/');
return $out;
@@ -558,7 +596,7 @@ protected function _createRequest(string $method, string $url, $data, $options):
*
* @param string $type short type alias or full mimetype.
* @return string[] Headers to set on the request.
- * @throws \Cake\Core\Exception\Exception When an unknown type alias is used.
+ * @throws \Cake\Core\Exception\CakeException When an unknown type alias is used.
* @psalm-return array{Accept: string, Content-Type: string}
*/
protected function _typeHeaders(string $type): array
@@ -574,7 +612,7 @@ protected function _typeHeaders(string $type): array
'xml' => 'application/xml',
];
if (!isset($typeMap[$type])) {
- throw new Exception("Unknown type alias '$type'.");
+ throw new CakeException("Unknown type alias '$type'.");
}
return [
@@ -630,7 +668,7 @@ protected function _addProxy(Request $request, array $options): Request
* @param array $auth The authentication options to use.
* @param array $options The overall request options to use.
* @return object Authentication strategy instance.
- * @throws \Cake\Core\Exception\Exception when an invalid strategy is chosen.
+ * @throws \Cake\Core\Exception\CakeException when an invalid strategy is chosen.
*/
protected function _createAuth(array $auth, array $options)
{
@@ -640,7 +678,7 @@ protected function _createAuth(array $auth, array $options)
$name = ucfirst($auth['type']);
$class = App::className($name, 'Http/Client/Auth');
if (!$class) {
- throw new Exception(
+ throw new CakeException(
sprintf('Invalid authentication type %s', $name)
);
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php
index 68dee3dda..7fc5df08b 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php
@@ -103,6 +103,10 @@ public function buildOptions(RequestInterface $request, array $options): array
$out[CURLOPT_POST] = true;
break;
+ case Request::METHOD_HEAD:
+ $out[CURLOPT_NOBODY] = true;
+ break;
+
default:
$out[CURLOPT_POST] = true;
$out[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
@@ -187,12 +191,14 @@ protected function getProtocolVersion(RequestInterface $request): int
/**
* Convert the raw curl response into an Http\Client\Response
*
- * @param resource $handle Curl handle
+ * @param resource|\CurlHandle $handle Curl handle
* @param string $responseData string The response data from curl_exec
* @return \Cake\Http\Client\Response[]
+ * @psalm-suppress UndefinedDocblockClass
*/
protected function createResponse($handle, $responseData): array
{
+ /** @psalm-suppress PossiblyInvalidArgument */
$headerSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
$headers = trim(substr($responseData, 0, $headerSize));
$body = substr($responseData, $headerSize);
@@ -204,11 +210,13 @@ protected function createResponse($handle, $responseData): array
/**
* Execute the curl handle.
*
- * @param resource $ch Curl Resource handle
+ * @param resource|\CurlHandle $ch Curl Resource handle
* @return string|bool
+ * @psalm-suppress UndefinedDocblockClass
*/
protected function exec($ch)
{
+ /** @psalm-suppress PossiblyInvalidArgument */
return curl_exec($ch);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php
index 6f5b31a9c..761b23a24 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php
@@ -104,6 +104,7 @@ public function createResponses(array $headers, string $content): array
foreach ($indexes as $i => $start) {
/** @psalm-suppress InvalidOperand */
$end = isset($indexes[$i + 1]) ? $indexes[$i + 1] - $start : null;
+ /** @psalm-suppress PossiblyInvalidArgument */
$headerSlice = array_slice($headers, $start, $end);
$body = $i === $last ? $content : '';
$responses[] = $this->_buildResponse($headerSlice, $body);
@@ -217,6 +218,7 @@ protected function _buildSslContext(RequestInterface $request, array $options):
'ssl_allow_self_signed',
'ssl_cafile',
'ssl_local_cert',
+ 'ssl_local_pk',
'ssl_passphrase',
];
if (empty($options['ssl_cafile'])) {
@@ -254,6 +256,7 @@ protected function _send(RequestInterface $request): array
$content = '';
$timedOut = false;
+ /** @psalm-suppress PossiblyNullArgument */
while (!feof($this->_stream)) {
if ($deadline !== false) {
stream_set_timeout($this->_stream, max($deadline - time(), 1));
diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php
index 46b1ab733..f066ee025 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php
@@ -15,7 +15,7 @@
*/
namespace Cake\Http\Client\Auth;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Http\Client\Request;
use Cake\Utility\Security;
use Psr\Http\Message\UriInterface;
@@ -39,7 +39,7 @@ class Oauth
* @param \Cake\Http\Client\Request $request The request object.
* @param array $credentials Authentication credentials.
* @return \Cake\Http\Client\Request The updated request.
- * @throws \Cake\Core\Exception\Exception On invalid signature types.
+ * @throws \Cake\Core\Exception\CakeException On invalid signature types.
*/
public function authentication(Request $request, array $credentials): Request
{
@@ -85,7 +85,7 @@ public function authentication(Request $request, array $credentials): Request
break;
default:
- throw new Exception(sprintf('Unknown Oauth signature method %s', $credentials['method']));
+ throw new CakeException(sprintf('Unknown Oauth signature method %s', $credentials['method']));
}
return $request->withHeader('Authorization', $value);
@@ -141,10 +141,14 @@ protected function _hmacSha1(Request $request, array $credentials): string
'oauth_timestamp' => $timestamp,
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_token' => $credentials['token'],
- 'oauth_consumer_key' => $credentials['consumerKey'],
+ 'oauth_consumer_key' => $this->_encode($credentials['consumerKey']),
];
$baseString = $this->baseString($request, $values);
+ // Consumer key should only be encoded for base string calculation as
+ // auth header generation already encodes independently
+ $values['oauth_consumer_key'] = $credentials['consumerKey'];
+
if (isset($credentials['realm'])) {
$values['oauth_realm'] = $credentials['realm'];
}
@@ -218,7 +222,9 @@ protected function _rsaSha1(Request $request, array $credentials): string
$privateKey = openssl_pkey_get_private($credentials['privateKey'], $credentials['privateKeyPassphrase']);
$signature = '';
openssl_sign($baseString, $signature, $privateKey);
- openssl_free_key($privateKey);
+ if (PHP_MAJOR_VERSION < 8) {
+ openssl_free_key($privateKey);
+ }
$values['oauth_signature'] = base64_encode($signature);
@@ -285,9 +291,10 @@ protected function _normalizedParams(Request $request, array $oauthValues): stri
parse_str((string)$query, $queryArgs);
$post = [];
- $body = (string)$request->getBody();
- parse_str($body, $post);
-
+ $contentType = $request->getHeaderLine('Content-Type');
+ if ($contentType === '' || $contentType === 'application/x-www-form-urlencoded') {
+ parse_str((string)$request->getBody(), $post);
+ }
$args = array_merge($queryArgs, $oauthValues, $post);
$pairs = $this->_normalizeData($args);
$data = [];
diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Response.php b/app/vendor/cakephp/cakephp/src/Http/Client/Response.php
index ee41e4f63..e89ff2693 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Client/Response.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Client/Response.php
@@ -64,9 +64,9 @@
* as SimpleXML nodes:
*
* ```
- * // Get as xml
+ * // Get as XML
* $content = $response->getXml()
- * // Get as json
+ * // Get as JSON
* $content = $response->getJson()
* ```
*
@@ -247,7 +247,7 @@ public function isRedirect(): bool
*/
public function getStatusCode(): int
{
- return (int)$this->code;
+ return $this->code;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Http/Cookie/Cookie.php b/app/vendor/cakephp/cakephp/src/Http/Cookie/Cookie.php
index 4ed707c00..1eaae0a61 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Cookie/Cookie.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Cookie/Cookie.php
@@ -236,7 +236,7 @@ public static function create(string $name, $value, array $options = [])
* Converts non null expiry value into DateTimeInterface instance.
*
* @param mixed $expires Expiry value.
- * @return \DateTimeInterface|null
+ * @return \DateTime|\DatetimeImmutable|null
*/
protected static function dateTimeInstance($expires): ?DateTimeInterface
{
@@ -431,6 +431,8 @@ public function getValue()
*/
public function getStringValue()
{
+ deprecationWarning('Cookie::getStringValue() is deprecated. Use getScalarValue() instead.');
+
return $this->getScalarValue();
}
@@ -793,7 +795,7 @@ public function toArray(): array
* Implode method to keep keys are multidimensional arrays
*
* @param array $array Map of key and values
- * @return string A json encoded string.
+ * @return string A JSON encoded string.
*/
protected function _flatten(array $array): string
{
diff --git a/app/vendor/cakephp/cakephp/src/Http/CorsBuilder.php b/app/vendor/cakephp/cakephp/src/Http/CorsBuilder.php
index dd32cdad2..6d155141f 100644
--- a/app/vendor/cakephp/cakephp/src/Http/CorsBuilder.php
+++ b/app/vendor/cakephp/cakephp/src/Http/CorsBuilder.php
@@ -174,7 +174,7 @@ public function allowCredentials()
}
/**
- * Whitelist headers that can be sent in CORS requests.
+ * Allowed headers that can be sent in CORS requests.
*
* @param string[] $headers The list of headers to accept in CORS requests.
* @return $this
diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php
index a03c195f5..2f7744cee 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php
@@ -14,16 +14,58 @@
*/
namespace Cake\Http\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Parent class for all of the HTTP related exceptions in CakePHP.
* All HTTP status/error related exceptions should extend this class so
* catch blocks can be specifically typed.
*
- * You may also use this as a meaningful bridge to Cake\Core\Exception\Exception, e.g.:
+ * You may also use this as a meaningful bridge to Cake\Core\Exception\CakeException, e.g.:
* throw new \Cake\Network\Exception\HttpException('HTTP Version Not Supported', 505);
*/
-class HttpException extends Exception
+class HttpException extends CakeException
{
+ /**
+ * @inheritDoc
+ */
+ protected $_defaultCode = 500;
+
+ /**
+ * @var array
+ */
+ protected $headers = [];
+
+ /**
+ * Set a single HTTP response header.
+ *
+ * @param string $header Header name
+ * @param string|string[]|null $value Header value
+ * @return void
+ */
+ public function setHeader(string $header, $value = null): void
+ {
+ $this->headers[$header] = $value;
+ }
+
+ /**
+ * Sets HTTP response headers.
+ *
+ * @param array $headers Array of header name and value pairs.
+ * @return void
+ */
+ public function setHeaders(array $headers): void
+ {
+ $this->headers = $headers;
+ }
+
+ /**
+ * Returns array of response headers.
+ *
+ * @return array
+ */
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php
index 4c0a4486d..4cbe6c15f 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php
@@ -14,13 +14,13 @@
*/
namespace Cake\Http\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Missing Controller exception - used when a controller
* cannot be found.
*/
-class MissingControllerException extends Exception
+class MissingControllerException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php
new file mode 100644
index 000000000..5d6b1065f
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php
@@ -0,0 +1,84 @@
+ $value) {
+ $this->setHeader($key, (array)$value);
+ }
+ }
+
+ /**
+ * Add headers to be included in the response generated from this exception
+ *
+ * @param array $headers An array of `header => value` to append to the exception.
+ * If a header already exists, the new values will be appended to the existing ones.
+ * @return $this
+ * @deprecated 4.2.0 Use `setHeaders()` instead.
+ */
+ public function addHeaders(array $headers)
+ {
+ deprecationWarning('RedirectException::addHeaders() is deprecated, use setHeaders() instead.');
+
+ foreach ($headers as $key => $value) {
+ $this->headers[$key][] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove a header from the exception.
+ *
+ * @param string $key The header to remove.
+ * @return $this
+ * @deprecated 4.2.0 Use `setHeaders()` instead.
+ */
+ public function removeHeader(string $key)
+ {
+ deprecationWarning('RedirectException::removeHeader() is deprecated, use setHeaders() instead.');
+
+ unset($this->headers[$key]);
+
+ return $this;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php b/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php
new file mode 100644
index 000000000..2b5270a7d
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php
@@ -0,0 +1,226 @@
+ 'flash',
+ 'element' => 'default',
+ 'plugin' => null,
+ 'params' => [],
+ 'clear' => false,
+ 'duplicate' => true,
+ ];
+
+ /**
+ * @var \Cake\Http\Session
+ */
+ protected $session;
+
+ /**
+ * Constructor
+ *
+ * @param \Cake\Http\Session $session Session instance.
+ * @param array $config Config array.
+ * @see FlashMessage::set() For list of valid config keys.
+ */
+ public function __construct(Session $session, array $config = [])
+ {
+ $this->session = $session;
+ $this->setConfig($config);
+ }
+
+ /**
+ * Store flash messages that can be output in the view.
+ *
+ * If you make consecutive calls to this method, the messages will stack
+ * (if they are set with the same flash key)
+ *
+ * ### Options:
+ *
+ * - `key` The key to set under the session's Flash key.
+ * - `element` The element used to render the flash message. You can use
+ * `'SomePlugin.name'` style value for flash elements from a plugin.
+ * - `plugin` Plugin name to use element from.
+ * - `params` An array of variables to be made available to the element.
+ * - `clear` A bool stating if the current stack should be cleared to start a new one.
+ * - `escape` Set to false to allow templates to print out HTML content.
+ *
+ * @param string $message Message to be flashed.
+ * @param array $options An array of options
+ * @return void
+ * @see FlashMessage::$_defaultConfig For default values for the options.
+ */
+ public function set($message, array $options = []): void
+ {
+ $options += (array)$this->getConfig();
+
+ if (isset($options['escape']) && !isset($options['params']['escape'])) {
+ $options['params']['escape'] = $options['escape'];
+ }
+
+ [$plugin, $element] = pluginSplit($options['element']);
+ if ($options['plugin']) {
+ $plugin = $options['plugin'];
+ }
+
+ if ($plugin) {
+ $options['element'] = $plugin . '.flash/' . $element;
+ } else {
+ $options['element'] = 'flash/' . $element;
+ }
+
+ $messages = [];
+ if (!$options['clear']) {
+ $messages = (array)$this->session->read('Flash.' . $options['key']);
+ }
+
+ if (!$options['duplicate']) {
+ foreach ($messages as $existingMessage) {
+ if ($existingMessage['message'] === $message) {
+ return;
+ }
+ }
+ }
+
+ $messages[] = [
+ 'message' => $message,
+ 'key' => $options['key'],
+ 'element' => $options['element'],
+ 'params' => $options['params'],
+ ];
+
+ $this->session->write('Flash.' . $options['key'], $messages);
+ }
+
+ /**
+ * Set an exception's message as flash message.
+ *
+ * The following options will be set by default if unset:
+ * ```
+ * 'element' => 'error',
+ * `params' => ['code' => $exception->getCode()]
+ * ```
+ *
+ * @param \Throwable $exception Exception instance.
+ * @param array $options An array of options.
+ * @return void
+ * @see FlashMessage::set() For list of valid options
+ */
+ public function setExceptionMessage(Throwable $exception, array $options = []): void
+ {
+ if (!isset($options['element'])) {
+ $options['element'] = 'error';
+ }
+ if (!isset($options['params']['code'])) {
+ $options['params']['code'] = $exception->getCode();
+ }
+
+ $message = $exception->getMessage();
+ $this->set($message, $options);
+ }
+
+ /**
+ * Get the messages for given key and remove from session.
+ *
+ * @param string $key The key for get messages for.
+ * @return array|null
+ */
+ public function consume(string $key): ?array
+ {
+ return $this->session->consume("Flash.{$key}");
+ }
+
+ /**
+ * Set a success message.
+ *
+ * The `'element'` option will be set to `'success'`.
+ *
+ * @param string $message Message to flash.
+ * @param array $options An array of options.
+ * @return void
+ * @see FlashMessage::set() For list of valid options
+ */
+ public function success(string $message, array $options = []): void
+ {
+ $options['element'] = 'success';
+ $this->set($message, $options);
+ }
+
+ /**
+ * Set an success message.
+ *
+ * The `'element'` option will be set to `'error'`.
+ *
+ * @param string $message Message to flash.
+ * @param array $options An array of options.
+ * @return void
+ * @see FlashMessage::set() For list of valid options
+ */
+ public function error(string $message, array $options = []): void
+ {
+ $options['element'] = 'error';
+ $this->set($message, $options);
+ }
+
+ /**
+ * Set a warning message.
+ *
+ * The `'element'` option will be set to `'warning'`.
+ *
+ * @param string $message Message to flash.
+ * @param array $options An array of options.
+ * @return void
+ * @see FlashMessage::set() For list of valid options
+ */
+ public function warning(string $message, array $options = []): void
+ {
+ $options['element'] = 'warning';
+ $this->set($message, $options);
+ }
+
+ /**
+ * Set an info message.
+ *
+ * The `'element'` option will be set to `'info'`.
+ *
+ * @param string $message Message to flash.
+ * @param array $options An array of options.
+ * @return void
+ * @see FlashMessage::set() For list of valid options
+ */
+ public function info(string $message, array $options = []): void
+ {
+ $options['element'] = 'info';
+ $this->set($message, $options);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Http/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Http/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Http/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Http/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php
index ac4e260e2..2044a7474 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php
@@ -28,9 +28,7 @@
/**
* Parse encoded request body data.
*
- * Enables JSON and XML request payloads to be parsed into the request's
- * Provides CSRF protection & validation.
- *
+ * Enables JSON and XML request payloads to be parsed into the request's body.
* You can also add your own request body parsers using the `addParser()` method.
*/
class BodyParserMiddleware implements MiddlewareInterface
@@ -54,7 +52,7 @@ class BodyParserMiddleware implements MiddlewareInterface
*
* ### Options
*
- * - `json` Set to false to disable json body parsing.
+ * - `json` Set to false to disable JSON body parsing.
* - `xml` Set to true to enable XML parsing. Defaults to false, as XML
* handling requires more care than JSON does.
* - `methods` The HTTP methods to parse on. Defaults to PUT, POST, PATCH DELETE.
@@ -178,11 +176,19 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
* Decode JSON into an array.
*
* @param string $body The request body to decode
- * @return mixed
+ * @return array|null
*/
protected function decodeJson(string $body)
{
- return json_decode($body, true);
+ if ($body === '') {
+ return [];
+ }
+ $decoded = json_decode($body, true);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ return (array)$decoded;
+ }
+
+ return null;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php
index 8ce975d08..f880bc02c 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php
@@ -66,7 +66,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
{
$response = $handler->handle($request);
- // phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat
/** @var \Psr\Http\Message\ResponseInterface */
return $this->csp->injectCSPHeader($response);
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php
index 3c7c292eb..d96655777 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php
@@ -23,10 +23,12 @@
use Cake\Http\Response;
use Cake\Utility\Hash;
use Cake\Utility\Security;
+use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
+use RuntimeException;
/**
* Provides CSRF protection & validation.
@@ -53,7 +55,10 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
* - `expiry` A strotime compatible value of how long the CSRF token should last.
* Defaults to browser session.
* - `secure` Whether or not the cookie will be set with the Secure flag. Defaults to false.
- * - `httpOnly` Whether or not the cookie will be set with the HttpOnly flag. Defaults to false.
+ * - `httponly` Whether or not the cookie will be set with the HttpOnly flag. Defaults to false.
+ * - `samesite` "SameSite" attribute for cookies. Defaults to `null`.
+ * Valid values: `CookieInterface::SAMESITE_LAX`, `CookieInterface::SAMESITE_STRICT`,
+ * `CookieInterface::SAMESITE_NONE` or `null`.
* - `field` The form field to check. Changing this will also require configuring
* FormHelper.
*
@@ -63,7 +68,8 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
'cookieName' => 'csrfToken',
'expiry' => 0,
'secure' => false,
- 'httpOnly' => false,
+ 'httponly' => false,
+ 'samesite' => null,
'field' => '_csrfToken',
];
@@ -74,13 +80,26 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
*
* @var callable|null
*/
- protected $whitelistCallback;
+ protected $skipCheckCallback;
/**
* @var int
*/
public const TOKEN_VALUE_LENGTH = 16;
+ /**
+ * Tokens have an hmac generated so we can ensure
+ * that tokens were generated by our application.
+ *
+ * Should be TOKEN_VALUE_LENGTH + strlen(hmac)
+ *
+ * We are currently using sha1 for the hmac which
+ * creates 40 bytes.
+ *
+ * @var int
+ */
+ public const TOKEN_WITH_CHECKSUM_LENGTH = 56;
+
/**
* Constructor
*
@@ -88,6 +107,11 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
*/
public function __construct(array $config = [])
{
+ if (array_key_exists('httpOnly', $config)) {
+ $config['httponly'] = $config['httpOnly'];
+ deprecationWarning('Option `httpOnly` is deprecated. Use lowercased `httponly` instead.');
+ }
+
$this->_config = $config + $this->_config;
}
@@ -106,24 +130,36 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
if (
$hasData
- && $this->whitelistCallback !== null
- && call_user_func($this->whitelistCallback, $request) === true
+ && $this->skipCheckCallback !== null
+ && call_user_func($this->skipCheckCallback, $request) === true
) {
$request = $this->_unsetTokenField($request);
return $handler->handle($request);
}
+ if ($request->getAttribute('csrfToken')) {
+ throw new RuntimeException(
+ 'A CSRF token is already set in the request.' .
+ "\n" .
+ 'Ensure you do not have the CSRF middleware applied more than once. ' .
+ 'Check both your `Application::middleware()` method and `config/routes.php`.'
+ );
+ }
$cookies = $request->getCookieParams();
$cookieData = Hash::get($cookies, $this->_config['cookieName']);
if (is_string($cookieData) && strlen($cookieData) > 0) {
- $request = $request->withAttribute('csrfToken', $cookieData);
+ try {
+ $request = $request->withAttribute('csrfToken', $this->saltToken($cookieData));
+ } catch (InvalidArgumentException $e) {
+ $cookieData = null;
+ }
}
if ($method === 'GET' && $cookieData === null) {
$token = $this->createToken();
- $request = $request->withAttribute('csrfToken', $token);
+ $request = $request->withAttribute('csrfToken', $this->saltToken($token));
/** @var mixed $response */
$response = $handler->handle($request);
@@ -144,12 +180,30 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
* The callback will receive request instance as argument and must return
* `true` if you want to skip token check for the current request.
*
+ * @deprecated 4.1.0 Use skipCheckCallback instead.
* @param callable $callback A callable.
* @return $this
*/
public function whitelistCallback(callable $callback)
{
- $this->whitelistCallback = $callback;
+ deprecationWarning('`whitelistCallback()` is deprecated. Use `skipCheckCallback()` instead.');
+ $this->skipCheckCallback = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Set callback for allowing to skip token check for particular request.
+ *
+ * The callback will receive request instance as argument and must return
+ * `true` if you want to skip token check for the current request.
+ *
+ * @param callable $callback A callable.
+ * @return $this
+ */
+ public function skipCheckCallback(callable $callback)
+ {
+ $this->skipCheckCallback = $callback;
return $this;
}
@@ -179,9 +233,27 @@ protected function _unsetTokenField(ServerRequestInterface $request): ServerRequ
*/
protected function _createToken(): string
{
+ deprecationWarning('_createToken() is deprecated. Use createToken() instead.');
+
return $this->createToken();
}
+ /**
+ * Test if the token predates salted tokens.
+ *
+ * These tokens are hexadecimal values and equal
+ * to the token with checksum length. While they are vulnerable
+ * to BREACH they should rotate over time and support will be dropped
+ * in 5.x.
+ *
+ * @param string $token The token to test.
+ * @return bool
+ */
+ protected function isHexadecimalToken(string $token): bool
+ {
+ return preg_match('/^[a-f0-9]{' . static::TOKEN_WITH_CHECKSUM_LENGTH . '}$/', $token) === 1;
+ }
+
/**
* Create a new token to be used for CSRF protection
*
@@ -189,9 +261,70 @@ protected function _createToken(): string
*/
public function createToken(): string
{
- $value = Security::randomString(static::TOKEN_VALUE_LENGTH);
+ $value = Security::randomBytes(static::TOKEN_VALUE_LENGTH);
- return $value . hash_hmac('sha1', $value, Security::getSalt());
+ return base64_encode($value . hash_hmac('sha1', $value, Security::getSalt()));
+ }
+
+ /**
+ * Apply entropy to a CSRF token
+ *
+ * To avoid BREACH apply a random salt value to a token
+ * When the token is compared to the session the token needs
+ * to be unsalted.
+ *
+ * @param string $token The token to salt.
+ * @return string The salted token with the salt appended.
+ */
+ public function saltToken(string $token): string
+ {
+ if ($this->isHexadecimalToken($token)) {
+ return $token;
+ }
+ $decoded = base64_decode($token, true);
+ if ($decoded === false) {
+ throw new InvalidArgumentException('Invalid token data.');
+ }
+
+ $length = strlen($decoded);
+ $salt = Security::randomBytes($length);
+ $salted = '';
+ for ($i = 0; $i < $length; $i++) {
+ // XOR the token and salt together so that we can reverse it later.
+ $salted .= chr(ord($decoded[$i]) ^ ord($salt[$i]));
+ }
+
+ return base64_encode($salted . $salt);
+ }
+
+ /**
+ * Remove the salt from a CSRF token.
+ *
+ * If the token is not TOKEN_VALUE_LENGTH * 2 it is an old
+ * unsalted value that is supported for backwards compatibility.
+ *
+ * @param string $token The token that could be salty.
+ * @return string An unsalted token.
+ */
+ public function unsaltToken(string $token): string
+ {
+ if ($this->isHexadecimalToken($token)) {
+ return $token;
+ }
+ $decoded = base64_decode($token, true);
+ if ($decoded === false || strlen($decoded) !== static::TOKEN_WITH_CHECKSUM_LENGTH * 2) {
+ return $token;
+ }
+ $salted = substr($decoded, 0, static::TOKEN_WITH_CHECKSUM_LENGTH);
+ $salt = substr($decoded, static::TOKEN_WITH_CHECKSUM_LENGTH);
+
+ $unsalted = '';
+ for ($i = 0; $i < static::TOKEN_WITH_CHECKSUM_LENGTH; $i++) {
+ // Reverse the XOR to desalt.
+ $unsalted .= chr(ord($salted[$i]) ^ ord($salt[$i]));
+ }
+
+ return base64_encode($unsalted);
}
/**
@@ -202,12 +335,19 @@ public function createToken(): string
*/
protected function _verifyToken(string $token): bool
{
- if (strlen($token) <= self::TOKEN_VALUE_LENGTH) {
+ // If we have a hexadecimal value we're in a compatibility mode from before
+ // tokens were salted on each request.
+ if ($this->isHexadecimalToken($token)) {
+ $decoded = $token;
+ } else {
+ $decoded = base64_decode($token, true);
+ }
+ if (strlen($decoded) <= static::TOKEN_VALUE_LENGTH) {
return false;
}
- $key = substr($token, 0, static::TOKEN_VALUE_LENGTH);
- $hmac = substr($token, static::TOKEN_VALUE_LENGTH);
+ $key = substr($decoded, 0, static::TOKEN_VALUE_LENGTH);
+ $hmac = substr($decoded, static::TOKEN_VALUE_LENGTH);
$expectedHmac = hash_hmac('sha1', $key, Security::getSalt());
@@ -254,7 +394,7 @@ protected function _validateToken(ServerRequestInterface $request): void
$exception = new InvalidCsrfTokenException(__d('cake', 'Missing or invalid CSRF cookie.'));
$expiredCookie = $this->_createCookie('', $request)->withExpired();
- $exception->responseHeader('Set-Cookie', $expiredCookie->toHeaderValue());
+ $exception->setHeader('Set-Cookie', $expiredCookie->toHeaderValue());
throw $exception;
}
@@ -262,12 +402,14 @@ protected function _validateToken(ServerRequestInterface $request): void
$body = $request->getParsedBody();
if (is_array($body) || $body instanceof ArrayAccess) {
$post = (string)Hash::get($body, $this->_config['field']);
+ $post = $this->unsaltToken($post);
if (hash_equals($post, $cookie)) {
return;
}
}
$header = $request->getHeaderLine('X-CSRF-Token');
+ $header = $this->unsaltToken($header);
if (hash_equals($header, $cookie)) {
return;
}
@@ -294,7 +436,8 @@ protected function _createCookie(string $value, ServerRequestInterface $request)
'expires' => $this->_config['expiry'] ?: null,
'path' => $request->getAttribute('webroot'),
'secure' => $this->_config['secure'],
- 'httponly' => $this->_config['httpOnly'],
+ 'httponly' => $this->_config['httponly'],
+ 'samesite' => $this->_config['samesite'],
]
);
diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php
index 132b24f21..4786cb589 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php
@@ -25,7 +25,7 @@
use Psr\Http\Server\RequestHandlerInterface;
/**
- * Middlware for encrypting & decrypting cookies.
+ * Middleware for encrypting & decrypting cookies.
*
* This middleware layer will encrypt/decrypt the named cookies with the given key
* and cipher type. To support multiple keys/cipher types use this middleware multiple
diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php
index 01c4bb2e4..b458522fa 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php
@@ -192,7 +192,7 @@ public function setXFrameOptions(string $option = self::SAMEORIGIN, ?string $url
*/
public function setXssProtection(string $mode = self::XSS_BLOCK)
{
- $mode = (string)$mode;
+ $mode = $mode;
if ($mode === self::XSS_BLOCK) {
$mode = self::XSS_ENABLED_BLOCK;
diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php
new file mode 100644
index 000000000..4b3beae79
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php
@@ -0,0 +1,270 @@
+Form->create(...)` is used in a view.
+ *
+ * If you use this middleware *do not* also use CsrfProtectionMiddleware.
+ *
+ * @see https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#sychronizer-token-pattern
+ */
+class SessionCsrfProtectionMiddleware implements MiddlewareInterface
+{
+ /**
+ * Config for the CSRF handling.
+ *
+ * - `key` The session key to use. Defaults to `csrfToken`
+ * - `field` The form field to check. Changing this will also require configuring
+ * FormHelper.
+ *
+ * @var array
+ */
+ protected $_config = [
+ 'key' => 'csrfToken',
+ 'field' => '_csrfToken',
+ ];
+
+ /**
+ * Callback for deciding whether or not to skip the token check for particular request.
+ *
+ * CSRF protection token check will be skipped if the callback returns `true`.
+ *
+ * @var callable|null
+ */
+ protected $skipCheckCallback;
+
+ /**
+ * @var int
+ */
+ public const TOKEN_VALUE_LENGTH = 32;
+
+ /**
+ * Constructor
+ *
+ * @param array $config Config options. See $_config for valid keys.
+ */
+ public function __construct(array $config = [])
+ {
+ $this->_config = $config + $this->_config;
+ }
+
+ /**
+ * Checks and sets the CSRF token depending on the HTTP verb.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request The request.
+ * @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler.
+ * @return \Psr\Http\Message\ResponseInterface A response.
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ $method = $request->getMethod();
+ $hasData = in_array($method, ['PUT', 'POST', 'DELETE', 'PATCH'], true)
+ || $request->getParsedBody();
+
+ if (
+ $hasData
+ && $this->skipCheckCallback !== null
+ && call_user_func($this->skipCheckCallback, $request) === true
+ ) {
+ $request = $this->unsetTokenField($request);
+
+ return $handler->handle($request);
+ }
+
+ $session = $request->getAttribute('session');
+ if (!$session || !($session instanceof Session)) {
+ throw new RuntimeException('You must have a `session` attribute to use session based CSRF tokens');
+ }
+
+ $token = $session->read($this->_config['key']);
+ if ($token === null) {
+ $token = $this->createToken();
+ $session->write($this->_config['key'], $token);
+ }
+ $request = $request->withAttribute('csrfToken', $this->saltToken($token));
+
+ if ($method === 'GET') {
+ return $handler->handle($request);
+ }
+
+ if ($hasData) {
+ $this->validateToken($request, $session);
+ $request = $this->unsetTokenField($request);
+ }
+
+ return $handler->handle($request);
+ }
+
+ /**
+ * Set callback for allowing to skip token check for particular request.
+ *
+ * The callback will receive request instance as argument and must return
+ * `true` if you want to skip token check for the current request.
+ *
+ * @param callable $callback A callable.
+ * @return $this
+ */
+ public function skipCheckCallback(callable $callback)
+ {
+ $this->skipCheckCallback = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Apply entropy to a CSRF token
+ *
+ * To avoid BREACH apply a random salt value to a token
+ * When the token is compared to the session the token needs
+ * to be unsalted.
+ *
+ * @param string $token The token to salt.
+ * @return string The salted token with the salt appended.
+ */
+ public function saltToken(string $token): string
+ {
+ $decoded = base64_decode($token);
+ $length = strlen($decoded);
+ $salt = Security::randomBytes($length);
+ $salted = '';
+ for ($i = 0; $i < $length; $i++) {
+ // XOR the token and salt together so that we can reverse it later.
+ $salted .= chr(ord($decoded[$i]) ^ ord($salt[$i]));
+ }
+
+ return base64_encode($salted . $salt);
+ }
+
+ /**
+ * Remove the salt from a CSRF token.
+ *
+ * If the token is not TOKEN_VALUE_LENGTH * 2 it is an old
+ * unsalted value that is supported for backwards compatibility.
+ *
+ * @param string $token The token that could be salty.
+ * @return string An unsalted token.
+ */
+ protected function unsaltToken(string $token): string
+ {
+ $decoded = base64_decode($token, true);
+ if ($decoded === false || strlen($decoded) !== static::TOKEN_VALUE_LENGTH * 2) {
+ return $token;
+ }
+ $salted = substr($decoded, 0, static::TOKEN_VALUE_LENGTH);
+ $salt = substr($decoded, static::TOKEN_VALUE_LENGTH);
+
+ $unsalted = '';
+ for ($i = 0; $i < static::TOKEN_VALUE_LENGTH; $i++) {
+ // Reverse the XOR to desalt.
+ $unsalted .= chr(ord($salted[$i]) ^ ord($salt[$i]));
+ }
+
+ return base64_encode($unsalted);
+ }
+
+ /**
+ * Remove CSRF protection token from request data.
+ *
+ * This ensures that the token does not cause failures during
+ * form tampering protection.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request The request object.
+ * @return \Psr\Http\Message\ServerRequestInterface
+ */
+ protected function unsetTokenField(ServerRequestInterface $request): ServerRequestInterface
+ {
+ $body = $request->getParsedBody();
+ if (is_array($body)) {
+ unset($body[$this->_config['field']]);
+ $request = $request->withParsedBody($body);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Create a new token to be used for CSRF protection
+ *
+ * This token is a simple unique random value as the compare
+ * value is stored in the session where it cannot be tampered with.
+ *
+ * @return string
+ */
+ public function createToken(): string
+ {
+ return base64_encode(Security::randomBytes(static::TOKEN_VALUE_LENGTH));
+ }
+
+ /**
+ * Validate the request data against the cookie token.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request The request to validate against.
+ * @param \Cake\Http\Session $session The session instance.
+ * @return void
+ * @throws \Cake\Http\Exception\InvalidCsrfTokenException When the CSRF token is invalid or missing.
+ */
+ protected function validateToken(ServerRequestInterface $request, Session $session): void
+ {
+ $token = $session->read($this->_config['key']);
+ if (!$token || !is_string($token)) {
+ throw new InvalidCsrfTokenException(__d('cake', 'Missing or incorrect CSRF session key'));
+ }
+
+ $body = $request->getParsedBody();
+ if (is_array($body) || $body instanceof ArrayAccess) {
+ $post = (string)Hash::get($body, $this->_config['field']);
+ $post = $this->unsaltToken($post);
+ if (hash_equals($post, $token)) {
+ return;
+ }
+ }
+
+ $header = $request->getHeaderLine('X-CSRF-Token');
+ $header = $this->unsaltToken($header);
+ if (hash_equals($header, $token)) {
+ return;
+ }
+
+ throw new InvalidCsrfTokenException(__d(
+ 'cake',
+ 'CSRF token from either the request body or request headers did not match or is missing.'
+ ));
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php b/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php
index ccdd4a87f..ee5704183 100644
--- a/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php
+++ b/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php
@@ -31,6 +31,8 @@
/**
* Provides methods for creating and manipulating a "queue" of middlewares.
* This queue is used to process a request and generate response via \Cake\Http\Runner.
+ *
+ * @template-implements \SeekableIterator
*/
class MiddlewareQueue implements Countable, SeekableIterator
{
diff --git a/app/vendor/cakephp/cakephp/src/Http/README.md b/app/vendor/cakephp/cakephp/src/Http/README.md
index e67f57a18..f305fdd5c 100644
--- a/app/vendor/cakephp/cakephp/src/Http/README.md
+++ b/app/vendor/cakephp/cakephp/src/Http/README.md
@@ -9,7 +9,7 @@ handle incoming server requests and send outgoing HTTP requests.
## Using the Http Client
-Sending requests is straight forward. Doing a GET request looks like
+Sending requests is straight forward. Doing a GET request looks like:
```php
use Cake\Http\Client;
@@ -34,13 +34,16 @@ To learn more read the [Http Client documentation](https://book.cakephp.org/4/en
The Http Server allows an `HttpApplicationInterface` to process requests and
emit responses. To get started first implement the
-`Cake\Http\HttpApplicationInterface` A minimal example would could look like:
+`Cake\Http\HttpApplicationInterface` A minimal example could look like:
```php
namespace App;
use Cake\Core\HttpApplicationInterface;
use Cake\Http\MiddlewareQueue;
+use Cake\Http\Response;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
class Application implements HttpApplicationInterface
{
@@ -66,6 +69,17 @@ class Application implements HttpApplicationInterface
// Add middleware for your application.
return $middlewareQueue;
}
+
+ /**
+ * Handle incoming server request and return a response.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request The request
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ return new Response(['body'=>'Hello World!']);
+ }
}
```
diff --git a/app/vendor/cakephp/cakephp/src/Http/Response.php b/app/vendor/cakephp/cakephp/src/Http/Response.php
index 3276cd9cc..385bdee9d 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Response.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Response.php
@@ -35,7 +35,7 @@
*
* There are external packages such as `fig/http-message-util` that provide HTTP
* status code constants. These can be used with any method that accepts or
- * returns a status code integer. Keep in mind that these consants might
+ * returns a status code integer. Keep in mind that these constants might
* include status codes that are now allowed which will throw an
* `\InvalidArgumentException`.
*/
@@ -167,6 +167,8 @@ class Response implements ResponseInterface
'gz' => 'application/x-gzip',
'bz2' => 'application/x-bzip',
'7z' => 'application/x-7z-compressed',
+ 'haljson' => ['application/hal+json', 'application/vnd.hal+json'],
+ 'halxml' => ['application/hal+xml', 'application/vnd.hal+xml'],
'hdf' => 'application/x-hdf',
'hqx' => 'application/mac-binhex40',
'ico' => 'image/x-icon',
@@ -175,6 +177,7 @@ class Response implements ResponseInterface
'js' => 'application/javascript',
'jsonapi' => 'application/vnd.api+json',
'latex' => 'application/x-latex',
+ 'jsonld' => 'application/ld+json',
'lha' => 'application/octet-stream',
'lsp' => 'application/x-lisp',
'lzh' => 'application/octet-stream',
@@ -496,7 +499,7 @@ protected function _setContentType(string $type): void
return;
}
- $whitelist = [
+ $allowed = [
'application/javascript', 'application/xml', 'application/rss+xml',
];
@@ -505,7 +508,7 @@ protected function _setContentType(string $type): void
$this->_charset &&
(
strpos($type, 'text/') === 0 ||
- in_array($type, $whitelist, true)
+ in_array($type, $allowed, true)
)
) {
$charset = true;
@@ -596,7 +599,7 @@ public function getStatusCode(): int
*
* There are external packages such as `fig/http-message-util` that provide HTTP
* status code constants. These can be used with any method that accepts or
- * returns a status code integer. However, keep in mind that these consants
+ * returns a status code integer. However, keep in mind that these constants
* might include status codes that are now allowed which will throw an
* `\InvalidArgumentException`.
*
@@ -806,7 +809,7 @@ public function withCharset(string $charset)
public function withDisabledCache()
{
return $this->withHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
- ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT')
+ ->withHeader('Last-Modified', gmdate(DATE_RFC7231))
->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
}
@@ -828,7 +831,7 @@ public function withCache($since, $time = '+1 day')
}
}
- return $this->withHeader('Date', gmdate('D, j M Y G:i:s ', time()) . 'GMT')
+ return $this->withHeader('Date', gmdate(DATE_RFC7231, time()))
->withModified($since)
->withExpires($time)
->withSharable(true)
@@ -956,7 +959,7 @@ public function withExpires($time)
{
$date = $this->_getUTCDate($time);
- return $this->withHeader('Expires', $date->format('D, j M Y H:i:s') . ' GMT');
+ return $this->withHeader('Expires', $date->format(DATE_RFC7231));
}
/**
@@ -979,7 +982,7 @@ public function withModified($time)
{
$date = $this->_getUTCDate($time);
- return $this->withHeader('Last-Modified', $date->format('D, j M Y H:i:s') . ' GMT');
+ return $this->withHeader('Last-Modified', $date->format(DATE_RFC7231));
}
/**
@@ -1206,7 +1209,7 @@ public function withAddedLink(string $url, array $options = [])
*/
public function checkNotModified(ServerRequest $request): bool
{
- $etags = preg_split('/\s*,\s*/', (string)$request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY);
+ $etags = preg_split('/\s*,\s*/', $request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY);
$responseTag = $this->getHeaderLine('Etag');
$etagMatches = null;
if ($responseTag) {
@@ -1240,7 +1243,7 @@ public function __toString(): string
{
$this->stream->rewind();
- return (string)$this->stream->getContents();
+ return $this->stream->getContents();
}
/**
@@ -1373,7 +1376,7 @@ public function withCookieCollection(CookieCollection $cookieCollection)
* cors($request, '*');
* ```
*
- * ### Whitelist of URIs
+ * ### Allowed list of URIs
* ```
* cors($request, ['http://www.cakephp.org', '*.google.com', 'https://myproject.github.io']);
* ```
diff --git a/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php b/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php
index a0779bb8a..a55a082cf 100644
--- a/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php
+++ b/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php
@@ -232,6 +232,7 @@ protected function setCookie($cookie): bool
}
if (PHP_VERSION_ID >= 70300) {
+ /** @psalm-suppress InvalidArgument */
return setcookie($cookie->getName(), $cookie->getScalarValue(), $cookie->getOptions());
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/Server.php b/app/vendor/cakephp/cakephp/src/Http/Server.php
index 416808662..913fbe053 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Server.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Server.php
@@ -99,16 +99,14 @@ public function run(
/**
* Application bootstrap wrapper.
*
- * Calls `bootstrap()` and `events()` if application implements `EventApplicationInterface`.
- * After the application is bootstrapped and events are attached, plugins are bootstrapped
- * and have their events attached.
+ * Calls the application's `bootstrap()` hook. After the application the
+ * plugins are bootstrapped.
*
* @return void
*/
protected function bootstrap(): void
{
$this->app->bootstrap();
-
if ($this->app instanceof PluginApplicationInterface) {
$this->app->pluginBootstrap();
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php b/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php
index 098a8297e..2be352824 100644
--- a/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php
+++ b/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php
@@ -18,6 +18,7 @@
use BadMethodCallException;
use Cake\Core\Configure;
+use Cake\Core\Exception\CakeException;
use Cake\Http\Cookie\CookieCollection;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Utility\Hash;
@@ -159,6 +160,13 @@ class ServerRequest implements ServerRequestInterface
*/
protected $session;
+ /**
+ * Instance of a FlashMessage object relative to this request
+ *
+ * @var \Cake\Http\FlashMessage
+ */
+ protected $flash;
+
/**
* Store the additional attributes attached to the request.
*
@@ -171,7 +179,7 @@ class ServerRequest implements ServerRequestInterface
*
* @var array
*/
- protected $emulatedAttributes = ['session', 'webroot', 'base', 'params', 'here'];
+ protected $emulatedAttributes = ['session', 'flash', 'webroot', 'base', 'params', 'here'];
/**
* Array of Psr\Http\Message\UploadedFileInterface objects.
@@ -194,13 +202,6 @@ class ServerRequest implements ServerRequestInterface
*/
protected $requestTarget;
- /**
- * Whether to merge file uploads as objects (`true`) or arrays (`false`).
- *
- * @var bool
- */
- protected $mergeFilesAsObjects = true;
-
/**
* Create a new request object.
*
@@ -210,7 +211,7 @@ class ServerRequest implements ServerRequestInterface
*
* - `post` POST data or non query string data
* - `query` Additional data from the query string.
- * - `files` Uploaded file data formatted like $_FILES.
+ * - `files` Uploaded files in a normalized structure, with each leaf an instance of UploadedFileInterface.
* - `cookies` Cookies for this request.
* - `environment` $_SERVER and $_ENV data.
* - `url` The URL without the base path for the request.
@@ -220,7 +221,6 @@ class ServerRequest implements ServerRequestInterface
* - `input` The data that would come from php://input this is useful for simulating
* requests with put, patch or delete data.
* - `session` An instance of a Session object
- * - `mergeFilesAsObjects` Whether to merge file uploads as objects (`true`) or arrays (`false`).
*
* @param array $config An array of request data to create a request with.
*/
@@ -238,7 +238,6 @@ public function __construct(array $config = [])
'base' => '',
'webroot' => '',
'input' => null,
- 'mergeFilesAsObjects' => true,
];
$this->_setConfig($config);
@@ -252,38 +251,31 @@ public function __construct(array $config = [])
*/
protected function _setConfig(array $config): void
{
- if (strlen($config['url']) > 1 && $config['url'][0] === '/') {
- $config['url'] = substr($config['url'], 1);
- }
-
if (empty($config['session'])) {
$config['session'] = new Session([
'cookiePath' => $config['base'],
]);
}
- $this->_environment = $config['environment'];
+ if (empty($config['environment']['REQUEST_METHOD'])) {
+ $config['environment']['REQUEST_METHOD'] = 'GET';
+ }
+
$this->cookies = $config['cookies'];
- if (isset($config['uri']) && $config['uri'] instanceof UriInterface) {
+ if (isset($config['uri'])) {
+ if (!$config['uri'] instanceof UriInterface) {
+ throw new CakeException('The `uri` key must be an instance of ' . UriInterface::class);
+ }
$uri = $config['uri'];
} else {
+ if ($config['url'] !== '') {
+ $config = $this->processUrlOption($config);
+ }
$uri = ServerRequestFactory::createUri($config['environment']);
}
- // Extract a query string from config[url] if present.
- // This is required for backwards compatibility and keeping
- // UriInterface implementations happy.
- $querystr = '';
- if (strpos($config['url'], '?') !== false) {
- [$config['url'], $querystr] = explode('?', $config['url']);
- }
- if (strlen($config['url'])) {
- $uri = $uri->withPath('/' . $config['url']);
- }
- if (strlen($querystr)) {
- $uri = $uri->withQuery($querystr);
- }
+ $this->_environment = $config['environment'];
$this->uri = $uri;
$this->base = $config['base'];
@@ -298,176 +290,38 @@ protected function _setConfig(array $config): void
}
$this->stream = $stream;
- $this->mergeFilesAsObjects = $config['mergeFilesAsObjects'];
-
- $config['post'] = $this->_processPost($config['post']);
- $this->data = $this->_processFiles($config['post'], $config['files']);
- $this->query = $this->_processGet($config['query'], $querystr);
+ $this->data = $config['post'];
+ $this->uploadedFiles = $config['files'];
+ $this->query = $config['query'];
$this->params = $config['params'];
$this->session = $config['session'];
+ $this->flash = new FlashMessage($this->session);
}
/**
- * Sets the REQUEST_METHOD environment variable based on the simulated _method
- * HTTP override value. The 'ORIGINAL_REQUEST_METHOD' is also preserved, if you
- * want the read the non-simulated HTTP method the client used.
+ * Set environment vars based on `url` option to facilitate UriInterface instance generation.
*
- * @param mixed $data Array of post data.
- * @return mixed
- */
- protected function _processPost($data)
- {
- $method = $this->getEnv('REQUEST_METHOD');
- $override = false;
-
- if (
- in_array($method, ['PUT', 'DELETE', 'PATCH'], true) &&
- strpos((string)$this->contentType(), 'application/x-www-form-urlencoded') === 0
- ) {
- $data = $this->input();
- parse_str($data, $data);
- }
- if ($this->hasHeader('X-Http-Method-Override')) {
- $data['_method'] = $this->getHeaderLine('X-Http-Method-Override');
- $override = true;
- }
- $this->_environment['ORIGINAL_REQUEST_METHOD'] = $method;
- if (isset($data['_method'])) {
- $this->_environment['REQUEST_METHOD'] = $data['_method'];
- unset($data['_method']);
- $override = true;
- }
-
- if ($override && !in_array($this->_environment['REQUEST_METHOD'], ['PUT', 'POST', 'DELETE', 'PATCH'], true)) {
- $data = [];
- }
-
- return $data;
- }
-
- /**
- * Process the GET parameters and move things into the object.
- *
- * @param array $query The array to which the parsed keys/values are being added.
- * @param string $queryString A query string from the URL if provided
- * @return array An array containing the parsed query string as keys/values.
- */
- protected function _processGet(array $query, string $queryString = ''): array
- {
- $unsetUrl = str_replace(['.', ' '], '_', urldecode($this->uri->getPath()));
- unset($query[$unsetUrl], $query[$this->base . $unsetUrl]);
- if (strlen($queryString)) {
- parse_str($queryString, $queryArgs);
- $query += $queryArgs;
- }
-
- return $query;
- }
-
- /**
- * Process uploaded files and move things onto the post data.
+ * `query` option is also updated based on URL's querystring.
*
- * @param mixed $post Post data to merge files onto.
- * @param mixed $files Uploaded files to merge in.
- * @return array merged post + file data.
+ * @param array $config Config array.
+ * @return array Update config.
*/
- protected function _processFiles($post, $files)
+ protected function processUrlOption(array $config): array
{
- if (!is_array($post) || !is_array($files) || empty($files)) {
- return $post;
+ if ($config['url'][0] !== '/') {
+ $config['url'] = '/' . $config['url'];
}
- $fileData = [];
- foreach ($files as $key => $value) {
- if ($value instanceof UploadedFileInterface) {
- $fileData[$key] = $value;
- continue;
- }
-
- if (is_array($value) && isset($value['tmp_name'])) {
- $fileData[$key] = $this->_createUploadedFile($value);
- continue;
- }
-
- throw new InvalidArgumentException(sprintf(
- 'Invalid value in FILES "%s"',
- json_encode($value)
- ));
- }
- $this->uploadedFiles = $fileData;
-
- if ($this->mergeFilesAsObjects) {
- return Hash::merge($post, $fileData);
- }
-
- // Make a flat map that can be inserted into $post for BC.
- $fileMap = Hash::flatten($fileData);
- foreach ($fileMap as $key => $file) {
- $error = $file->getError();
- $tmpName = '';
- if ($error === UPLOAD_ERR_OK) {
- $tmpName = $file->getStream()->getMetadata('uri');
- }
- $post = Hash::insert($post, (string)$key, [
- 'tmp_name' => $tmpName,
- 'error' => $error,
- 'name' => $file->getClientFilename(),
- 'type' => $file->getClientMediaType(),
- 'size' => $file->getSize(),
- ]);
- }
-
- return $post;
- }
+ if (strpos($config['url'], '?') !== false) {
+ [$config['url'], $config['environment']['QUERY_STRING']] = explode('?', $config['url']);
- /**
- * Create an UploadedFile instance from a $_FILES array.
- *
- * If the value represents an array of values, this method will
- * recursively process the data.
- *
- * @param array $value $_FILES struct
- * @return array|\Psr\Http\Message\UploadedFileInterface
- */
- protected function _createUploadedFile(array $value)
- {
- if (is_array($value['tmp_name'])) {
- return $this->_normalizeNestedFiles($value);
+ parse_str($config['environment']['QUERY_STRING'], $queryArgs);
+ $config['query'] += $queryArgs;
}
- return new UploadedFile(
- $value['tmp_name'],
- $value['size'],
- $value['error'],
- $value['name'],
- $value['type']
- );
- }
+ $config['environment']['REQUEST_URI'] = $config['url'];
- /**
- * Normalize an array of file specifications.
- *
- * Loops through all nested files and returns a normalized array of
- * UploadedFileInterface instances.
- *
- * @param array $files The file data to normalize & convert.
- * @return array An array of UploadedFileInterface objects.
- */
- protected function _normalizeNestedFiles(array $files = []): array
- {
- $normalizedFiles = [];
- foreach (array_keys($files['tmp_name']) as $key) {
- $spec = [
- 'tmp_name' => $files['tmp_name'][$key],
- 'size' => $files['size'][$key],
- 'error' => $files['error'][$key],
- 'name' => $files['name'][$key],
- 'type' => $files['type'][$key],
- ];
- $normalizedFiles[$key] = $this->_createUploadedFile($spec);
- }
-
- return $normalizedFiles;
+ return $config;
}
/**
@@ -495,6 +349,16 @@ public function getSession(): Session
return $this->session;
}
+ /**
+ * Returns the instance of the FlashMessage object for this request
+ *
+ * @return \Cake\Http\FlashMessage
+ */
+ public function getFlash(): FlashMessage
+ {
+ return $this->flash;
+ }
+
/**
* Get the IP the client is using, or says they are using.
*
@@ -613,7 +477,7 @@ public function __call(string $name, array $params)
*
* @param string|string[] $type The type of request you want to check. If an array
* this method will return true if the request matches any type.
- * @param string ...$args List of arguments
+ * @param mixed ...$args List of arguments
* @return bool Whether or not the request is the type you are checking.
*/
public function is($type, ...$args): bool
@@ -713,7 +577,7 @@ protected function _headerDetector(array $detect): bool
$header = $this->getEnv('http_' . $header);
if ($header !== null) {
if (!is_string($value) && !is_bool($value) && is_callable($value)) {
- return call_user_func($value, $header);
+ return $value($header);
}
return $header === $value;
@@ -1421,6 +1285,9 @@ public function getData(?string $name = null, $default = null)
*
* Any additional parameters are applied to the callback in the order they are given.
*
+ * @deprecated 4.1.0 Use `(string)$request->getBody()` to get the raw PHP input
+ * as string; use `BodyParserMiddleware` to parse the request body so that it's
+ * available as array/object through `$request->getParsedBody()`.
* @param callable|null $callback A decoding callback that will convert the string data to another
* representation. Leave empty to access the raw input data. You can also
* supply additional parameters for the decoding callback using var args, see above.
@@ -1429,12 +1296,17 @@ public function getData(?string $name = null, $default = null)
*/
public function input(?callable $callback = null, ...$args)
{
+ deprecationWarning(
+ 'Use `(string)$request->getBody()` to get the raw PHP input as string; '
+ . 'use `BodyParserMiddleware` to parse the request body so that it\'s available as array/object '
+ . 'through $request->getParsedBody()'
+ );
$this->stream->rewind();
$input = $this->stream->getContents();
if ($callback) {
array_unshift($args, $input);
- return call_user_func_array($callback, $args);
+ return $callback(...$args);
}
return $input;
@@ -1653,7 +1525,7 @@ public function allowMethod($methods): bool
}
$allowed = strtoupper(implode(', ', $methods));
$e = new MethodNotAllowedException();
- $e->responseHeader('Allow', $allowed);
+ $e->setHeader('Allow', $allowed);
throw $e;
}
@@ -1841,15 +1713,15 @@ public function getUploadedFiles(): array
/**
* Update the request replacing the files, and creating a new instance.
*
- * @param array $files An array of uploaded file objects.
+ * @param array $uploadedFiles An array of uploaded file objects.
* @return static
* @throws \InvalidArgumentException when $files contains an invalid object.
*/
- public function withUploadedFiles(array $files)
+ public function withUploadedFiles(array $uploadedFiles)
{
- $this->validateUploadedFiles($files, '');
+ $this->validateUploadedFiles($uploadedFiles, '');
$new = clone $this;
- $new->uploadedFiles = $files;
+ $new->uploadedFiles = $uploadedFiles;
return $new;
}
@@ -1952,14 +1824,14 @@ public function withUri(UriInterface $uri, $preserveHost = false)
*
* @link https://tools.ietf.org/html/rfc7230#section-2.7 (for the various
* request-target forms allowed in request messages)
- * @param string $target The request target.
+ * @param string $requestTarget The request target.
* @return static
* @psalm-suppress MoreSpecificImplementedParamType
*/
- public function withRequestTarget($target)
+ public function withRequestTarget($requestTarget)
{
$new = clone $this;
- $new->requestTarget = $target;
+ $new->requestTarget = $requestTarget;
return $new;
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php b/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php
index d1e9a8967..9a39a4cf9 100644
--- a/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php
+++ b/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php
@@ -24,6 +24,7 @@
use function Laminas\Diactoros\marshalHeadersFromSapi;
use function Laminas\Diactoros\marshalUriFromSapi;
use function Laminas\Diactoros\normalizeServer;
+use function Laminas\Diactoros\normalizeUploadedFiles;
/**
* Factory for making ServerRequest instances.
@@ -46,7 +47,7 @@ abstract class ServerRequestFactory implements ServerRequestFactoryInterface
* @see fromServer()
* @param array $server $_SERVER superglobal
* @param array $query $_GET superglobal
- * @param array $body $_POST superglobal
+ * @param array $parsedBody $_POST superglobal
* @param array $cookies $_COOKIE superglobal
* @param array $files $_FILES superglobal
* @return \Cake\Http\ServerRequest
@@ -55,36 +56,125 @@ abstract class ServerRequestFactory implements ServerRequestFactoryInterface
public static function fromGlobals(
?array $server = null,
?array $query = null,
- ?array $body = null,
+ ?array $parsedBody = null,
?array $cookies = null,
?array $files = null
): ServerRequest {
$server = normalizeServer($server ?: $_SERVER);
$uri = static::createUri($server);
+
/** @psalm-suppress NoInterfaceProperties */
$sessionConfig = (array)Configure::read('Session') + [
'defaults' => 'php',
'cookiePath' => $uri->webroot,
];
$session = Session::create($sessionConfig);
+
/** @psalm-suppress NoInterfaceProperties */
$request = new ServerRequest([
'environment' => $server,
'uri' => $uri,
- 'files' => $files ?: $_FILES,
'cookies' => $cookies ?: $_COOKIE,
'query' => $query ?: $_GET,
- 'post' => $body ?: $_POST,
'webroot' => $uri->webroot,
'base' => $uri->base,
'session' => $session,
- 'mergeFilesAsObjects' => Configure::read('App.uploadedFilesAsObjects', true),
'input' => $server['CAKEPHP_INPUT'] ?? null,
]);
+ $request = static::marshalBodyAndRequestMethod($parsedBody ?? $_POST, $request);
+ $request = static::marshalFiles($files ?? $_FILES, $request);
+
return $request;
}
+ /**
+ * Sets the REQUEST_METHOD environment variable based on the simulated _method
+ * HTTP override value. The 'ORIGINAL_REQUEST_METHOD' is also preserved, if you
+ * want the read the non-simulated HTTP method the client used.
+ *
+ * Request body of content type "application/x-www-form-urlencoded" is parsed
+ * into array for PUT/PATCH/DELETE requests.
+ *
+ * @param array $parsedBody Parsed body.
+ * @param \Cake\Http\ServerRequest $request Request instance.
+ * @return \Cake\Http\ServerRequest
+ */
+ protected static function marshalBodyAndRequestMethod(array $parsedBody, ServerRequest $request): ServerRequest
+ {
+ $method = $request->getMethod();
+ $override = false;
+
+ if (
+ in_array($method, ['PUT', 'DELETE', 'PATCH'], true) &&
+ strpos((string)$request->contentType(), 'application/x-www-form-urlencoded') === 0
+ ) {
+ $data = (string)$request->getBody();
+ parse_str($data, $parsedBody);
+ }
+ if ($request->hasHeader('X-Http-Method-Override')) {
+ $parsedBody['_method'] = $request->getHeaderLine('X-Http-Method-Override');
+ $override = true;
+ }
+
+ $request = $request->withEnv('ORIGINAL_REQUEST_METHOD', $method);
+ if (isset($parsedBody['_method'])) {
+ $request = $request->withEnv('REQUEST_METHOD', $parsedBody['_method']);
+ unset($parsedBody['_method']);
+ $override = true;
+ }
+
+ if (
+ $override &&
+ !in_array($request->getMethod(), ['PUT', 'POST', 'DELETE', 'PATCH'], true)
+ ) {
+ $parsedBody = [];
+ }
+
+ return $request->withParsedBody($parsedBody);
+ }
+
+ /**
+ * Process uploaded files and move things onto the parsed body.
+ *
+ * @param array $files Files array for normalization and merging in parsed body.
+ * @param \Cake\Http\ServerRequest $request Request instance.
+ * @return \Cake\Http\ServerRequest
+ */
+ protected static function marshalFiles(array $files, ServerRequest $request): ServerRequest
+ {
+ $files = normalizeUploadedFiles($files);
+ $request = $request->withUploadedFiles($files);
+
+ $parsedBody = $request->getParsedBody();
+ if (!is_array($parsedBody)) {
+ return $request;
+ }
+
+ if (Configure::read('App.uploadedFilesAsObjects', true)) {
+ $parsedBody = Hash::merge($parsedBody, $files);
+ } else {
+ // Make a flat map that can be inserted into body for BC.
+ $fileMap = Hash::flatten($files);
+ foreach ($fileMap as $key => $file) {
+ $error = $file->getError();
+ $tmpName = '';
+ if ($error === UPLOAD_ERR_OK) {
+ $tmpName = $file->getStream()->getMetadata('uri');
+ }
+ $parsedBody = Hash::insert($parsedBody, (string)$key, [
+ 'tmp_name' => $tmpName,
+ 'error' => $error,
+ 'name' => $file->getClientFilename(),
+ 'type' => $file->getClientMediaType(),
+ 'size' => $file->getSize(),
+ ]);
+ }
+ }
+
+ return $request->withParsedBody($parsedBody);
+ }
+
/**
* Create a new server request.
*
diff --git a/app/vendor/cakephp/cakephp/src/Http/Session.php b/app/vendor/cakephp/cakephp/src/Http/Session.php
index 77cb73845..99792b9b2 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Session.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Session.php
@@ -25,7 +25,7 @@
/**
* This class is a wrapper for the native PHP session functions. It provides
* several defaults for the most common session configuration
- * via external handlers and helps with using session in cli without any warnings.
+ * via external handlers and helps with using session in CLI without any warnings.
*
* Sessions can be created from the defaults using `Session::create()` or you can get
* an instance of a new session by just instantiating this class and passing the complete
@@ -430,31 +430,48 @@ public function check(?string $name = null): bool
* Returns given session variable, or all of them, if no parameters given.
*
* @param string|null $name The name of the session variable (or a path as sent to Hash.extract)
- * @return string|array|null The value of the session variable, null if session not available,
- * session not started, or provided name not found in the session.
+ * @param mixed $default The return value when the path does not exist
+ * @return mixed|null The value of the session variable, or default value if a session
+ * is not available, can't be started, or provided $name is not found in the session.
*/
- public function read(?string $name = null)
+ public function read(?string $name = null, $default = null)
{
if ($this->_hasSession() && !$this->started()) {
$this->start();
}
if (!isset($_SESSION)) {
- return null;
+ return $default;
}
if ($name === null) {
return $_SESSION ?: [];
}
- return Hash::get($_SESSION, $name);
+ return Hash::get($_SESSION, $name, $default);
+ }
+
+ /**
+ * Returns given session variable, or throws Exception if not found.
+ *
+ * @param string $name The name of the session variable (or a path as sent to Hash.extract)
+ * @throws \RuntimeException
+ * @return mixed|null
+ */
+ public function readOrFail(string $name)
+ {
+ if (!$this->check($name)) {
+ throw new RuntimeException(sprintf('Expected session key "%s" not found.', $name));
+ }
+
+ return $this->read($name);
}
/**
* Reads and deletes a variable from session.
*
* @param string $name The key to read and remove (or a path as sent to Hash.extract).
- * @return mixed The value of the session variable, null if session not available,
+ * @return mixed|null The value of the session variable, null if session not available,
* session not started, or provided name not found in the session.
*/
public function consume(string $name)
@@ -492,6 +509,7 @@ public function write($name, $value = null): void
$data = Hash::insert($data, $key, $val);
}
+ /** @psalm-suppress PossiblyNullArgument */
$this->_overwrite($_SESSION, $data);
}
diff --git a/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php b/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php
index 1b4366cdb..55790ce1c 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php
@@ -104,7 +104,7 @@ public function write($id, $data): bool
return false;
}
- return (bool)Cache::write($id, $data, $this->_options['config']);
+ return Cache::write($id, $data, $this->_options['config']);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php b/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php
index 42b3e9fc4..5dc76644a 100644
--- a/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php
+++ b/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php
@@ -18,7 +18,6 @@
*/
namespace Cake\Http\Session;
-use Cake\ORM\Entity;
use Cake\ORM\Locator\LocatorAwareTrait;
use SessionHandlerInterface;
@@ -58,7 +57,7 @@ public function __construct(array $config = [])
$tableLocator = $this->getTableLocator();
if (empty($config['model'])) {
- $config = $tableLocator->exists('Sessions') ? [] : ['table' => 'sessions'];
+ $config = $tableLocator->exists('Sessions') ? [] : ['table' => 'sessions', 'allowFallbackClass' => true];
$this->_table = $tableLocator->get('Sessions', $config);
} else {
$this->_table = $tableLocator->get($config['model']);
@@ -150,14 +149,16 @@ public function write($id, $data): bool
if (!$id) {
return false;
}
- $expires = time() + $this->_timeout;
- $record = compact('data', 'expires');
+
/** @var string $pkField */
$pkField = $this->_table->getPrimaryKey();
- $record[$pkField] = $id;
- $result = $this->_table->save(new Entity($record));
+ $session = $this->_table->newEntity([
+ $pkField => $id,
+ 'data' => $data,
+ 'expires' => time() + $this->_timeout,
+ ], ['accessibleFields' => [$pkField => true]]);
- return (bool)$result;
+ return (bool)$this->_table->save($session);
}
/**
@@ -170,10 +171,7 @@ public function destroy($id): bool
{
/** @var string $pkField */
$pkField = $this->_table->getPrimaryKey();
- $this->_table->delete(new Entity(
- [$pkField => $id],
- ['markNew' => false]
- ));
+ $this->_table->deleteAll([$pkField => $id]);
return true;
}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php b/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php
index 80693adc5..27ccd6cf2 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php
@@ -16,7 +16,6 @@
*/
namespace Cake\I18n;
-use Aura\Intl\Package;
use RuntimeException;
/**
@@ -47,7 +46,7 @@ public function __construct(array $loaders)
* Executes this object returning the translations package as configured in
* the chain.
*
- * @return \Aura\Intl\Package
+ * @return \Cake\I18n\Package
* @throws \RuntimeException if any of the loaders in the chain is not a valid callable
*/
public function __invoke(): Package
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Date.php b/app/vendor/cakephp/cakephp/src/I18n/Date.php
index 9ae44c786..450018d00 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/Date.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Date.php
@@ -40,10 +40,10 @@ class Date extends MutableDate implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\DateFormatTrait::i18nFormat()
*/
- protected static $_toStringFormat = [IntlDateFormatter::SHORT, -1];
+ protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE];
/**
* The format to use when converting this object to JSON.
@@ -56,7 +56,7 @@ class Date extends MutableDate implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]|\Closure
* @see \Cake\I18n\Time::i18nFormat()
*/
protected static $_jsonEncodeFormat = 'yyyy-MM-dd';
@@ -65,10 +65,10 @@ class Date extends MutableDate implements I18nDateTimeInterface
* The format to use when formatting a time using `Cake\I18n\Date::timeAgoInWords()`
* and the difference is more than `Cake\I18n\Date::$wordEnd`
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\DateFormatTrait::parseDate()
*/
- public static $wordFormat = [IntlDateFormatter::SHORT, -1];
+ public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE];
/**
* The format to use when formatting a time using `Cake\I18n\Date::nice()`
@@ -81,10 +81,10 @@ class Date extends MutableDate implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\DateFormatTrait::nice()
*/
- public static $niceFormat = [IntlDateFormatter::MEDIUM, -1];
+ public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE];
/**
* The format to use when formatting a time using `Date::timeAgoInWords()`
@@ -122,10 +122,10 @@ class Date extends MutableDate implements I18nDateTimeInterface
*
* Date instances lack time components, however due to limitations in PHP's
* internal Datetime object the time will always be set to 00:00:00, and the
- * timezone will always be UTC. Normalizing the timezone allows for
+ * timezone will always be the server local time. Normalizing the timezone allows for
* subtraction/addition to have deterministic results.
*
- * @param string|int|\DateTimeInterface|null $time Fixed or relative time
+ * @param string|int|\DateTime|\DateTimeImmutable|null $time Fixed or relative time
* @param \DateTimeZone|string|null $tz The timezone in which the date is taken.
* Ignored if `$time` is a DateTimeInterface instance.
*/
diff --git a/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php b/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php
index f50cc1a63..e08a17a04 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php
@@ -17,10 +17,10 @@
namespace Cake\I18n;
use Cake\Chronos\DifferenceFormatterInterface;
+use Closure;
use DateTime;
use DateTimeZone;
use IntlDateFormatter;
-use InvalidArgumentException;
use RuntimeException;
/**
@@ -35,10 +35,19 @@ trait DateFormatTrait
*
* Use static::setDefaultLocale() and static::getDefaultLocale() instead.
*
- * @var string
+ * @var string|null
*/
protected static $defaultLocale;
+ /**
+ * Whether lenient parsing is enabled for IntlDateFormatter.
+ *
+ * Defaults to true which is the default for IntlDateFormatter.
+ *
+ * @var bool
+ */
+ protected static $lenientParsing = true;
+
/**
* In-memory cache of date formatters
*
@@ -59,7 +68,9 @@ public static function getDefaultLocale(): ?string
/**
* Sets the default locale.
*
- * @param string|null $locale The default locale string to be used or null.
+ * Set to null to use IntlDateFormatter default.
+ *
+ * @param string|null $locale The default locale string to be used.
* @return void
*/
public static function setDefaultLocale(?string $locale = null): void
@@ -67,6 +78,36 @@ public static function setDefaultLocale(?string $locale = null): void
static::$defaultLocale = $locale;
}
+ /**
+ * Gets whether locale format parsing is set to lenient.
+ *
+ * @return bool
+ */
+ public static function lenientParsingEnabled(): bool
+ {
+ return static::$lenientParsing;
+ }
+
+ /**
+ * Enables lenient parsing for locale formats.
+ *
+ * @return void
+ */
+ public static function enableLenientParsing(): void
+ {
+ static::$lenientParsing = true;
+ }
+
+ /**
+ * Enables lenient parsing for locale formats.
+ *
+ * @return void
+ */
+ public static function disableLenientParsing(): void
+ {
+ static::$lenientParsing = false;
+ }
+
/**
* Returns a nicely formatted date string for this object.
*
@@ -90,7 +131,7 @@ public function nice($timezone = null, $locale = null): string
* It is possible to specify the desired format for the string to be displayed.
* You can either pass `IntlDateFormatter` constants as the first argument of this
* function, or pass a full ICU date formatting string as specified in the following
- * resource: http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details.
+ * resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax.
*
* Additional to `IntlDateFormatter` constants and date formatting string you can use
* Time::UNIX_TIMESTAMP_FORMAT to get a unix timestamp
@@ -129,7 +170,7 @@ public function nice($timezone = null, $locale = null): string
* You can control the default locale used through `Time::setDefaultLocale()`.
* If empty, the default will be taken from the `intl.default_locale` ini config.
*
- * @param string|int|array|null $format Format string.
+ * @param string|int|int[]|null $format Format string.
* @param string|\DateTimeZone|null $timezone Timezone string or DateTimeZone object
* in which the date will be displayed. The timezone stored for this object will not
* be changed.
@@ -161,18 +202,18 @@ public function i18nFormat($format = null, $timezone = null, $locale = null)
* Implements what IntlDateFormatter::formatObject() is in PHP 5.5+
*
* @param \DateTime|\DateTimeImmutable $date Date.
- * @param string|int|array $format Format.
+ * @param string|int|int[] $format Format.
* @param string|null $locale The locale name in which the date should be displayed.
* @return string
*/
protected function _formatObject($date, $format, ?string $locale): string
{
- $pattern = $timeFormat = null;
+ $pattern = '';
if (is_array($format)) {
[$dateFormat, $timeFormat] = $format;
- } elseif (is_numeric($format)) {
- $dateFormat = $format;
+ } elseif (is_int($format)) {
+ $dateFormat = $timeFormat = $format;
} else {
$dateFormat = $timeFormat = IntlDateFormatter::FULL;
$pattern = $format;
@@ -182,8 +223,12 @@ protected function _formatObject($date, $format, ?string $locale): string
$locale = I18n::getLocale();
}
- // phpcs:ignore Generic.Files.LineLength
- if (preg_match('/@calendar=(japanese|buddhist|chinese|persian|indian|islamic|hebrew|coptic|ethiopic)/', $locale)) {
+ if (
+ preg_match(
+ '/@calendar=(japanese|buddhist|chinese|persian|indian|islamic|hebrew|coptic|ethiopic)/',
+ $locale
+ )
+ ) {
$calendar = IntlDateFormatter::TRADITIONAL;
} else {
$calendar = IntlDateFormatter::GREGORIAN;
@@ -200,13 +245,13 @@ protected function _formatObject($date, $format, ?string $locale): string
}
$formatter = datefmt_create(
$locale,
- (int)$dateFormat,
- (int)$timeFormat,
+ $dateFormat,
+ $timeFormat,
$timezone,
$calendar,
- (string)$pattern
+ $pattern
);
- if ($formatter === false) {
+ if (empty($formatter)) {
throw new RuntimeException(
'Your version of icu does not support creating a date formatter for ' .
"`$key`. You should try to upgrade libicu and the intl extension."
@@ -248,7 +293,7 @@ public static function resetToStringFormat(): void
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @param string|array|int $format Format.
+ * @param string|int|int[] $format Format.
* @return void
*/
public static function setToStringFormat($format): void
@@ -257,19 +302,7 @@ public static function setToStringFormat($format): void
}
/**
- * Sets the default format used when converting this object to json
- *
- * The format should be either the formatting constants from IntlDateFormatter as
- * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern
- * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details)
- *
- * It is possible to provide an array of 2 constants. In this case, the first position
- * will be used for formatting the date part of the object and the second position
- * will be used to format the time part.
- *
- * @see \Cake\I18n\Time::i18nFormat()
- * @param string|array|int $format Format.
- * @return void
+ * @inheritDoc
*/
public static function setJsonEncodeFormat($format): void
{
@@ -295,42 +328,39 @@ public static function setJsonEncodeFormat($format): void
* ```
* $time = Time::parseDateTime('10/13/2013 12:54am');
* $time = Time::parseDateTime('13 Oct, 2013 13:54', 'dd MMM, y H:mm');
- * $time = Time::parseDateTime('10/10/2015', [IntlDateFormatter::SHORT, -1]);
+ * $time = Time::parseDateTime('10/10/2015', [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]);
* ```
*
* @param string $time The time string to parse.
- * @param string|int[]|null $format Any format accepted by IntlDateFormatter.
+ * @param string|int|int[]|null $format Any format accepted by IntlDateFormatter.
* @param \DateTimeZone|string|null $tz The timezone for the instance
* @return static|null
- * @throws \InvalidArgumentException If $format is a single int instead of array of constants
*/
public static function parseDateTime(string $time, $format = null, $tz = null)
{
- $dateFormat = $format ?: static::$_toStringFormat;
- $timeFormat = $pattern = null;
+ $format = $format ?? static::$_toStringFormat;
+ $pattern = '';
- if (is_array($dateFormat)) {
- [$newDateFormat, $timeFormat] = $dateFormat;
- $dateFormat = $newDateFormat;
+ if (is_array($format)) {
+ [$dateFormat, $timeFormat] = $format;
+ } elseif (is_int($format)) {
+ $dateFormat = $timeFormat = $format;
} else {
- $pattern = $dateFormat;
- $dateFormat = null;
-
- if (is_int($pattern)) {
- throw new InvalidArgumentException(
- 'If $format is an IntlDateFormatter constant, must be an array.'
- );
- }
+ $dateFormat = $timeFormat = IntlDateFormatter::FULL;
+ $pattern = $format;
}
+ $locale = static::$defaultLocale ?? I18n::getLocale();
$formatter = datefmt_create(
- (string)static::$defaultLocale,
- $dateFormat ?? 0,
- $timeFormat ?? 0,
+ $locale,
+ $dateFormat,
+ $timeFormat,
$tz,
null,
- $pattern ?? ''
+ $pattern
);
+ $formatter->setLenient(static::$lenientParsing);
+
$time = $formatter->parse($time);
if ($time !== false) {
$dateTime = new DateTime('@' . $time);
@@ -371,7 +401,7 @@ public static function parseDateTime(string $time, $format = null, $tz = null)
public static function parseDate(string $date, $format = null)
{
if (is_int($format)) {
- $format = [$format, -1];
+ $format = [$format, IntlDateFormatter::NONE];
}
$format = $format ?: static::$wordFormat;
@@ -401,20 +431,24 @@ public static function parseDate(string $date, $format = null)
public static function parseTime(string $time, $format = null)
{
if (is_int($format)) {
- $format = [-1, $format];
+ $format = [IntlDateFormatter::NONE, $format];
}
- $format = $format ?: [-1, IntlDateFormatter::SHORT];
+ $format = $format ?: [IntlDateFormatter::NONE, IntlDateFormatter::SHORT];
return static::parseDateTime($time, $format);
}
/**
- * Returns a string that should be serialized when converting this object to json
+ * Returns a string that should be serialized when converting this object to JSON
*
* @return string|int
*/
public function jsonSerialize()
{
+ if (static::$_jsonEncodeFormat instanceof Closure) {
+ return call_user_func(static::$_jsonEncodeFormat, $this);
+ }
+
return $this->i18nFormat(static::$_jsonEncodeFormat);
}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Exception/I18nException.php b/app/vendor/cakephp/cakephp/src/I18n/Exception/I18nException.php
new file mode 100644
index 000000000..3b3819e3e
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/I18n/Exception/I18nException.php
@@ -0,0 +1,27 @@
+_formatMessage($locale, $message, $vars);
- }
-
- /**
- * Does the actual formatting using the MessageFormatter class
- *
- * @param string $locale The locale in which the message is presented.
- * @param string $message The message to be translated
- * @param array $vars The list of values to interpolate in the message
- * @return string The formatted message
- * @throws \Aura\Intl\Exception\CannotInstantiateFormatter if any error occurred
- * while parsing the message
- * @throws \Aura\Intl\Exception\CannotFormat If any error related to the passed
- * variables is found
- */
- protected function _formatMessage(string $locale, string $message, array $vars): string
+ public function format(string $locale, string $message, array $tokenValues): string
{
if ($message === '') {
return $message;
}
- // Using procedural style as it showed twice as fast as
- // its counterpart in PHP 5.5
- $result = MessageFormatter::formatMessage($locale, $message, $vars);
+ $formatter = new MessageFormatter($locale, $message);
+ $result = $formatter->format($tokenValues);
if ($result === false) {
- // The user might be interested in what went wrong, so replay the
- // previous action using the object oriented style to figure out
- $formatter = new MessageFormatter($locale, $message);
- $formatter->format($vars);
- throw new CannotFormat($formatter->getErrorMessage(), $formatter->getErrorCode());
+ throw new I18nException($formatter->getErrorMessage(), $formatter->getErrorCode());
}
return $result;
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Formatter/SprintfFormatter.php b/app/vendor/cakephp/cakephp/src/I18n/Formatter/SprintfFormatter.php
index 2609ce023..84c5d7bdd 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/Formatter/SprintfFormatter.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Formatter/SprintfFormatter.php
@@ -16,7 +16,7 @@
*/
namespace Cake\I18n\Formatter;
-use Aura\Intl\FormatterInterface;
+use Cake\I18n\FormatterInterface;
/**
* A formatter that will interpolate variables using sprintf and
@@ -30,13 +30,11 @@ class SprintfFormatter implements FormatterInterface
*
* @param string $locale The locale in which the message is presented.
* @param string $message The message to be translated
- * @param array $vars The list of values to interpolate in the message
+ * @param array $tokenValues The list of values to interpolate in the message
* @return string The formatted message
*/
- public function format($locale, $message, array $vars): string
+ public function format(string $locale, string $message, array $tokenValues): string
{
- unset($vars['_singular']);
-
- return vsprintf($message, $vars);
+ return vsprintf($message, $tokenValues);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/FormatterInterface.php b/app/vendor/cakephp/cakephp/src/I18n/FormatterInterface.php
new file mode 100644
index 000000000..0f8311670
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/I18n/FormatterInterface.php
@@ -0,0 +1,35 @@
+ $spec) {
+ $this->set($name, $spec);
+ }
+ }
+
+ /**
+ * Sets a formatter into the registry by name.
+ *
+ * @param string $name The formatter name.
+ * @param string $className A FQCN for a formatter.
+ * @return void
+ */
+ public function set(string $name, string $className): void
+ {
+ $this->registry[$name] = $className;
+ $this->converted[$name] = false;
+ }
+
+ /**
+ * Gets a formatter from the registry by name.
+ *
+ * @param string $name The formatter to retrieve.
+ * @return \Cake\I18n\FormatterInterface A formatter object.
+ * @throws \Cake\I18n\Exception\I18nException
+ */
+ public function get(string $name): FormatterInterface
+ {
+ if (!isset($this->registry[$name])) {
+ throw new I18nException("Formatter named `{$name}` has not been registered");
+ }
+
+ if (!$this->converted[$name]) {
+ $this->registry[$name] = new $this->registry[$name]();
+ $this->converted[$name] = true;
+ }
+
+ return $this->registry[$name];
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php b/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php
index a87f0132c..a4602bfdd 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php
@@ -42,7 +42,7 @@ class FrozenDate extends ChronosDate implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\DateFormatTrait::i18nFormat()
*/
protected static $_toStringFormat = [IntlDateFormatter::SHORT, -1];
@@ -58,7 +58,7 @@ class FrozenDate extends ChronosDate implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]|\Closure
* @see \Cake\I18n\Time::i18nFormat()
*/
protected static $_jsonEncodeFormat = 'yyyy-MM-dd';
@@ -67,10 +67,10 @@ class FrozenDate extends ChronosDate implements I18nDateTimeInterface
* The format to use when formatting a time using `Cake\I18n\Date::timeAgoInWords()`
* and the difference is more than `Cake\I18n\Date::$wordEnd`
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\DateFormatTrait::parseDate()
*/
- public static $wordFormat = [IntlDateFormatter::SHORT, -1];
+ public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE];
/**
* The format to use when formatting a time using `Cake\I18n\Date::nice()`
@@ -83,10 +83,10 @@ class FrozenDate extends ChronosDate implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\DateFormatTrait::nice()
*/
- public static $niceFormat = [IntlDateFormatter::MEDIUM, -1];
+ public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE];
/**
* The format to use when formatting a time using `Date::timeAgoInWords()`
@@ -124,10 +124,10 @@ class FrozenDate extends ChronosDate implements I18nDateTimeInterface
*
* Date instances lack time components, however due to limitations in PHP's
* internal Datetime object the time will always be set to 00:00:00, and the
- * timezone will always be UTC. Normalizing the timezone allows for
+ * timezone will always be the server local time. Normalizing the timezone allows for
* subtraction/addition to have deterministic results.
*
- * @param string|int|\DateTimeInterface|null $time Fixed or relative time
+ * @param string|int|\DateTime|\DateTimeImmutable|null $time Fixed or relative time
* @param \DateTimeZone|string|null $tz The timezone in which the date is taken.
* Ignored if `$time` is a DateTimeInterface instance.
*/
diff --git a/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php b/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php
index bd342a5eb..2bf669c0e 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php
@@ -43,7 +43,7 @@ class FrozenTime extends Chronos implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\FrozenTime::i18nFormat()
*/
protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::SHORT];
@@ -59,7 +59,7 @@ class FrozenTime extends Chronos implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]|\Closure
* @see \Cake\I18n\Time::i18nFormat()
*/
protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH':'mm':'ssxxx";
@@ -75,7 +75,7 @@ class FrozenTime extends Chronos implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\FrozenTime::nice()
*/
public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT];
@@ -84,10 +84,10 @@ class FrozenTime extends Chronos implements I18nDateTimeInterface
* The format to use when formatting a time using `Cake\I18n\FrozenTime::timeAgoInWords()`
* and the difference is more than `Cake\I18n\FrozenTime::$wordEnd`
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\FrozenTime::timeAgoInWords()
*/
- public static $wordFormat = [IntlDateFormatter::SHORT, -1];
+ public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE];
/**
* The format to use when formatting a time using `Time::timeAgoInWords()`
diff --git a/app/vendor/cakephp/cakephp/src/I18n/I18n.php b/app/vendor/cakephp/cakephp/src/I18n/I18n.php
index b1ae94ef9..2e5c539d7 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/I18n.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/I18n.php
@@ -16,11 +16,8 @@
*/
namespace Cake\I18n;
-use Aura\Intl\FormatterLocator;
-use Aura\Intl\PackageLocator;
-use Aura\Intl\TranslatorInterface;
use Cake\Cache\Cache;
-use Cake\Core\Exception\Exception;
+use Cake\I18n\Exception\I18nException;
use Cake\I18n\Formatter\IcuFormatter;
use Cake\I18n\Formatter\SprintfFormatter;
use Locale;
@@ -47,7 +44,7 @@ class I18n
/**
* The environment default locale
*
- * @var string
+ * @var string|null
*/
protected static $_defaultLocale;
@@ -67,14 +64,9 @@ public static function translators(): TranslatorRegistry
static::$_collection = new TranslatorRegistry(
new PackageLocator(),
new FormatterLocator([
- 'sprintf' => function () {
- return new SprintfFormatter();
- },
- 'default' => function () {
- return new IcuFormatter();
- },
+ 'default' => IcuFormatter::class,
+ 'sprintf' => SprintfFormatter::class,
]),
- new TranslatorFactory(),
static::getLocale()
);
@@ -95,7 +87,7 @@ public static function translators(): TranslatorRegistry
*
* ```
* I18n::setTranslator('default', function () {
- * $package = new \Aura\Intl\Package();
+ * $package = new \Cake\I18n\Package();
* $package->setMessages([
* 'Cake' => 'Gâteau'
* ]);
@@ -141,10 +133,10 @@ public static function setTranslator(string $name, callable $loader, ?string $lo
*
* @param string $name The domain of the translation messages.
* @param string|null $locale The locale for the translator.
- * @return \Aura\Intl\TranslatorInterface The configured translator.
- * @throws \Aura\Intl\Exception
+ * @return \Cake\I18n\Translator The configured translator.
+ * @throws \Cake\I18n\Exception\I18nException
*/
- public static function getTranslator(string $name = 'default', ?string $locale = null): TranslatorInterface
+ public static function getTranslator(string $name = 'default', ?string $locale = null): Translator
{
$translators = static::translators();
@@ -155,7 +147,7 @@ public static function getTranslator(string $name = 'default', ?string $locale =
$translator = $translators->get($name);
if ($translator === null) {
- throw new Exception(sprintf(
+ throw new I18nException(sprintf(
'Translator for domain "%s" could not be found.',
$name
));
@@ -180,7 +172,7 @@ public static function getTranslator(string $name = 'default', ?string $locale =
*
* Loader objects will receive two arguments: The domain name that needs to be
* built, and the locale that is requested. These objects can assemble the messages
- * from any source, but must return an `Aura\Intl\Package` object.
+ * from any source, but must return an `Cake\I18n\Package` object.
*
* ### Example:
*
@@ -196,7 +188,7 @@ public static function getTranslator(string $name = 'default', ?string $locale =
* You can also assemble the package object yourself:
*
* ```
- * use Aura\Intl\Package;
+ * use Cake\I18n\Package;
* I18n::config('my_domain', function ($name, $locale) {
* $package = new Package('default');
* $messages = (...); // Fetch messages for locale from external service.
diff --git a/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php b/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php
index 02822624a..88b5060ce 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php
@@ -122,15 +122,27 @@ public static function resetToStringFormat(): void;
/**
* Sets the default format used when type converting instances of this type to string
*
- * @param string|array|int $format Format.
+ * @param string|int|int[] $format Format.
* @return void
*/
public static function setToStringFormat($format): void;
/**
- * Sets the default format used when converting this object to json
+ * Sets the default format used when converting this object to JSON
*
- * @param string|array|int $format Format.
+ * The format should be either the formatting constants from IntlDateFormatter as
+ * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern
+ * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details)
+ *
+ * It is possible to provide an array of 2 constants. In this case, the first position
+ * will be used for formatting the date part of the object and the second position
+ * will be used to format the time part.
+ *
+ * Alternatively, the format can provide a callback. In this case, the callback
+ * can receive this datetime object and return a formatted string.
+ *
+ * @see \Cake\I18n\Time::i18nFormat()
+ * @param string|array|int|\Closure $format Format.
* @return void
*/
public static function setJsonEncodeFormat($format): void;
diff --git a/app/vendor/cakephp/cakephp/src/I18n/LICENSE.txt b/app/vendor/cakephp/cakephp/src/I18n/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/I18n/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php b/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php
index 662625b00..564077d6c 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php
@@ -16,7 +16,6 @@
*/
namespace Cake\I18n;
-use Aura\Intl\Package;
use Cake\Core\App;
use Cake\Core\Plugin;
use Cake\Utility\Inflector;
@@ -102,7 +101,7 @@ public function __construct(string $name, string $locale, string $extension = 'p
* Loads the translation file and parses it. Returns an instance of a translations
* package containing the messages loaded from the file.
*
- * @return \Aura\Intl\Package|false
+ * @return \Cake\I18n\Package|false
* @throws \RuntimeException if no file parser class could be found for the specified
* file extension.
*/
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Number.php b/app/vendor/cakephp/cakephp/src/I18n/Number.php
index 33d70c814..03f53f996 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/Number.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Number.php
@@ -74,7 +74,7 @@ class Number
/**
* Default currency format used by Number::currency()
*
- * @var string
+ * @var string|null
*/
protected static $_defaultCurrencyFormat;
@@ -141,7 +141,7 @@ public static function toPercentage($value, int $precision = 2, array $options =
{
$options += ['multiply' => false, 'type' => NumberFormatter::PERCENT];
if (!$options['multiply']) {
- $value /= 100;
+ $value = (float)$value / 100;
}
return static::precision($value, $precision, $options);
@@ -259,11 +259,7 @@ public static function currency($value, ?string $currency = null, array $options
$before = $options['before'] ?? '';
$after = $options['after'] ?? '';
- if ($currency) {
- $value = $formatter->formatCurrency($value, $currency);
- } else {
- $formatter->format($value, NumberFormatter::TYPE_CURRENCY);
- }
+ $value = $formatter->formatCurrency($value, $currency);
return $before . $value . $after;
}
@@ -404,14 +400,17 @@ public static function formatter(array $options = []): NumberFormatter
/** @var \NumberFormatter $formatter */
$formatter = static::$_formatters[$locale][$type];
- $options = array_intersect_key($options, [
- 'places' => null,
- 'precision' => null,
- 'pattern' => null,
- 'useIntlCode' => null,
- ]);
- if (empty($options)) {
- return $formatter;
+ // PHP 8.0.0 - 8.0.6 throws an exception when cloning NumberFormatter after a failed parse
+ if (version_compare(PHP_VERSION, '8.0.6', '>') || version_compare(PHP_VERSION, '8.0.0', '<')) {
+ $options = array_intersect_key($options, [
+ 'places' => null,
+ 'precision' => null,
+ 'pattern' => null,
+ 'useIntlCode' => null,
+ ]);
+ if (empty($options)) {
+ return $formatter;
+ }
}
$formatter = clone $formatter;
diff --git a/app/vendor/aura/intl/src/Package.php b/app/vendor/cakephp/cakephp/src/I18n/Package.php
similarity index 52%
rename from app/vendor/aura/intl/src/Package.php
rename to app/vendor/cakephp/cakephp/src/I18n/Package.php
index b714e8325..680e13bbd 100644
--- a/app/vendor/aura/intl/src/Package.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Package.php
@@ -1,140 +1,117 @@
*/
- protected $messages;
+ protected $messages = [];
/**
- *
* The name of a fallback package to use when a message key does not
* exist.
*
- * @var string
- *
+ * @var string|null
*/
protected $fallback;
/**
- *
* The name of the formatter to use when formatting translated messages.
*
* @var string
- *
*/
protected $formatter;
/**
- *
* Constructor.
*
* @param string $formatter The name of the formatter to use.
- *
- * @param string $fallback The name of the fallback package to use.
- *
- * @param array $messages The messages in this package.
- *
+ * @param string|null $fallback The name of the fallback package to use.
+ * @param array $messages The messages in this package.
*/
public function __construct(
- $formatter = 'basic',
- $fallback = null,
+ string $formatter = 'default',
+ ?string $fallback = null,
array $messages = []
) {
$this->formatter = $formatter;
- $this->fallback = $fallback;
- $this->messages = $messages;
+ $this->fallback = $fallback;
+ $this->messages = $messages;
}
/**
- *
* Sets the messages for this package.
*
- * @param array $messages The messages for this package.
- *
+ * @param array $messages The messages for this package.
* @return void
- *
*/
- public function setMessages(array $messages)
+ public function setMessages(array $messages): void
{
$this->messages = $messages;
}
/**
- *
* Adds one message for this package.
*
* @param string $key the key of the message
- *
- * @param string $message the actual message
- *
+ * @param string|array $message the actual message
* @return void
- *
*/
- public function addMessage($key, $message)
+ public function addMessage(string $key, $message): void
{
$this->messages[$key] = $message;
}
/**
- *
* Adds new messages for this package.
*
- * @param array $messages The messages to add in this package.
- *
+ * @param array $messages The messages to add in this package.
* @return void
- *
*/
- public function addMessages($messages)
+ public function addMessages(array $messages): void
{
$this->messages = array_merge($this->messages, $messages);
}
/**
- *
* Gets the messages for this package.
*
- * @return array
- *
+ * @return array
*/
- public function getMessages()
+ public function getMessages(): array
{
return $this->messages;
}
-
/**
- *
* Gets the message of the given key for this package.
*
* @param string $key the key of the message to return
- *
- * @return mixed The message translation string, or false if not found.
- *
+ * @return string|array|false The message translation, or false if not found.
*/
- public function getMessage($key)
+ public function getMessage(string $key)
{
if (isset($this->messages[$key])) {
return $this->messages[$key];
@@ -144,53 +121,43 @@ public function getMessage($key)
}
/**
- *
* Sets the formatter name for this package.
*
* @param string $formatter The formatter name for this package.
- *
* @return void
- *
*/
- public function setFormatter($formatter)
+ public function setFormatter(string $formatter): void
{
$this->formatter = $formatter;
}
/**
- *
* Gets the formatter name for this package.
*
* @return string
- *
*/
- public function getFormatter()
+ public function getFormatter(): string
{
return $this->formatter;
}
/**
- *
* Sets the fallback package name.
*
- * @param string $fallback The fallback package name.
- *
+ * @param string|null $fallback The fallback package name.
* @return void
- *
*/
- public function setFallback($fallback)
+ public function setFallback(?string $fallback): void
{
$this->fallback = $fallback;
}
/**
- *
* Gets the fallback package name.
*
- * @return string
- *
+ * @return string|null
*/
- public function getFallback()
+ public function getFallback(): ?string
{
return $this->fallback;
}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php b/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php
new file mode 100644
index 000000000..6cc3f8bcf
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php
@@ -0,0 +1,110 @@
+ $locales) {
+ foreach ($locales as $locale => $spec) {
+ $this->set($name, $locale, $spec);
+ }
+ }
+ }
+
+ /**
+ * Sets a Package loader.
+ *
+ * @param string $name The package name.
+ * @param string $locale The locale for the package.
+ * @param callable|\Cake\I18n\Package $spec A callable that returns a package or Package instance.
+ * @return void
+ */
+ public function set(string $name, string $locale, $spec): void
+ {
+ $this->registry[$name][$locale] = $spec;
+ $this->converted[$name][$locale] = $spec instanceof Package;
+ }
+
+ /**
+ * Gets a Package object.
+ *
+ * @param string $name The package name.
+ * @param string $locale The locale for the package.
+ * @return \Cake\I18n\Package
+ */
+ public function get(string $name, string $locale): Package
+ {
+ if (!isset($this->registry[$name][$locale])) {
+ throw new I18nException("Package '$name' with locale '$locale' is not registered.");
+ }
+
+ if (!$this->converted[$name][$locale]) {
+ $func = $this->registry[$name][$locale];
+ $this->registry[$name][$locale] = $func();
+ $this->converted[$name][$locale] = true;
+ }
+
+ return $this->registry[$name][$locale];
+ }
+
+ /**
+ * Check if a Package object for given name and locale exists in registry.
+ *
+ * @param string $name The package name.
+ * @param string $locale The locale for the package.
+ * @return bool
+ */
+ public function has(string $name, string $locale): bool
+ {
+ return isset($this->registry[$name][$locale]);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php b/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php
index 7285ad224..69633363a 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php
@@ -121,11 +121,11 @@ public function parse($file): array
if ($pluralId !== null || strpos($translated, "\000") !== false) {
$translated = explode("\000", $translated);
- $plurals = $pluralId !== null ? array_map('stripcslashes', $translated) : null;
+ $plurals = $pluralId !== null ? $translated : null;
$translated = $translated[0];
}
- $singular = stripcslashes($translated);
+ $singular = $translated;
if ($context !== null) {
$messages[$singularId]['_context'][$context] = $singular;
if ($pluralId !== null) {
diff --git a/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php b/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php
index bad31ab85..b52514fe1 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php
@@ -16,7 +16,7 @@
*/
namespace Cake\I18n;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Utility class used to determine the plural number to be used for a variable
@@ -200,6 +200,6 @@ public static function calculate(string $locale, $n): int
return $n % 10 !== 1 || $n % 100 === 11 ? 1 : 0;
}
- throw new Exception('Unable to find plural rule number.');
+ throw new CakeException('Unable to find plural rule number.');
}
}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/README.md b/app/vendor/cakephp/cakephp/src/I18n/README.md
index 74df408b8..e7724b8fe 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/README.md
+++ b/app/vendor/cakephp/cakephp/src/I18n/README.md
@@ -50,7 +50,7 @@ Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37
```php
use Cake\I18n\I18n;
-use Aura\Intl\Package;
+use Cake\I18n\Package;
I18n::translator('animals', 'fr_FR', function () {
$package = new Package(
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Time.php b/app/vendor/cakephp/cakephp/src/I18n/Time.php
index 2705dbcdd..fdafc256e 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/Time.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Time.php
@@ -41,7 +41,7 @@ class Time extends MutableDateTime implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\Time::i18nFormat()
*/
protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::SHORT];
@@ -57,7 +57,7 @@ class Time extends MutableDateTime implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]|\Closure
* @see \Cake\I18n\Time::i18nFormat()
*/
protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH':'mm':'ssxxx";
@@ -73,7 +73,7 @@ class Time extends MutableDateTime implements I18nDateTimeInterface
* will be used for formatting the date part of the object and the second position
* will be used to format the time part.
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\Time::nice()
*/
public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT];
@@ -82,10 +82,10 @@ class Time extends MutableDateTime implements I18nDateTimeInterface
* The format to use when formatting a time using `Cake\I18n\Time::timeAgoInWords()`
* and the difference is more than `Cake\I18n\Time::$wordEnd`
*
- * @var string|array|int
+ * @var string|int|int[]
* @see \Cake\I18n\Time::timeAgoInWords()
*/
- public static $wordFormat = [IntlDateFormatter::SHORT, -1];
+ public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE];
/**
* The format to use when formatting a time using `Time::timeAgoInWords()`
diff --git a/app/vendor/cakephp/cakephp/src/I18n/Translator.php b/app/vendor/cakephp/cakephp/src/I18n/Translator.php
index 50ecd85a5..58f771fbc 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/Translator.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/Translator.php
@@ -7,27 +7,100 @@
*
* 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
+ * Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
+ * @since 3.3.12
+ * @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\I18n;
-use Aura\Intl\Translator as BaseTranslator;
-
/**
- * Provides missing message behavior for CakePHP internal message formats.
+ * Translator to translate the message.
*
* @internal
*/
-class Translator extends BaseTranslator
+class Translator
{
/**
* @var string
*/
public const PLURAL_PREFIX = 'p:';
+ /**
+ * A fallback translator.
+ *
+ * @var \Cake\I18n\Translator|null
+ */
+ protected $fallback;
+
+ /**
+ * The formatter to use when translating messages.
+ *
+ * @var \Cake\I18n\FormatterInterface
+ */
+ protected $formatter;
+
+ /**
+ * The locale being used for translations.
+ *
+ * @var string
+ */
+ protected $locale;
+
+ /**
+ * The Package containing keys and translations.
+ *
+ * @var \Cake\I18n\Package
+ */
+ protected $package;
+
+ /**
+ * Constructor
+ *
+ * @param string $locale The locale being used.
+ * @param \Cake\I18n\Package $package The Package containing keys and translations.
+ * @param \Cake\I18n\FormatterInterface $formatter A message formatter.
+ * @param \Cake\I18n\Translator $fallback A fallback translator.
+ */
+ public function __construct(
+ string $locale,
+ Package $package,
+ FormatterInterface $formatter,
+ ?Translator $fallback = null
+ ) {
+ $this->locale = $locale;
+ $this->package = $package;
+ $this->formatter = $formatter;
+ $this->fallback = $fallback;
+ }
+
+ /**
+ * Gets the message translation by its key.
+ *
+ * @param string $key The message key.
+ * @return mixed The message translation string, or false if not found.
+ */
+ protected function getMessage(string $key)
+ {
+ $message = $this->package->getMessage($key);
+ if ($message) {
+ return $message;
+ }
+
+ if ($this->fallback) {
+ $message = $this->fallback->getMessage($key);
+ if ($message) {
+ $this->package->addMessage($key, $message);
+
+ return $message;
+ }
+ }
+
+ return false;
+ }
+
/**
* Translates the message formatting any placeholders
*
@@ -36,7 +109,7 @@ class Translator extends BaseTranslator
* message.
* @return string The translated message with tokens replaced.
*/
- public function translate($key, array $tokensValues = []): string
+ public function translate(string $key, array $tokensValues = []): string
{
if (isset($tokensValues['_count'])) {
$message = $this->getMessage(static::PLURAL_PREFIX . $key);
@@ -56,7 +129,7 @@ public function translate($key, array $tokensValues = []): string
}
// Check for missing/invalid context
- if (isset($message['_context'])) {
+ if (is_array($message) && isset($message['_context'])) {
$message = $this->resolveContext($key, $message, $tokensValues);
unset($tokensValues['_context']);
}
@@ -86,6 +159,8 @@ public function translate($key, array $tokensValues = []): string
$message = $key;
}
+ unset($tokensValues['_count'], $tokensValues['_singular']);
+
return $this->formatter->format($this->locale, $message, $tokensValues);
}
@@ -118,4 +193,14 @@ protected function resolveContext(string $key, array $message, array $vars)
return $message['_context'][$context];
}
+
+ /**
+ * Returns the translator package
+ *
+ * @return \Cake\I18n\Package
+ */
+ public function getPackage(): Package
+ {
+ return $this->package;
+ }
}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/TranslatorFactory.php b/app/vendor/cakephp/cakephp/src/I18n/TranslatorFactory.php
deleted file mode 100644
index 29478392e..000000000
--- a/app/vendor/cakephp/cakephp/src/I18n/TranslatorFactory.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
- */
- protected $class = Translator::class;
-
- /**
- * Returns a new Translator.
- *
- * @param string $locale The locale code for the translator.
- * @param \Aura\Intl\Package $package The Package containing keys and translations.
- * @param \Aura\Intl\FormatterInterface $formatter The formatter to use for interpolating token values.
- * @param \Aura\Intl\TranslatorInterface $fallback A fallback translator to use, if any.
- * @throws \Cake\Core\Exception\Exception If fallback class does not match Cake\I18n\Translator
- * @return \Cake\I18n\Translator
- */
- public function newInstance(
- $locale,
- Package $package,
- FormatterInterface $formatter,
- ?TranslatorInterface $fallback = null
- ) {
- $class = $this->class;
- if ($fallback !== null && get_class($fallback) !== $class) {
- throw new RuntimeException(sprintf(
- 'Translator fallback class %s does not match Cake\I18n\Translator, try clearing your _cake_core_ cache',
- get_class($fallback)
- ));
- }
-
- return new $class($locale, $package, $formatter, $fallback);
- }
-}
diff --git a/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php b/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php
index e00734528..181dde02b 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php
@@ -16,19 +16,48 @@
*/
namespace Cake\I18n;
-use Aura\Intl\Exception;
-use Aura\Intl\FormatterLocator;
-use Aura\Intl\PackageLocator;
-use Aura\Intl\TranslatorInterface;
-use Aura\Intl\TranslatorLocator;
-use Closure;
-
/**
* Constructs and stores instances of translators that can be
* retrieved by name and locale.
*/
-class TranslatorRegistry extends TranslatorLocator
+class TranslatorRegistry
{
+ /**
+ * Fallback loader name.
+ *
+ * @var string
+ */
+ public const FALLBACK_LOADER = '_fallback';
+
+ /**
+ * A registry to retain translator objects.
+ *
+ * @var array
+ * @psalm-var array>
+ */
+ protected $registry = [];
+
+ /**
+ * The current locale code.
+ *
+ * @var string
+ */
+ protected $locale;
+
+ /**
+ * A package locator.
+ *
+ * @var \Cake\I18n\PackageLocator
+ */
+ protected $packages;
+
+ /**
+ * A formatter locator.
+ *
+ * @var \Cake\I18n\FormatterLocator
+ */
+ protected $formatters;
+
/**
* A list of loader functions indexed by domain name. Loaders are
* callables that are invoked as a default for building translation
@@ -39,13 +68,6 @@ class TranslatorRegistry extends TranslatorLocator
*/
protected $_loaders = [];
- /**
- * Fallback loader name
- *
- * @var string
- */
- protected $_fallbackLoader = '_fallback';
-
/**
* The name of the default formatter to use for newly created
* translators from the fallback loader
@@ -72,40 +94,74 @@ class TranslatorRegistry extends TranslatorLocator
/**
* Constructor.
*
- * @param \Aura\Intl\PackageLocator $packages The package locator.
- * @param \Aura\Intl\FormatterLocator $formatters The formatter locator.
- * @param \Cake\I18n\TranslatorFactory $factory A translator factory to
- * create translator objects for the locale and package.
+ * @param \Cake\I18n\PackageLocator $packages The package locator.
+ * @param \Cake\I18n\FormatterLocator $formatters The formatter locator.
* @param string $locale The default locale code to use.
*/
public function __construct(
PackageLocator $packages,
FormatterLocator $formatters,
- TranslatorFactory $factory,
string $locale
) {
- parent::__construct($packages, $formatters, $factory, $locale);
+ $this->packages = $packages;
+ $this->formatters = $formatters;
+ $this->setLocale($locale);
- $this->registerLoader($this->_fallbackLoader, function ($name, $locale) {
- $chain = new ChainMessagesLoader([
+ $this->registerLoader(static::FALLBACK_LOADER, function ($name, $locale) {
+ $loader = new ChainMessagesLoader([
new MessagesFileLoader($name, $locale, 'mo'),
new MessagesFileLoader($name, $locale, 'po'),
]);
- // \Aura\Intl\Package by default uses formatter configured with key "basic".
- // and we want to make sure the cake domain always uses the default formatter
$formatter = $name === 'cake' ? 'default' : $this->_defaultFormatter;
- $chain = function () use ($formatter, $chain) {
- $package = $chain();
- $package->setFormatter($formatter);
-
- return $package;
- };
+ $package = $loader();
+ $package->setFormatter($formatter);
- return $chain;
+ return $package;
});
}
+ /**
+ * Sets the default locale code.
+ *
+ * @param string $locale The new locale code.
+ * @return void
+ */
+ public function setLocale(string $locale): void
+ {
+ $this->locale = $locale;
+ }
+
+ /**
+ * Returns the default locale code.
+ *
+ * @return string
+ */
+ public function getLocale(): string
+ {
+ return $this->locale;
+ }
+
+ /**
+ * Returns the translator packages
+ *
+ * @return \Cake\I18n\PackageLocator
+ */
+ public function getPackages(): PackageLocator
+ {
+ return $this->packages;
+ }
+
+ /**
+ * An object of type FormatterLocator
+ *
+ * @return \Cake\I18n\FormatterLocator
+ */
+ public function getFormatters(): FormatterLocator
+ {
+ return $this->formatters;
+ }
+
/**
* Sets the CacheEngine instance used to remember translators across
* requests.
@@ -121,20 +177,15 @@ public function setCacher($cacher): void
/**
* Gets a translator from the registry by package for a locale.
*
- * @param string|null $name The translator package to retrieve.
+ * @param string $name The translator package to retrieve.
* @param string|null $locale The locale to use; if empty, uses the default
* locale.
- * @return \Aura\Intl\TranslatorInterface|null A translator object.
- * @throws \Aura\Intl\Exception If no translator with that name could be found
+ * @return \Cake\I18n\Translator|null A translator object.
+ * @throws \Cake\I18n\Exception\I18nException If no translator with that name could be found
* for the given locale.
- * @psalm-suppress ImplementedReturnTypeMismatch
*/
- public function get($name, $locale = null)
+ public function get(string $name, ?string $locale = null): ?Translator
{
- if (!$name) {
- return null;
- }
-
if ($locale === null) {
$locale = $this->getLocale();
}
@@ -165,20 +216,43 @@ public function get($name, $locale = null)
* @param string $name The translator package to retrieve.
* @param string $locale The locale to use; if empty, uses the default
* locale.
- * @return \Aura\Intl\TranslatorInterface A translator object.
+ * @return \Cake\I18n\Translator A translator object.
*/
- protected function _getTranslator(string $name, string $locale): TranslatorInterface
+ protected function _getTranslator(string $name, string $locale): Translator
{
- try {
- return parent::get($name, $locale);
- } catch (Exception $e) {
+ if ($this->packages->has($name, $locale)) {
+ return $this->createInstance($name, $locale);
}
- if (!isset($this->_loaders[$name])) {
- $this->registerLoader($name, $this->_partialLoader());
+ if (isset($this->_loaders[$name])) {
+ $package = $this->_loaders[$name]($name, $locale);
+ } else {
+ $package = $this->_loaders[static::FALLBACK_LOADER]($name, $locale);
+ }
+
+ $package = $this->setFallbackPackage($name, $package);
+ $this->packages->set($name, $locale, $package);
+
+ return $this->createInstance($name, $locale);
+ }
+
+ /**
+ * Create translator instance.
+ *
+ * @param string $name The translator package to retrieve.
+ * @param string $locale The locale to use; if empty, uses the default locale.
+ * @return \Cake\I18n\Translator A translator object.
+ */
+ protected function createInstance(string $name, string $locale): Translator
+ {
+ $package = $this->packages->get($name, $locale);
+ $fallback = $package->getFallback();
+ if ($fallback !== null) {
+ $fallback = $this->get($fallback, $locale);
}
+ $formatter = $this->formatters->get($package->getFormatter());
- return $this->_getFromLoader($name, $locale);
+ return new Translator($locale, $package, $formatter, $fallback);
}
/**
@@ -227,54 +301,26 @@ public function useFallback(bool $enable = true): void
}
/**
- * Returns a new translator instance for the given name and locale
- * based of conventions.
- *
- * @param string $name The translation package name.
- * @param string $locale The locale to create the translator for.
- * @return \Aura\Intl\TranslatorInterface|\Closure
- */
- protected function _fallbackLoader(string $name, string $locale)
- {
- return $this->_loaders[$this->_fallbackLoader]($name, $locale);
- }
-
- /**
- * Returns a function that can be used as a loader for the registerLoaderMethod
+ * Set fallback domain for package.
*
- * @return \Closure
+ * @param string $name The name of the package.
+ * @param \Cake\I18n\Package $package Package instance
+ * @return \Cake\I18n\Package
*/
- protected function _partialLoader(): Closure
+ public function setFallbackPackage(string $name, Package $package): Package
{
- return function ($name, $locale) {
- return $this->_fallbackLoader($name, $locale);
- };
- }
-
- /**
- * Registers a new package by passing the register loaded function for the
- * package name.
- *
- * @param string $name The name of the translator package
- * @param string $locale The locale that should be built the package for
- * @return \Aura\Intl\TranslatorInterface A translator object.
- */
- protected function _getFromLoader(string $name, string $locale): TranslatorInterface
- {
- $loader = $this->_loaders[$name]($name, $locale);
- $package = $loader;
-
- if (!is_callable($loader)) {
- $loader = function () use ($package) {
- return $package;
- };
+ if ($package->getFallback()) {
+ return $package;
}
- $loader = $this->setLoaderFallback($name, $loader);
+ $fallbackDomain = null;
+ if ($this->_useFallback && $name !== 'default') {
+ $fallbackDomain = 'default';
+ }
- $this->packages->set($name, $locale, $loader);
+ $package->setFallback($fallbackDomain);
- return parent::get($name, $locale);
+ return $package;
}
/**
@@ -291,7 +337,7 @@ public function setLoaderFallback(string $name, callable $loader): callable
return $loader;
}
$loader = function () use ($loader, $fallbackDomain) {
- /** @var \Aura\Intl\Package $package */
+ /** @var \Cake\I18n\Package $package */
$package = $loader();
if (!$package->getFallback()) {
$package->setFallback($fallbackDomain);
diff --git a/app/vendor/cakephp/cakephp/src/I18n/composer.json b/app/vendor/cakephp/cakephp/src/I18n/composer.json
index 94976f05e..8b75144a9 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/composer.json
+++ b/app/vendor/cakephp/cakephp/src/I18n/composer.json
@@ -31,8 +31,7 @@
"php": ">=7.2.0",
"ext-intl": "*",
"cakephp/core": "^4.0",
- "cakephp/chronos": "^2.0.0",
- "aura/intl": "^3.0.0"
+ "cakephp/chronos": "^2.0.0"
},
"suggest": {
"cakephp/cache": "Require this if you want automatic caching of translators"
diff --git a/app/vendor/cakephp/cakephp/src/I18n/functions.php b/app/vendor/cakephp/cakephp/src/I18n/functions.php
index 5775595c9..45f17250f 100644
--- a/app/vendor/cakephp/cakephp/src/I18n/functions.php
+++ b/app/vendor/cakephp/cakephp/src/I18n/functions.php
@@ -17,6 +17,13 @@
use Cake\I18n\I18n;
+// Backwards compatibility alias for custom translation messages loaders which return a Package instance.
+// phpcs:disable
+if (!class_exists('Aura\Intl\Package')) {
+ class_alias('Cake\I18n\Package', 'Aura\Intl\Package');
+}
+// phpcs:enable
+
if (!function_exists('__')) {
/**
* Returns a translated string if one is found; Otherwise, the submitted message.
diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php
index 28f0a9146..03edaa0ed 100644
--- a/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php
+++ b/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php
@@ -16,8 +16,12 @@
*/
namespace Cake\Log\Engine;
+use ArrayObject;
use Cake\Core\InstanceConfigTrait;
+use DateTimeImmutable;
+use JsonSerializable;
use Psr\Log\AbstractLogger;
+use Serializable;
/**
* Base log engine class.
@@ -34,6 +38,7 @@ abstract class BaseLog extends AbstractLogger
protected $_defaultConfig = [
'levels' => [],
'scopes' => [],
+ 'dateFormat' => 'Y-m-d H:i:s',
];
/**
@@ -90,6 +95,83 @@ public function scopes()
*/
protected function _format(string $message, array $context = []): string
{
- return $message;
+ if (strpos($message, '{') === false && strpos($message, '}') === false) {
+ return $message;
+ }
+
+ preg_match_all(
+ '/(?getArrayCopy(), JSON_UNESCAPED_UNICODE);
+ continue;
+ }
+
+ if ($value instanceof Serializable) {
+ $replacements['{' . $key . '}'] = $value->serialize();
+ continue;
+ }
+
+ if (is_object($value)) {
+ if (method_exists($value, '__toString')) {
+ $replacements['{' . $key . '}'] = (string)$value;
+ continue;
+ }
+
+ if (method_exists($value, 'toArray')) {
+ $replacements['{' . $key . '}'] = json_encode($value->toArray(), JSON_UNESCAPED_UNICODE);
+ continue;
+ }
+
+ if (method_exists($value, '__debugInfo')) {
+ $replacements['{' . $key . '}'] = json_encode($value->__debugInfo(), JSON_UNESCAPED_UNICODE);
+ continue;
+ }
+ }
+
+ $replacements['{' . $key . '}'] = sprintf('[unhandled value of type %s]', getTypeName($value));
+ }
+
+ /** @psalm-suppress InvalidArgument */
+ return str_replace(array_keys($replacements), $replacements, $message);
+ }
+
+ /**
+ * Returns date formatted according to given `dateFormat` option format.
+ *
+ * This function affects `FileLog` or` ConsoleLog` datetime information format.
+ *
+ * @return string
+ */
+ protected function _getFormattedDate(): string
+ {
+ return (new DateTimeImmutable())->format($this->_config['dateFormat']);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php
index fbc7899b1..76e02ad88 100644
--- a/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php
+++ b/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php
@@ -34,6 +34,7 @@ class ConsoleLog extends BaseLog
'levels' => null,
'scopes' => [],
'outputAs' => null,
+ 'dateFormat' => 'Y-m-d H:i:s',
];
/**
@@ -52,6 +53,7 @@ class ConsoleLog extends BaseLog
* - `scopes` string or array, scopes the engine is interested in
* - `stream` the path to save logs on.
* - `outputAs` integer or ConsoleOutput::[RAW|PLAIN|COLOR]
+ * - `dateFormat` PHP date() format.
*
* @param array $config Options for the FileLog, see above.
* @throws \InvalidArgumentException
@@ -86,7 +88,7 @@ public function __construct(array $config = [])
public function log($level, $message, array $context = [])
{
$message = $this->_format($message, $context);
- $output = date('Y-m-d H:i:s') . ' ' . ucfirst($level) . ': ' . $message;
+ $output = $this->_getFormattedDate() . ' ' . ucfirst($level) . ': ' . $message;
$this->_output->write(sprintf('<%s>%s%s>', $level, $output, $level));
}
diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php
index e52fe7629..b844a8c8e 100644
--- a/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php
+++ b/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php
@@ -40,6 +40,7 @@ class FileLog extends BaseLog
* If value is 0, old versions are removed rather then rotated.
* - `mask` A mask is applied when log files are created. Left empty no chmod
* is made.
+ * - `dateFormat` PHP date() format.
*
* @var array
*/
@@ -52,6 +53,7 @@ class FileLog extends BaseLog
'rotate' => 10,
'size' => 10485760, // 10MB
'mask' => null,
+ 'dateFormat' => 'Y-m-d H:i:s',
];
/**
@@ -84,7 +86,7 @@ public function __construct(array $config = [])
{
parent::__construct($config);
- $this->_path = $this->getConfig('path', sys_get_temp_dir());
+ $this->_path = $this->getConfig('path', sys_get_temp_dir() . DIRECTORY_SEPARATOR);
if (Configure::read('debug') && !is_dir($this->_path)) {
mkdir($this->_path, 0775, true);
}
@@ -117,7 +119,7 @@ public function __construct(array $config = [])
public function log($level, $message, array $context = []): void
{
$message = $this->_format($message, $context);
- $output = date('Y-m-d H:i:s') . ' ' . ucfirst($level) . ': ' . $message . "\n";
+ $output = $this->_getFormattedDate() . ' ' . ucfirst($level) . ': ' . $message . "\n";
$filename = $this->_getFilename($level);
if ($this->_size) {
$this->_rotateFile($filename);
@@ -131,7 +133,7 @@ public function log($level, $message, array $context = []): void
return;
}
- $exists = file_exists($pathname);
+ $exists = is_file($pathname);
file_put_contents($pathname, $output, FILE_APPEND);
static $selfError = false;
@@ -182,7 +184,7 @@ protected function _rotateFile(string $filename): ?bool
clearstatcache(true, $filePath);
if (
- !file_exists($filePath) ||
+ !is_file($filePath) ||
filesize($filePath) < $this->_size
) {
return null;
diff --git a/app/vendor/cakephp/cakephp/src/Log/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Log/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Log/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Log/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php b/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php
index e90b4b634..92fd1ee2e 100644
--- a/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php
+++ b/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php
@@ -64,11 +64,11 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void
*
* @param string|\Psr\Log\LoggerInterface $class The classname or object to make.
* @param string $alias The alias of the object.
- * @param array $settings An array of settings to use for the logger.
+ * @param array $config An array of settings to use for the logger.
* @return \Psr\Log\LoggerInterface The constructed logger class.
* @throws \RuntimeException when an object doesn't implement the correct interface.
*/
- protected function _create($class, string $alias, array $settings): LoggerInterface
+ protected function _create($class, string $alias, array $config): LoggerInterface
{
if (is_callable($class)) {
$class = $class($alias);
@@ -80,7 +80,7 @@ protected function _create($class, string $alias, array $settings): LoggerInterf
if (!isset($instance)) {
/** @psalm-suppress UndefinedClass */
- $instance = new $class($settings);
+ $instance = new $class($config);
}
if ($instance instanceof LoggerInterface) {
diff --git a/app/vendor/cakephp/cakephp/src/Log/README.md b/app/vendor/cakephp/cakephp/src/Log/README.md
index 91016928f..5056f84e5 100644
--- a/app/vendor/cakephp/cakephp/src/Log/README.md
+++ b/app/vendor/cakephp/cakephp/src/Log/README.md
@@ -8,7 +8,7 @@ multiple logging backends using a simple interface. With the `Log` class it is
possible to send a single message to multiple logging backends at the same time
or just a subset of them based on the log level or context.
-By default you can use Files or Syslog as logging backends, but you can use any
+By default, you can use Files or Syslog as logging backends, but you can use any
object implementing `Psr\Log\LoggerInterface` as an engine for the `Log` class.
## Usage
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php
index 772e0c05c..89786f4d2 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Mailer;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\InstanceConfigTrait;
/**
@@ -36,7 +36,7 @@ abstract class AbstractTransport
/**
* Send mail
*
- * @param \Cake\Mailer\Message $message Email mesage.
+ * @param \Cake\Mailer\Message $message Email message.
* @return array
* @psalm-return array{headers: string, message: string}
*/
@@ -57,7 +57,7 @@ public function __construct(array $config = [])
*
* @param \Cake\Mailer\Message $message Message instance.
* @return void
- * @throws \Cake\Core\Exception\Exception If at least one of to, cc or bcc is not specified.
+ * @throws \Cake\Core\Exception\CakeException If at least one of to, cc or bcc is not specified.
*/
protected function checkRecipient(Message $message): void
{
@@ -66,7 +66,7 @@ protected function checkRecipient(Message $message): void
&& $message->getCc() === []
&& $message->getBcc() === []
) {
- throw new Exception(
+ throw new CakeException(
'You must specify at least one recipient.'
. ' Use one of `setTo`, `setCc` or `setBcc` to define a recipient.'
);
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Email.php b/app/vendor/cakephp/cakephp/src/Mailer/Email.php
index a22cb19e1..0362070e0 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Email.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Email.php
@@ -38,6 +38,7 @@
* Once made configuration profiles can be used to re-use across various email messages your
* application sends.
*
+ * @mixin \Cake\Mailer\Mailer
* @deprecated 4.0.0 This class will be removed in CakePHP 5.0, use {@link \Cake\Mailer\Mailer} instead.
*/
class Email implements JsonSerializable, Serializable
@@ -607,7 +608,7 @@ public function unserialize($data): void
}
/**
- * Proxy all static method calls (for methods provided by StaticConfigTrat) to Mailer.
+ * Proxy all static method calls (for methods provided by StaticConfigTrait) to Mailer.
*
* @param string $name Method name.
* @param array $arguments Method argument.
@@ -615,6 +616,6 @@ public function unserialize($data): void
*/
public static function __callStatic($name, $arguments)
{
- return call_user_func_array([Mailer::class, $name], $arguments);
+ return [Mailer::class, $name](...$arguments);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php
index 9c6d3f88f..eafd5be0a 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php
@@ -14,20 +14,15 @@
*/
namespace Cake\Mailer\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Missing Action exception - used when a mailer action cannot be found.
*/
-class MissingActionException extends Exception
+class MissingActionException extends CakeException
{
/**
* @inheritDoc
*/
protected $_messageTemplate = 'Mail %s::%s() could not be found, or is not accessible.';
-
- /**
- * @inheritDoc
- */
- protected $_defaultCode = 404;
}
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php
index 197982c00..4b3efcee3 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php
@@ -16,12 +16,12 @@
*/
namespace Cake\Mailer\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a mailer cannot be found.
*/
-class MissingMailerException extends Exception
+class MissingMailerException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php b/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php
index df9e5dbf1..efa8d069e 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php
@@ -15,7 +15,7 @@
namespace Cake\Mailer;
use BadMethodCallException;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\StaticConfigTrait;
use Cake\Datasource\ModelAwareTrait;
use Cake\Event\EventListenerInterface;
@@ -95,6 +95,7 @@
* @method array getSender() Gets "sender" address. {@see \Cake\Mailer\Message::getSender()}
* @method $this setReplyTo($email, $name = null) Sets "Reply-To" address. {@see \Cake\Mailer\Message::setReplyTo()}
* @method array getReplyTo() Gets "Reply-To" address. {@see \Cake\Mailer\Message::getReplyTo()}
+ * @method $this addReplyTo($email, $name = null) Add "Reply-To" address. {@see \Cake\Mailer\Message::addReplyTo()}
* @method $this setReadReceipt($email, $name = null) Sets Read Receipt (Disposition-Notification-To header).
* {@see \Cake\Mailer\Message::setReadReceipt()}
* @method array getReadReceipt() Gets Read Receipt (Disposition-Notification-To header).
@@ -300,6 +301,8 @@ public function __call(string $method, array $args)
*/
public function set($key, $value = null)
{
+ deprecationWarning('Mailer::set() is deprecated. Use setViewVars() instead.');
+
return $this->setViewVars($key, $value);
}
@@ -478,7 +481,7 @@ public function setTransport($name)
} elseif (is_object($name)) {
$transport = $name;
if (!$transport instanceof AbstractTransport) {
- throw new Exception('Transport class must extend Cake\Mailer\AbstractTransport');
+ throw new CakeException('Transport class must extend Cake\Mailer\AbstractTransport');
}
} else {
throw new InvalidArgumentException(sprintf(
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Message.php b/app/vendor/cakephp/cakephp/src/Mailer/Message.php
index 29e5fa28e..09248ab48 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Message.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Message.php
@@ -17,7 +17,7 @@
namespace Cake\Mailer;
use Cake\Core\Configure;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Http\Client\FormDataPart;
use Cake\Utility\Hash;
use Cake\Utility\Security;
@@ -101,7 +101,7 @@ class Message implements JsonSerializable, Serializable
protected $sender = [];
/**
- * The email the recipient will reply to
+ * List of email(s) that the recipient will reply to
*
* @var array
*/
@@ -325,7 +325,7 @@ public function __construct(?array $config = null)
/**
* Sets "from" address.
*
- * @param string|array $email Null to get, String with email,
+ * @param string|array $email String with email,
* Array with email as key, name as value or email as value (without name)
* @param string|null $name Name
* @return $this
@@ -347,13 +347,14 @@ public function getFrom(): array
}
/**
- * Sets "sender" address.
+ * Sets the "sender" address. See RFC link below for full explanation.
*
* @param string|array $email String with email,
* Array with email as key, name as value or email as value (without name)
* @param string|null $name Name
* @return $this
* @throws \InvalidArgumentException
+ * @link https://tools.ietf.org/html/rfc2822.html#section-3.6.2
*/
public function setSender($email, ?string $name = null)
{
@@ -361,9 +362,10 @@ public function setSender($email, ?string $name = null)
}
/**
- * Gets "sender" address.
+ * Gets the "sender" address. See RFC link below for full explanation.
*
* @return array
+ * @link https://tools.ietf.org/html/rfc2822.html#section-3.6.2
*/
public function getSender(): array
{
@@ -381,7 +383,7 @@ public function getSender(): array
*/
public function setReplyTo($email, ?string $name = null)
{
- return $this->setEmailSingle('replyTo', $email, $name, 'Reply-To requires only 1 email address.');
+ return $this->setEmail('replyTo', $email, $name);
}
/**
@@ -394,6 +396,19 @@ public function getReplyTo(): array
return $this->replyTo;
}
+ /**
+ * Add "Reply-To" address.
+ *
+ * @param string|array $email String with email,
+ * Array with email as key, name as value or email as value (without name)
+ * @param string|null $name Name
+ * @return $this
+ */
+ public function addReplyTo($email, ?string $name = null)
+ {
+ return $this->addEmail('replyTo', $email, $name);
+ }
+
/**
* Sets Read Receipt (Disposition-Notification-To header).
*
@@ -473,7 +488,7 @@ public function getTo(): array
/**
* Add "To" address.
*
- * @param string|array $email Null to get, String with email,
+ * @param string|array $email String with email,
* Array with email as key, name as value or email as value (without name)
* @param string|null $name Name
* @return $this
@@ -509,7 +524,7 @@ public function getCc(): array
/**
* Add "cc" address.
*
- * @param string|array $email Null to get, String with email,
+ * @param string|array $email String with email,
* Array with email as key, name as value or email as value (without name)
* @param string|null $name Name
* @return $this
@@ -545,7 +560,7 @@ public function getBcc(): array
/**
* Add "bcc" address.
*
- * @param string|array $email Null to get, String with email,
+ * @param string|array $email String with email,
* Array with email as key, name as value or email as value (without name)
* @param string|null $name Name
* @return $this
@@ -706,7 +721,7 @@ protected function validateEmail(string $email, string $context): void
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
return;
}
- } elseif (preg_match($this->emailPattern, (string)$email)) {
+ } elseif (preg_match($this->emailPattern, $email)) {
return;
}
@@ -878,10 +893,18 @@ public function getHeaders(array $include = []): array
'replyTo' => 'Reply-To',
'readReceipt' => 'Disposition-Notification-To',
'returnPath' => 'Return-Path',
+ 'to' => 'To',
+ 'cc' => 'Cc',
+ 'bcc' => 'Bcc',
];
+ $headersMultipleEmails = ['to', 'cc', 'bcc', 'replyTo'];
foreach ($relation as $var => $header) {
if ($include[$var]) {
- $headers[$header] = (string)current($this->formatAddress($this->{$var}));
+ if (in_array($var, $headersMultipleEmails)) {
+ $headers[$header] = implode(', ', $this->formatAddress($this->{$var}));
+ } else {
+ $headers[$header] = (string)current($this->formatAddress($this->{$var}));
+ }
}
}
if ($include['sender']) {
@@ -892,12 +915,6 @@ public function getHeaders(array $include = []): array
}
}
- foreach (['to', 'cc', 'bcc'] as $var) {
- if ($include[$var]) {
- $headers[ucfirst($var)] = implode(', ', $this->formatAddress($this->{$var}));
- }
- }
-
$headers += $this->headers;
if (!isset($headers['Date'])) {
$headers['Date'] = date(DATE_RFC2822);
@@ -1836,6 +1853,7 @@ public function jsonSerialize(): array
'to', 'from', 'sender', 'replyTo', 'cc', 'bcc', 'subject',
'returnPath', 'readReceipt', 'emailFormat', 'emailPattern', 'domain',
'attachments', 'messageId', 'headers', 'appCharset', 'charset', 'headerCharset',
+ 'textMessage', 'htmlMessage',
];
$array = [];
@@ -1888,7 +1906,7 @@ public function serialize(): string
}
/**
- * Unserializes the Email object.
+ * Unserializes the Message object.
*
* @param string $data Serialized string.
* @return void
@@ -1897,7 +1915,7 @@ public function unserialize($data)
{
$array = unserialize($data);
if (!is_array($array)) {
- throw new Exception('Unable to unserialize message.');
+ throw new CakeException('Unable to unserialize message.');
}
$this->createFromArray($array);
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php b/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php
index 566418d1b..118b41f70 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php
@@ -68,7 +68,7 @@ public function render(string $content, array $types = []): array
$view = $this->createView();
[$templatePlugin] = pluginSplit($view->getTemplate());
- [$layoutPlugin] = pluginSplit((string)$view->getLayout());
+ [$layoutPlugin] = pluginSplit($view->getLayout());
if ($templatePlugin) {
$view->setPlugin($templatePlugin);
} elseif ($layoutPlugin) {
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php
index b73ce77bd..52671bd77 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php
@@ -35,7 +35,7 @@ public function send(Message $message): array
$headers = $message->getHeadersString(
['from', 'sender', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'subject']
);
- $message = implode("\r\n", (array)$message->getBody());
+ $message = implode("\r\n", $message->getBody());
return ['headers' => $headers, 'message' => $message];
}
diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php
index 391985828..3442bd921 100644
--- a/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php
+++ b/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php
@@ -18,7 +18,7 @@
*/
namespace Cake\Mailer\Transport;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Message;
@@ -41,7 +41,7 @@ public function send(Message $message): array
$to = $message->getHeaders(['to'])['To'];
$to = str_replace("\r\n", '', $to);
- $eol = $this->getConfig('eol', PHP_EOL);
+ $eol = $this->getConfig('eol', version_compare(PHP_VERSION, '8.0', '>=') ? "\r\n" : "\n");
$headers = $message->getHeadersString(
[
'from',
@@ -91,7 +91,7 @@ protected function _mail(
if (!@mail($to, $subject, $message, $headers, $params)) {
$error = error_get_last();
$msg = 'Could not send email: ' . ($error['message'] ?? 'unknown');
- throw new Exception($msg);
+ throw new CakeException($msg);
}
// phpcs:enable
}
diff --git a/app/vendor/cakephp/cakephp/src/Network/Exception/SocketException.php b/app/vendor/cakephp/cakephp/src/Network/Exception/SocketException.php
index 92f70cba5..dadfb4997 100644
--- a/app/vendor/cakephp/cakephp/src/Network/Exception/SocketException.php
+++ b/app/vendor/cakephp/cakephp/src/Network/Exception/SocketException.php
@@ -14,16 +14,12 @@
*/
namespace Cake\Network\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Exception class for Socket. This exception will be thrown from Socket, Email, HttpSocket
* SmtpTransport, MailTransport and HttpResponse when it encounters an error.
*/
-class SocketException extends Exception
+class SocketException extends CakeException
{
- /**
- * @inheritDoc
- */
- protected $_defaultCode = 0;
}
diff --git a/app/vendor/cakephp/cakephp/src/Network/Socket.php b/app/vendor/cakephp/cakephp/src/Network/Socket.php
index 0d12ebfdd..f1214671d 100644
--- a/app/vendor/cakephp/cakephp/src/Network/Socket.php
+++ b/app/vendor/cakephp/cakephp/src/Network/Socket.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Network;
-use Cake\Core\Exception\Exception as CakeException;
+use Cake\Core\Exception\CakeException;
use Cake\Core\InstanceConfigTrait;
use Cake\Network\Exception\SocketException;
use Cake\Validation\Validation;
@@ -167,7 +167,7 @@ public function connect(): bool
$remoteSocketTarget,
$errNum,
$errStr,
- $this->_config['timeout'],
+ (int)$this->_config['timeout'],
$connectAs,
$context
);
@@ -186,7 +186,7 @@ public function connect(): bool
$this->connected = is_resource($this->connection);
if ($this->connected) {
/** @psalm-suppress PossiblyNullArgument */
- stream_set_timeout($this->connection, $this->_config['timeout']);
+ stream_set_timeout($this->connection, (int)$this->_config['timeout']);
}
return $this->connected;
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association.php b/app/vendor/cakephp/cakephp/src/ORM/Association.php
index 41bcf4861..218cade86 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Association.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Association.php
@@ -106,7 +106,7 @@ abstract class Association
/**
* The field name in the owning side table that is used to match with the foreignKey
*
- * @var string|string[]
+ * @var string|string[]|null
*/
protected $_bindingKey;
@@ -1000,7 +1000,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr
$property = $options['propertyPath'];
$propertyPath = explode('.', $property);
- $query->formatResults(function ($results) use ($formatters, $property, $propertyPath, $query) {
+ $query->formatResults(function ($results, $query) use ($formatters, $property, $propertyPath) {
$extracted = [];
foreach ($results as $result) {
foreach ($propertyPath as $propertyPathItem) {
@@ -1014,7 +1014,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr
}
$extracted = new Collection($extracted);
foreach ($formatters as $callable) {
- $extracted = new ResultSetDecorator($callable($extracted));
+ $extracted = new ResultSetDecorator($callable($extracted, $query));
}
/** @var \Cake\Collection\CollectionInterface $results */
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php b/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php
index 9423783c4..1ba41d9eb 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php
@@ -106,7 +106,7 @@ class BelongsToMany extends Association
/**
* The name of the field representing the foreign key to the target table
*
- * @var string|string[]
+ * @var string|string[]|null
*/
protected $_targetForeignKey;
@@ -253,6 +253,7 @@ public function defaultRowValue(array $row, bool $joined): array
*
* @param string|\Cake\ORM\Table|null $table Name or instance for the join table
* @return \Cake\ORM\Table
+ * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations.
*/
public function junction($table = null): Table
{
@@ -269,7 +270,7 @@ public function junction($table = null): Table
$config = [];
if (!$tableLocator->exists($tableAlias)) {
- $config = ['table' => $tableName];
+ $config = ['table' => $tableName, 'allowFallbackClass' => true];
// Propagate the connection if we'll get an auto-model
if (!App::className($tableAlias, 'Model/Table', 'Table')) {
@@ -282,8 +283,16 @@ public function junction($table = null): Table
if (is_string($table)) {
$table = $tableLocator->get($table);
}
+
$source = $this->getSource();
$target = $this->getTarget();
+ if ($source->getAlias() === $target->getAlias()) {
+ throw new InvalidArgumentException(sprintf(
+ 'The `%s` association on `%s` cannot target the same table.',
+ $this->getName(),
+ $source->getAlias()
+ ));
+ }
$this->_generateSourceAssociations($table, $source);
$this->_generateTargetAssociations($table, $source, $target);
@@ -312,10 +321,17 @@ protected function _generateTargetAssociations(Table $junction, Table $source, T
{
$junctionAlias = $junction->getAlias();
$sAlias = $source->getAlias();
+ $tAlias = $target->getAlias();
+
+ $targetBindingKey = null;
+ if ($junction->hasAssociation($tAlias)) {
+ $targetBindingKey = $junction->getAssociation($tAlias)->getBindingKey();
+ }
if (!$target->hasAssociation($junctionAlias)) {
$target->hasMany($junctionAlias, [
'targetTable' => $junction,
+ 'bindingKey' => $targetBindingKey,
'foreignKey' => $this->getTargetForeignKey(),
'strategy' => $this->_strategy,
]);
@@ -350,9 +366,17 @@ protected function _generateTargetAssociations(Table $junction, Table $source, T
protected function _generateSourceAssociations(Table $junction, Table $source): void
{
$junctionAlias = $junction->getAlias();
+ $sAlias = $source->getAlias();
+
+ $sourceBindingKey = null;
+ if ($junction->hasAssociation($sAlias)) {
+ $sourceBindingKey = $junction->getAssociation($sAlias)->getBindingKey();
+ }
+
if (!$source->hasAssociation($junctionAlias)) {
$source->hasMany($junctionAlias, [
'targetTable' => $junction,
+ 'bindingKey' => $sourceBindingKey,
'foreignKey' => $this->getForeignKey(),
'strategy' => $this->_strategy,
]);
@@ -374,6 +398,7 @@ protected function _generateSourceAssociations(Table $junction, Table $source):
* @param \Cake\ORM\Table $source The source table.
* @param \Cake\ORM\Table $target The target table.
* @return void
+ * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations.
*/
protected function _generateJunctionAssociations(Table $junction, Table $source, Table $target): void
{
@@ -385,7 +410,19 @@ protected function _generateJunctionAssociations(Table $junction, Table $source,
'foreignKey' => $this->getTargetForeignKey(),
'targetTable' => $target,
]);
+ } else {
+ $belongsTo = $junction->getAssociation($tAlias);
+ if (
+ $this->getTargetForeignKey() !== $belongsTo->getForeignKey() ||
+ $target !== $belongsTo->getTarget()
+ ) {
+ throw new InvalidArgumentException(
+ "The existing `{$tAlias}` association on `{$junction->getAlias()}` " .
+ "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`"
+ );
+ }
}
+
if (!$junction->hasAssociation($sAlias)) {
$junction->belongsTo($sAlias, [
'foreignKey' => $this->getForeignKey(),
@@ -561,7 +598,10 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo
$hasMany = $this->getSource()->getAssociation($table->getAlias());
if ($this->_cascadeCallbacks) {
foreach ($hasMany->find('all')->where($conditions)->all()->toList() as $related) {
- $table->delete($related, $options);
+ $success = $table->delete($related, $options);
+ if (!$success) {
+ return false;
+ }
}
return true;
@@ -751,7 +791,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti
$belongsTo = $junction->getAssociation($target->getAlias());
$foreignKey = (array)$this->getForeignKey();
$assocForeignKey = (array)$belongsTo->getForeignKey();
- $targetPrimaryKey = (array)$target->getPrimaryKey();
+ $targetBindingKey = (array)$belongsTo->getBindingKey();
$bindingKey = (array)$this->getBindingKey();
$jointProperty = $this->_junctionProperty;
$junctionRegistryAlias = $junction->getRegistryAlias();
@@ -762,7 +802,7 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti
$joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]);
}
$sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey));
- $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey));
+ $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey));
$changedKeys = (
$sourceKeys !== $joint->extract($foreignKey) ||
@@ -897,6 +937,7 @@ function () use ($sourceEntity, $targetEntities, $options): void {
return true;
}
+ /** @var \SplObjectStorage<\Cake\Datasource\EntityInterface, null> $storage*/
$storage = new SplObjectStorage();
foreach ($targetEntities as $e) {
$storage->attach($e);
@@ -1051,8 +1092,9 @@ public function find($type = null, array $options = []): Query
*/
protected function _appendJunctionJoin(Query $query, ?array $conditions = null): Query
{
+ $junctionTable = $this->junction();
if ($conditions === null) {
- $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias());
+ $belongsTo = $junctionTable->getAssociation($this->getTarget()->getAlias());
$conditions = $belongsTo->_joinCondition([
'foreignKey' => $this->getTargetForeignKey(),
]);
@@ -1064,15 +1106,14 @@ protected function _appendJunctionJoin(Query $query, ?array $conditions = null):
$joins = $query->clause('join');
$matching = [
$name => [
- 'table' => $this->junction()->getTable(),
+ 'table' => $junctionTable->getTable(),
'conditions' => $conditions,
'type' => Query::JOIN_TYPE_INNER,
],
];
- $assoc = $this->getTarget()->getAssociation($name);
$query
- ->addDefaultTypes($assoc->getTarget())
+ ->addDefaultTypes($junctionTable)
->join($matching + $joins, [], true);
return $query;
@@ -1155,13 +1196,24 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) {
// Find existing rows so that we can diff with new entities.
// Only hydrate primary/foreign key columns to save time.
- $existing = $this->find()
+ // Attach joins first to ensure where conditions have correct
+ // column types set.
+ $existing = $this->_appendJunctionJoin($this->find())
->select($keys)
->where(array_combine($prefixedForeignKey, $primaryValue));
- $existing = $this->_appendJunctionJoin($existing);
+
+ // Because we're aliasing key fields to look like they are not
+ // from joined table we need to overwrite the type map as the junction
+ // table can have a surrogate primary key that doesn't share a type
+ // with the target table.
+ $junctionTypes = array_intersect_key($junction->getSchema()->typeMap(), $keys);
+ $existing->getSelectTypeMap()->setTypes($junctionTypes);
$jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities);
$inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities, $options);
+ if ($inserts === false) {
+ return false;
+ }
if ($inserts && !$this->_saveTarget($sourceEntity, $inserts, $options)) {
return false;
@@ -1196,14 +1248,14 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) {
* @param array $targetEntities entities in target table that are related to
* the `$jointEntities`
* @param array $options list of options accepted by `Table::delete()`
- * @return array
+ * @return array|false Array of entities not deleted or false in case of deletion failure for atomic saves.
*/
protected function _diffLinks(
Query $existing,
array $jointEntities,
array $targetEntities,
array $options = []
- ): array {
+ ) {
$junction = $this->junction();
$target = $this->getTarget();
$belongsTo = $junction->getAssociation($target->getAlias());
@@ -1249,9 +1301,9 @@ protected function _diffLinks(
}
}
- if ($deletes) {
- foreach ($deletes as $entity) {
- $junction->delete($entity, $options);
+ foreach ($deletes as $entity) {
+ if (!$junction->delete($entity, $options) && !empty($options['atomic'])) {
+ return false;
}
}
@@ -1400,25 +1452,25 @@ protected function _junctionTableName(?string $name = null): string
/**
* Parse extra options passed in the constructor.
*
- * @param array $opts original list of options passed in constructor
+ * @param array $options original list of options passed in constructor
* @return void
*/
- protected function _options(array $opts): void
+ protected function _options(array $options): void
{
- if (!empty($opts['targetForeignKey'])) {
- $this->setTargetForeignKey($opts['targetForeignKey']);
+ if (!empty($options['targetForeignKey'])) {
+ $this->setTargetForeignKey($options['targetForeignKey']);
}
- if (!empty($opts['joinTable'])) {
- $this->_junctionTableName($opts['joinTable']);
+ if (!empty($options['joinTable'])) {
+ $this->_junctionTableName($options['joinTable']);
}
- if (!empty($opts['through'])) {
- $this->setThrough($opts['through']);
+ if (!empty($options['through'])) {
+ $this->setThrough($options['through']);
}
- if (!empty($opts['saveStrategy'])) {
- $this->setSaveStrategy($opts['saveStrategy']);
+ if (!empty($options['saveStrategy'])) {
+ $this->setSaveStrategy($options['saveStrategy']);
}
- if (isset($opts['sort'])) {
- $this->setSort($opts['sort']);
+ if (isset($options['sort'])) {
+ $this->setSort($options['sort']);
}
}
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php b/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php
index 9b6c5161a..9311eb9e5 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php
@@ -45,16 +45,25 @@ public function cascadeDelete(Association $association, EntityInterface $entity,
/** @psalm-suppress InvalidArgument */
$foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey());
$bindingKey = (array)$association->getBindingKey();
- $conditions = array_combine($foreignKey, $entity->extract($bindingKey));
+ $bindingValue = $entity->extract($bindingKey);
+ if (in_array(null, $bindingValue, true)) {
+ return true;
+ }
+ $conditions = array_combine($foreignKey, $bindingValue);
if ($association->getCascadeCallbacks()) {
foreach ($association->find()->where($conditions)->all()->toList() as $related) {
- $table->delete($related, $options);
+ $success = $table->delete($related, $options);
+ if (!$success) {
+ return false;
+ }
}
return true;
}
- return (bool)$association->deleteAll($conditions);
+ $association->deleteAll($conditions);
+
+ return true;
}
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php b/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php
index 235017b7e..fa59e21f7 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php
@@ -637,16 +637,16 @@ public function defaultRowValue(array $row, bool $joined): array
/**
* Parse extra options passed in the constructor.
*
- * @param array $opts original list of options passed in constructor
+ * @param array $options original list of options passed in constructor
* @return void
*/
- protected function _options(array $opts): void
+ protected function _options(array $options): void
{
- if (!empty($opts['saveStrategy'])) {
- $this->setSaveStrategy($opts['saveStrategy']);
+ if (!empty($options['saveStrategy'])) {
+ $this->setSaveStrategy($options['saveStrategy']);
}
- if (isset($opts['sort'])) {
- $this->setSort($opts['sort']);
+ if (isset($options['sort'])) {
+ $this->setSort($options['sort']);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php b/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php
index 421baa087..b4c88b336 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php
@@ -267,7 +267,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void
throw new InvalidArgumentException(
sprintf(
'You are required to select the "%s" field(s)',
- implode(', ', (array)$key)
+ implode(', ', $key)
)
);
}
@@ -404,7 +404,8 @@ protected function _buildSubquery(Query $query): Query
$filterQuery->contain([], true);
$filterQuery->setValueBinder(new ValueBinder());
- if (!$filterQuery->clause('limit')) {
+ // Ignore limit if there is no order since we need all rows to find matches
+ if (!$filterQuery->clause('limit') || !$filterQuery->clause('order')) {
$filterQuery->limit(null);
$filterQuery->order([], true);
$filterQuery->offset(null);
diff --git a/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php b/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php
index 8bfe8a384..6a025dd35 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php
@@ -71,7 +71,7 @@ public function add(string $alias, Association $association): Association
{
[, $alias] = pluginSplit($alias);
- return $this->_items[strtolower($alias)] = $association;
+ return $this->_items[$alias] = $association;
}
/**
@@ -110,7 +110,6 @@ public function load(string $className, string $associated, array $options = [])
*/
public function get(string $alias): ?Association
{
- $alias = strtolower($alias);
if (isset($this->_items[$alias])) {
return $this->_items[$alias];
}
@@ -143,7 +142,7 @@ public function getByProperty(string $prop): ?Association
*/
public function has(string $alias): bool
{
- return isset($this->_items[strtolower($alias)]);
+ return isset($this->_items[$alias]);
}
/**
@@ -187,7 +186,7 @@ public function getByType($class): array
*/
public function remove(string $alias): void
{
- unset($this->_items[strtolower($alias)]);
+ unset($this->_items[$alias]);
}
/**
@@ -312,7 +311,7 @@ protected function _save(
return true;
}
if (!empty($nested)) {
- $options = (array)$nested + $options;
+ $options = $nested + $options;
}
return (bool)$association->saveAssociated($entity, $options);
@@ -324,24 +323,9 @@ protected function _save(
*
* @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for.
* @param array $options The options used in the delete operation.
- * @return void
- */
- public function cascadeDelete(EntityInterface $entity, array $options): void
- {
- $noCascade = $this->_getNoCascadeItems($entity, $options);
- foreach ($noCascade as $assoc) {
- $assoc->cascadeDelete($entity, $options);
- }
- }
-
- /**
- * Returns items that have no cascade callback.
- *
- * @param \Cake\Datasource\EntityInterface $entity The entity to delete associations for.
- * @param array $options The options used in the delete operation.
- * @return \Cake\ORM\Association[]
+ * @return bool
*/
- protected function _getNoCascadeItems(EntityInterface $entity, array $options): array
+ public function cascadeDelete(EntityInterface $entity, array $options): bool
{
$noCascade = [];
foreach ($this->_items as $assoc) {
@@ -349,10 +333,20 @@ protected function _getNoCascadeItems(EntityInterface $entity, array $options):
$noCascade[] = $assoc;
continue;
}
- $assoc->cascadeDelete($entity, $options);
+ $success = $assoc->cascadeDelete($entity, $options);
+ if (!$success) {
+ return false;
+ }
+ }
+
+ foreach ($noCascade as $assoc) {
+ $success = $assoc->cascadeDelete($entity, $options);
+ if (!$success) {
+ return false;
+ }
}
- return $noCascade;
+ return true;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php b/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php
index 6d6ba4504..5b27a7dbe 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php
@@ -47,6 +47,7 @@ protected function _normalizeAssociations($associations): array
$path = explode('.', $table);
$table = array_pop($path);
+ /** @var string $first */
$first = array_shift($path);
$pointer += [$first => []];
$pointer = &$pointer[$first];
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior.php
index d1562d0be..1f7939ab8 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior.php
@@ -16,7 +16,7 @@
*/
namespace Cake\ORM;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\InstanceConfigTrait;
use Cake\Event\EventListenerInterface;
use ReflectionClass;
@@ -184,8 +184,21 @@ public function initialize(array $config): void
* Get the table instance this behavior is bound to.
*
* @return \Cake\ORM\Table The bound table instance.
+ * @deprecated 4.2.0 Use table() instead.
*/
public function getTable(): Table
+ {
+ deprecationWarning('Behavior::getTable() is deprecated. Use table() instead.');
+
+ return $this->table();
+ }
+
+ /**
+ * Get the table instance this behavior is bound to.
+ *
+ * @return \Cake\ORM\Table The bound table instance.
+ */
+ public function table(): Table
{
return $this->_table;
}
@@ -229,7 +242,7 @@ protected function _resolveMethodAliases(string $key, array $defaults, array $co
* Checks that implemented keys contain values pointing at callable.
*
* @return void
- * @throws \Cake\Core\Exception\Exception if config are invalid
+ * @throws \Cake\Core\Exception\CakeException if config are invalid
*/
public function verifyConfig(): void
{
@@ -241,7 +254,7 @@ public function verifyConfig(): void
foreach ($this->_config[$key] as $method) {
if (!is_callable([$this, $method])) {
- throw new Exception(sprintf(
+ throw new CakeException(sprintf(
'The method %s is not callable on class %s',
$method,
static::class
@@ -266,6 +279,7 @@ public function implementedEvents(): array
{
$eventMap = [
'Model.beforeMarshal' => 'beforeMarshal',
+ 'Model.afterMarshal' => 'afterMarshal',
'Model.beforeFind' => 'beforeFind',
'Model.beforeSave' => 'beforeSave',
'Model.afterSave' => 'afterSave',
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php
index dfeb29137..d2b6f7f82 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php
@@ -64,7 +64,7 @@ class TimestampBehavior extends Behavior
/**
* Current timestamp
*
- * @var \Cake\I18n\Time
+ * @var \Cake\I18n\Time|null
*/
protected $_ts;
@@ -211,7 +211,7 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re
$ts = $this->timestamp(null, $refreshTimestamp);
- $columnType = $this->getTable()->getSchema()->getColumnType($field);
+ $columnType = $this->table()->getSchema()->getColumnType($field);
if (!$columnType) {
return;
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php
index 5c33edfb9..2a9f0e46e 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php
@@ -26,6 +26,7 @@
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\ORM\Query;
use Cake\ORM\Table;
+use Cake\Utility\Hash;
/**
* This class provides a way to translate dynamic data by keeping translations
@@ -79,7 +80,10 @@ public function __construct(Table $table, array $config = [])
$this->setConfig($config);
$this->table = $table;
- $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']);
+ $this->translationTable = $this->getTableLocator()->get(
+ $this->_config['translationTable'],
+ ['allowFallbackClass' => true]
+ );
$this->setupAssociations();
}
@@ -113,6 +117,7 @@ protected function setupAssociations()
'className' => $table,
'alias' => $name,
'table' => $this->translationTable->getTable(),
+ 'allowFallbackClass' => true,
]);
} else {
$fieldTable = $tableLocator->get($name);
@@ -162,7 +167,7 @@ protected function setupAssociations()
*/
public function beforeFind(EventInterface $event, Query $query, ArrayObject $options)
{
- $locale = $this->getLocale();
+ $locale = Hash::get($options, 'locale', $this->getLocale());
if ($locale === $this->getConfig('defaultLocale')) {
return;
@@ -281,7 +286,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array
$preexistent = [];
if ($key) {
- /** @psalm-suppress UndefinedClass */
$preexistent = $this->translationTable->find()
->select(['id', 'field'])
->where([
@@ -379,6 +383,7 @@ protected function rowMapper($results, $locale)
$row['_locale'] = $locale;
if ($hydrated) {
+ /** @psalm-suppress PossiblyInvalidMethodCall */
$row->clean();
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php
index 4365a7f73..f1391aa5d 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php
@@ -26,6 +26,7 @@
use Cake\ORM\Marshaller;
use Cake\ORM\Query;
use Cake\ORM\Table;
+use Cake\Utility\Hash;
/**
* This class provides a way to translate dynamic data by keeping translations
@@ -81,7 +82,10 @@ public function __construct(Table $table, array $config = [])
$this->setConfig($config);
$this->table = $table;
- $this->translationTable = $this->getTableLocator()->get($this->_config['translationTable']);
+ $this->translationTable = $this->getTableLocator()->get(
+ $this->_config['translationTable'],
+ ['allowFallbackClass' => true]
+ );
$this->setupAssociations();
}
@@ -98,7 +102,8 @@ protected function setupAssociations()
{
$config = $this->getConfig();
- $this->table->hasMany($config['translationTable'], [
+ $targetAlias = $this->translationTable->getAlias();
+ $this->table->hasMany($targetAlias, [
'className' => $config['translationTable'],
'foreignKey' => 'id',
'strategy' => $config['strategy'],
@@ -119,14 +124,54 @@ protected function setupAssociations()
*/
public function beforeFind(EventInterface $event, Query $query, ArrayObject $options)
{
- $locale = $this->getLocale();
+ $locale = Hash::get($options, 'locale', $this->getLocale());
+ $config = $this->getConfig();
+
+ if ($locale === $config['defaultLocale']) {
+ return;
+ }
+
+ $this->setupHasOneAssociation($locale, $options);
- if ($locale === $this->getConfig('defaultLocale')) {
+ $fieldsAdded = $this->addFieldsToQuery($query, $config);
+ $orderByTranslatedField = $this->iterateClause($query, 'order', $config);
+ $filteredByTranslatedField = $this->traverseClause($query, 'where', $config);
+
+ if (!$fieldsAdded && !$orderByTranslatedField && !$filteredByTranslatedField) {
return;
}
+ $query->contain([$config['hasOneAlias']]);
+
+ $query->formatResults(function ($results) use ($locale) {
+ return $this->rowMapper($results, $locale);
+ }, $query::PREPEND);
+ }
+
+ /**
+ * Create a hasOne association for record with required locale.
+ *
+ * @param string $locale Locale
+ * @param \ArrayObject $options Find options
+ * @return void
+ */
+ protected function setupHasOneAssociation(string $locale, ArrayObject $options): void
+ {
$config = $this->getConfig();
+ [$plugin] = pluginSplit($config['translationTable']);
+ $hasOneTargetAlias = $plugin ? ($plugin . '.' . $config['hasOneAlias']) : $config['hasOneAlias'];
+ if (!$this->getTableLocator()->exists($hasOneTargetAlias)) {
+ // Load table before hand with fallback class usage enabled
+ $this->getTableLocator()->get(
+ $hasOneTargetAlias,
+ [
+ 'className' => $config['translationTable'],
+ 'allowFallbackClass' => true,
+ ]
+ );
+ }
+
if (isset($options['filterByCurrentLocale'])) {
$joinType = $options['filterByCurrentLocale'] ? 'INNER' : 'LEFT';
} else {
@@ -142,20 +187,6 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt
$config['hasOneAlias'] . '.locale' => $locale,
],
]);
-
- $fieldsAdded = $this->addFieldsToQuery($query, $config);
- $orderByTranslatedField = $this->iterateClause($query, 'order', $config);
- $filteredByTranslatedField = $this->traverseClause($query, 'where', $config);
-
- if (!$fieldsAdded && !$orderByTranslatedField && !$filteredByTranslatedField) {
- return;
- }
-
- $query->contain([$config['hasOneAlias']]);
-
- $query->formatResults(function ($results) use ($locale) {
- return $this->rowMapper($results, $locale);
- }, $query::PREPEND);
}
/**
@@ -232,6 +263,7 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields,
return $c;
}
+ /** @psalm-suppress ParadoxicalCondition */
if (in_array($field, $fields, true)) {
$joinRequired = true;
$field = "$alias.$field";
@@ -288,6 +320,7 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields,
return;
}
+ /** @psalm-suppress ParadoxicalCondition */
if (in_array($field, $mainTableFields, true)) {
$expression->setField("$mainTableAlias.$field");
}
@@ -438,7 +471,7 @@ protected function rowMapper($results, $locale)
{
$allowEmpty = $this->_config['allowEmptyTranslations'];
- return $results->map(function ($row) use ($allowEmpty) {
+ return $results->map(function ($row) use ($allowEmpty, $locale) {
/** @var \Cake\Datasource\EntityInterface|array|null $row */
if ($row === null) {
return $row;
@@ -447,10 +480,11 @@ protected function rowMapper($results, $locale)
$hydrated = !is_array($row);
if (empty($row['translation'])) {
- $row['_locale'] = $this->getLocale();
+ $row['_locale'] = $locale;
unset($row['translation']);
if ($hydrated) {
+ /** @psalm-suppress PossiblyInvalidMethodCall */
$row->clean();
}
@@ -482,6 +516,7 @@ protected function rowMapper($results, $locale)
unset($row['translation']);
if ($hydrated) {
+ /** @psalm-suppress PossiblyInvalidMethodCall */
$row->clean();
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php
index 42ce87a4f..ea1fc2902 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php
@@ -111,7 +111,7 @@ protected function unsetEmptyFields($entity)
foreach ($translations as $locale => $translation) {
$fields = $translation->extract($this->_config['fields'], false);
foreach ($fields as $field => $value) {
- if (strlen($value) === 0) {
+ if ($value === null || $value === '') {
$translation->unset($field);
}
}
@@ -152,27 +152,32 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio
return [
'_translations' => function ($value, $entity) use ($marshaller, $options) {
- /** @var \Cake\Datasource\EntityInterface $entity */
+ if (!is_array($value)) {
+ return null;
+ }
+
+ /** @var array|null $translations */
$translations = $entity->get('_translations');
- foreach ($this->_config['fields'] as $field) {
- $options['validate'] = $this->_config['validator'];
- $errors = [];
- if (!is_array($value)) {
- return null;
+ if ($translations === null) {
+ $translations = [];
+ }
+
+ $options['validate'] = $this->_config['validator'];
+ $errors = [];
+ foreach ($value as $language => $fields) {
+ if (!isset($translations[$language])) {
+ $translations[$language] = $this->table->newEmptyEntity();
}
- foreach ($value as $language => $fields) {
- if (!isset($translations[$language])) {
- $translations[$language] = $this->table->newEmptyEntity();
- }
- $marshaller->merge($translations[$language], $fields, $options);
- /** @var \Cake\Datasource\EntityInterface $translation */
- $translation = $translations[$language];
- if ((bool)$translation->getErrors()) {
- $errors[$language] = $translation->getErrors();
- }
+ $marshaller->merge($translations[$language], $fields, $options);
+
+ $translationErrors = $translations[$language]->getErrors();
+ if ($translationErrors) {
+ $errors[$language] = $translationErrors;
}
- // Set errors into the root entity, so validation errors
- // match the original form data position.
+ }
+
+ // Set errors into the root entity, so validation errors match the original form data position.
+ if ($errors) {
$entity->setErrors($errors);
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php
index d8c0d049f..5daecbb6b 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php
@@ -335,7 +335,7 @@ public function findTranslations(Query $query, array $options): Query
*/
public function __call($method, $args)
{
- return call_user_func_array([$this->strategy, $method], $args);
+ return $this->strategy->{$method}(...$args);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php
index 66971c4a4..af4f3282e 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php
@@ -135,7 +135,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity)
return;
}
- if (!$isNew && $dirty && $parent) {
+ if ($dirty && $parent) {
$this->_setParent($entity, $parent);
if ($level) {
@@ -146,7 +146,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity)
return;
}
- if (!$isNew && $dirty && !$parent) {
+ if ($dirty && !$parent) {
$this->_setAsRoot($entity);
if ($level) {
diff --git a/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php b/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php
index 772145a0e..7f7b2c2c1 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php
@@ -249,7 +249,7 @@ public function call(string $method, array $args = [])
if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) {
[$behavior, $callMethod] = $this->_methodMap[$method];
- return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args);
+ return $this->_loaded[$behavior]->{$callMethod}(...$args);
}
throw new BadMethodCallException(
@@ -271,8 +271,9 @@ public function callFinder(string $type, array $args = []): Query
if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) {
[$behavior, $callMethod] = $this->_finderMap[$type];
+ $callable = [$this->_loaded[$behavior], $callMethod];
- return call_user_func_array([$this->_loaded[$behavior], $callMethod], $args);
+ return $callable(...$args);
}
throw new BadMethodCallException(
diff --git a/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php b/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php
index 820f9fcbf..c6e98c881 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php
@@ -217,7 +217,7 @@ public function propertyPath(): ?string
*/
public function setCanBeJoined(bool $possible)
{
- $this->_canBeJoined = (bool)$possible;
+ $this->_canBeJoined = $possible;
return $this;
}
@@ -311,4 +311,16 @@ public function asContainArray(): array
],
];
}
+
+ /**
+ * Handles cloning eager loadables.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ foreach ($this->_associations as $i => $association) {
+ $this->_associations[$i] = clone $association;
+ }
+ }
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php b/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php
index 252673123..671acfa3f 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php
@@ -308,7 +308,7 @@ public function normalized(Table $repository): array
$contain = [];
foreach ($this->_containments as $alias => $options) {
if (!empty($options['instance'])) {
- $contain = (array)$this->_containments;
+ $contain = $this->_containments;
break;
}
$contain[$alias] = $this->_normalizeContain(
@@ -336,7 +336,7 @@ protected function _reformatContain(array $associations, array $original): array
{
$result = $original;
- foreach ((array)$associations as $table => $options) {
+ foreach ($associations as $table => $options) {
$pointer = &$result;
if (is_int($table)) {
$table = $options;
@@ -388,6 +388,7 @@ protected function _reformatContain(array $associations, array $original): array
}
if (!is_array($options)) {
+ /** @psalm-suppress InvalidArrayOffset */
$options = [$options => []];
}
@@ -627,7 +628,6 @@ protected function _resolveJoins(array $associations, array $matching = []): arr
*/
public function loadExternal(Query $query, StatementInterface $statement): StatementInterface
{
- /** @var \Cake\ORM\Table $table */
$table = $query->getRepository();
$external = $this->externalAssociations($table);
if (empty($external)) {
@@ -862,9 +862,7 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys):
}
/**
- * Clone hook implementation
- *
- * Clone the _matching eager loader as well.
+ * Handles cloning eager loaders and eager loadables.
*
* @return void
*/
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php
index eaaf511ad..fe6411eb0 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php
@@ -14,12 +14,12 @@
*/
namespace Cake\ORM\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a behavior cannot be found.
*/
-class MissingBehaviorException extends Exception
+class MissingBehaviorException extends CakeException
{
/**
* @var string
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php
index 50001358d..c363c123c 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php
@@ -18,12 +18,12 @@
*/
namespace Cake\ORM\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Exception raised when an Entity could not be found.
*/
-class MissingEntityException extends Exception
+class MissingEntityException extends CakeException
{
/**
* @var string
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php
index 699a0f296..accf334b2 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php
@@ -16,12 +16,12 @@
*/
namespace Cake\ORM\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Exception raised when a Table could not be found.
*/
-class MissingTableClassException extends Exception
+class MissingTableClassException extends CakeException
{
/**
* @var string
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php
index edf142318..5d9d03028 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php
@@ -14,7 +14,7 @@
*/
namespace Cake\ORM\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Datasource\EntityInterface;
use Cake\Utility\Hash;
use Throwable;
@@ -22,7 +22,7 @@
/**
* Used when a strict save or delete fails
*/
-class PersistenceFailedException extends Exception
+class PersistenceFailedException extends CakeException
{
/**
* The entity on which the persistence operation failed
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php
index ebabcd79d..b1f107bd8 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php
@@ -14,12 +14,12 @@
*/
namespace Cake\ORM\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a transaction was rolled back from a callback event.
*/
-class RolledbackTransactionException extends Exception
+class RolledbackTransactionException extends CakeException
{
/**
* @var string
diff --git a/app/vendor/cakephp/cakephp/src/ORM/LICENSE.txt b/app/vendor/cakephp/cakephp/src/ORM/LICENSE.txt
index 0c4b7932c..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/ORM/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2016, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php
index 203d78b0a..3c17d4376 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php
@@ -16,7 +16,7 @@
*/
namespace Cake\ORM\Locator;
-use Cake\ORM\TableRegistry;
+use Cake\Datasource\FactoryLocator;
/**
* Contains method for setting and accessing LocatorInterface instance
@@ -51,9 +51,11 @@ public function setTableLocator(LocatorInterface $tableLocator)
public function getTableLocator(): LocatorInterface
{
if ($this->_tableLocator === null) {
- $this->_tableLocator = TableRegistry::getTableLocator();
+ /** @psalm-suppress InvalidPropertyAssignmentValue */
+ $this->_tableLocator = FactoryLocator::get('Table');
}
+ /** @var \Cake\ORM\Locator\LocatorInterface */
return $this->_tableLocator;
}
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php
index 7559e87f3..68db76d87 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php
@@ -16,12 +16,13 @@
*/
namespace Cake\ORM\Locator;
+use Cake\Datasource\RepositoryInterface;
use Cake\ORM\Table;
/**
* Registries for Table objects should implement this interface.
*/
-interface LocatorInterface
+interface LocatorInterface extends \Cake\Datasource\Locator\LocatorInterface
{
/**
* Returns configuration for an alias or the full configuration array for
@@ -55,34 +56,12 @@ public function setConfig($alias, $options = null);
public function get(string $alias, array $options = []): Table;
/**
- * Check to see if an instance exists in the registry.
- *
- * @param string $alias The alias to check for.
- * @return bool
- */
- public function exists(string $alias): bool;
-
- /**
- * Set an instance.
+ * Set a table instance.
*
* @param string $alias The alias to set.
- * @param \Cake\ORM\Table $object The table to set.
+ * @param \Cake\ORM\Table $repository The table to set.
* @return \Cake\ORM\Table
+ * @psalm-suppress MoreSpecificImplementedParamType
*/
- public function set(string $alias, Table $object): Table;
-
- /**
- * Clears the registry of configuration and instances.
- *
- * @return void
- */
- public function clear(): void;
-
- /**
- * Removes an instance from the registry.
- *
- * @param string $alias The alias to remove.
- * @return void
- */
- public function remove(string $alias): void;
+ public function set(string $alias, RepositoryInterface $repository): Table;
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php b/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php
index 70b0b4489..a89d14014 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php
@@ -18,7 +18,10 @@
use Cake\Core\App;
use Cake\Datasource\ConnectionManager;
+use Cake\Datasource\Locator\AbstractLocator;
+use Cake\Datasource\RepositoryInterface;
use Cake\ORM\AssociationCollection;
+use Cake\ORM\Exception\MissingTableClassException;
use Cake\ORM\Table;
use Cake\Utility\Inflector;
use RuntimeException;
@@ -26,7 +29,7 @@
/**
* Provides a default registry/factory for Table objects.
*/
-class TableLocator implements LocatorInterface
+class TableLocator extends AbstractLocator implements LocatorInterface
{
/**
* Contains a list of locations where table classes should be looked for.
@@ -45,9 +48,9 @@ class TableLocator implements LocatorInterface
/**
* Instances that belong to the registry.
*
- * @var \Cake\ORM\Table[]
+ * @var array
*/
- protected $_instances = [];
+ protected $instances = [];
/**
* Contains a list of Table objects that were created out of the
@@ -58,11 +61,19 @@ class TableLocator implements LocatorInterface
protected $_fallbacked = [];
/**
- * Contains a list of options that were passed to get() method.
+ * Fallback class to use
*
- * @var array
+ * @var string
+ * @psalm-var class-string<\Cake\ORM\Table>
*/
- protected $_options = [];
+ protected $fallbackClassName = Table::class;
+
+ /**
+ * Whether fallback class should be used if a table class could not be found.
+ *
+ * @var bool
+ */
+ protected $allowFallbackClass = true;
/**
* Constructor.
@@ -84,13 +95,41 @@ public function __construct(?array $locations = null)
}
/**
- * Stores a list of options to be used when instantiating an object
- * with a matching alias.
+ * Set if fallback class should be used.
+ *
+ * Controls whether a fallback class should be used to create a table
+ * instance if a concrete class for alias used in `get()` could not be found.
+ *
+ * @param bool $allow Flag to enable or disable fallback
+ * @return $this
+ */
+ public function allowFallbackClass(bool $allow)
+ {
+ $this->allowFallbackClass = $allow;
+
+ return $this;
+ }
+
+ /**
+ * Set fallback class name.
+ *
+ * The class that should be used to create a table instance if a concrete
+ * class for alias used in `get()` could not be found. Defaults to
+ * `Cake\ORM\Table`.
*
- * @param string|array $alias Name of the alias or array to completely overwrite current config.
- * @param array|null $options list of options for the alias
+ * @param string $className Fallback class name
* @return $this
- * @throws \RuntimeException When you attempt to configure an existing table instance.
+ * @psalm-param class-string<\Cake\ORM\Table> $className
+ */
+ public function setFallbackClassName($className)
+ {
+ $this->fallbackClassName = $className;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
*/
public function setConfig($alias, $options = null)
{
@@ -100,7 +139,7 @@ public function setConfig($alias, $options = null)
return $this;
}
- if (isset($this->_instances[$alias])) {
+ if (isset($this->instances[$alias])) {
throw new RuntimeException(sprintf(
'You cannot configure "%s", it has already been constructed.',
$alias
@@ -113,10 +152,7 @@ public function setConfig($alias, $options = null)
}
/**
- * Returns configuration for an alias or the full configuration array for all aliases.
- *
- * @param string|null $alias Alias to get config for, null for complete config.
- * @return array The config data.
+ * @inheritDoc
*/
public function getConfig(?string $alias = null): array
{
@@ -165,29 +201,33 @@ public function getConfig(?string $alias = null): array
*/
public function get(string $alias, array $options = []): Table
{
- if (isset($this->_instances[$alias])) {
- if (!empty($options) && $this->_options[$alias] !== $options) {
- throw new RuntimeException(sprintf(
- 'You cannot configure "%s", it already exists in the registry.',
- $alias
- ));
- }
+ /** @var \Cake\ORM\Table */
+ return parent::get($alias, $options);
+ }
- return $this->_instances[$alias];
+ /**
+ * @inheritDoc
+ */
+ protected function createInstance(string $alias, array $options)
+ {
+ if (strpos($alias, '\\') === false) {
+ [, $classAlias] = pluginSplit($alias);
+ $options = ['alias' => $classAlias] + $options;
+ } elseif (!isset($options['alias'])) {
+ $options['className'] = $alias;
+ /** @psalm-suppress PossiblyFalseOperand */
+ $alias = substr($alias, strrpos($alias, '\\') + 1, -5);
}
- $this->_options[$alias] = $options;
- [, $classAlias] = pluginSplit($alias);
- $options = ['alias' => $classAlias] + $options;
-
if (isset($this->_config[$alias])) {
$options += $this->_config[$alias];
}
+ $allowFallbackClass = $options['allowFallbackClass'] ?? $this->allowFallbackClass;
$className = $this->_getClassName($alias, $options);
if ($className) {
$options['className'] = $className;
- } else {
+ } elseif ($allowFallbackClass) {
if (empty($options['className'])) {
$options['className'] = $alias;
}
@@ -195,7 +235,14 @@ public function get(string $alias, array $options = []): Table
[, $table] = pluginSplit($options['className']);
$options['table'] = Inflector::underscore($table);
}
- $options['className'] = Table::class;
+ $options['className'] = $this->fallbackClassName;
+ } else {
+ $message = $options['className'] ?? $alias;
+ $message = '`' . $message . '`';
+ if (strpos($message, '\\') === false) {
+ $message = 'for alias ' . $message;
+ }
+ throw new MissingTableClassException([$message]);
}
if (empty($options['connection'])) {
@@ -214,13 +261,13 @@ public function get(string $alias, array $options = []): Table
}
$options['registryAlias'] = $alias;
- $this->_instances[$alias] = $this->_create($options);
+ $instance = $this->_create($options);
- if ($options['className'] === Table::class) {
- $this->_fallbacked[$alias] = $this->_instances[$alias];
+ if ($options['className'] === $this->fallbackClassName) {
+ $this->_fallbacked[$alias] = $instance;
}
- return $this->_instances[$alias];
+ return $instance;
}
/**
@@ -258,25 +305,21 @@ protected function _getClassName(string $alias, array $options = []): ?string
*/
protected function _create(array $options): Table
{
- // phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat
/** @var \Cake\ORM\Table */
return new $options['className']($options);
}
/**
- * @inheritDoc
- */
- public function exists(string $alias): bool
- {
- return isset($this->_instances[$alias]);
- }
-
- /**
- * @inheritDoc
+ * Set a Table instance.
+ *
+ * @param string $alias The alias to set.
+ * @param \Cake\ORM\Table $repository The Table to set.
+ * @return \Cake\ORM\Table
+ * @psalm-suppress MoreSpecificImplementedParamType
*/
- public function set(string $alias, Table $object): Table
+ public function set(string $alias, RepositoryInterface $repository): Table
{
- return $this->_instances[$alias] = $object;
+ return $this->instances[$alias] = $repository;
}
/**
@@ -284,10 +327,10 @@ public function set(string $alias, Table $object): Table
*/
public function clear(): void
{
- $this->_instances = [];
- $this->_config = [];
+ parent::clear();
+
$this->_fallbacked = [];
- $this->_options = [];
+ $this->_config = [];
}
/**
@@ -308,11 +351,9 @@ public function genericInstances(): array
*/
public function remove(string $alias): void
{
- unset(
- $this->_instances[$alias],
- $this->_config[$alias],
- $this->_fallbacked[$alias]
- );
+ parent::remove($alias);
+
+ unset($this->_fallbacked[$alias]);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php b/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php
index 5586b4b17..ad3e1429e 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php
@@ -72,6 +72,7 @@ protected function _buildPropertyMap(array $data, array $options): array
// Is a concrete column?
foreach (array_keys($data) as $prop) {
+ $prop = (string)$prop;
$columnType = $schema->getColumnType($prop);
if ($columnType) {
$map[$prop] = function ($value, $entity) use ($columnType) {
@@ -143,7 +144,7 @@ protected function _buildPropertyMap(array $data, array $options): array
* - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied.
* Defaults to true/default.
* - associated: Associations listed here will be marshalled as well. Defaults to null.
- * - fields: A whitelist of fields to be assigned to the entity. If not present,
+ * - fields: An allowed list of fields to be assigned to the entity. If not present,
* the accessible fields list in the entity will be used. Defaults to null.
* - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null
* - forceNew: When enabled, belongsToMany associations will have 'new' entities created
@@ -180,7 +181,6 @@ public function one(array $data, array $options = []): EntityInterface
$primaryKey = (array)$this->_table->getPrimaryKey();
$entityClass = $this->_table->getEntityClass();
- /** @var \Cake\Datasource\EntityInterface $entity */
$entity = new $entityClass();
$entity->setSource($this->_table->getRegistryAlias());
@@ -232,6 +232,7 @@ public function one(array $data, array $options = []): EntityInterface
}
$entity->setErrors($errors);
+ $this->dispatchAfterMarshal($entity, $data, $options);
return $entity;
}
@@ -312,7 +313,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option
$types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE];
$type = $assoc->type();
if (in_array($type, $types, true)) {
- return $marshaller->one($value, (array)$options);
+ return $marshaller->one($value, $options);
}
if ($type === Association::ONE_TO_MANY || $type === Association::MANY_TO_MANY) {
$hasIds = array_key_exists('_ids', $value);
@@ -326,10 +327,11 @@ protected function _marshalAssociation(Association $assoc, $value, array $option
}
}
if ($type === Association::MANY_TO_MANY) {
- return $marshaller->_belongsToMany($assoc, $value, (array)$options);
+ /** @psalm-suppress ArgumentTypeCoercion */
+ return $marshaller->_belongsToMany($assoc, $value, $options);
}
- return $marshaller->many($value, (array)$options);
+ return $marshaller->many($value, $options);
}
/**
@@ -340,7 +342,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option
* - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied.
* Defaults to true/default.
* - associated: Associations listed here will be marshalled as well. Defaults to null.
- * - fields: A whitelist of fields to be assigned to the entity. If not present,
+ * - fields: An allowed list of fields to be assigned to the entity. If not present,
* the accessible fields list in the entity will be used. Defaults to null.
* - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null
* - forceNew: When enabled, belongsToMany associations will have 'new' entities created
@@ -487,7 +489,12 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array
if (!is_array($first) || count($first) !== count($primaryKey)) {
return [];
}
- $filter = new TupleComparison($primaryKey, $ids, [], 'IN');
+ $type = [];
+ $schema = $target->getSchema();
+ foreach ((array)$target->getPrimaryKey() as $column) {
+ $type[] = $schema->getColumnType($column);
+ }
+ $filter = new TupleComparison($primaryKey, $ids, $type, 'IN');
} else {
$filter = [$primaryKey[0] . ' IN' => $ids];
}
@@ -511,7 +518,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array
* - associated: Associations listed here will be marshalled as well.
* - validate: Whether or not to validate data before hydrating the entities. Can
* also be set to a string to use a specific validator. Defaults to true/default.
- * - fields: A whitelist of fields to be assigned to the entity. If not present
+ * - fields: An allowed list of fields to be assigned to the entity. If not present
* the accessible fields list in the entity will be used.
* - accessibleFields: A list of fields to allow or deny in entity accessible fields.
*
@@ -581,7 +588,7 @@ public function merge(EntityInterface $entity, array $data, array $options = [])
|| (
is_object($value)
&& !($value instanceof EntityInterface)
- && $original === $value
+ && $original == $value
)
) {
continue;
@@ -599,6 +606,7 @@ public function merge(EntityInterface $entity, array $data, array $options = [])
$entity->setDirty($field, $value->isDirty());
}
}
+ $this->dispatchAfterMarshal($entity, $data, $options);
return $entity;
}
@@ -612,6 +620,7 @@ public function merge(EntityInterface $entity, array $data, array $options = [])
$entity->setDirty($field, $properties[$field]->isDirty());
}
}
+ $this->dispatchAfterMarshal($entity, $data, $options);
return $entity;
}
@@ -636,7 +645,7 @@ public function merge(EntityInterface $entity, array $data, array $options = [])
* - validate: Whether or not to validate data before hydrating the entities. Can
* also be set to a string to use a specific validator. Defaults to true/default.
* - associated: Associations listed here will be marshalled as well.
- * - fields: A whitelist of fields to be assigned to the entity. If not present,
+ * - fields: An allowed list of fields to be assigned to the entity. If not present,
* the accessible fields list in the entity will be used.
* - accessibleFields: A list of fields to allow or deny in entity accessible fields.
*
@@ -666,9 +675,8 @@ public function mergeMany(iterable $entities, array $data, array $options = []):
})
->toArray();
- $new = $indexed[null] ?? [];
- /** @psalm-suppress PossiblyNullArrayOffset */
- unset($indexed[null]);
+ $new = $indexed[''] ?? [];
+ unset($indexed['']);
$output = [];
foreach ($entities as $entity) {
@@ -743,12 +751,12 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra
$types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE];
$type = $assoc->type();
if (in_array($type, $types, true)) {
- /** @psalm-suppress PossiblyInvalidArgument */
- return $marshaller->merge($original, $value, (array)$options);
+ /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */
+ return $marshaller->merge($original, $value, $options);
}
if ($type === Association::MANY_TO_MANY) {
- /** @psalm-suppress PossiblyInvalidArgument */
- return $marshaller->_mergeBelongsToMany($original, $assoc, $value, (array)$options);
+ /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */
+ return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options);
}
if ($type === Association::ONE_TO_MANY) {
@@ -763,7 +771,7 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra
}
/** @psalm-suppress PossiblyInvalidArgument */
- return $marshaller->mergeMany($original, $value, (array)$options);
+ return $marshaller->mergeMany($original, $value, $options);
}
/**
@@ -857,4 +865,19 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $
return $records;
}
+
+ /**
+ * dispatch Model.afterMarshal event.
+ *
+ * @param \Cake\Datasource\EntityInterface $entity The entity that was marshaled.
+ * @param array $data readOnly $data to use.
+ * @param array $options List of options that are readOnly.
+ * @return void
+ */
+ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, array $options = []): void
+ {
+ $data = new ArrayObject($data);
+ $options = new ArrayObject($options);
+ $this->_table->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options'));
+ }
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query.php b/app/vendor/cakephp/cakephp/src/ORM/Query.php
index de6ac1033..d7dba8ece 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Query.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Query.php
@@ -43,7 +43,7 @@
* @method \Cake\ORM\Table getRepository() Returns the default table object that will be used by this query,
* that is, the table that will appear in the from clause.
* @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable
- * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir, int $type) Sorts the query with the callback
+ * @method \Cake\Collection\CollectionInterface sortBy($callback, int $dir) Sorts the query with the callback
* @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test
* @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test
* @method bool every(callable $c) Returns true if all the results pass the callable test
@@ -51,8 +51,8 @@
* @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable
* @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable.
* @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row
- * @method mixed max($field, int $type) Returns the maximum value for a single column in all the results.
- * @method mixed min($field, int $type) Returns the minimum value for a single column in all the results.
+ * @method mixed max($field) Returns the maximum value for a single column in all the results.
+ * @method mixed min($field) Returns the minimum value for a single column in all the results.
* @method \Cake\Collection\CollectionInterface groupBy(string|callable $field) In-memory group all results by the value of a column.
* @method \Cake\Collection\CollectionInterface indexBy(string|callable $callback) Returns the results indexed by the value of a column.
* @method \Cake\Collection\CollectionInterface countBy(string|callable $field) Returns the number of unique values for a column
@@ -130,6 +130,13 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
*/
protected $_hydrate = true;
+ /**
+ * Whether aliases are generated for fields.
+ *
+ * @var bool
+ */
+ protected $aliasingEnabled = true;
+
/**
* A callable function that can be used to calculate the total amount of
* records this query will match when not using `limit`
@@ -225,7 +232,11 @@ public function select($fields = [], bool $overwrite = false)
}
if ($fields instanceof Table) {
- $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias());
+ if ($this->aliasingEnabled) {
+ $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias());
+ } else {
+ $fields = $fields->getSchema()->columns();
+ }
}
return parent::select($fields, $overwrite);
@@ -255,9 +266,11 @@ public function selectAllExcept($table, array $excludedFields, bool $overwrite =
}
$fields = array_diff($table->getSchema()->columns(), $excludedFields);
- $aliasedFields = $this->aliasFields($fields);
+ if ($this->aliasingEnabled) {
+ $fields = $this->aliasFields($fields);
+ }
- return $this->select($aliasedFields, $overwrite);
+ return $this->select($fields, $overwrite);
}
/**
@@ -513,7 +526,7 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr
* // Bring only articles that were tagged with 'cake'
* $query->matching('Tags', function ($q) {
* return $q->where(['name' => 'cake']);
- * );
+ * });
* ```
*
* It is possible to filter by deep associations by using dot notation:
@@ -524,7 +537,7 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr
* // Bring only articles that were commented by 'markstory'
* $query->matching('Comments.Users', function ($q) {
* return $q->where(['username' => 'markstory']);
- * );
+ * });
* ```
*
* As this function will create `INNER JOIN`, you might want to consider
@@ -537,9 +550,9 @@ protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, arr
* ```
* // Bring unique articles that were commented by 'markstory'
* $query->distinct(['Articles.id'])
- * ->matching('Comments.Users', function ($q) {
- * return $q->where(['username' => 'markstory']);
- * );
+ * ->matching('Comments.Users', function ($q) {
+ * return $q->where(['username' => 'markstory']);
+ * });
* ```
*
* Please note that the query passed to the closure will only accept calling
@@ -607,11 +620,11 @@ public function matching(string $assoc, ?callable $builder = null)
* ```
* // Total comments in articles by 'markstory'
* $query
- * ->select(['total_comments' => $query->func()->count('Comments.id')])
- * ->leftJoinWith('Comments.Users', function ($q) {
- * return $q->where(['username' => 'markstory']);
- * )
- * ->group(['Users.id']);
+ * ->select(['total_comments' => $query->func()->count('Comments.id')])
+ * ->leftJoinWith('Comments.Users', function ($q) {
+ * return $q->where(['username' => 'markstory']);
+ * })
+ * ->group(['Users.id']);
* ```
*
* Please note that the query passed to the closure will only accept calling
@@ -650,7 +663,7 @@ public function leftJoinWith(string $assoc, ?callable $builder = null)
* // Bring only articles that were tagged with 'cake'
* $query->innerJoinWith('Tags', function ($q) {
* return $q->where(['name' => 'cake']);
- * );
+ * });
* ```
*
* This will create the following SQL:
@@ -698,7 +711,7 @@ public function innerJoinWith(string $assoc, ?callable $builder = null)
* // Bring only articles that were not tagged with 'cake'
* $query->notMatching('Tags', function ($q) {
* return $q->where(['name' => 'cake']);
- * );
+ * });
* ```
*
* It is possible to filter by deep associations by using dot notation:
@@ -709,7 +722,7 @@ public function innerJoinWith(string $assoc, ?callable $builder = null)
* // Bring only articles that weren't commented by 'markstory'
* $query->notMatching('Comments.Users', function ($q) {
* return $q->where(['username' => 'markstory']);
- * );
+ * });
* ```
*
* As this function will create a `LEFT JOIN`, you might want to consider
@@ -722,9 +735,9 @@ public function innerJoinWith(string $assoc, ?callable $builder = null)
* ```
* // Bring unique articles that were commented by 'markstory'
* $query->distinct(['Articles.id'])
- * ->notMatching('Comments.Users', function ($q) {
- * return $q->where(['username' => 'markstory']);
- * );
+ * ->notMatching('Comments.Users', function ($q) {
+ * return $q->where(['username' => 'markstory']);
+ * });
* ```
*
* Please note that the query passed to the closure will only accept calling
@@ -753,7 +766,9 @@ public function notMatching(string $assoc, ?callable $builder = null)
/**
* Populates or adds parts to current query clauses using an array.
- * This is handy for passing all query clauses at once. The option array accepts:
+ * This is handy for passing all query clauses at once.
+ *
+ * The method accepts the following query clause related options:
*
* - fields: Maps to the select method
* - conditions: Maps to the where method
@@ -766,6 +781,10 @@ public function notMatching(string $assoc, ?callable $builder = null)
* - join: Maps to the join method
* - page: Maps to the page method
*
+ * All other options will not affect the query, but will be stored
+ * as custom options that can be read via `getOptions()`. Furthermore
+ * they are automatically passed to `Model.beforeFind`.
+ *
* ### Example:
*
* ```
@@ -774,7 +793,7 @@ public function notMatching(string $assoc, ?callable $builder = null)
* 'conditions' => [
* 'created >=' => '2013-01-01'
* ],
- * 'limit' => 10
+ * 'limit' => 10,
* ]);
* ```
*
@@ -787,8 +806,26 @@ public function notMatching(string $assoc, ?callable $builder = null)
* ->limit(10)
* ```
*
- * @param array $options the options to be applied
+ * Custom options can be read via `getOptions()`:
+ *
+ * ```
+ * $query->applyOptions([
+ * 'fields' => ['id', 'name'],
+ * 'custom' => 'value',
+ * ]);
+ * ```
+ *
+ * Here `$options` will hold `['custom' => 'value']` (the `fields`
+ * option will be applied to the query instead of being stored, as
+ * it's a query clause related option):
+ *
+ * ```
+ * $options = $query->getOptions();
+ * ```
+ *
+ * @param array $options The options to be applied
* @return $this
+ * @see getOptions()
*/
public function applyOptions(array $options)
{
@@ -837,7 +874,6 @@ public function applyOptions(array $options)
public function cleanCopy()
{
$clone = clone $this;
- $clone->setEagerLoader(clone $this->getEagerLoader());
$clone->triggerBeforeFind();
$clone->disableAutoFields();
$clone->limit(null);
@@ -852,11 +888,22 @@ public function cleanCopy()
}
/**
- * Object clone hook.
+ * Clears the internal result cache and the internal count value from the current
+ * query object.
*
- * Destroys the clones inner iterator and clones the value binder, and eagerloader instances.
+ * @return $this
+ */
+ public function clearResult()
+ {
+ $this->_dirty();
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
*
- * @return void
+ * Handles cloning eager loaders.
*/
public function __clone()
{
@@ -1061,7 +1108,6 @@ public function triggerBeforeFind(): void
if (!$this->_beforeFindFired && $this->_type === 'select') {
$this->_beforeFindFired = true;
- /** @var \Cake\Event\EventDispatcherInterface $repository */
$repository = $this->getRepository();
$repository->dispatchEvent('Model.beforeFind', [
$this,
@@ -1074,13 +1120,13 @@ public function triggerBeforeFind(): void
/**
* @inheritDoc
*/
- public function sql(?ValueBinder $generator = null): string
+ public function sql(?ValueBinder $binder = null): string
{
$this->triggerBeforeFind();
$this->_transformQuery();
- return parent::sql($generator);
+ return parent::sql($binder);
}
/**
@@ -1122,7 +1168,6 @@ protected function _transformQuery(): void
return;
}
- /** @var \Cake\ORM\Table $repository */
$repository = $this->getRepository();
if (empty($this->_parts['from'])) {
@@ -1144,7 +1189,6 @@ protected function _addDefaultFields(): void
$select = $this->clause('select');
$this->_hasFields = true;
- /** @var \Cake\ORM\Table $repository */
$repository = $this->getRepository();
if (!count($select) || $this->_autoFields === true) {
@@ -1153,8 +1197,10 @@ protected function _addDefaultFields(): void
$select = $this->clause('select');
}
- $aliased = $this->aliasFields($select, $repository->getAlias());
- $this->select($aliased, true);
+ if ($this->aliasingEnabled) {
+ $select = $this->aliasFields($select, $repository->getAlias());
+ }
+ $this->select($select, true);
}
/**
@@ -1193,7 +1239,6 @@ protected function _addDefaultSelectTypes(): void
*/
public function find(string $finder, array $options = [])
{
- /** @var \Cake\ORM\Table $table */
$table = $this->getRepository();
/** @psalm-suppress LessSpecificReturnStatement */
@@ -1225,7 +1270,6 @@ protected function _dirty(): void
public function update($table = null)
{
if (!$table) {
- /** @var \Cake\ORM\Table $repository */
$repository = $this->getRepository();
$table = $repository->getTable();
}
@@ -1244,7 +1288,6 @@ public function update($table = null)
*/
public function delete(?string $table = null)
{
- /** @var \Cake\ORM\Table $repository */
$repository = $this->getRepository();
$this->from([$repository->getAlias() => $repository->getTable()]);
@@ -1267,7 +1310,6 @@ public function delete(?string $table = null)
*/
public function insert(array $columns, array $types = [])
{
- /** @var \Cake\ORM\Table $repository */
$repository = $this->getRepository();
$table = $repository->getTable();
$this->into($table);
@@ -1275,6 +1317,20 @@ public function insert(array $columns, array $types = [])
return parent::insert($columns, $types);
}
+ /**
+ * Returns a new Query that has automatic field aliasing disabled.
+ *
+ * @param \Cake\ORM\Table $table The table this query is starting on
+ * @return static
+ */
+ public static function subquery(Table $table)
+ {
+ $query = new static($table->getConnection(), $table);
+ $query->aliasingEnabled = false;
+
+ return $query;
+ }
+
/**
* {@inheritDoc}
*
@@ -1378,7 +1434,6 @@ protected function _decorateResults(Traversable $result): ResultSetInterface
if (!($result instanceof ResultSet) && $this->isBufferedResultsEnabled()) {
$class = $this->_decoratorClass();
- /** @var \Cake\Datasource\ResultSetInterface $result */
$result = new $class($result->buffered());
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/README.md b/app/vendor/cakephp/cakephp/src/ORM/README.md
index 077b5fd5c..5af443ac1 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/README.md
+++ b/app/vendor/cakephp/cakephp/src/ORM/README.md
@@ -59,7 +59,7 @@ use Cake\ORM\Locator\LocatorAwareTrait;
$articles = $this->getTableLocator()->get('Articles');
```
-By default classes using `LocatorAwareTrait` will share a global locator instance.
+By default, classes using `LocatorAwareTrait` will share a global locator instance.
You can inject your own locator instance into the object:
```php
@@ -78,8 +78,8 @@ In your table classes you can define the relations between your tables. CakePHP'
supports 4 association types out of the box:
* belongsTo - E.g. Many articles belong to a user.
-* hasOne - E.g. A user has one profile
-* hasMany - E.g. A user has many articles
+* hasOne - E.g. A user has one profile.
+* hasMany - E.g. A user has many articles.
* belongsToMany - E.g. An article belongsToMany tags.
You define associations in your table's `initialize()` method. See the
@@ -149,7 +149,7 @@ $articles->delete($article);
## Meta Data Cache
-It is recommended to enable meta data cache for production systems to avoid performance issues.
+It is recommended to enable metadata cache for production systems to avoid performance issues.
For e.g. file system strategy your bootstrap file could look like this:
```php
@@ -164,7 +164,7 @@ $cacheConfig = [
Cache::setConfig('_cake_model_', $cacheConfig);
```
-Cache configs are optional so you must require ``cachephp/cache`` to add one.
+Cache configs are optional, so you must require ``cachephp/cache`` to add one.
## Creating Custom Table and Entity Classes
diff --git a/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php b/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php
index b5b0333bb..147322b17 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php
@@ -18,7 +18,7 @@
use Cake\Collection\Collection;
use Cake\Collection\CollectionTrait;
-use Cake\Database\Exception;
+use Cake\Database\Exception\DatabaseException;
use Cake\Database\StatementInterface;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
@@ -159,7 +159,6 @@ class ResultSet implements ResultSetInterface
*/
public function __construct(Query $query, StatementInterface $statement)
{
- /** @var \Cake\ORM\Table $repository */
$repository = $query->getRepository();
$this->_statement = $statement;
$this->_driver = $query->getConnection()->getDriver();
@@ -219,7 +218,7 @@ public function next(): void
*
* Part of Iterator interface.
*
- * @throws \Cake\Database\Exception
+ * @throws \Cake\Database\Exception\DatabaseException
* @return void
*/
public function rewind(): void
@@ -231,7 +230,7 @@ public function rewind(): void
if (!$this->_useBuffering) {
$msg = 'You cannot rewind an un-buffered ResultSet. '
. 'Use Query::bufferResults() to get a buffered ResultSet.';
- throw new Exception($msg);
+ throw new DatabaseException($msg);
}
$this->_index = 0;
@@ -303,7 +302,7 @@ public function serialize(): string
if (!$this->_useBuffering) {
$msg = 'You cannot serialize an un-buffered ResultSet. '
. 'Use Query::bufferResults() to get a buffered ResultSet.';
- throw new Exception($msg);
+ throw new DatabaseException($msg);
}
while ($this->valid()) {
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php b/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php
index 76ea4a015..24cfeae05 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php
@@ -31,20 +31,28 @@ class IsUnique
protected $_fields;
/**
- * The options to use.
+ * The unique check options
*
* @var array
*/
- protected $_options;
+ protected $_options = [
+ 'allowMultipleNulls' => false,
+ ];
/**
* Constructor.
*
+ * ### Options
+ *
+ * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false.
+ *
* @param string[] $fields The list of fields to check uniqueness for
+ * @param array $options The options for unique checks.
*/
- public function __construct(array $fields)
+ public function __construct(array $fields, array $options = [])
{
$this->_fields = $fields;
+ $this->_options = $options + $this->_options;
}
/**
@@ -61,8 +69,13 @@ public function __invoke(EntityInterface $entity, array $options): bool
return true;
}
+ $fields = $entity->extract($this->_fields);
+ if ($this->_options['allowMultipleNulls'] && array_filter($fields, 'is_null')) {
+ return true;
+ }
+
$alias = $options['repository']->getAlias();
- $conditions = $this->_alias($alias, $entity->extract($this->_fields));
+ $conditions = $this->_alias($alias, $fields);
if ($entity->isNew() === false) {
$keys = (array)$options['repository']->getPrimaryKey();
$keys = $this->_alias($alias, $entity->extract($keys));
diff --git a/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php b/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php
index bb12b95da..561843225 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php
@@ -37,12 +37,16 @@ class RulesChecker extends BaseRulesChecker
* Returns a callable that can be used as a rule for checking the uniqueness of a value
* in the table.
*
- * ### Example:
+ * ### Example
*
* ```
* $rules->add($rules->isUnique(['email'], 'The email should be unique'));
* ```
*
+ * ### Options
+ *
+ * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false.
+ *
* @param string[] $fields The list of fields to check for uniqueness.
* @param string|array|null $message The error message to show in case the rule does not pass. Can
* also be an array of options. When an array, the 'message' key can be used to provide a message.
@@ -50,6 +54,10 @@ class RulesChecker extends BaseRulesChecker
*/
public function isUnique(array $fields, $message = null): RuleInvoker
{
+ $options = is_array($message) ? $message : ['message' => $message];
+ $message = $options['message'] ?? null;
+ unset($options['message']);
+
if (!$message) {
if ($this->_useI18n) {
$message = __d('cake', 'This value is already in use');
@@ -60,7 +68,7 @@ public function isUnique(array $fields, $message = null): RuleInvoker
$errorField = current($fields);
- return $this->_addError(new IsUnique($fields), '_isUnique', compact('errorField', 'message'));
+ return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message'));
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php b/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php
index 5553e4e48..12270d58e 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php
@@ -141,7 +141,7 @@ protected function _checkAssociation(Table $table, string $association): void
*/
public function guard(bool $guard)
{
- $this->_options['guard'] = (bool)$guard;
+ $this->_options['guard'] = $guard;
return $this;
}
@@ -168,7 +168,7 @@ public function validate(string $validate)
*/
public function checkExisting(bool $checkExisting)
{
- $this->_options['checkExisting'] = (bool)$checkExisting;
+ $this->_options['checkExisting'] = $checkExisting;
return $this;
}
@@ -181,7 +181,7 @@ public function checkExisting(bool $checkExisting)
*/
public function checkRules(bool $checkRules)
{
- $this->_options['checkRules'] = (bool)$checkRules;
+ $this->_options['checkRules'] = $checkRules;
return $this;
}
@@ -194,7 +194,7 @@ public function checkRules(bool $checkRules)
*/
public function atomic(bool $atomic)
{
- $this->_options['atomic'] = (bool)$atomic;
+ $this->_options['atomic'] = $atomic;
return $this;
}
diff --git a/app/vendor/cakephp/cakephp/src/ORM/Table.php b/app/vendor/cakephp/cakephp/src/ORM/Table.php
index 890777559..bc837d5b1 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/Table.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/Table.php
@@ -125,6 +125,8 @@
* lifecycle methods below:
*
* - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)`
+ * - `beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)`
+ * - `afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options)`
* - `buildValidator(EventInterface $event, Validator $validator, string $name)`
* - `buildRules(RulesChecker $rules)`
* - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)`
@@ -134,8 +136,10 @@
* - `afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)`
* - `beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)`
* - `afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)`
+ * - `afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)`
*
* @see \Cake\Event\EventManager for reference on the events system.
+ * @link https://book.cakephp.org/4/en/orm/table-objects.html#event-list
*/
class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface
{
@@ -181,7 +185,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
/**
* Name of the table as it can be found in the database
*
- * @var string
+ * @var string|null
*/
protected $_table;
@@ -189,7 +193,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
* Human name giving to this particular instance. Multiple objects representing
* the same database table can exist by using different aliases.
*
- * @var string
+ * @var string|null
*/
protected $_alias;
@@ -210,14 +214,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
/**
* The name of the field that represents the primary key in the table
*
- * @var string|string[]
+ * @var string|string[]|null
*/
protected $_primaryKey;
/**
* The name of the field that represents a human readable representation of a row
*
- * @var string|string[]
+ * @var string|string[]|null
*/
protected $_displayField;
@@ -415,7 +419,7 @@ public function getAlias(): string
{
if ($this->_alias === null) {
$alias = namespaceSplit(static::class);
- $alias = substr(end($alias), 0, -5) ?: $this->_table;
+ $alias = substr(end($alias), 0, -5) ?: $this->getTable();
$this->_alias = $alias;
}
@@ -646,7 +650,7 @@ public function setPrimaryKey($key)
public function getPrimaryKey()
{
if ($this->_primaryKey === null) {
- $key = (array)$this->getSchema()->getPrimaryKey();
+ $key = $this->getSchema()->getPrimaryKey();
if (count($key) === 1) {
$key = $key[0];
}
@@ -856,7 +860,6 @@ public function getBehavior(string $name): Behavior
));
}
- /** @var \Cake\ORM\Behavior $behavior */
$behavior = $this->_behaviors->get($name);
return $behavior;
@@ -894,7 +897,13 @@ public function getAssociation(string $name): Association
{
$association = $this->findAssociation($name);
if (!$association) {
- throw new InvalidArgumentException("The {$name} association is not defined on {$this->getAlias()}.");
+ $assocations = $this->associations()->keys();
+
+ $message = "The `{$name}` association is not defined on `{$this->getAlias()}`.";
+ if ($assocations) {
+ $message .= "\nValid associations are: " . implode(', ', $assocations);
+ }
+ throw new InvalidArgumentException($message);
}
return $association;
@@ -1303,6 +1312,25 @@ public function findAll(Query $query, array $options): Query
* ]);
* ```
*
+ * The `valueField` can also be an array, in which case you can also specify
+ * the `valueSeparator` option to control how the values will be concatinated:
+ *
+ * ```
+ * $table->find('list', [
+ * 'valueField' => ['first_name', 'last_name'],
+ * 'valueSeparator' => ' | ',
+ * ]);
+ * ```
+ *
+ * The results of this finder will be in the following form:
+ *
+ * ```
+ * [
+ * 1 => 'John | Doe',
+ * 2 => 'Steve | Smith'
+ * ]
+ * ```
+ *
* Results can be put together in bigger groups when they share a property, you
* can customize the property to use for grouping by setting `groupField`:
*
@@ -1336,6 +1364,7 @@ public function findList(Query $query, array $options): Query
'keyField' => $this->getPrimaryKey(),
'valueField' => $this->getDisplayField(),
'groupField' => null,
+ 'valueSeparator' => ';',
];
if (
@@ -1436,13 +1465,14 @@ protected function _setFieldMatchers(array $options, array $keys): array
}
$fields = $options[$field];
- $options[$field] = function ($row) use ($fields) {
+ $glue = in_array($field, ['keyField', 'parentField'], true) ? ';' : $options['valueSeparator'];
+ $options[$field] = function ($row) use ($fields, $glue) {
$matches = [];
foreach ($fields as $field) {
$matches[] = $row[$field];
}
- return implode(';', $matches);
+ return implode($glue, $matches);
};
}
@@ -1673,6 +1703,17 @@ public function query(): Query
return new Query($this->getConnection(), $this);
}
+ /**
+ * Creates a new Query::subquery() instance for a table.
+ *
+ * @return \Cake\ORM\Query
+ * @see \Cake\ORM\Query::subquery()
+ */
+ public function subquery(): Query
+ {
+ return Query::subquery($this);
+ }
+
/**
* @inheritDoc
*/
@@ -2070,7 +2111,7 @@ protected function _insert(EntityInterface $entity, array $data)
*/
protected function _newId(array $primary)
{
- if (!$primary || count((array)$primary) > 1) {
+ if (!$primary || count($primary) > 1) {
return null;
}
/** @var string $typeName */
@@ -2133,9 +2174,9 @@ protected function _update(EntityInterface $entity, array $data)
* any one of the records fails to save due to failed validation or database
* error.
*
- * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save.
+ * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save.
* @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity.
- * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success.
+ * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false False on failure, entities list on success.
* @throws \Exception
*/
public function saveMany(iterable $entities, $options = [])
@@ -2154,9 +2195,9 @@ public function saveMany(iterable $entities, $options = [])
* any one of the records fails to save due to failed validation or database
* error.
*
- * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save.
+ * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save.
* @param array|\ArrayAccess $options Options used when calling Table::save() for each entity.
- * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list.
+ * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list.
* @throws \Exception
* @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved.
*/
@@ -2166,18 +2207,26 @@ public function saveManyOrFail(iterable $entities, $options = []): iterable
}
/**
- * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save.
+ * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to save.
* @param array|\ArrayAccess|\Cake\ORM\SaveOptionsBuilder $options Options used when calling Table::save() for each entity.
* @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved.
* @throws \Exception If an entity couldn't be saved.
- * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list.
+ * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list.
*/
protected function _saveMany(iterable $entities, $options = []): iterable
{
+ $options = new ArrayObject(
+ (array)$options + [
+ 'atomic' => true,
+ 'checkRules' => true,
+ '_primary' => true,
+ ]
+ );
+
/** @var bool[] $isNew */
$isNew = [];
$cleanup = function ($entities) use (&$isNew): void {
- /** @var \Cake\Datasource\EntityInterface[] $entities */
+ /** @var array<\Cake\Datasource\EntityInterface> $entities */
foreach ($entities as $key => $entity) {
if (isset($isNew[$key]) && $isNew[$key]) {
$entity->unset($this->getPrimaryKey());
@@ -2212,6 +2261,12 @@ protected function _saveMany(iterable $entities, $options = []): iterable
throw new PersistenceFailedException($failed, ['saveMany']);
}
+ if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) {
+ foreach ($entities as $entity) {
+ $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
+ }
+ }
+
return $entities;
}
@@ -2274,9 +2329,9 @@ public function delete(EntityInterface $entity, $options = []): bool
* any one of the records fails to delete due to failed validation or database
* error.
*
- * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete.
+ * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete.
* @param array|\ArrayAccess $options Options used when calling Table::save() for each entity.
- * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface|false Entities list
+ * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface|false Entities list
* on success, false on failure.
* @see \Cake\ORM\Table::delete() for options and events related to this method.
*/
@@ -2298,9 +2353,9 @@ public function deleteMany(iterable $entities, $options = [])
* any one of the records fails to delete due to failed validation or database
* error.
*
- * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete.
+ * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete.
* @param array|\ArrayAccess $options Options used when calling Table::save() for each entity.
- * @return \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface Entities list.
+ * @return array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface Entities list.
* @throws \Cake\ORM\Exception\PersistenceFailedException
* @see \Cake\ORM\Table::delete() for options and events related to this method.
*/
@@ -2316,7 +2371,7 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable
}
/**
- * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to delete.
+ * @param array<\Cake\Datasource\EntityInterface>|\Cake\Datasource\ResultSetInterface $entities Entities to delete.
* @param array|\ArrayAccess $options Options used.
* @return \Cake\Datasource\EntityInterface|null
*/
@@ -2407,13 +2462,16 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options)
return (bool)$event->getResult();
}
- $this->_associations->cascadeDelete(
+ $success = $this->_associations->cascadeDelete(
$entity,
['_primary' => false] + $options->getArrayCopy()
);
+ if (!$success) {
+ return $success;
+ }
$query = $this->query();
- $conditions = (array)$entity->extract($primaryKey);
+ $conditions = $entity->extract($primaryKey);
$statement = $query->delete()
->where($conditions)
->execute();
@@ -2475,7 +2533,7 @@ public function callFinder(string $type, Query $query, array $options = []): Que
}
/**
- * Provides the dynamic findBy and findByAll methods.
+ * Provides the dynamic findBy and findAllBy methods.
*
* @param string $method The method name that was fired.
* @param array $args List of arguments passed to the function.
@@ -2520,7 +2578,6 @@ protected function _dynamicFinder(string $method, array $args)
);
}
- $conditions = [];
if ($hasOr === false && $hasAnd === false) {
$conditions = $makeConditions([$fields], $args);
} elseif ($hasOr !== false) {
@@ -2682,6 +2739,7 @@ public function newEmptyEntity(): EntityInterface
* @param array $data The data to build an entity with.
* @param array $options A list of options for the object hydration.
* @return \Cake\Datasource\EntityInterface
+ * @see \Cake\ORM\Marshaller::one()
*/
public function newEntity(array $data, array $options = []): EntityInterface
{
@@ -2723,7 +2781,7 @@ public function newEntity(array $data, array $options = []): EntityInterface
*
* @param array $data The data to build an entity with.
* @param array $options A list of options for the objects hydration.
- * @return \Cake\Datasource\EntityInterface[] An array of hydrated records.
+ * @return array<\Cake\Datasource\EntityInterface> An array of hydrated records.
*/
public function newEntities(array $data, array $options = []): array
{
@@ -2784,6 +2842,7 @@ public function newEntities(array $data, array $options = []): array
* @param array $data key value list of fields to be merged into the entity
* @param array $options A list of options for the object hydration.
* @return \Cake\Datasource\EntityInterface
+ * @see \Cake\ORM\Marshaller::merge()
*/
public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface
{
@@ -2820,11 +2879,11 @@ public function patchEntity(EntityInterface $entity, array $data, array $options
* You can use the `Model.beforeMarshal` event to modify request data
* before it is converted into entities.
*
- * @param \Cake\Datasource\EntityInterface[]|\Traversable $entities the entities that will get the
+ * @param array<\Cake\Datasource\EntityInterface>|\Traversable $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.
- * @return \Cake\Datasource\EntityInterface[]
+ * @return array<\Cake\Datasource\EntityInterface>
*/
public function patchEntities(iterable $entities, array $data, array $options = []): array
{
@@ -2912,6 +2971,7 @@ public function validateUnique($value, array $options, ?array $context = null):
* The conventional method map is:
*
* - Model.beforeMarshal => beforeMarshal
+ * - Model.afterMarshal => afterMarshal
* - Model.buildValidator => buildValidator
* - Model.beforeFind => beforeFind
* - Model.beforeSave => beforeSave
@@ -2929,6 +2989,7 @@ public function implementedEvents(): array
{
$eventMap = [
'Model.beforeMarshal' => 'beforeMarshal',
+ 'Model.afterMarshal' => 'afterMarshal',
'Model.buildValidator' => 'buildValidator',
'Model.beforeFind' => 'beforeFind',
'Model.beforeSave' => 'beforeSave',
@@ -2999,10 +3060,10 @@ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder
*
* The properties for the associations to be loaded will be overwritten on each entity.
*
- * @param \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[] $entities a single entity or list of entities
+ * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $entities a single entity or list of entities
* @param array $contain A `contain()` compatible array.
* @see \Cake\ORM\Query::contain()
- * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]
+ * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>
*/
public function loadInto($entities, array $contain)
{
diff --git a/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php b/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php
index 779003100..52a0c42ae 100644
--- a/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php
+++ b/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php
@@ -16,6 +16,7 @@
*/
namespace Cake\ORM;
+use Cake\Datasource\FactoryLocator;
use Cake\ORM\Locator\LocatorInterface;
/**
@@ -57,21 +58,6 @@
*/
class TableRegistry
{
- /**
- * LocatorInterface implementation instance.
- *
- * @var \Cake\ORM\Locator\LocatorInterface
- */
- protected static $_locator;
-
- /**
- * Default LocatorInterface implementation class.
- *
- * @var string
- * @psalm-var class-string<\Cake\ORM\Locator\TableLocator>
- */
- protected static $_defaultLocatorClass = Locator\TableLocator::class;
-
/**
* Returns a singleton instance of LocatorInterface implementation.
*
@@ -79,11 +65,8 @@ class TableRegistry
*/
public static function getTableLocator(): LocatorInterface
{
- if (static::$_locator === null) {
- static::$_locator = new static::$_defaultLocatorClass();
- }
-
- return static::$_locator;
+ /** @var \Cake\ORM\Locator\LocatorInterface */
+ return FactoryLocator::get('Table');
}
/**
@@ -94,7 +77,7 @@ public static function getTableLocator(): LocatorInterface
*/
public static function setTableLocator(LocatorInterface $tableLocator): void
{
- static::$_locator = $tableLocator;
+ FactoryLocator::add('Table', $tableLocator);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Asset.php b/app/vendor/cakephp/cakephp/src/Routing/Asset.php
index eac796eaa..2e5e1215c 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Asset.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Asset.php
@@ -214,13 +214,15 @@ public static function url(string $path, array $options = []): string
protected static function encodeUrl(string $url): string
{
$path = parse_url($url, PHP_URL_PATH);
+ if ($path === false) {
+ $path = $url;
+ }
+
$parts = array_map('rawurldecode', explode('/', $path));
$parts = array_map('rawurlencode', $parts);
$encoded = implode('/', $parts);
- $url = str_replace($path, $encoded, $url);
-
- return $url;
+ return str_replace($path, $encoded, $url);
}
/**
@@ -249,18 +251,23 @@ public static function assetTimestamp(string $path, $timestamp = null): string
urldecode($path)
);
$webrootPath = Configure::read('App.wwwRoot') . str_replace('/', DIRECTORY_SEPARATOR, $filepath);
- if (file_exists($webrootPath)) {
+ if (is_file($webrootPath)) {
return $path . '?' . filemtime($webrootPath);
}
+ // Check for plugins and org prefixed plugins.
$segments = explode('/', ltrim($filepath, '/'));
$plugin = Inflector::camelize($segments[0]);
+ if (!Plugin::isLoaded($plugin) && count($segments) > 1) {
+ $plugin = implode('/', [$plugin, Inflector::camelize($segments[1])]);
+ unset($segments[1]);
+ }
if (Plugin::isLoaded($plugin)) {
unset($segments[0]);
$pluginPath = Plugin::path($plugin)
. 'webroot'
. DIRECTORY_SEPARATOR
. implode(DIRECTORY_SEPARATOR, $segments);
- if (file_exists($pluginPath)) {
+ if (is_file($pluginPath)) {
return $path . '?' . filemtime($pluginPath);
}
}
@@ -299,12 +306,12 @@ public static function webroot(string $file, array $options = []): string
$file = str_replace('/', '\\', $file);
}
- if (file_exists(Configure::read('App.wwwRoot') . $theme . $file)) {
+ if (is_file(Configure::read('App.wwwRoot') . $theme . $file)) {
$webPath = $requestWebroot . $theme . $asset[0];
} else {
$themePath = Plugin::path($themeName);
$path = $themePath . 'webroot/' . $file;
- if (file_exists($path)) {
+ if (is_file($path)) {
$webPath = $requestWebroot . $theme . $asset[0];
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php b/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php
index 93372e0c2..4bff190d6 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php
@@ -14,13 +14,13 @@
*/
namespace Cake\Routing\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Throwable;
/**
* Exception raised when a route names used twice.
*/
-class DuplicateNamedRouteException extends Exception
+class DuplicateNamedRouteException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingDispatcherFilterException.php b/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingDispatcherFilterException.php
index 6baa91ec4..d3b6985ff 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingDispatcherFilterException.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingDispatcherFilterException.php
@@ -14,12 +14,12 @@
*/
namespace Cake\Routing\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Exception raised when a Dispatcher filter could not be found
*/
-class MissingDispatcherFilterException extends Exception
+class MissingDispatcherFilterException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingRouteException.php b/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingRouteException.php
index dacb615e2..b731c61b7 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingRouteException.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Exception/MissingRouteException.php
@@ -14,14 +14,14 @@
*/
namespace Cake\Routing\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Throwable;
/**
* Exception raised when a URL cannot be reverse routed
* or when a URL cannot be parsed.
*/
-class MissingRouteException extends Exception
+class MissingRouteException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Exception/RedirectException.php b/app/vendor/cakephp/cakephp/src/Routing/Exception/RedirectException.php
index d000b4b4d..ff1beffdc 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Exception/RedirectException.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Exception/RedirectException.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Routing\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* An exception subclass used by the routing layer to indicate
@@ -27,8 +27,13 @@
* ```
* throw new RedirectException('http://example.com/some/path', 301);
* ```
+ *
+ * If you need a more general purpose redirect exception use
+ * Cake\Http\Exception\RedirectException instead of this class.
+ *
+ * @deprecated 4.1.0 Use Cake\Http\Exception\RedirectException instead.
*/
-class RedirectException extends Exception
+class RedirectException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php b/app/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php
index c9ed66f5a..0eb2aee56 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php
@@ -73,7 +73,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}
$assetFile = $this->_getAssetFile($url);
- if ($assetFile === null || !file_exists($assetFile)) {
+ if ($assetFile === null || !is_file($assetFile)) {
return $handler->handle($request);
}
@@ -158,8 +158,8 @@ protected function deliverAsset(ServerRequestInterface $request, SplFileInfo $fi
return $response
->withHeader('Content-Type', $contentType)
->withHeader('Cache-Control', 'public,max-age=' . $maxAge)
- ->withHeader('Date', gmdate('D, j M Y G:i:s \G\M\T', time()))
- ->withHeader('Last-Modified', gmdate('D, j M Y G:i:s \G\M\T', $modified))
- ->withHeader('Expires', gmdate('D, j M Y G:i:s \G\M\T', $expire));
+ ->withHeader('Date', gmdate(DATE_RFC7231, time()))
+ ->withHeader('Last-Modified', gmdate(DATE_RFC7231, $modified))
+ ->withHeader('Expires', gmdate(DATE_RFC7231, $expire));
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php b/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php
index 04ddeef8f..66d56a80b 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php
@@ -18,9 +18,10 @@
use Cake\Cache\Cache;
use Cake\Core\PluginApplicationInterface;
+use Cake\Http\Exception\RedirectException;
use Cake\Http\MiddlewareQueue;
use Cake\Http\Runner;
-use Cake\Routing\Exception\RedirectException;
+use Cake\Routing\Exception\RedirectException as DeprecatedRedirectException;
use Cake\Routing\RouteCollection;
use Cake\Routing\Router;
use Cake\Routing\RoutingApplicationInterface;
@@ -135,11 +136,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
$params = (array)$request->getAttribute('params', []);
$middleware = [];
if (empty($params['controller'])) {
- $parsedBody = $request->getParsedBody();
- if (is_array($parsedBody) && isset($parsedBody['_method'])) {
- /** @var \Cake\Http\ServerRequest $request */
- $request = $request->withMethod($parsedBody['_method']);
- }
$params = Router::parseRequest($request) + $params;
if (isset($params['_middleware'])) {
$middleware = $params['_middleware'];
@@ -152,7 +148,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
} catch (RedirectException $e) {
return new RedirectResponse(
$e->getMessage(),
- (int)$e->getCode()
+ $e->getCode()
+ );
+ } catch (DeprecatedRedirectException $e) {
+ return new RedirectResponse(
+ $e->getMessage(),
+ $e->getCode()
);
}
$matching = Router::getRouteCollection()->getMiddleware($middleware);
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php b/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php
index 3db3b4b9f..3be731f98 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Routing\Route;
-use Cake\Routing\Exception\RedirectException;
+use Cake\Http\Exception\RedirectException;
use Cake\Routing\Router;
/**
@@ -59,7 +59,7 @@ public function __construct(string $template, array $defaults = [], array $optio
* @param string $url The URL to parse.
* @param string $method The HTTP method being used.
* @return array|null Null on failure. An exception is raised on a successful match. Array return type is unused.
- * @throws \Cake\Routing\Exception\RedirectException An exception is raised on successful match.
+ * @throws \Cake\Http\Exception\RedirectException An exception is raised on successful match.
* This is used to halt route matching and signal to the middleware that a redirect should happen.
*/
public function parse(string $url, string $method = ''): ?array
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php b/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php
index a70ae0cc6..4e55b5796 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php
@@ -115,13 +115,17 @@ class Route
* - `_ext` - Defines the extensions used for this route.
* - `_middleware` - Define the middleware names for this route.
* - `pass` - Copies the listed parameters into params['pass'].
+ * - `_method` - Defines the HTTP method(s) the route applies to. It can be
+ * a string or array of valid HTTP method name.
* - `_host` - Define the host name pattern if you want this route to only match
* specific host names. You can use `.*` and to create wildcard subdomains/hosts
* e.g. `*.example.com` matches all subdomains on `example.com`.
+ * - '_port` - Define the port if you want this route to only match specific port number.
*
* @param string $template Template string with parameter placeholders
* @param array $defaults Defaults for the route.
* @param array $options Array of additional options for the Route
+ * @throws \InvalidArgumentException When `$options['_method']` are not in `VALID_METHODS` list.
*/
public function __construct(string $template, array $defaults = [], array $options = [])
{
@@ -168,7 +172,7 @@ public function getExtensions(): array
*
* @param string[] $methods The HTTP methods to accept.
* @return $this
- * @throws \InvalidArgumentException
+ * @throws \InvalidArgumentException When methods are not in `VALID_METHODS` list.
*/
public function setMethods(array $methods)
{
@@ -180,8 +184,9 @@ public function setMethods(array $methods)
/**
* Normalize method names to upper case and validate that they are valid HTTP methods.
*
- * @param string|array $methods Methods.
- * @return string|array
+ * @param string|string[] $methods Methods.
+ * @return string|string[]
+ * @throws \InvalidArgumentException When methods are not in `VALID_METHODS` list.
*/
protected function normalizeAndValidateMethods($methods)
{
@@ -291,6 +296,7 @@ public function compile(): string
$this->_writeRoute();
}
+ /** @var string */
return $this->_compiledRoute;
}
@@ -424,7 +430,7 @@ public function parseRequest(ServerRequestInterface $request): ?array
return null;
}
- return $this->parse($uri->getPath(), (string)$request->getMethod());
+ return $this->parse($uri->getPath(), $request->getMethod());
}
/**
@@ -436,6 +442,7 @@ public function parseRequest(ServerRequestInterface $request): ?array
* @param string $url The URL to attempt to parse.
* @param string $method The HTTP method of the request being parsed.
* @return array|null An array of request parameters, or null on failure.
+ * @throws \InvalidArgumentException When method is not an empty string or in `VALID_METHODS` list.
*/
public function parse(string $url, string $method): ?array
{
@@ -765,7 +772,7 @@ protected function _matchMethod(array $url): bool
$defaults = (array)$this->defaults['_method'];
$methods = (array)$this->normalizeAndValidateMethods($url['_method']);
foreach ($methods as $value) {
- if (in_array($value, (array)$this->defaults['_method'], true)) {
+ if (in_array($value, $defaults, true)) {
return true;
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php b/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php
index 426d903c7..04dc5e1ce 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php
@@ -596,7 +596,7 @@ public function loadPlugin(string $name)
* Examples:
*
* ```
- * $routes->connect('/:controller/:action/*');
+ * $routes->connect('/{controller}/{action}/*');
* ```
*
* The first parameter will be used as a controller name while the second is
@@ -613,7 +613,7 @@ public function loadPlugin(string $name)
*
* ```
* $routes->connect(
- * '/:lang/:controller/:action/:id',
+ * '/{lang}/{controller}/{action}/{id}',
* [],
* ['id' => '[0-9]+', 'lang' => '[a-z]{3}']
* );
@@ -644,6 +644,10 @@ public function loadPlugin(string $name)
* - `_ext` is an array of filename extensions that will be parsed out of the url if present.
* See {@link \Cake\Routing\RouteCollection::setExtensions()}.
* - `_method` Only match requests with specific HTTP verbs.
+ * - `_host` - Define the host name pattern if you want this route to only match
+ * specific host names. You can use `.*` and to create wildcard subdomains/hosts
+ * e.g. `*.example.com` matches all subdomains on `example.com`.
+ * - '_port` - Define the port if you want this route to only match specific port number.
*
* Example of using the `_method` condition:
*
@@ -766,7 +770,7 @@ protected function _makeRoute($route, $defaults, $options): Route
* Examples:
*
* ```
- * $routes->redirect('/home/*', ['controller' => 'posts', 'action' => 'view']);
+ * $routes->redirect('/home/*', ['controller' => 'Posts', 'action' => 'view']);
* ```
*
* Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the
@@ -868,8 +872,14 @@ public function prefix(string $name, $params = [], $callback = null)
* Routes connected in the scoped collection will have the correct path segment
* prepended, and have a matching plugin routing key set.
*
+ * ### Options
+ *
+ * - `path` The path prefix to use. Defaults to `Inflector::dasherize($name)`.
+ * - `_namePrefix` Set a prefix used for named routes. The prefix is prepended to the
+ * name of any route created in a scope callback.
+ *
* @param string $name The plugin name to build routes for
- * @param array|callable $options Either the options to use, or a callback
+ * @param array|callable $options Either the options to use, or a callback to build routes.
* @param callable|null $callback The callback to invoke that builds the plugin routes
* Only required when $options is defined.
* @return $this
@@ -881,9 +891,10 @@ public function plugin(string $name, $options = [], $callback = null)
$options = [];
}
- $params = ['plugin' => $name] + $this->_params;
$path = $options['path'] ?? '/' . Inflector::dasherize($name);
- $this->scope($path, $params, $callback);
+ unset($options['path']);
+ $options = ['plugin' => $name] + $options;
+ $this->scope($path, $options, $callback);
return $this;
}
@@ -895,6 +906,11 @@ public function plugin(string $name, $options = [], $callback = null)
* added to. This means that both the current path and parameters will be appended
* to the supplied parameters.
*
+ * ### Special Keys in $params
+ *
+ * - `_namePrefix` Set a prefix used for named routes. The prefix is prepended to the
+ * name of any route created in a scope callback.
+ *
* @param string $path The path to create a scope for.
* @param array|callable $params Either the parameters to add to routes, or a callback.
* @param callable|null $callback The callback to invoke that builds the plugin routes.
@@ -937,7 +953,7 @@ public function scope(string $path, $params, $callback = null)
}
/**
- * Connect the `/:controller` and `/:controller/:action/*` fallback routes.
+ * Connect the `/{controller}` and `/{controller}/{action}/*` fallback routes.
*
* This is a shortcut method for connecting fallback routes in a given scope.
*
diff --git a/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php b/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php
index 892eec919..0cb87e739 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php
@@ -39,13 +39,6 @@ class RouteCollection
*/
protected $_routeTable = [];
- /**
- * The routes connected to this collection.
- *
- * @var \Cake\Routing\Route\Route[]
- */
- protected $_routes = [];
-
/**
* The hash map of named routes that are in this collection.
*
@@ -56,7 +49,7 @@ class RouteCollection
/**
* Routes indexed by path prefix.
*
- * @var array
+ * @var array>
*/
protected $_paths = [];
@@ -91,8 +84,6 @@ class RouteCollection
*/
public function add(Route $route, array $options = []): void
{
- $this->_routes[] = $route;
-
// Explicit names
if (isset($options['_name'])) {
if (isset($this->_named[$options['_name']])) {
@@ -136,10 +127,9 @@ public function parse(string $url, string $method = ''): array
$decoded = urldecode($url);
// Sort path segments matching longest paths first.
- $paths = array_keys($this->_paths);
- rsort($paths);
+ krsort($this->_paths);
- foreach ($paths as $path) {
+ foreach ($this->_paths as $path => $routes) {
if (strpos($decoded, $path) !== 0) {
continue;
}
@@ -149,8 +139,8 @@ public function parse(string $url, string $method = ''): array
[$url, $qs] = explode('?', $url, 2);
parse_str($qs, $queryParameters);
}
- /** @var \Cake\Routing\Route\Route $route */
- foreach ($this->_paths[$path] as $route) {
+
+ foreach ($routes as $route) {
$r = $route->parse($url, $method);
if ($r === null) {
continue;
@@ -186,16 +176,14 @@ public function parseRequest(ServerRequestInterface $request): array
$urlPath = urldecode($uri->getPath());
// Sort path segments matching longest paths first.
- $paths = array_keys($this->_paths);
- rsort($paths);
+ krsort($this->_paths);
- foreach ($paths as $path) {
+ foreach ($this->_paths as $path => $routes) {
if (strpos($urlPath, $path) !== 0) {
continue;
}
- /** @var \Cake\Routing\Route\Route $route */
- foreach ($this->_paths[$path] as $route) {
+ foreach ($routes as $route) {
$r = $route->parseRequest($request);
if ($r === null) {
continue;
@@ -320,7 +308,7 @@ public function match(array $url, array $context): string
throw new MissingRouteException([
'url' => $name,
'context' => $context,
- 'message' => 'A named route was found for "%s", but matching failed.',
+ 'message' => "A named route was found for `{$name}`, but matching failed.",
]);
}
throw new MissingRouteException(['url' => $name, 'context' => $context]);
@@ -348,7 +336,13 @@ public function match(array $url, array $context): string
*/
public function routes(): array
{
- return $this->_routes;
+ krsort($this->_paths);
+
+ return array_reduce(
+ $this->_paths,
+ 'array_merge',
+ []
+ );
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Routing/Router.php b/app/vendor/cakephp/cakephp/src/Routing/Router.php
index d8ff527ad..0c2743b6d 100644
--- a/app/vendor/cakephp/cakephp/src/Routing/Router.php
+++ b/app/vendor/cakephp/cakephp/src/Routing/Router.php
@@ -146,7 +146,6 @@ class Router
* parameters to the route collection.
*
* @var callable[]
- * @psalm-var array
*/
protected static $_urlFilters = [];
@@ -204,7 +203,7 @@ public static function getNamedExpressions(): array
* shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
* custom routing class.
* @return void
- * @throws \Cake\Core\Exception\Exception
+ * @throws \Cake\Core\Exception\CakeException
* @see \Cake\Routing\RouteBuilder::connect()
* @see \Cake\Routing\Router::scope()
*/
@@ -332,7 +331,6 @@ public static function resetRoutes(): void
*
* @param callable $function The function to add
* @return void
- * @psalm-param \Closure|callable-string $function
*/
public static function addUrlFilter(callable $function): void
{
@@ -357,6 +355,7 @@ protected static function _applyUrlFilters(array $url): array
if (is_array($filter)) {
$ref = new ReflectionMethod($filter[0], $filter[1]);
} else {
+ /** @psalm-var \Closure|callable-string $filter */
$ref = new ReflectionFunction($filter);
}
$message = sprintf(
@@ -381,7 +380,7 @@ protected static function _applyUrlFilters(array $url): array
*
* - `Router::url('/posts/edit/1');` Returns the string with the base dir prepended.
* This usage does not use reverser routing.
- * - `Router::url(['controller' => 'posts', 'action' => 'edit']);` Returns a URL
+ * - `Router::url(['controller' => 'Posts', 'action' => 'edit']);` Returns a URL
* generated through reverse routing.
* - `Router::url(['_name' => 'custom-name', ...]);` Returns a URL generated
* through reverse routing. This form allows you to leverage named routes.
@@ -408,7 +407,7 @@ protected static function _applyUrlFilters(array $url): array
* @param bool $full If true, the full base URL will be prepended to the result.
* Default is false.
* @return string Full translated URL with base path.
- * @throws \Cake\Core\Exception\Exception When the route name is not found
+ * @throws \Cake\Core\Exception\CakeException When the route name is not found
*/
public static function url($url = null, bool $full = false): string
{
diff --git a/app/vendor/cakephp/cakephp/src/Routing/functions.php b/app/vendor/cakephp/cakephp/src/Routing/functions.php
new file mode 100644
index 000000000..d43f6b788
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Routing/functions.php
@@ -0,0 +1,40 @@
+ false,
+ 'prefix' => false,
+ ];
+
+ return $url + $params;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php b/app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php
index 8949aabba..b384e9fbc 100644
--- a/app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php
+++ b/app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php
@@ -16,6 +16,7 @@
namespace Cake\Shell\Helper;
use Cake\Console\Helper;
+use UnexpectedValueException;
/**
* Create a visually pleasing ASCII art table
@@ -45,7 +46,7 @@ protected function _calculateWidths(array $rows): array
$widths = [];
foreach ($rows as $line) {
foreach (array_values($line) as $k => $v) {
- $columnLength = $this->_cellWidth($v);
+ $columnLength = $this->_cellWidth((string)$v);
if ($columnLength >= ($widths[$k] ?? 0)) {
$widths[$k] = $columnLength;
}
@@ -58,12 +59,12 @@ protected function _calculateWidths(array $rows): array
/**
* Get the width of a cell exclusive of style tags.
*
- * @param string|null $text The text to calculate a width for.
+ * @param string $text The text to calculate a width for.
* @return int The width of the textual content in visible characters.
*/
- protected function _cellWidth(?string $text): int
+ protected function _cellWidth(string $text): int
{
- if ($text === null) {
+ if (strlen($text) === 0) {
return 0;
}
@@ -110,11 +111,20 @@ protected function _render(array $row, array $widths, array $options = []): void
$out = '';
foreach (array_values($row) as $i => $column) {
+ $column = (string)$column;
$pad = $widths[$i] - $this->_cellWidth($column);
if (!empty($options['style'])) {
$column = $this->_addStyle($column, $options['style']);
}
- $out .= '| ' . $column . str_repeat(' ', $pad) . ' ';
+ if (strlen($column) > 0 && preg_match('#(.*).+(.*)#', $column, $matches)) {
+ if ($matches[1] !== '' || $matches[2] !== '') {
+ throw new UnexpectedValueException('You cannot include text before or after the text-right tag.');
+ }
+ $column = str_replace(['', ''], '', $column);
+ $out .= '| ' . str_repeat(' ', $pad) . $column . ' ';
+ } else {
+ $out .= '| ' . $column . str_repeat(' ', $pad) . ' ';
+ }
}
$out .= '|';
$this->_io->out($out);
@@ -126,29 +136,31 @@ protected function _render(array $row, array $widths, array $options = []): void
* Data will be output based on the order of the values
* in the array. The keys will not be used to align data.
*
- * @param array $rows The data to render out.
+ * @param array $args The data to render out.
* @return void
*/
- public function output(array $rows): void
+ public function output(array $args): void
{
- if (empty($rows)) {
+ if (empty($args)) {
return;
}
+ $this->_io->setStyle('text-right', ['text' => null]);
+
$config = $this->getConfig();
- $widths = $this->_calculateWidths($rows);
+ $widths = $this->_calculateWidths($args);
$this->_rowSeparator($widths);
if ($config['headers'] === true) {
- $this->_render(array_shift($rows), $widths, ['style' => $config['headerStyle']]);
+ $this->_render(array_shift($args), $widths, ['style' => $config['headerStyle']]);
$this->_rowSeparator($widths);
}
- if (empty($rows)) {
+ if (empty($args)) {
return;
}
- foreach ($rows as $line) {
+ foreach ($args as $line) {
$this->_render($line, $widths);
if ($config['rowSeparator'] === true) {
$this->_rowSeparator($widths);
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestTrait.php
index cb66dd39a..45f02d294 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestTrait.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestTrait.php
@@ -19,7 +19,6 @@
use Cake\Console\CommandRunner;
use Cake\Console\ConsoleIo;
use Cake\Console\Exception\StopException;
-use Cake\Core\Configure;
use Cake\TestSuite\Constraint\Console\ContentsContain;
use Cake\TestSuite\Constraint\Console\ContentsContainRow;
use Cake\TestSuite\Constraint\Console\ContentsEmpty;
@@ -32,11 +31,16 @@
use RuntimeException;
/**
- * A test case class intended to make integration tests of cake console commands
- * easier.
+ * A bundle of methods that makes testing commands
+ * and shell classes easier.
+ *
+ * Enables you to call commands/shells with a
+ * full application context.
*/
trait ConsoleIntegrationTestTrait
{
+ use ContainerStubTrait;
+
/**
* Whether or not to use the CommandRunner
*
@@ -44,21 +48,6 @@ trait ConsoleIntegrationTestTrait
*/
protected $_useCommandRunner = false;
- /**
- * The customized application class name.
- *
- * @var string|null
- * @psalm-var class-string<\Cake\Core\ConsoleApplicationInterface>|null
- */
- protected $_appClass;
-
- /**
- * The customized application constructor arguments.
- *
- * @var array|null
- */
- protected $_appArgs;
-
/**
* Last exit code
*
@@ -88,7 +77,7 @@ trait ConsoleIntegrationTestTrait
protected $_in;
/**
- * Runs cli integration test
+ * Runs CLI integration test
*
* @param string $command Command to run
* @param array $input Input values to pass to an interactive shell
@@ -142,8 +131,6 @@ public function cleanupConsoleTrait(): void
$this->_err = null;
$this->_in = null;
$this->_useCommandRunner = false;
- $this->_appClass = null;
- $this->_appArgs = null;
}
/**
@@ -157,20 +144,6 @@ public function useCommandRunner(): void
$this->_useCommandRunner = true;
}
- /**
- * Configure the application class to use in console 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\ConsoleApplicationInterface> $class
- */
- public function configApplication(string $class, ?array $constructorArgs): void
- {
- $this->_appClass = $class;
- $this->_appArgs = $constructorArgs;
- }
-
/**
* Asserts shell exited with the expected code
*
@@ -307,15 +280,10 @@ public function assertErrorEmpty(string $message = ''): void
protected function makeRunner()
{
if ($this->_useCommandRunner) {
- if ($this->_appClass) {
- $appClass = $this->_appClass;
- } else {
- /** @psalm-var class-string<\Cake\Core\ConsoleApplicationInterface> */
- $appClass = Configure::read('App.namespace') . '\Application';
- }
- $appArgs = $this->_appArgs ?: [CONFIG];
+ /** @var \Cake\Core\ConsoleApplicationInterface $app */
+ $app = $this->createApp();
- return new CommandRunner(new $appClass(...$appArgs));
+ return new CommandRunner($app);
}
return new LegacyCommandRunner();
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Console/ContentsContain.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Console/ContentsContain.php
index 71978a6ab..d700c9664 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Console/ContentsContain.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Console/ContentsContain.php
@@ -40,6 +40,6 @@ public function matches($other): bool
*/
public function toString(): string
{
- return sprintf('is in %s,' . PHP_EOL . 'actual result:' . PHP_EOL . $this->contents, $this->output);
+ return sprintf('is in %s,' . PHP_EOL . 'actual result:' . PHP_EOL, $this->output) . $this->contents;
}
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailConstraintBase.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailConstraintBase.php
index b9ab96ace..fcb26812d 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailConstraintBase.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailConstraintBase.php
@@ -51,7 +51,7 @@ public function getMessages()
{
$messages = TestEmailTransport::getMessages();
- if ($this->at) {
+ if ($this->at !== null) {
if (!isset($messages[$this->at])) {
return [];
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php
index bbb5407c1..74ba2aace 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php
@@ -38,12 +38,12 @@ class MailContains extends MailConstraintBase
*/
public function matches($other): bool
{
+ $other = preg_quote($other, '/');
$messages = $this->getMessages();
foreach ($messages as $message) {
- $method = 'getBody' . ($this->type ? ucfirst($this->type) : 'String');
+ $method = $this->getTypeMethod();
$message = $message->$method();
- $other = preg_quote($other, '/');
if (preg_match("/$other/", $message) > 0) {
return true;
}
@@ -52,6 +52,36 @@ public function matches($other): bool
return false;
}
+ /**
+ * @return string
+ */
+ protected function getTypeMethod(): string
+ {
+ return 'getBody' . ($this->type ? ucfirst($this->type) : 'String');
+ }
+
+ /**
+ * Returns the type-dependent strings of all messages
+ * respects $this->at
+ *
+ * @return string
+ */
+ protected function getAssertedMessages(): string
+ {
+ $messageMembers = [];
+ $messages = $this->getMessages();
+ foreach ($messages as $message) {
+ $method = $this->getTypeMethod();
+ $messageMembers[] = $message->$method();
+ }
+ if ($this->at && isset($messageMembers[$this->at - 1])) {
+ $messageMembers = [$messageMembers[$this->at - 1]];
+ }
+ $result = implode(PHP_EOL, $messageMembers);
+
+ return PHP_EOL . 'was: ' . mb_substr($result, 0, 1000);
+ }
+
/**
* Assertion message string
*
@@ -60,9 +90,9 @@ public function matches($other): bool
public function toString(): string
{
if ($this->at) {
- return sprintf('is in email #%d', $this->at);
+ return sprintf('is in email #%d', $this->at) . $this->getAssertedMessages();
}
- return 'is in an email';
+ return 'is in an email' . $this->getAssertedMessages();
}
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php
index d9a351196..70f5c869e 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php
@@ -38,9 +38,9 @@ class MailContainsHtml extends MailContains
public function toString(): string
{
if ($this->at) {
- return sprintf('is in the html message of email #%d', $this->at);
+ return sprintf('is in the html message of email #%d', $this->at) . $this->getAssertedMessages();
}
- return 'is in the html message of an email';
+ return 'is in the html message of an email' . $this->getAssertedMessages();
}
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php
index a81fac490..a2c047a19 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php
@@ -38,9 +38,9 @@ class MailContainsText extends MailContains
public function toString(): string
{
if ($this->at) {
- return sprintf('is in the text message of email #%d', $this->at);
+ return sprintf('is in the text message of email #%d', $this->at) . $this->getAssertedMessages();
}
- return 'is in the text message of an email';
+ return 'is in the text message of an email' . $this->getAssertedMessages();
}
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php
new file mode 100644
index 000000000..ca5d7c93f
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php
@@ -0,0 +1,86 @@
+getMessages();
+ foreach ($messages as $message) {
+ $subject = $message->getOriginalSubject();
+ if (strpos($subject, $other) !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the subjects of all messages
+ * respects $this->at
+ *
+ * @return string
+ */
+ protected function getAssertedMessages(): string
+ {
+ $messageMembers = [];
+ $messages = $this->getMessages();
+ foreach ($messages as $message) {
+ $messageMembers[] = $message->getSubject();
+ }
+ if ($this->at && isset($messageMembers[$this->at - 1])) {
+ $messageMembers = [$messageMembers[$this->at - 1]];
+ }
+ $result = implode(PHP_EOL, $messageMembers);
+
+ return PHP_EOL . 'was: ' . mb_substr($result, 0, 1000);
+ }
+
+ /**
+ * Assertion message string
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ if ($this->at) {
+ return sprintf('is in an email subject #%d', $this->at) . $this->getAssertedMessages();
+ }
+
+ return 'is in an email subject' . $this->getAssertedMessages();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php
index 5e4fc70b8..4940081e6 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php
@@ -19,11 +19,13 @@
* StatusCodeBase
*
* @internal
+ * @template TCode as int|array
*/
abstract class StatusCodeBase extends ResponseBase
{
/**
* @var int|array
+ * @psalm-var TCode
*/
protected $code;
@@ -67,6 +69,7 @@ protected function statusCodeBetween(int $min, int $max): bool
*/
protected function failureDescription($other): string
{
+ /** @psalm-suppress InternalMethod */
return $this->toString();
}
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php
index cd3e5712b..189d8112c 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php
@@ -19,11 +19,12 @@
* StatusError
*
* @internal
+ * @extends \Cake\TestSuite\Constraint\Response\StatusCodeBase>
*/
class StatusError extends StatusCodeBase
{
/**
- * @var int[]
+ * @var array
*/
protected $code = [400, 429];
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php
index b77e43284..33d4df2de 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php
@@ -19,11 +19,12 @@
* StatusFailure
*
* @internal
+ * @extends \Cake\TestSuite\Constraint\Response\StatusCodeBase>
*/
class StatusFailure extends StatusCodeBase
{
/**
- * @var int[]
+ * @var array
*/
protected $code = [500, 505];
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php
index 2497b08e6..8047ff918 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php
@@ -19,11 +19,12 @@
* StatusOk
*
* @internal
+ * @extends \Cake\TestSuite\Constraint\Response\StatusCodeBase>
*/
class StatusOk extends StatusCodeBase
{
/**
- * @var int[]
+ * @var array
*/
protected $code = [200, 204];
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php
index 6752bdb6e..10b873dee 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php
@@ -19,11 +19,12 @@
* StatusSuccess
*
* @internal
+ * @extends \Cake\TestSuite\Constraint\Response\StatusCodeBase>
*/
class StatusSuccess extends StatusCodeBase
{
/**
- * @var int[]
+ * @var array
*/
protected $code = [200, 308];
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php
index 3df049e44..5cb470394 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php
@@ -15,9 +15,7 @@
*/
namespace Cake\TestSuite\Constraint\Session;
-use Cake\Http\Session;
use Cake\Utility\Hash;
-use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Constraint\Constraint;
/**
@@ -27,34 +25,18 @@
*/
class SessionEquals extends Constraint
{
- /**
- * @var \Cake\Http\Session
- */
- protected $session;
-
/**
* @var string
*/
protected $path;
- /**
- * @var mixed
- */
- protected $value;
-
/**
* Constructor
*
- * @param \Cake\Http\Session|null $session Session
* @param string $path Session Path
*/
- public function __construct(?Session $session, string $path)
+ public function __construct(string $path)
{
- if (!$session) {
- throw new AssertionFailedError('There is no stored session data. Perhaps you need to run a request?');
- }
-
- $this->session = $session;
$this->path = $path;
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php
new file mode 100644
index 000000000..2240d3e0f
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php
@@ -0,0 +1,66 @@
+path = $path;
+ }
+
+ /**
+ * Compare session value
+ *
+ * @param mixed $other Value to compare with
+ * @return bool
+ */
+ public function matches($other): bool
+ {
+ // Server::run calls Session::close at the end of the request.
+ // Which means, that we cannot use Session object here to access the session data.
+ // Call to Session::read will start new session (and will erase the data).
+ return Hash::check($_SESSION, $this->path) === true;
+ }
+
+ /**
+ * Assertion message
+ *
+ * @return string
+ */
+ public function toString(): string
+ {
+ return 'is a path present in the session';
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/ContainerStubTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/ContainerStubTrait.php
new file mode 100644
index 000000000..35b68ac7a
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/ContainerStubTrait.php
@@ -0,0 +1,169 @@
+|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 null|\Cake\Core\ContainerInterface
+ */
+ public function modifyContainer(EventInterface $event, ContainerInterface $container): ?ContainerInterface
+ {
+ if (empty($this->containerServices)) {
+ return null;
+ }
+ foreach ($this->containerServices as $key => $factory) {
+ if ($container->has($key)) {
+ $container->extend($key)->setConcrete($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/TestSuite/EmailTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/EmailTrait.php
index fb54ad535..7d6926af5 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/EmailTrait.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/EmailTrait.php
@@ -24,6 +24,7 @@
use Cake\TestSuite\Constraint\Email\MailSentFrom;
use Cake\TestSuite\Constraint\Email\MailSentTo;
use Cake\TestSuite\Constraint\Email\MailSentWith;
+use Cake\TestSuite\Constraint\Email\MailSubjectContains;
use Cake\TestSuite\Constraint\Email\NoMailSent;
/**
@@ -150,7 +151,7 @@ public function assertMailContainsTextAt(int $at, string $contents, string $mess
*
* @param int $at Email index
* @param string $expected Contents
- * @param string $parameter Email getter parameter (e.g. "cc", "subject")
+ * @param string $parameter Email getter parameter (e.g. "cc", "bcc")
* @param string $message Message
* @return void
*/
@@ -244,4 +245,29 @@ public function assertMailSentWith(string $expected, string $parameter, string $
{
$this->assertThat($expected, new MailSentWith(null, $parameter), $message);
}
+
+ /**
+ * Asserts an email subject contains expected contents
+ *
+ * @param string $contents Contents
+ * @param string $message Message
+ * @return void
+ */
+ public function assertMailSubjectContains(string $contents, string $message = ''): void
+ {
+ $this->assertThat($contents, new MailSubjectContains(), $message);
+ }
+
+ /**
+ * Asserts an email at a specific index contains expected html contents
+ *
+ * @param int $at Email index
+ * @param string $contents Contents
+ * @param string $message Message
+ * @return void
+ */
+ public function assertMailSubjectContainsAt(int $at, string $contents, string $message = ''): void
+ {
+ $this->assertThat($contents, new MailSubjectContains($at), $message);
+ }
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php
index a8bbbd1dc..e0f00874e 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php
@@ -17,7 +17,7 @@
namespace Cake\TestSuite\Fixture;
use Cake\Core\Configure;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Database\ConstraintsInterface;
use Cake\Database\Schema\TableSchema;
use Cake\Database\Schema\TableSchemaAwareInterface;
@@ -273,7 +273,7 @@ protected function _setupTable(
*
* @param \Cake\TestSuite\TestCase $test The test to inspect for fixture loading.
* @return void
- * @throws \Cake\Core\Exception\Exception When fixture records cannot be inserted.
+ * @throws \Cake\Core\Exception\CakeException When fixture records cannot be inserted.
* @throws \RuntimeException
*/
public function load(TestCase $test): void
@@ -307,7 +307,7 @@ public function load(TestCase $test): void
get_class($test),
$e->getMessage()
);
- throw new Exception($msg, null, $e);
+ throw new CakeException($msg, null, $e);
}
}
}
@@ -334,7 +334,7 @@ public function load(TestCase $test): void
get_class($test),
$e->getMessage()
);
- throw new Exception($msg, null, $e);
+ throw new CakeException($msg, null, $e);
}
}
};
@@ -352,7 +352,7 @@ public function load(TestCase $test): void
get_class($test),
$e->getMessage()
);
- throw new Exception($msg, null, $e);
+ throw new CakeException($msg, null, $e);
}
}
};
@@ -437,12 +437,6 @@ public function unload(TestCase $test): void
$fixture->dropConstraints($db);
}
}
-
- foreach ($fixtures as $fixture) {
- if ($this->isFixtureSetup($configName, $fixture)) {
- $fixture->truncate($db);
- }
- }
};
$this->_runOperation($fixtures, $truncate);
}
@@ -451,39 +445,39 @@ public function unload(TestCase $test): void
* Creates a single fixture table and loads data into it.
*
* @param string $name of the fixture
- * @param \Cake\Datasource\ConnectionInterface|null $db Connection instance or null
+ * @param \Cake\Datasource\ConnectionInterface|null $connection Connection instance or null
* to get a Connection from the fixture.
* @param bool $dropTables Whether or not tables should be dropped and re-created.
* @return void
* @throws \UnexpectedValueException if $name is not a previously loaded class
*/
- public function loadSingle(string $name, ?ConnectionInterface $db = null, bool $dropTables = true): void
+ public function loadSingle(string $name, ?ConnectionInterface $connection = null, bool $dropTables = true): void
{
if (!isset($this->_fixtureMap[$name])) {
throw new UnexpectedValueException(sprintf('Referenced fixture class %s not found', $name));
}
$fixture = $this->_fixtureMap[$name];
- if (!$db) {
- $db = ConnectionManager::get($fixture->connection());
+ if (!$connection) {
+ $connection = ConnectionManager::get($fixture->connection());
}
- if (!$this->isFixtureSetup($db->configName(), $fixture)) {
- $sources = $db->getSchemaCollection()->listTables();
- $this->_setupTable($fixture, $db, $sources, $dropTables);
+ if (!$this->isFixtureSetup($connection->configName(), $fixture)) {
+ $sources = $connection->getSchemaCollection()->listTables();
+ $this->_setupTable($fixture, $connection, $sources, $dropTables);
}
if (!$dropTables) {
if ($fixture instanceof ConstraintsInterface) {
- $fixture->dropConstraints($db);
+ $fixture->dropConstraints($connection);
}
- $fixture->truncate($db);
+ $fixture->truncate($connection);
}
if ($fixture instanceof ConstraintsInterface) {
- $fixture->createConstraints($db);
+ $fixture->createConstraints($connection);
}
- $fixture->insert($db);
+ $fixture->insert($connection);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php
index a97dc8ac1..f51277028 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php
@@ -15,7 +15,7 @@
*/
namespace Cake\TestSuite\Fixture;
-use Cake\Core\Exception\Exception as CakeException;
+use Cake\Core\Exception\CakeException;
use Cake\Database\ConstraintsInterface;
use Cake\Database\Schema\TableSchema;
use Cake\Database\Schema\TableSchemaAwareInterface;
@@ -97,7 +97,7 @@ class TestFixture implements ConstraintsInterface, FixtureInterface, TableSchema
/**
* Instantiate the fixture.
*
- * @throws \Cake\Core\Exception\Exception on invalid datasource usage.
+ * @throws \Cake\Core\Exception\CakeException on invalid datasource usage.
*/
public function __construct()
{
@@ -212,7 +212,7 @@ protected function _schemaFromFields(): void
* Build fixture schema from a table in another datasource.
*
* @return void
- * @throws \Cake\Core\Exception\Exception when trying to import from an empty table.
+ * @throws \Cake\Core\Exception\CakeException when trying to import from an empty table.
*/
protected function _schemaFromImport(): void
{
@@ -244,7 +244,7 @@ protected function _schemaFromImport(): void
* Build fixture schema directly from the datasource
*
* @return void
- * @throws \Cake\Core\Exception\Exception when trying to reflect a table that does not exist
+ * @throws \Cake\Core\Exception\CakeException when trying to reflect a table that does not exist
*/
protected function _schemaFromReflection(): void
{
@@ -268,7 +268,7 @@ protected function _schemaFromReflection(): void
/**
* @inheritDoc
*/
- public function create(ConnectionInterface $db): bool
+ public function create(ConnectionInterface $connection): bool
{
if (empty($this->_schema)) {
return false;
@@ -279,9 +279,10 @@ public function create(ConnectionInterface $db): bool
}
try {
- $queries = $this->_schema->createSql($db);
+ /** @psalm-suppress ArgumentTypeCoercion */
+ $queries = $this->_schema->createSql($connection);
foreach ($queries as $query) {
- $stmt = $db->prepare($query);
+ $stmt = $connection->prepare($query);
$stmt->execute();
$stmt->closeCursor();
}
@@ -303,20 +304,21 @@ public function create(ConnectionInterface $db): bool
/**
* @inheritDoc
*/
- public function drop(ConnectionInterface $db): bool
+ public function drop(ConnectionInterface $connection): bool
{
if (empty($this->_schema)) {
return false;
}
if (empty($this->import) && empty($this->fields)) {
- return true;
+ return $this->truncate($connection);
}
try {
- $sql = $this->_schema->dropSql($db);
+ /** @psalm-suppress ArgumentTypeCoercion */
+ $sql = $this->_schema->dropSql($connection);
foreach ($sql as $stmt) {
- $db->execute($stmt)->closeCursor();
+ $connection->execute($stmt)->closeCursor();
}
} catch (Exception $e) {
return false;
@@ -328,11 +330,11 @@ public function drop(ConnectionInterface $db): bool
/**
* @inheritDoc
*/
- public function insert(ConnectionInterface $db)
+ public function insert(ConnectionInterface $connection)
{
if (isset($this->records) && !empty($this->records)) {
[$fields, $values, $types] = $this->_getRecords();
- $query = $db->newQuery()
+ $query = $connection->newQuery()
->insert($fields, $types)
->into($this->sourceName());
@@ -351,7 +353,7 @@ public function insert(ConnectionInterface $db)
/**
* @inheritDoc
*/
- public function createConstraints(ConnectionInterface $db): bool
+ public function createConstraints(ConnectionInterface $connection): bool
{
if (empty($this->_constraints)) {
return true;
@@ -361,14 +363,15 @@ public function createConstraints(ConnectionInterface $db): bool
$this->_schema->addConstraint($name, $data);
}
- $sql = $this->_schema->addConstraintSql($db);
+ /** @psalm-suppress ArgumentTypeCoercion */
+ $sql = $this->_schema->addConstraintSql($connection);
if (empty($sql)) {
return true;
}
foreach ($sql as $stmt) {
- $db->execute($stmt)->closeCursor();
+ $connection->execute($stmt)->closeCursor();
}
return true;
@@ -377,20 +380,21 @@ public function createConstraints(ConnectionInterface $db): bool
/**
* @inheritDoc
*/
- public function dropConstraints(ConnectionInterface $db): bool
+ public function dropConstraints(ConnectionInterface $connection): bool
{
if (empty($this->_constraints)) {
return true;
}
- $sql = $this->_schema->dropConstraintSql($db);
+ /** @psalm-suppress ArgumentTypeCoercion */
+ $sql = $this->_schema->dropConstraintSql($connection);
if (empty($sql)) {
return true;
}
foreach ($sql as $stmt) {
- $db->execute($stmt)->closeCursor();
+ $connection->execute($stmt)->closeCursor();
}
foreach ($this->_constraints as $name => $data) {
@@ -429,11 +433,12 @@ protected function _getRecords(): array
/**
* @inheritDoc
*/
- public function truncate(ConnectionInterface $db): bool
+ public function truncate(ConnectionInterface $connection): bool
{
- $sql = $this->_schema->truncateSql($db);
+ /** @psalm-suppress ArgumentTypeCoercion */
+ $sql = $this->_schema->truncateSql($connection);
foreach ($sql as $stmt) {
- $db->execute($stmt)->closeCursor();
+ $connection->execute($stmt)->closeCursor();
}
return true;
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php
index e964e2946..929cc79e1 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php
@@ -17,9 +17,10 @@
use Cake\Controller\Controller;
use Cake\Core\Configure;
-use Cake\Database\Exception as DatabaseException;
+use Cake\Database\Exception\DatabaseException;
use Cake\Error\ExceptionRenderer;
use Cake\Event\EventInterface;
+use Cake\Event\EventManager;
use Cake\Form\FormProtector;
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\Session;
@@ -51,6 +52,7 @@
use Cake\TestSuite\Constraint\Response\StatusSuccess;
use Cake\TestSuite\Constraint\Session\FlashParamEquals;
use Cake\TestSuite\Constraint\Session\SessionEquals;
+use Cake\TestSuite\Constraint\Session\SessionHasKey;
use Cake\TestSuite\Constraint\View\LayoutFileEquals;
use Cake\TestSuite\Constraint\View\TemplateFileEquals;
use Cake\TestSuite\Stub\TestExceptionRenderer;
@@ -74,21 +76,7 @@
trait IntegrationTestTrait
{
use CookieCryptTrait;
-
- /**
- * The customized application class name.
- *
- * @var string|null
- * @psalm-var class-string<\Cake\Core\HttpApplicationInterface>|null
- */
- protected $_appClass;
-
- /**
- * The customized application constructor arguments.
- *
- * @var array|null
- */
- protected $_appArgs;
+ use ContainerStubTrait;
/**
* The data used to build the next request.
@@ -100,7 +88,7 @@ trait IntegrationTestTrait
/**
* The response for the most recent request.
*
- * @var \Psr\Http\Message\ResponseInterface
+ * @var \Psr\Http\Message\ResponseInterface|null
*/
protected $_response;
@@ -128,7 +116,7 @@ trait IntegrationTestTrait
/**
* The controller used in the last request.
*
- * @var \Cake\Controller\Controller
+ * @var \Cake\Controller\Controller|null
*/
protected $_controller;
@@ -180,9 +168,9 @@ trait IntegrationTestTrait
/**
* Stored flash messages before render
*
- * @var array|null
+ * @var array
*/
- protected $_flashMessages;
+ protected $_flashMessages = [];
/**
* @var string|null
@@ -214,25 +202,10 @@ public function cleanup(): void
$this->_viewName = null;
$this->_layoutName = null;
$this->_requestSession = null;
- $this->_appClass = null;
- $this->_appArgs = null;
$this->_securityToken = false;
$this->_csrfToken = false;
$this->_retainFlashMessages = false;
- }
-
- /**
- * 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
- */
- public function configApplication(string $class, ?array $constructorArgs): void
- {
- $this->_appClass = $class;
- $this->_appArgs = $constructorArgs;
+ $this->_flashMessages = [];
}
/**
@@ -517,7 +490,11 @@ protected function _sendRequest($url, $method, $data = []): void
*/
protected function _makeDispatcher(): MiddlewareDispatcher
{
- return new MiddlewareDispatcher($this, $this->_appClass, $this->_appArgs);
+ EventManager::instance()->on('Controller.initialize', [$this, 'controllerSpy']);
+ /** @var \Cake\Core\HttpApplicationInterface $app */
+ $app = $this->createApp();
+
+ return new MiddlewareDispatcher($app);
}
/**
@@ -535,14 +512,22 @@ public function controllerSpy(EventInterface $event, ?Controller $controller = n
}
$this->_controller = $controller;
$events = $controller->getEventManager();
- $events->on('View.beforeRender', function ($event, $viewFile) use ($controller): void {
+ $flashCapture = function (EventInterface $event): void {
+ if (!$this->_retainFlashMessages) {
+ return;
+ }
+ $controller = $event->getSubject();
+ $this->_flashMessages = Hash::merge(
+ $this->_flashMessages,
+ $controller->getRequest()->getSession()->read('Flash')
+ );
+ };
+ $events->on('Controller.beforeRedirect', ['priority' => -100], $flashCapture);
+ $events->on('Controller.beforeRender', ['priority' => -100], $flashCapture);
+ $events->on('View.beforeRender', function ($event, $viewFile): void {
if (!$this->_viewName) {
$this->_viewName = $viewFile;
}
- if ($this->_retainFlashMessages) {
- /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */
- $this->_flashMessages = $controller->getRequest()->getSession()->read('Flash');
- }
});
$events->on('View.beforeLayout', function ($event, $viewFile): void {
$this->_layoutName = $viewFile;
@@ -583,7 +568,6 @@ protected function _buildRequest(string $url, $method, $data = []): array
'defaults' => 'php',
];
$session = Session::create($sessionConfig);
- $session->write($this->_session);
[$url, $query, $hostInfo] = $this->_url($url);
$tokenUrl = $url;
@@ -636,6 +620,7 @@ protected function _buildRequest(string $url, $method, $data = []): array
}
$props['cookies'] = $this->_cookie;
+ $session->write($this->_session);
$props = Hash::merge($props, $this->_request);
return $props;
@@ -659,7 +644,6 @@ protected function _addTokens(string $url, array $data): array
$formProtector = new FormProtector(['unlockedFields' => $this->_unlockedFields]);
foreach ($keys as $field) {
- /** @psalm-suppress PossiblyNullArgument */
$formProtector->addField($field);
}
$tokenData = $formProtector->buildTokenData($url, 'cli');
@@ -670,11 +654,23 @@ protected function _addTokens(string $url, array $data): array
if ($this->_csrfToken === true) {
$middleware = new CsrfProtectionMiddleware();
- if (!isset($this->_cookie['csrfToken'])) {
- $this->_cookie['csrfToken'] = $middleware->createToken();
+ $token = null;
+ if (!isset($this->_cookie['csrfToken']) && !isset($this->_session['csrfToken'])) {
+ $token = $middleware->createToken();
+ } elseif (isset($this->_cookie['csrfToken'])) {
+ $token = $this->_cookie['csrfToken'];
+ } else {
+ $token = $this->_session['csrfToken'];
}
+
+ // Add the token to both the session and cookie to cover
+ // both types of CSRF tokens. We generate the token with the cookie
+ // middleware as cookie tokens will be accepted by session csrf, but not
+ // the inverse.
+ $this->_session['csrfToken'] = $token;
+ $this->_cookie['csrfToken'] = $token;
if (!isset($data['_csrfToken'])) {
- $data['_csrfToken'] = $this->_cookie['csrfToken'];
+ $data['_csrfToken'] = $token;
}
}
@@ -829,6 +825,10 @@ public function assertResponseCode(int $code, string $message = ''): void
*/
public function assertRedirect($url = null, $message = ''): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
@@ -852,6 +852,10 @@ public function assertRedirect($url = null, $message = ''): void
*/
public function assertRedirectEquals($url = null, $message = '')
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
@@ -869,6 +873,10 @@ public function assertRedirectEquals($url = null, $message = '')
*/
public function assertRedirectContains(string $url, string $message = ''): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
$this->assertThat($url, new HeaderContains($this->_response, 'Location'), $verboseMessage);
@@ -883,6 +891,10 @@ public function assertRedirectContains(string $url, string $message = ''): void
*/
public function assertRedirectNotContains(string $url, string $message = ''): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, 'Location'), $verboseMessage);
$this->assertThat($url, new HeaderNotContains($this->_response, 'Location'), $verboseMessage);
@@ -910,6 +922,10 @@ public function assertNoRedirect(string $message = ''): void
*/
public function assertHeader(string $header, string $content, string $message = ''): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, $header), $verboseMessage);
$this->assertThat($content, new HeaderEquals($this->_response, $header), $verboseMessage);
@@ -925,6 +941,10 @@ public function assertHeader(string $header, string $content, string $message =
*/
public function assertHeaderContains(string $header, string $content, string $message = ''): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, $header), $verboseMessage);
$this->assertThat($content, new HeaderContains($this->_response, $header), $verboseMessage);
@@ -940,6 +960,10 @@ public function assertHeaderContains(string $header, string $content, string $me
*/
public function assertHeaderNotContains(string $header, string $content, string $message = ''): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert header.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat(null, new HeaderSet($this->_response, $header), $verboseMessage);
$this->assertThat($content, new HeaderNotContains($this->_response, $header), $verboseMessage);
@@ -994,6 +1018,10 @@ public function assertResponseNotEquals($content, $message = ''): void
*/
public function assertResponseContains(string $content, string $message = '', bool $ignoreCase = false): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert content.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat($content, new BodyContains($this->_response, $ignoreCase), $verboseMessage);
}
@@ -1008,6 +1036,10 @@ public function assertResponseContains(string $content, string $message = '', bo
*/
public function assertResponseNotContains(string $content, string $message = '', bool $ignoreCase = false): void
{
+ if (!$this->_response) {
+ $this->fail('No response set, cannot assert content.');
+ }
+
$verboseMessage = $this->extractVerboseMessage($message);
$this->assertThat($content, new BodyNotContains($this->_response, $ignoreCase), $verboseMessage);
}
@@ -1097,7 +1129,33 @@ public function assertLayout(string $content, string $message = ''): void
public function assertSession($expected, string $path, string $message = ''): void
{
$verboseMessage = $this->extractVerboseMessage($message);
- $this->assertThat($expected, new SessionEquals($this->_requestSession, $path), $verboseMessage);
+ $this->assertThat($expected, new SessionEquals($path), $verboseMessage);
+ }
+
+ /**
+ * Asserts session key exists.
+ *
+ * @param string $path The session data path. Uses Hash::get() compatible notation.
+ * @param string $message The failure message that will be appended to the generated message.
+ * @return void
+ */
+ public function assertSessionHasKey(string $path, string $message = ''): void
+ {
+ $verboseMessage = $this->extractVerboseMessage($message);
+ $this->assertThat($path, new SessionHasKey($path), $verboseMessage);
+ }
+
+ /**
+ * Asserts a session key does not exist.
+ *
+ * @param string $path The session data path. Uses Hash::get() compatible notation.
+ * @param string $message The failure message that will be appended to the generated message.
+ * @return void
+ */
+ public function assertSessionNotHasKey(string $path, string $message = ''): void
+ {
+ $verboseMessage = $this->extractVerboseMessage($message);
+ $this->assertThat($path, $this->logicalNot(new SessionHasKey($path)), $verboseMessage);
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php b/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php
index c8803c175..7deff01f4 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php
@@ -15,15 +15,14 @@
*/
namespace Cake\TestSuite;
-use Cake\Core\Configure;
+use Cake\Core\HttpApplicationInterface;
use Cake\Core\PluginApplicationInterface;
-use Cake\Event\EventManager;
+use Cake\Http\FlashMessage;
use Cake\Http\Server;
use Cake\Http\ServerRequest;
use Cake\Http\ServerRequestFactory;
use Cake\Routing\Router;
use Cake\Routing\RoutingApplicationInterface;
-use LogicException;
use Psr\Http\Message\ResponseInterface;
/**
@@ -34,28 +33,6 @@
*/
class MiddlewareDispatcher
{
- /**
- * The test case being run.
- *
- * @var \Cake\TestSuite\TestCase
- */
- protected $_test;
-
- /**
- * The application class name
- *
- * @var string
- * @psalm-var class-string<\Cake\Core\HttpApplicationInterface>
- */
- protected $_class;
-
- /**
- * Constructor arguments for your application class.
- *
- * @var array
- */
- protected $_constructorArgs;
-
/**
* The application that is being dispatched.
*
@@ -66,31 +43,11 @@ class MiddlewareDispatcher
/**
* Constructor
*
- * @param \Cake\TestSuite\TestCase $test The test case to run.
- * @param string|null $class The application class name. Defaults to App\Application.
- * @param array|null $constructorArgs The constructor arguments for your application class.
- * Defaults to `['./config']`
- * @throws \LogicException If it cannot load class for use in integration testing.
- * @psalm-param class-string<\Cake\Core\HttpApplicationInterface>|null $class
+ * @param \Cake\Core\HttpApplicationInterface $app The test case to run.
*/
- public function __construct(
- TestCase $test,
- ?string $class = null,
- ?array $constructorArgs = null
- ) {
- $this->_test = $test;
- if ($class === null) {
- /** @psalm-var class-string<\Cake\Core\HttpApplicationInterface> */
- $class = Configure::read('App.namespace') . '\Application';
- }
- $this->_class = $class;
- $this->_constructorArgs = $constructorArgs ?: [CONFIG];
-
- if (!class_exists($this->_class)) {
- throw new LogicException("Cannot load `{$this->_class}` for use in integration testing.", 0);
- }
-
- $this->app = new $this->_class(...$this->_constructorArgs);
+ public function __construct(HttpApplicationInterface $app)
+ {
+ $this->app = $app;
}
/**
@@ -164,7 +121,9 @@ protected function _createRequest(array $spec): ServerRequest
$spec['cookies'],
$spec['files']
);
- $request = $request->withAttribute('session', $spec['session']);
+ $request = $request
+ ->withAttribute('session', $spec['session'])
+ ->withAttribute('flash', new FlashMessage($spec['session']));
return $request;
}
@@ -178,13 +137,6 @@ protected function _createRequest(array $spec): ServerRequest
*/
public function execute(array $requestSpec): ResponseInterface
{
- // Spy on the controller using the initialize hook instead
- // of the dispatcher hooks as those will be going away one day.
- EventManager::instance()->on(
- 'Controller.initialize',
- [$this->_test, 'controllerSpy']
- );
-
$server = new Server($this->app);
return $server->run($this->_createRequest($requestSpec));
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php
index d83b8381b..bcac1cfa6 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php
@@ -64,6 +64,6 @@ public function assertSameAsFile(string $path, string $result): void
}
$expected = file_get_contents($path);
- $this->assertTextEquals($expected, $result);
+ $this->assertTextEquals($expected, $result, 'Content does not match file ' . $path);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/TestCase.php b/app/vendor/cakephp/cakephp/src/TestSuite/TestCase.php
index 439e17600..2d40cda1c 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/TestCase.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/TestCase.php
@@ -29,6 +29,10 @@
use Cake\TestSuite\Constraint\EventFiredWith;
use Cake\Utility\Inflector;
use LogicException;
+use PHPUnit\Framework\Constraint\DirectoryExists;
+use PHPUnit\Framework\Constraint\FileExists;
+use PHPUnit\Framework\Constraint\LogicalNot;
+use PHPUnit\Framework\Constraint\RegularExpression;
use PHPUnit\Framework\TestCase as BaseTestCase;
use ReflectionClass;
use ReflectionException;
@@ -80,6 +84,72 @@ abstract class TestCase extends BaseTestCase
*/
protected $_configure = [];
+ /**
+ * Asserts that a string matches a given regular expression.
+ *
+ * @param string $pattern Regex pattern
+ * @param string $string String to test
+ * @param string $message Message
+ * @return void
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ * @codeCoverageIgnore
+ */
+ public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void
+ {
+ static::assertThat($string, new RegularExpression($pattern), $message);
+ }
+
+ /**
+ * Asserts that a string does not match a given regular expression.
+ *
+ * @param string $pattern Regex pattern
+ * @param string $string String to test
+ * @param string $message Message
+ * @return void
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public static function assertDoesNotMatchRegularExpression(
+ string $pattern,
+ string $string,
+ string $message = ''
+ ): void {
+ static::assertThat(
+ $string,
+ new LogicalNot(
+ new RegularExpression($pattern)
+ ),
+ $message
+ );
+ }
+
+ /**
+ * Asserts that a file does not exist.
+ *
+ * @param string $filename Filename
+ * @param string $message Message
+ * @return void
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ * @codeCoverageIgnore
+ */
+ public static function assertFileDoesNotExist(string $filename, string $message = ''): void
+ {
+ static::assertThat($filename, new LogicalNot(new FileExists()), $message);
+ }
+
+ /**
+ * Asserts that a directory does not exist.
+ *
+ * @param string $directory Directory
+ * @param string $message Message
+ * @return void
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ * @codeCoverageIgnore
+ */
+ public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void
+ {
+ static::assertThat($directory, new LogicalNot(new DirectoryExists()), $message);
+ }
+
/**
* Overrides SimpleTestCase::skipIf to provide a boolean return value
*
@@ -138,7 +208,7 @@ public function deprecated(callable $callable): void
*
* @return void
*/
- public function setUp(): void
+ protected function setUp(): void
{
parent::setUp();
@@ -157,7 +227,7 @@ public function setUp(): void
*
* @return void
*/
- public function tearDown(): void
+ protected function tearDown(): void
{
parent::tearDown();
if ($this->_configure) {
@@ -182,9 +252,13 @@ public function tearDown(): void
*/
public function loadFixtures(): void
{
+ if ($this->autoFixtures) {
+ throw new RuntimeException('Cannot use `loadFixtures()` with `$autoFixtures` enabled.');
+ }
if ($this->fixtureManager === null) {
throw new RuntimeException('No fixture manager to load the test fixture');
}
+
$args = func_get_args();
foreach ($args as $class) {
$this->fixtureManager->loadSingle($class, null, $this->dropTables);
@@ -206,7 +280,7 @@ public function loadFixtures(): void
* `Cake\TestSuite\IntegrationTestCaseTrait` to better simulate all routes
* and plugins being loaded.
*
- * @param array|null $appArgs Constuctor parameters for the application class.
+ * @param array|null $appArgs Constructor parameters for the application class.
* @return void
* @since 4.0.1
*/
@@ -474,6 +548,41 @@ public function assertTextNotContains(
}
}
+ /**
+ * Assert that a string matches SQL with db-specific characters like quotes removed.
+ *
+ * @param string $expected The expected sql
+ * @param string $actual The sql to compare
+ * @param string $message The message to display on failure
+ * @return void
+ */
+ public function assertEqualsSql(
+ string $expected,
+ string $actual,
+ string $message = ''
+ ): void {
+ $this->assertEquals($expected, preg_replace('/[`"\[\]]/', '', $actual), $message);
+ }
+
+ /**
+ * Assertion for comparing a regex pattern against a query having its identifiers
+ * quoted. It accepts queries quoted with the characters `<` and `>`. If the third
+ * parameter is set to true, it will alter the pattern to both accept quoted and
+ * unquoted queries
+ *
+ * @param string $pattern The expected sql pattern
+ * @param string $actual The sql to compare
+ * @param bool $optional Whether quote characters (marked with <>) are optional
+ * @return void
+ */
+ public function assertRegExpSql(string $pattern, string $actual, bool $optional = false): void
+ {
+ $optional = $optional ? '?' : '';
+ $pattern = str_replace('<', '[`"\[]' . $optional, $pattern);
+ $pattern = str_replace('>', '[`"\]]' . $optional, $pattern);
+ $this->assertMatchesRegularExpression('#' . $pattern . '#', $actual);
+ }
+
/**
* Asserts HTML tags.
*
@@ -522,7 +631,7 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa
{
$regex = [];
$normalized = [];
- foreach ((array)$expected as $key => $val) {
+ foreach ($expected as $key => $val) {
if (!is_numeric($key)) {
$normalized[] = [$key => $val];
} else {
@@ -536,6 +645,7 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa
}
$i++;
if (is_string($tags) && $tags[0] === '<') {
+ /** @psalm-suppress InvalidArrayOffset */
$tags = [substr($tags, 1) => []];
} elseif (is_string($tags)) {
$tagsTrimmed = preg_replace('/\s+/m', '', $tags);
@@ -651,7 +761,7 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa
debug($string);
debug($regex);
}
- $this->assertRegExp(
+ $this->assertMatchesRegularExpression(
$expression,
$string,
sprintf('Item #%d / regex #%d failed: %s', $itemNum, $i, $description)
@@ -802,12 +912,27 @@ public function getMockForModel(string $alias, array $methods = [], array $optio
[, $baseClass] = pluginSplit($alias);
$options += ['alias' => $baseClass, 'connection' => $connection];
$options += $locator->getConfig($alias);
+ $reflection = new ReflectionClass($className);
+ $classMethods = array_map(function ($method) {
+ return $method->name;
+ }, $reflection->getMethods());
+
+ $existingMethods = array_intersect($classMethods, $methods);
+ $nonExistingMethods = array_diff($methods, $existingMethods);
+
+ $builder = $this->getMockBuilder($className)
+ ->setConstructorArgs([$options]);
+
+ if ($existingMethods || !$nonExistingMethods) {
+ $builder->onlyMethods($existingMethods);
+ }
+
+ if ($nonExistingMethods) {
+ $builder->addMethods($nonExistingMethods);
+ }
/** @var \Cake\ORM\Table $mock */
- $mock = $this->getMockBuilder($className)
- ->onlyMethods($methods)
- ->setConstructorArgs([$options])
- ->getMock();
+ $mock = $builder->getMock();
if (empty($options['entityClass']) && $mock->getEntityClass() === Entity::class) {
$parts = explode('\\', $className);
@@ -856,11 +981,35 @@ protected function _getTableClassName(string $alias, array $options): string
* Set the app namespace
*
* @param string $appNamespace The app namespace, defaults to "TestApp".
- * @return void
+ * @return string|null The previous app namespace or null if not set.
*/
- public static function setAppNamespace(string $appNamespace = 'TestApp'): void
+ public static function setAppNamespace(string $appNamespace = 'TestApp'): ?string
{
+ $previous = Configure::read('App.namespace');
Configure::write('App.namespace', $appNamespace);
+
+ return $previous;
+ }
+
+ /**
+ * Adds a fixture to this test case.
+ *
+ * Examples:
+ * - core.Tags
+ * - app.MyRecords
+ * - plugin.MyPluginName.MyModelName
+ *
+ * Use this method inside your test cases' {@link getFixtures()} method
+ * to build up the fixture list.
+ *
+ * @param string $fixture Fixture
+ * @return $this
+ */
+ protected function addFixture(string $fixture)
+ {
+ $this->fixtures[] = $fixture;
+
+ return $this;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php
index 5821a2ab1..440530d7d 100644
--- a/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php
+++ b/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php
@@ -86,14 +86,14 @@ public function addFailure(Test $test, AssertionFailedError $e, float $time): vo
/**
* @inheritDoc
*/
- public function addRiskyTest(Test $test, Throwable $e, float $time): void
+ public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
}
/**
* @inheritDoc
*/
- public function addIncompleteTest(Test $test, Throwable $e, float $time): void
+ public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php b/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php
index 390b50d43..afe10e028 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php
@@ -156,7 +156,7 @@ protected function _decode(string $value, $encrypt, ?string $key)
* Implode method to keep keys are multidimensional arrays
*
* @param array $array Map of key and values
- * @return string A json encoded string.
+ * @return string A JSON encoded string.
*/
protected function _implode(array $array): string
{
diff --git a/app/vendor/cakephp/cakephp/src/Utility/Exception/XmlException.php b/app/vendor/cakephp/cakephp/src/Utility/Exception/XmlException.php
index 70d08e331..b4779d6b5 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/Exception/XmlException.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/Exception/XmlException.php
@@ -14,16 +14,12 @@
*/
namespace Cake\Utility\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Exception class for Xml. This exception will be thrown from Xml when it
* encounters an error.
*/
-class XmlException extends Exception
+class XmlException extends CakeException
{
- /**
- * @inheritDoc
- */
- protected $_defaultCode = 0;
}
diff --git a/app/vendor/cakephp/cakephp/src/Utility/Hash.php b/app/vendor/cakephp/cakephp/src/Utility/Hash.php
index bdc17bf66..827540144 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/Hash.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/Hash.php
@@ -461,7 +461,7 @@ public static function remove(array $data, string $path): array
* following the path specified in `$groupPath`.
*
* @param array $data Array from where to extract keys and values
- * @param string|string[] $keyPath A dot-separated string.
+ * @param string|string[]|null $keyPath A dot-separated string.
* @param string|string[]|null $valuePath A dot-separated string.
* @param string|null $groupPath A dot-separated string.
* @return array Combined array
@@ -478,11 +478,13 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string
$format = array_shift($keyPath);
/** @var array $keys */
$keys = static::format($data, $keyPath, $format);
+ } elseif ($keyPath === null) {
+ $keys = $keyPath;
} else {
/** @var array $keys */
$keys = static::extract($data, $keyPath);
}
- if (empty($keys)) {
+ if ($keyPath !== null && empty($keys)) {
return [];
}
@@ -496,10 +498,10 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string
$vals = static::extract($data, $valuePath);
}
if (empty($vals)) {
- $vals = array_fill(0, count($keys), null);
+ $vals = array_fill(0, $keys === null ? count($data) : count($keys), null);
}
- if (count($keys) !== count($vals)) {
+ if (is_array($keys) && count($keys) !== count($vals)) {
throw new RuntimeException(
'Hash::combine() needs an equal number of keys + values.'
);
@@ -508,7 +510,7 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string
if ($groupPath !== null) {
$group = static::extract($data, $groupPath);
if (!empty($group)) {
- $c = count($keys);
+ $c = is_array($keys) ? count($keys) : count($vals);
$out = [];
for ($i = 0; $i < $c; $i++) {
if (!isset($group[$i])) {
@@ -517,7 +519,11 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string
if (!isset($out[$group[$i]])) {
$out[$group[$i]] = [];
}
- $out[$group[$i]][$keys[$i]] = $vals[$i];
+ if ($keys === null) {
+ $out[$group[$i]][] = $vals[$i];
+ } else {
+ $out[$group[$i]][$keys[$i]] = $vals[$i];
+ }
}
return $out;
@@ -527,7 +533,7 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string
return [];
}
- return array_combine($keys, $vals);
+ return array_combine($keys ?? range(0, count($vals) - 1), $vals);
}
/**
@@ -947,7 +953,7 @@ public static function apply(array $data, string $path, callable $function)
{
$values = (array)static::extract($data, $path);
- return call_user_func($function, $values);
+ return $function($values);
}
/**
@@ -1105,10 +1111,10 @@ protected static function _squash(array $data, $key = null): array
public static function diff(array $data, array $compare): array
{
if (empty($data)) {
- return (array)$compare;
+ return $compare;
}
if (empty($compare)) {
- return (array)$data;
+ return $data;
}
$intersection = array_intersect_key($data, $compare);
while (($key = key($intersection)) !== null) {
@@ -1140,8 +1146,8 @@ public static function mergeDiff(array $data, array $compare): array
foreach ($compare as $key => $value) {
if (!array_key_exists($key, $data)) {
$data[$key] = $value;
- } elseif (is_array($value)) {
- $data[$key] = static::mergeDiff($data[$key], $compare[$key]);
+ } elseif (is_array($value) && is_array($data[$key])) {
+ $data[$key] = static::mergeDiff($data[$key], $value);
}
}
@@ -1235,7 +1241,7 @@ public static function nest(array $data, array $options = []): array
$parentId = static::get($result, $parentKeys);
if (isset($idMap[$id][$options['children']])) {
- $idMap[$id] = array_merge($result, (array)$idMap[$id]);
+ $idMap[$id] = array_merge($result, $idMap[$id]);
} else {
$idMap[$id] = array_merge($result, [$options['children'] => []]);
}
diff --git a/app/vendor/cakephp/cakephp/src/Utility/Inflector.php b/app/vendor/cakephp/cakephp/src/Utility/Inflector.php
index 62abe2ca5..73eed4c1d 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/Inflector.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/Inflector.php
@@ -271,10 +271,17 @@ public static function pluralize(string $word): string
}
if (!isset(static::$_cache['irregular']['pluralize'])) {
- static::$_cache['irregular']['pluralize'] = '(?:' . implode('|', array_keys(static::$_irregular)) . ')';
+ $words = array_keys(static::$_irregular);
+ static::$_cache['irregular']['pluralize'] = '/(.*?(?:\\b|_))(' . implode('|', $words) . ')$/i';
+
+ $upperWords = array_map('ucfirst', $words);
+ static::$_cache['irregular']['upperPluralize'] = '/(.*?(?:\\b|[a-z]))(' . implode('|', $upperWords) . ')$/';
}
- if (preg_match('/(.*?(?:\\b|_))(' . static::$_cache['irregular']['pluralize'] . ')$/i', $word, $regs)) {
+ if (
+ preg_match(static::$_cache['irregular']['pluralize'], $word, $regs) ||
+ preg_match(static::$_cache['irregular']['upperPluralize'], $word, $regs)
+ ) {
static::$_cache['pluralize'][$word] = $regs[1] . substr($regs[2], 0, 1) .
substr(static::$_irregular[strtolower($regs[2])], 1);
@@ -282,10 +289,10 @@ public static function pluralize(string $word): string
}
if (!isset(static::$_cache['uninflected'])) {
- static::$_cache['uninflected'] = '(?:' . implode('|', static::$_uninflected) . ')';
+ static::$_cache['uninflected'] = '/^(' . implode('|', static::$_uninflected) . ')$/i';
}
- if (preg_match('/^(' . static::$_cache['uninflected'] . ')$/i', $word, $regs)) {
+ if (preg_match(static::$_cache['uninflected'], $word, $regs)) {
static::$_cache['pluralize'][$word] = $word;
return $word;
@@ -316,10 +323,19 @@ public static function singularize(string $word): string
}
if (!isset(static::$_cache['irregular']['singular'])) {
- static::$_cache['irregular']['singular'] = '(?:' . implode('|', static::$_irregular) . ')';
+ $wordList = array_values(static::$_irregular);
+ static::$_cache['irregular']['singular'] = '/(.*?(?:\\b|_))(' . implode('|', $wordList) . ')$/i';
+
+ $upperWordList = array_map('ucfirst', $wordList);
+ static::$_cache['irregular']['singularUpper'] = '/(.*?(?:\\b|[a-z]))(' .
+ implode('|', $upperWordList) .
+ ')$/';
}
- if (preg_match('/(.*?(?:\\b|_))(' . static::$_cache['irregular']['singular'] . ')$/i', $word, $regs)) {
+ if (
+ preg_match(static::$_cache['irregular']['singular'], $word, $regs) ||
+ preg_match(static::$_cache['irregular']['singularUpper'], $word, $regs)
+ ) {
$suffix = array_search(strtolower($regs[2]), static::$_irregular, true);
$suffix = $suffix ? substr($suffix, 1) : '';
static::$_cache['singularize'][$word] = $regs[1] . substr($regs[2], 0, 1) . $suffix;
@@ -328,10 +344,10 @@ public static function singularize(string $word): string
}
if (!isset(static::$_cache['uninflected'])) {
- static::$_cache['uninflected'] = '(?:' . implode('|', static::$_uninflected) . ')';
+ static::$_cache['uninflected'] = '/^(' . implode('|', static::$_uninflected) . ')$/i';
}
- if (preg_match('/^(' . static::$_cache['uninflected'] . ')$/i', $word, $regs)) {
+ if (preg_match(static::$_cache['uninflected'], $word, $regs)) {
static::$_cache['pluralize'][$word] = $word;
return $word;
diff --git a/app/vendor/cakephp/cakephp/src/Utility/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Utility/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Utility/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Utility/Security.php b/app/vendor/cakephp/cakephp/src/Utility/Security.php
index f5238bc9b..5fcdc4fcd 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/Security.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/Security.php
@@ -43,7 +43,7 @@ class Security
/**
* The crypto implementation to use.
*
- * @var object
+ * @var object|null
*/
protected static $_instance;
diff --git a/app/vendor/cakephp/cakephp/src/Utility/Text.php b/app/vendor/cakephp/cakephp/src/Utility/Text.php
index c7a9376af..6b1e25232 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/Text.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/Text.php
@@ -16,7 +16,7 @@
*/
namespace Cake\Utility;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use InvalidArgumentException;
use Transliterator;
@@ -198,22 +198,16 @@ public static function insert(string $str, array $data, array $options = []): st
'before' => ':', 'after' => '', 'escape' => '\\', 'format' => null, 'clean' => false,
];
$options += $defaults;
- $format = $options['format'];
- $data = $data;
if (empty($data)) {
return $options['clean'] ? static::cleanInsert($str, $options) : $str;
}
- if (!isset($format)) {
- $format = sprintf(
- '/(? $tempData */
$tempData = array_combine($dataKeys, $hashKeys);
krsort($tempData);
@@ -237,7 +241,7 @@ public static function insert(string $str, array $data, array $options = []): st
/** @var array $dataReplacements */
$dataReplacements = array_combine($hashKeys, array_values($data));
foreach ($dataReplacements as $tmpHash => $tmpValue) {
- $tmpValue = is_array($tmpValue) ? '' : $tmpValue;
+ $tmpValue = is_array($tmpValue) ? '' : (string)$tmpValue;
$str = str_replace($tmpHash, $tmpValue, $str);
}
@@ -622,8 +626,12 @@ public static function truncate(string $text, int $length = 100, array $options
}
if ($truncate === '') {
- // phpcs:ignore Generic.Files.LineLength
- if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/i', $tag[2])) {
+ if (
+ !preg_match(
+ '/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/i',
+ $tag[2]
+ )
+ ) {
if (preg_match('/<[\w]+[^>]*>/', $tag[0])) {
array_unshift($openTags, $tag[2]);
} elseif (preg_match('/<\/([\w]+)[^>]*>/', $tag[0], $closeTag)) {
@@ -1092,7 +1100,7 @@ public static function setTransliteratorId(string $transliteratorId): void
{
$transliterator = transliterator_create($transliteratorId);
if ($transliterator === null) {
- throw new Exception('Unable to create transliterator for id: ' . $transliteratorId);
+ throw new CakeException('Unable to create transliterator for id: ' . $transliteratorId);
}
static::setTransliterator($transliterator);
@@ -1118,7 +1126,7 @@ public static function transliterate(string $string, $transliterator = null): st
$return = transliterator_transliterate($transliterator, $string);
if ($return === false) {
- throw new Exception(sprintf('Unable to transliterate string: %s', $string));
+ throw new CakeException(sprintf('Unable to transliterate string: %s', $string));
}
return $return;
diff --git a/app/vendor/cakephp/cakephp/src/Utility/Xml.php b/app/vendor/cakephp/cakephp/src/Utility/Xml.php
index 17fee8d3b..a888d825a 100644
--- a/app/vendor/cakephp/cakephp/src/Utility/Xml.php
+++ b/app/vendor/cakephp/cakephp/src/Utility/Xml.php
@@ -17,6 +17,7 @@
namespace Cake\Utility;
use Cake\Utility\Exception\XmlException;
+use Closure;
use DOMDocument;
use DOMNode;
use DOMText;
@@ -141,32 +142,21 @@ public static function build($input, array $options = [])
*/
protected static function _loadXml(string $input, array $options)
{
- $internalErrors = libxml_use_internal_errors(true);
- if (!$options['loadEntities']) {
- libxml_disable_entity_loader(true);
- }
- $flags = 0;
- if (!empty($options['parseHuge'])) {
- $flags |= LIBXML_PARSEHUGE;
- }
- try {
- if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
- $flags |= LIBXML_NOCDATA;
- $xml = new SimpleXMLElement($input, $flags);
- } else {
- $xml = new DOMDocument();
- $xml->loadXML($input, $flags);
- }
+ return static::load(
+ $input,
+ $options,
+ function ($input, $options, $flags) {
+ if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
+ $flags |= LIBXML_NOCDATA;
+ $xml = new SimpleXMLElement($input, $flags);
+ } else {
+ $xml = new DOMDocument();
+ $xml->loadXML($input, $flags);
+ }
- return $xml;
- } catch (Exception $e) {
- throw new XmlException('Xml cannot be read. ' . $e->getMessage(), null, $e);
- } finally {
- if (!$options['loadEntities']) {
- libxml_disable_entity_loader(false);
+ return $xml;
}
- libxml_use_internal_errors($internalErrors);
- }
+ );
}
/**
@@ -185,28 +175,52 @@ public static function loadHtml(string $input, array $options = [])
];
$options += $defaults;
- $internalErrors = libxml_use_internal_errors(true);
- if (!$options['loadEntities']) {
- libxml_disable_entity_loader(true);
- }
+ return static::load(
+ $input,
+ $options,
+ function ($input, $options, $flags) {
+ $xml = new DOMDocument();
+ $xml->loadHTML($input, $flags);
+
+ if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
+ $xml = simplexml_import_dom($xml);
+ }
+
+ return $xml;
+ }
+ );
+ }
+
+ /**
+ * Parse the input data and create either a SimpleXmlElement object or a DOMDocument.
+ *
+ * @param string $input The input to load.
+ * @param array $options The options to use. See Xml::build()
+ * @param \Closure $callable Closure that should return SimpleXMLElement or DOMDocument instance.
+ * @return \SimpleXMLElement|\DOMDocument
+ * @throws \Cake\Utility\Exception\XmlException
+ */
+ protected static function load(string $input, array $options, Closure $callable)
+ {
$flags = 0;
if (!empty($options['parseHuge'])) {
$flags |= LIBXML_PARSEHUGE;
}
- try {
- $xml = new DOMDocument();
- $xml->loadHTML($input, $flags);
- if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
- $xml = simplexml_import_dom($xml);
- }
+ $internalErrors = libxml_use_internal_errors(true);
+ if (LIBXML_VERSION < 20900 && !$options['loadEntities']) {
+ $previousDisabledEntityLoader = libxml_disable_entity_loader(true);
+ } elseif ($options['loadEntities']) {
+ $flags |= LIBXML_NOENT;
+ }
- return $xml;
+ try {
+ return $callable($input, $options, $flags);
} catch (Exception $e) {
throw new XmlException('Xml cannot be read. ' . $e->getMessage(), null, $e);
} finally {
- if (!$options['loadEntities']) {
- libxml_disable_entity_loader(false);
+ if (isset($previousDisabledEntityLoader)) {
+ libxml_disable_entity_loader($previousDisabledEntityLoader);
}
libxml_use_internal_errors($internalErrors);
}
@@ -217,7 +231,7 @@ public static function loadHtml(string $input, array $options = [])
*
* ### Options
*
- * - `format` If create childs ('tags') or attributes ('attributes').
+ * - `format` If create children ('tags') or attributes ('attributes').
* - `pretty` Returns formatted Xml when set to `true`. Defaults to `false`
* - `version` Version of XML document. Default is 1.0.
* - `encoding` Encoding of XML document. If null remove from XML header.
@@ -255,7 +269,7 @@ public static function loadHtml(string $input, array $options = [])
public static function fromArray($input, array $options = [])
{
if (is_object($input) && method_exists($input, 'toArray') && is_callable([$input, 'toArray'])) {
- $input = call_user_func([$input, 'toArray']);
+ $input = $input->toArray();
}
if (!is_array($input) || count($input) !== 1) {
throw new XmlException('Invalid input.');
@@ -289,7 +303,7 @@ public static function fromArray($input, array $options = [])
}
/**
- * Recursive method to create childs from array
+ * Recursive method to create children from array
*
* @param \DOMDocument $dom Handler to DOMDocument
* @param \DOMDocument|\DOMElement $node Handler to DOMElement (child)
@@ -306,7 +320,7 @@ protected static function _fromArray(DOMDocument $dom, $node, &$data, $format):
foreach ($data as $key => $value) {
if (is_string($key)) {
if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) {
- $value = call_user_func([$value, 'toArray']);
+ $value = $value->toArray();
}
if (!is_array($value)) {
@@ -363,9 +377,9 @@ protected static function _fromArray(DOMDocument $dom, $node, &$data, $format):
}
/**
- * Helper to _fromArray(). It will create childs of arrays
+ * Helper to _fromArray(). It will create children of arrays
*
- * @param array $data Array with information to create childs
+ * @param array $data Array with information to create children
* @return void
*/
protected static function _createChild(array $data): void
@@ -386,7 +400,7 @@ protected static function _createChild(array $data): void
$childNS = $childValue = null;
if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) {
- $value = call_user_func([$value, 'toArray']);
+ $value = $value->toArray();
}
if (is_array($value)) {
if (isset($value['@'])) {
@@ -458,6 +472,7 @@ protected static function _toArray(SimpleXMLElement $xml, array &$parentData, st
}
foreach ($xml->children($namespace, true) as $child) {
+ /** @psalm-suppress PossiblyNullArgument */
static::_toArray($child, $data, $namespace, $namespaces);
}
}
diff --git a/app/vendor/cakephp/cakephp/src/Validation/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Validation/LICENSE.txt
index 0b3b94303..b938c9e8e 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/LICENSE.txt
+++ b/app/vendor/cakephp/cakephp/src/Validation/LICENSE.txt
@@ -1,7 +1,7 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
-Copyright (c) 2005-2019, Cake Software Foundation, Inc. (https://cakefoundation.org)
+Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/vendor/cakephp/cakephp/src/Validation/README.md b/app/vendor/cakephp/cakephp/src/Validation/README.md
index 228e0a495..d3484fac3 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/README.md
+++ b/app/vendor/cakephp/cakephp/src/Validation/README.md
@@ -22,9 +22,9 @@ $validator
'message' => 'E-mail must be valid'
])
->requirePresence('name')
- ->notEmpty('name', 'We need your name.')
+ ->notEmptyString('name', 'We need your name.')
->requirePresence('comment')
- ->notEmpty('comment', 'You need to give a comment.');
+ ->notEmptyString('comment', 'You need to give a comment.');
$errors = $validator->validate($_POST);
if (!empty($errors)) {
diff --git a/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php b/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php
index f5d8c3d53..843f03255 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php
+++ b/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php
@@ -21,6 +21,8 @@
/**
* A Proxy class used to remove any extra arguments when the user intended to call
* a method in another class that is not aware of validation providers signature
+ *
+ * @method bool extension(mixed $check, array $extensions, array $context = [])
*/
class RulesProvider
{
diff --git a/app/vendor/cakephp/cakephp/src/Validation/Validation.php b/app/vendor/cakephp/cakephp/src/Validation/Validation.php
index d27299711..f5edb9781 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/Validation.php
+++ b/app/vendor/cakephp/cakephp/src/Validation/Validation.php
@@ -96,6 +96,16 @@ class Validation
*/
public const COMPARE_LESS_OR_EQUAL = '<=';
+ /**
+ * @var string[]
+ */
+ protected const COMPARE_STRING = [
+ self::COMPARE_EQUAL,
+ self::COMPARE_NOT_EQUAL,
+ self::COMPARE_SAME,
+ self::COMPARE_NOT_SAME,
+ ];
+
/**
* Datetime ISO8601 format
*
@@ -315,19 +325,21 @@ public static function numElements($check, string $operator, int $expectedCount)
* Used to compare 2 numeric values.
*
* @param string|int $check1 The left value to compare.
- * @param string $operator Can be either a word or operand
- * is greater >, is less <, greater or equal >=
- * less or equal <=, is less <, equal to ==, not equal !=
+ * @param string $operator Can be one of following operator strings:
+ * '>', '<', '>=', '<=', '==', '!=', '===' and '!=='. You can use one of
+ * the Validation::COMPARE_* constants.
* @param string|int $check2 The right value to compare.
* @return bool Success
*/
public static function comparison($check1, string $operator, $check2): bool
{
- if ((float)$check1 != $check1) {
+ if (
+ (!is_numeric($check1) || !is_numeric($check2)) &&
+ !in_array($operator, static::COMPARE_STRING)
+ ) {
return false;
}
- $operator = str_replace([' ', "\t", "\n", "\r", "\0", "\x0B"], '', strtolower($operator));
switch ($operator) {
case static::COMPARE_GREATER:
if ($check1 > $check2) {
@@ -370,7 +382,7 @@ public static function comparison($check1, string $operator, $check2): bool
}
break;
default:
- static::$errors[] = 'You must define the $operator parameter for Validation::comparison()';
+ static::$errors[] = 'You must define a valid $operator parameter for Validation::comparison()';
}
return false;
@@ -398,7 +410,7 @@ public static function compareWith($check, string $field, array $context): bool
*
* @param mixed $check The value to find in $field.
* @param string $field The field to check $check against. This field must be present in $context.
- * @param string $operator Comparison operator.
+ * @param string $operator Comparison operator. See Validation::comparison().
* @param array $context The validation context.
* @return bool
* @since 3.6.0
@@ -424,6 +436,7 @@ public static function compareFields($check, string $field, string $operator, ar
*/
public static function containsNonAlphaNumeric($check, int $count = 1): bool
{
+ deprecationWarning('Validation::containsNonAlphaNumeric() is deprecated. Use notAlphaNumeric() instead.');
if (!is_string($check)) {
return false;
}
@@ -436,12 +449,15 @@ public static function containsNonAlphaNumeric($check, int $count = 1): bool
/**
* Used when a custom regular expression is needed.
*
- * @param string $check The value to check.
+ * @param mixed $check The value to check.
* @param string|null $regex If $check is passed as a string, $regex must also be set to valid regular expression
* @return bool Success
*/
- public static function custom(string $check, ?string $regex = null): bool
+ public static function custom($check, ?string $regex = null): bool
{
+ if (!is_scalar($check)) {
+ return false;
+ }
if ($regex === null) {
static::$errors[] = 'You must define a regular expression for Validation::custom()';
@@ -505,7 +521,7 @@ public static function date($check, $format = 'ymd', ?string $regex = null): boo
$fourDigitLeapYear = '(?:(?:(?:(?!0000)[012]\\d)(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))';
$regex['dmy'] = '%^(?:(?:31(\\/|-|\\.|\\x20)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)' .
- $separator . '(?:0?[1,3-9]|1[0-2])\\2))' . $year . '$|^(?:29' .
+ $separator . '(?:0?[13-9]|1[0-2])\\2))' . $year . '$|^(?:29' .
$separator . '0?2\\3' . $leapYear . ')$|^(?:0?[1-9]|1\\d|2[0-8])' .
$separator . '(?:(?:0?[1-9])|(?:1[0-2]))\\4' . $year . '$%';
@@ -515,7 +531,7 @@ public static function date($check, $format = 'ymd', ?string $regex = null): boo
$regex['ymd'] = '%^(?:(?:' . $leapYear .
$separator . '(?:0?2\\1(?:29)))|(?:' . $year .
- $separator . '(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$%';
+ $separator . '(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[13-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$%';
$regex['dMy'] = '/^((31(?!\\ (Feb(ruary)?|Apr(il)?|June?|(Sep(?=\\b|t)t?|Nov)(ember)?)))|((30|29)(?!\\ Feb(ruary)?))|(29(?=\\ Feb(ruary)?\\ ' . $fourDigitLeapYear . '))|(0?[1-9])|1\\d|2[0-8])\\ (Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)\\ ' . $fourDigitYear . '$/';
@@ -635,7 +651,7 @@ public static function time($check): bool
}
$meridianClockRegex = '^((0?[1-9]|1[012])(:[0-5]\d){0,2} ?([AP]M|[ap]m))$';
- $standardClockRegex = '^([01]\d|2[0-3])((:[0-5]\d){0,2}|(:[0-5]\d){2}\.\d{0,6})$';
+ $standardClockRegex = '^([01]\d|2[0-3])((:[0-5]\d){1,2}|(:[0-5]\d){2}\.\d{0,6})$';
return static::_check($check, '%' . $meridianClockRegex . '|' . $standardClockRegex . '%');
}
@@ -658,7 +674,7 @@ public static function localizedTime($check, string $type = 'datetime', $format
if ($check instanceof DateTimeInterface) {
return true;
}
- if (is_object($check)) {
+ if (!is_string($check)) {
return false;
}
static $methods = [
@@ -1085,7 +1101,7 @@ public static function range($check, ?float $lower = null, ?float $upper = null)
* The regex checks for the following component parts:
*
* - a valid, optional, scheme
- * - a valid ip address OR
+ * - a valid IP address OR
* a valid domain name as defined by section 2.3.1 of https://www.ietf.org/rfc/rfc1035.txt
* with an optional port number
* - an optional valid path
@@ -1226,15 +1242,13 @@ public static function mimeType($check, $mimeTypes = []): bool
throw new RuntimeException('Cannot validate mimetype for a missing file');
}
- $finfo = finfo_open(FILEINFO_MIME);
- $finfo = finfo_file($finfo, $file);
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $mime = finfo_file($finfo, $file);
- if (!$finfo) {
+ if (!$mime) {
throw new RuntimeException('Can not determine the mimetype.');
}
- [$mime] = explode(';', $finfo);
-
if (is_string($mimeTypes)) {
return self::_check($mime, $mimeTypes);
}
@@ -1250,7 +1264,7 @@ public static function mimeType($check, $mimeTypes = []): bool
* Helper for reading the file out of the various file implementations
* we accept.
*
- * @param string|array|\Psr\Http\Message\UploadedFileInterface $check The data to read a filename out of.
+ * @param mixed $check The data to read a filename out of.
* @return string|false Either the filename or false on failure.
*/
protected static function getFilename($check)
@@ -1448,12 +1462,12 @@ public static function imageSize($file, array $options): bool
/**
* Validates the image width.
*
- * @param array $file The uploaded file data from PHP.
+ * @param mixed $file The uploaded file data from PHP.
* @param string $operator Comparison operator.
* @param int $width Min or max width.
* @return bool
*/
- public static function imageWidth(array $file, string $operator, int $width): bool
+ public static function imageWidth($file, string $operator, int $width): bool
{
return self::imageSize($file, [
'width' => [
@@ -1464,14 +1478,14 @@ public static function imageWidth(array $file, string $operator, int $width): bo
}
/**
- * Validates the image width.
+ * Validates the image height.
*
- * @param array $file The uploaded file data from PHP.
+ * @param mixed $file The uploaded file data from PHP.
* @param string $operator Comparison operator.
- * @param int $height Min or max width.
+ * @param int $height Min or max height.
* @return bool
*/
- public static function imageHeight(array $file, string $operator, int $height): bool
+ public static function imageHeight($file, string $operator, int $height): bool
{
return self::imageSize($file, [
'height' => [
diff --git a/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php b/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php
index c98c6b2b8..087c0fd2d 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php
+++ b/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php
@@ -89,7 +89,7 @@ public function __construct(array $validator = [])
*/
public function isLast(): bool
{
- return (bool)$this->_last;
+ return $this->_last;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/Validation/Validator.php b/app/vendor/cakephp/cakephp/src/Validation/Validator.php
index 097e0129e..c08021f5d 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/Validator.php
+++ b/app/vendor/cakephp/cakephp/src/Validation/Validator.php
@@ -180,6 +180,13 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable
*/
protected $_allowEmptyFlags = [];
+ /**
+ * Whether to apply last flag to generated rule(s).
+ *
+ * @var bool
+ */
+ protected $_stopOnFailure = false;
+
/**
* Constructor
*/
@@ -189,6 +196,22 @@ public function __construct()
$this->_providers = self::$_defaultProviders;
}
+ /**
+ * Whether to stop validation rule evaluation on the first failed rule.
+ *
+ * When enabled the first failing rule per field will cause validation to stop.
+ * When disabled all rules will be run even if there are failures.
+ *
+ * @param bool $stopOnFailure If to apply last flag.
+ * @return $this
+ */
+ public function setStopOnFailure(bool $stopOnFailure = true)
+ {
+ $this->_stopOnFailure = $stopOnFailure;
+
+ return $this;
+ }
+
/**
* Validates and returns an array of failed fields and their error messages.
*
@@ -479,7 +502,10 @@ public function add(string $field, $name, $rule = [])
foreach ($rules as $name => $rule) {
if (is_array($rule)) {
- $rule += ['rule' => $name];
+ $rule += [
+ 'rule' => $name,
+ 'last' => $this->_stopOnFailure,
+ ];
}
if (!is_string($name)) {
/** @psalm-suppress PossiblyUndefinedMethod */
@@ -1691,6 +1717,7 @@ public function lessThanOrEqualToField(string $field, string $secondField, ?stri
*/
public function containsNonAlphaNumeric(string $field, int $limit = 1, ?string $message = null, $when = null)
{
+ deprecationWarning('Validator::containsNonAlphaNumeric() is deprecated. Use notAlphaNumeric() instead.');
$extra = array_filter(['on' => $when, 'message' => $message]);
return $this->add($field, 'containsNonAlphaNumeric', $extra + [
@@ -2099,7 +2126,7 @@ public function urlWithProtocol(string $field, ?string $message = null, $when =
}
/**
- * Add a validation rule to ensure the field value is within a whitelist.
+ * Add a validation rule to ensure the field value is within an allowed list.
*
* @param string $field The field you want to apply the rule to.
* @param array $list The list of valid options.
@@ -2715,6 +2742,7 @@ public function __debugInfo(): array
'_allowEmptyMessages' => $this->_allowEmptyMessages,
'_allowEmptyFlags' => $this->_allowEmptyFlags,
'_useI18n' => $this->_useI18n,
+ '_stopOnFailure' => $this->_stopOnFailure,
'_providers' => array_keys($this->_providers),
'_fields' => $fields,
];
diff --git a/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php b/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php
index 3327914b9..5d1c68c61 100644
--- a/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php
+++ b/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php
@@ -68,11 +68,12 @@ trait ValidatorAwareTrait
* ```
* public function validationForSubscription($validator)
* {
- * return $validator
- * ->add('email', 'valid-email', ['rule' => 'email'])
- * ->add('password', 'valid', ['rule' => 'notBlank'])
- * ->requirePresence('username');
+ * return $validator
+ * ->add('email', 'valid-email', ['rule' => 'email'])
+ * ->add('password', 'valid', ['rule' => 'notBlank'])
+ * ->requirePresence('username');
* }
+ *
* $validator = $this->getValidator('forSubscription');
* ```
*
@@ -143,11 +144,11 @@ protected function createValidator(string $name): Validator
* You can build the object by yourself and store it in your object:
*
* ```
- * $validator = new \Cake\Validation\Validator($table);
+ * $validator = new \Cake\Validation\Validator();
* $validator
- * ->add('email', 'valid-email', ['rule' => 'email'])
- * ->add('password', 'valid', ['rule' => 'notBlank'])
- * ->allowEmpty('bio');
+ * ->add('email', 'valid-email', ['rule' => 'email'])
+ * ->add('password', 'valid', ['rule' => 'notBlank'])
+ * ->allowEmpty('bio');
* $this->setValidator('forSubscription', $validator);
* ```
*
diff --git a/app/vendor/cakephp/cakephp/src/View/AjaxView.php b/app/vendor/cakephp/cakephp/src/View/AjaxView.php
index 94b08a0e1..f0fa4ae49 100644
--- a/app/vendor/cakephp/cakephp/src/View/AjaxView.php
+++ b/app/vendor/cakephp/cakephp/src/View/AjaxView.php
@@ -16,10 +16,6 @@
*/
namespace Cake\View;
-use Cake\Event\EventManager;
-use Cake\Http\Response;
-use Cake\Http\ServerRequest;
-
/**
* A view class that is used for AJAX responses.
* Currently only switches the default layout and sets the response type - which just maps to
@@ -33,23 +29,11 @@ class AjaxView extends View
protected $layout = 'ajax';
/**
- * Constructor
- *
- * @param \Cake\Http\ServerRequest|null $request The request object.
- * @param \Cake\Http\Response|null $response The response object.
- * @param \Cake\Event\EventManager|null $eventManager Event manager object.
- * @param array $viewOptions View options.
+ * @inheritDoc
*/
- public function __construct(
- ?ServerRequest $request = null,
- ?Response $response = null,
- ?EventManager $eventManager = null,
- array $viewOptions = []
- ) {
- if ($response) {
- $response = $response->withType('ajax');
- }
-
- parent::__construct($request, $response, $eventManager, $viewOptions);
+ public function initialize(): void
+ {
+ parent::initialize();
+ $this->setResponse($this->getResponse()->withType('ajax'));
}
}
diff --git a/app/vendor/cakephp/cakephp/src/View/Cell.php b/app/vendor/cakephp/cakephp/src/View/Cell.php
index 1df99d70b..774854c01 100644
--- a/app/vendor/cakephp/cakephp/src/View/Cell.php
+++ b/app/vendor/cakephp/cakephp/src/View/Cell.php
@@ -90,7 +90,7 @@ abstract class Cell implements EventDispatcherInterface
/**
* List of valid options (constructor's fourth arguments)
- * Override this property in subclasses to whitelist
+ * Override this property in subclasses to allow
* which options you want set as properties in your Cell.
*
* @var array
diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php
index e28cab8ae..3f473c831 100644
--- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php
+++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php
@@ -16,12 +16,12 @@
*/
namespace Cake\View\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a cell class file cannot be found.
*/
-class MissingCellException extends Exception
+class MissingCellException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php
index 775fb82bd..76d6c5685 100644
--- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php
+++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php
@@ -14,12 +14,12 @@
*/
namespace Cake\View\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a helper cannot be found.
*/
-class MissingHelperException extends Exception
+class MissingHelperException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php
index f4f92e043..d5fcfff8d 100644
--- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php
+++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php
@@ -14,14 +14,19 @@
*/
namespace Cake\View\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Throwable;
/**
* Used when a template file cannot be found.
*/
-class MissingTemplateException extends Exception
+class MissingTemplateException extends CakeException
{
+ /**
+ * @var string|null
+ */
+ protected $templateName;
+
/**
* @var string
*/
@@ -47,7 +52,13 @@ class MissingTemplateException extends Exception
*/
public function __construct($file, array $paths = [], ?int $code = null, ?Throwable $previous = null)
{
- $this->file = is_array($file) ? array_pop($file) : $file;
+ if (is_array($file)) {
+ $this->file = array_pop($file);
+ $this->templateName = array_pop($file);
+ } else {
+ $this->file = $file;
+ $this->templateName = null;
+ }
$this->paths = $paths;
parent::__construct($this->formatMessage(), $code, $previous);
@@ -60,7 +71,8 @@ public function __construct($file, array $paths = [], ?int $code = null, ?Throwa
*/
public function formatMessage(): string
{
- $message = "{$this->type} file `{$this->file}` could not be found.";
+ $name = $this->templateName ?? $this->file;
+ $message = "{$this->type} file `{$name}` could not be found.";
if ($this->paths) {
$message .= "\n\nThe following paths were searched:\n\n";
foreach ($this->paths as $path) {
diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php
index 69b1515ae..da0a3774d 100644
--- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php
+++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php
@@ -16,12 +16,12 @@
*/
namespace Cake\View\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a view class file cannot be found.
*/
-class MissingViewException extends Exception
+class MissingViewException extends CakeException
{
/**
* @inheritDoc
diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/SerializationFailureException.php b/app/vendor/cakephp/cakephp/src/View/Exception/SerializationFailureException.php
index c516157fd..3ce691e61 100644
--- a/app/vendor/cakephp/cakephp/src/View/Exception/SerializationFailureException.php
+++ b/app/vendor/cakephp/cakephp/src/View/Exception/SerializationFailureException.php
@@ -16,11 +16,11 @@
*/
namespace Cake\View\Exception;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* Used when a SerializedView class fails to serialize data.
*/
-class SerializationFailureException extends Exception
+class SerializationFailureException extends CakeException
{
}
diff --git a/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php b/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php
index 263d17da7..90a40072e 100644
--- a/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php
+++ b/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php
@@ -16,7 +16,6 @@
*/
namespace Cake\View\Form;
-use Cake\Http\ServerRequest;
use Cake\Utility\Hash;
/**
@@ -27,8 +26,9 @@
*
* Important keys:
*
+ * - `data` Holds the current values supplied for the fields.
* - `defaults` The default values for fields. These values
- * will be used when there is no request data set. Data should be nested following
+ * will be used when there is no data set. Data should be nested following
* the dot separated paths you access your fields with.
* - `required` A nested array of fields, relationships and boolean
* flags to indicate a field is required. The value can also be a string to be used
@@ -45,7 +45,11 @@
* ### Example
*
* ```
- * $data = [
+ * $article = [
+ * 'data' => [
+ * 'id' => '1',
+ * 'title' => 'First post!',
+ * ],
* 'schema' => [
* 'id' => ['type' => 'integer'],
* 'title' => ['type' => 'string', 'length' => 255],
@@ -54,8 +58,7 @@
* ]
* ],
* 'defaults' => [
- * 'id' => 1,
- * 'title' => 'First post!',
+ * 'title' => 'Default title',
* ],
* 'required' => [
* 'id' => true, // will use default required message
@@ -67,13 +70,6 @@
*/
class ArrayContext implements ContextInterface
{
- /**
- * The request object.
- *
- * @var \Cake\Http\ServerRequest
- */
- protected $_request;
-
/**
* Context data for this object.
*
@@ -84,13 +80,12 @@ class ArrayContext implements ContextInterface
/**
* Constructor.
*
- * @param \Cake\Http\ServerRequest $request The request object.
* @param array $context Context info.
*/
- public function __construct(ServerRequest $request, array $context)
+ public function __construct(array $context)
{
- $this->_request = $request;
$context += [
+ 'data' => [],
'schema' => [],
'required' => [],
'defaults' => [],
@@ -107,6 +102,8 @@ public function __construct(ServerRequest $request, array $context)
*/
public function primaryKey(): array
{
+ deprecationWarning('`ArrayContext::primaryKey()` is deprecated. Use `ArrayContext::getPrimaryKey()`.');
+
return $this->getPrimaryKey();
}
@@ -166,17 +163,16 @@ public function isCreate(): bool
/**
* Get the current value for a given field.
*
- * This method will coalesce the current request data and the 'defaults'
- * array.
+ * This method will coalesce the current data and the 'defaults' array.
*
* @param string $field A dot separated path to the field a value
* is needed for.
* @param array $options Options:
*
- * - `default`: Default value to return if no value found in request
- * data or context record.
+ * - `default`: Default value to return if no value found in data or
+ * context record.
* - `schemaDefault`: Boolean indicating whether default value from
- * context's schema should be used if it's not explicitly provided.
+ * context's schema should be used if it's not explicitly provided.
* @return mixed
*/
public function val(string $field, array $options = [])
@@ -186,10 +182,10 @@ public function val(string $field, array $options = [])
'schemaDefault' => true,
];
- $val = $this->_request->getData($field);
- if ($val !== null) {
- return $val;
+ if (Hash::check($this->_context['data'], $field)) {
+ return Hash::get($this->_context['data'], $field);
}
+
if ($options['default'] !== null || !$options['schemaDefault']) {
return $options['default'];
}
@@ -320,9 +316,11 @@ public function attributes(string $field): array
if ($schema === null) {
$schema = Hash::get($this->_context['schema'], $this->stripNesting($field));
}
- $whitelist = ['length' => null, 'precision' => null];
- return array_intersect_key((array)$schema, $whitelist);
+ return array_intersect_key(
+ (array)$schema,
+ array_flip(static::VALID_ATTRIBUTES)
+ );
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php b/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php
index b0e47e45b..490f2aee1 100644
--- a/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php
+++ b/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php
@@ -61,17 +61,17 @@ public static function createWithDefaults(array $providers = [])
'type' => 'orm',
'callable' => function ($request, $data) {
if ($data['entity'] instanceof EntityInterface) {
- return new EntityContext($request, $data);
+ return new EntityContext($data);
}
if (isset($data['table'])) {
- return new EntityContext($request, $data);
+ return new EntityContext($data);
}
if (is_iterable($data['entity'])) {
$pass = (new Collection($data['entity']))->first() !== null;
if ($pass) {
- return new EntityContext($request, $data);
+ return new EntityContext($data);
} else {
- return new NullContext($request, $data);
+ return new NullContext($data);
}
}
},
@@ -80,7 +80,7 @@ public static function createWithDefaults(array $providers = [])
'type' => 'form',
'callable' => function ($request, $data) {
if ($data['entity'] instanceof Form) {
- return new FormContext($request, $data);
+ return new FormContext($data);
}
},
],
@@ -88,7 +88,7 @@ public static function createWithDefaults(array $providers = [])
'type' => 'array',
'callable' => function ($request, $data) {
if (is_array($data['entity']) && isset($data['entity']['schema'])) {
- return new ArrayContext($request, $data['entity']);
+ return new ArrayContext($data['entity']);
}
},
],
@@ -96,7 +96,7 @@ public static function createWithDefaults(array $providers = [])
'type' => 'null',
'callable' => function ($request, $data) {
if ($data['entity'] === null) {
- return new NullContext($request, $data);
+ return new NullContext($data);
}
},
],
@@ -136,7 +136,7 @@ public function addProvider(string $type, callable $check)
* @param \Cake\Http\ServerRequest $request Request instance.
* @param array $data The data to get a context provider for.
* @return \Cake\View\Form\ContextInterface Context provider.
- * @throws \RuntimeException When a context instace cannot be generated for given entity.
+ * @throws \RuntimeException When a context instance cannot be generated for given entity.
*/
public function get(ServerRequest $request, array $data = []): ContextInterface
{
diff --git a/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php b/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php
index 314891ff2..0984c9d35 100644
--- a/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php
+++ b/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php
@@ -21,6 +21,11 @@
*/
interface ContextInterface
{
+ /**
+ * @var string[]
+ */
+ public const VALID_ATTRIBUTES = ['length', 'precision', 'comment', 'null', 'default'];
+
/**
* Get the fields used in the context as a primary key.
*
@@ -50,10 +55,10 @@ public function isCreate(): bool;
* Classes implementing this method can optionally have a second argument
* `$options`. Valid key for `$options` array are:
*
- * - `default`: Default value to return if no value found in request
- * data or context record.
+ * - `default`: Default value to return if no value found in data or
+ * context record.
* - `schemaDefault`: Boolean indicating whether default value from
- * context's schema should be used if it's not explicitly provided.
+ * context's schema should be used if it's not explicitly provided.
*
* @param string $field A dot separated path to the field a value
* @param array $options Options.
diff --git a/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php b/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php
index 5c072f084..4184c2d85 100644
--- a/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php
+++ b/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php
@@ -19,7 +19,7 @@
use ArrayAccess;
use Cake\Collection\Collection;
use Cake\Datasource\EntityInterface;
-use Cake\Http\ServerRequest;
+use Cake\Datasource\InvalidPropertyInterface;
use Cake\ORM\Entity;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\ORM\Table;
@@ -52,13 +52,6 @@ class EntityContext implements ContextInterface
{
use LocatorAwareTrait;
- /**
- * The request object.
- *
- * @var \Cake\Http\ServerRequest
- */
- protected $_request;
-
/**
* Context data for this object.
*
@@ -98,12 +91,10 @@ class EntityContext implements ContextInterface
/**
* Constructor.
*
- * @param \Cake\Http\ServerRequest $request The request object.
* @param array $context Context info.
*/
- public function __construct(ServerRequest $request, array $context)
+ public function __construct(array $context)
{
- $this->_request = $request;
$context += [
'entity' => null,
'table' => null,
@@ -180,6 +171,8 @@ protected function _prepare(): void
*/
public function primaryKey(): array
{
+ deprecationWarning('`EntityContext::primaryKey()` is deprecated. Use `EntityContext::getPrimaryKey()`.');
+
return (array)$this->_tables[$this->_rootName]->getPrimaryKey();
}
@@ -245,8 +238,8 @@ public function isCreate(): bool
* @param string $field The dot separated path to the value.
* @param array $options Options:
*
- * - `default`: Default value to return if no value found in request
- * data or entity.
+ * - `default`: Default value to return if no value found in data or
+ * entity.
* - `schemaDefault`: Boolean indicating whether default value from table
* schema should be used if it's not explicitly provided.
* @return mixed The value of the field or null on a miss.
@@ -258,10 +251,6 @@ public function val(string $field, array $options = [])
'schemaDefault' => true,
];
- $val = $this->_request->getData($field);
- if ($val !== null) {
- return $val;
- }
if (empty($this->_context['entity'])) {
return $options['default'];
}
@@ -274,6 +263,14 @@ public function val(string $field, array $options = [])
if ($entity instanceof EntityInterface) {
$part = end($parts);
+
+ if ($entity instanceof InvalidPropertyInterface) {
+ $val = $entity->getInvalidField($part);
+ if ($val !== null) {
+ return $val;
+ }
+ }
+
$val = $entity->get($part);
if ($val !== null) {
return $val;
@@ -632,7 +629,7 @@ protected function _getValidator(array $parts): Validator
*
* @param string[]|string|\Cake\Datasource\EntityInterface $parts Each one of the parts in a path for a field name
* @param bool $fallback Whether or not to fallback to the last found table
- * when a non-existent field/property is being encountered.
+ * when a nonexistent field/property is being encountered.
* @return \Cake\ORM\Table|null Table instance or null
*/
protected function _getTable($parts, $fallback = true): ?Table
@@ -714,10 +711,10 @@ public function attributes(string $field): array
return [];
}
- $column = (array)$table->getSchema()->getColumn(array_pop($parts));
- $whitelist = ['length' => null, 'precision' => null];
-
- return array_intersect_key($column, $whitelist);
+ return array_intersect_key(
+ (array)$table->getSchema()->getColumn(array_pop($parts)),
+ array_flip(static::VALID_ATTRIBUTES)
+ );
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php b/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php
index f3d5291c2..f0475ec69 100644
--- a/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php
+++ b/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php
@@ -16,24 +16,16 @@
*/
namespace Cake\View\Form;
-use Cake\Http\ServerRequest;
use Cake\Utility\Hash;
/**
* Provides a context provider for Cake\Form\Form instances.
*
* This context provider simply fulfils the interface requirements
- * that FormHelper has and allows access to the request data.
+ * that FormHelper has and allows access to the form data.
*/
class FormContext implements ContextInterface
{
- /**
- * The request object.
- *
- * @var \Cake\Http\ServerRequest
- */
- protected $_request;
-
/**
* The form object.
*
@@ -44,12 +36,10 @@ class FormContext implements ContextInterface
/**
* Constructor.
*
- * @param \Cake\Http\ServerRequest $request The request object.
* @param array $context Context info.
*/
- public function __construct(ServerRequest $request, array $context)
+ public function __construct(array $context)
{
- $this->_request = $request;
$context += [
'entity' => null,
];
@@ -64,6 +54,8 @@ public function __construct(ServerRequest $request, array $context)
*/
public function primaryKey(): array
{
+ deprecationWarning('`FormContext::primaryKey()` is deprecated. Use `FormContext::getPrimaryKey()`.');
+
return [];
}
@@ -101,11 +93,6 @@ public function val(string $field, array $options = [])
'schemaDefault' => true,
];
- $val = $this->_request->getData($field);
- if ($val !== null) {
- return $val;
- }
-
$val = $this->_form->getData($field);
if ($val !== null) {
return $val;
@@ -126,7 +113,7 @@ public function val(string $field, array $options = [])
*/
protected function _schemaDefault(string $field)
{
- $field = $this->_form->schema()->field($field);
+ $field = $this->_form->getSchema()->field($field);
if ($field) {
return $field['default'];
}
@@ -199,7 +186,7 @@ public function getMaxLength(string $field): ?int
*/
public function fieldNames(): array
{
- return $this->_form->schema()->fields();
+ return $this->_form->getSchema()->fields();
}
/**
@@ -207,7 +194,7 @@ public function fieldNames(): array
*/
public function type(string $field): ?string
{
- return $this->_form->schema()->fieldType($field);
+ return $this->_form->getSchema()->fieldType($field);
}
/**
@@ -215,10 +202,10 @@ public function type(string $field): ?string
*/
public function attributes(string $field): array
{
- $column = (array)$this->_form->schema()->field($field);
- $whiteList = ['length' => null, 'precision' => null];
-
- return array_intersect_key($column, $whiteList);
+ return array_intersect_key(
+ (array)$this->_form->getSchema()->field($field),
+ array_flip(static::VALID_ATTRIBUTES)
+ );
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php b/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php
index d78c1f3f3..5a0074684 100644
--- a/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php
+++ b/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php
@@ -16,32 +16,21 @@
*/
namespace Cake\View\Form;
-use Cake\Http\ServerRequest;
-
/**
* Provides a context provider that does nothing.
*
* This context provider simply fulfils the interface requirements
- * that FormHelper has and allows access to the request data.
+ * that FormHelper has.
*/
class NullContext implements ContextInterface
{
- /**
- * The request object.
- *
- * @var \Cake\Http\ServerRequest
- */
- protected $_request;
-
/**
* Constructor.
*
- * @param \Cake\Http\ServerRequest $request The request object.
* @param array $context Context info.
*/
- public function __construct(ServerRequest $request, array $context)
+ public function __construct(array $context)
{
- $this->_request = $request;
}
/**
@@ -52,6 +41,8 @@ public function __construct(ServerRequest $request, array $context)
*/
public function primaryKey(): array
{
+ deprecationWarning('`NullContext::primaryKey()` is deprecated. Use `NullContext::getPrimaryKey()`.');
+
return [];
}
@@ -84,7 +75,7 @@ public function isCreate(): bool
*/
public function val(string $field, array $options = [])
{
- return $this->_request->getData($field);
+ return null;
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php
index 93de6826f..11887ead5 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php
@@ -34,7 +34,7 @@ class BreadcrumbsHelper extends Helper
*
* @var array
*/
- public $helpers = ['Url'];
+ protected $helpers = ['Url'];
/**
* Default config for the helper.
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/FlashHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/FlashHelper.php
index 9b7f96193..78e2f252e 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/FlashHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/FlashHelper.php
@@ -17,7 +17,6 @@
namespace Cake\View\Helper;
use Cake\View\Helper;
-use UnexpectedValueException;
/**
* FlashHelper class to render flash messages.
@@ -67,27 +66,16 @@ class FlashHelper extends Helper
* Supports the 'params', and 'element' keys that are used in the helper.
* @return string|null Rendered flash message or null if flash key does not exist
* in session.
- * @throws \UnexpectedValueException If value for flash settings key is not an array.
*/
public function render(string $key = 'flash', array $options = []): ?string
{
- $session = $this->_View->getRequest()->getSession();
-
- if (!$session->check("Flash.$key")) {
+ $messages = $this->_View->getRequest()->getFlash()->consume($key);
+ if ($messages === null) {
return null;
}
- $flash = $session->read("Flash.$key");
- if (!is_array($flash)) {
- throw new UnexpectedValueException(sprintf(
- 'Value for flash setting key "%s" must be an array.',
- $key
- ));
- }
- $session->delete("Flash.$key");
-
$out = '';
- foreach ($flash as $message) {
+ foreach ($messages as $message) {
$message = $options + $message;
$out .= $this->_View->element($message['element'], $message);
}
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php
index 8b3deb2f5..322e9a930 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php
@@ -17,7 +17,7 @@
namespace Cake\View\Helper;
use Cake\Core\Configure;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Form\FormProtector;
use Cake\Routing\Router;
use Cake\Utility\Hash;
@@ -28,6 +28,7 @@
use Cake\View\StringTemplateTrait;
use Cake\View\View;
use Cake\View\Widget\WidgetLocator;
+use InvalidArgumentException;
use RuntimeException;
/**
@@ -54,7 +55,7 @@ class FormHelper extends Helper
*
* @var array
*/
- public $helpers = ['Url', 'Html'];
+ protected $helpers = ['Url', 'Html'];
/**
* Default config for the helper.
@@ -206,7 +207,7 @@ class FormHelper extends Helper
/**
* Context factory.
*
- * @var \Cake\View\Form\ContextFactory
+ * @var \Cake\View\Form\ContextFactory|null
*/
protected $_contextFactory;
@@ -219,11 +220,23 @@ class FormHelper extends Helper
protected $_lastAction = '';
/**
- * The sources to be used when retrieving prefilled input values.
+ * The supported sources that can be used to populate input values.
+ *
+ * `context` - Corresponds to `ContextInterface` instances.
+ * `data` - Corresponds to request data (POST/PUT).
+ * `query` - Corresponds to request's query string.
*
* @var string[]
*/
- protected $_valueSources = ['context'];
+ protected $supportedValueSources = ['context', 'data', 'query'];
+
+ /**
+ * The default sources.
+ *
+ * @see FormHelper::$supportedValueSources for valid values.
+ * @var string[]
+ */
+ protected $_valueSources = ['data', 'context'];
/**
* Grouped input types.
@@ -559,7 +572,7 @@ public function end(array $secureAttributes = []): string
$this->templater()->pop();
$this->requestType = null;
$this->_context = null;
- $this->_valueSources = ['context'];
+ $this->_valueSources = ['data', 'context'];
$this->_idPrefix = $this->getConfig('idPrefix');
$this->formProtector = null;
@@ -595,7 +608,7 @@ public function secure(array $fields = [], array $secureAttributes = []): string
$this->formProtector->addField($field, true, $value);
}
- $debugSecurity = Configure::read('debug');
+ $debugSecurity = (bool)Configure::read('debug');
if (isset($secureAttributes['debugSecurity'])) {
$debugSecurity = $debugSecurity && $secureAttributes['debugSecurity'];
unset($secureAttributes['debugSecurity']);
@@ -660,12 +673,15 @@ protected function createFormProtector(array $formTokenData): FormProtector
* Get form protector instance.
*
* @return \Cake\Form\FormProtector
- * @throws \Cake\Core\Exception\Exception
+ * @throws \Cake\Core\Exception\CakeException
*/
public function getFormProtector(): FormProtector
{
if ($this->formProtector === null) {
- throw new Exception('FormHelper::create() must be called first for FormProtector instance to be created.');
+ throw new CakeException(
+ '`FormProtector` instance has not been created. Ensure you have loaded the `FormProtectionComponent`'
+ . ' in your controller and called `FormHelper::create()` before calling `FormHelper::unlockField()`.'
+ );
}
return $this->formProtector;
@@ -1222,9 +1238,12 @@ protected function _inputType(string $fieldName, array $options): string
return 'select';
}
+ $type = 'text';
$internalType = $context->type($fieldName);
$map = $this->_config['typeMap'];
- $type = $map[$internalType] ?? 'text';
+ if ($internalType !== null && isset($map[$internalType])) {
+ $type = $map[$internalType];
+ }
$fieldName = array_slice(explode('.', $fieldName), -1)[0];
switch (true) {
@@ -1319,7 +1338,7 @@ protected function _magicOptions(string $fieldName, array $options, bool $allowO
}
/**
- * Set required attribute and custom validity js.
+ * Set required attribute and custom validity JS.
*
* @param string $fieldName The name of the field to generate options for.
* @param array $options Options list.
@@ -1556,13 +1575,13 @@ public function radio(string $fieldName, iterable $options = [], array $attribut
* @param string $method Method name / input type to make.
* @param array $params Parameters for the method call
* @return string Formatted input method.
- * @throws \Cake\Core\Exception\Exception When there are no params for the method call.
+ * @throws \Cake\Core\Exception\CakeException When there are no params for the method call.
*/
public function __call(string $method, array $params)
{
$options = [];
if (empty($params)) {
- throw new Exception(sprintf('Missing field name for FormHelper::%s', $method));
+ throw new CakeException(sprintf('Missing field name for FormHelper::%s', $method));
}
if (isset($params[1])) {
$options = $params[1];
@@ -1619,7 +1638,7 @@ public function hidden(string $fieldName, array $options = []): string
$this->formProtector->addField(
$options['name'],
true,
- (string)$options['val']
+ $options['val'] === false ? '0' : (string)$options['val']
);
}
@@ -2480,18 +2499,43 @@ public function getValueSources(): array
return $this->_valueSources;
}
+ /**
+ * Validate value sources.
+ *
+ * @param string[] $sources A list of strings identifying a source.
+ * @return void
+ * @throws \InvalidArgumentException If sources list contains invalid value.
+ */
+ protected function validateValueSources(array $sources): void
+ {
+ $diff = array_diff($sources, $this->supportedValueSources);
+
+ if ($diff) {
+ throw new InvalidArgumentException(sprintf(
+ 'Invalid value source(s): %s. Valid values are: %s',
+ implode(', ', $diff),
+ implode(', ', $this->supportedValueSources)
+ ));
+ }
+ }
+
/**
* Sets the value sources.
*
- * Valid values are `'context'`, `'data'` and `'query'`.
- * You need to supply one valid context or multiple, as a list of strings. Order sets priority.
+ * You need to supply one or more valid sources, as a list of strings.
+ * Order sets priority.
*
+ * @see FormHelper::$supportedValueSources for valid values.
* @param string|string[] $sources A string or a list of strings identifying a source.
* @return $this
+ * @throws \InvalidArgumentException If sources list contains invalid value.
*/
public function setValueSources($sources)
{
- $this->_valueSources = array_values(array_intersect((array)$sources, ['context', 'data', 'query']));
+ $sources = (array)$sources;
+
+ $this->validateValueSources($sources);
+ $this->_valueSources = $sources;
return $this;
}
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/HtmlHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/HtmlHelper.php
index 8da4a652d..089962220 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/HtmlHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/HtmlHelper.php
@@ -37,7 +37,7 @@ class HtmlHelper extends Helper
*
* @var array
*/
- public $helpers = ['Url'];
+ protected $helpers = ['Url'];
/**
* Default config for this class
@@ -299,6 +299,30 @@ public function link($title, $url = null, array $options = []): string
]);
}
+ /**
+ * Creates an HTML link from route path string.
+ *
+ * ### Options
+ *
+ * - `escape` Set to false to disable escaping of title and attributes.
+ * - `escapeTitle` Set to false to disable escaping of title. Takes precedence
+ * over value of `escape`)
+ * - `confirm` JavaScript confirmation message.
+ *
+ * @param string $title The content to be wrapped by `` tags.
+ * @param string $path Cake-relative route path.
+ * @param array $params An array specifying any additional parameters.
+ * Can be also any special parameters supported by `Router::url()`.
+ * @param array $options Array of options and HTML attributes.
+ * @return string An `` element.
+ * @see \Cake\Routing\Router::pathUrl()
+ * @link https://book.cakephp.org/3/en/views/helpers/html.html#creating-links
+ */
+ public function linkFromPath(string $title, string $path, array $params = [], array $options = []): string
+ {
+ return $this->link($title, ['_path' => $path] + $params, $options);
+ }
+
/**
* Creates a link element for CSS stylesheets.
*
@@ -704,7 +728,9 @@ public function tableCells(
bool $useCount = false,
bool $continueOddEven = true
): string {
- if (is_string($data) || empty($data[0]) || !is_array($data[0])) {
+ if (!is_array($data)) {
+ $data = [[$data]];
+ } elseif (empty($data[0]) || !is_array($data[0])) {
$data = [$data];
}
@@ -766,7 +792,7 @@ protected function _renderCells(array $line, bool $useCount = false): array
}
}
- $cellsOut[] = $this->tableCell($cell, $cellOptions);
+ $cellsOut[] = $this->tableCell((string)$cell, $cellOptions);
}
return $cellsOut;
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/NumberHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/NumberHelper.php
index cf21cfd96..1b3cf8fa3 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/NumberHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/NumberHelper.php
@@ -17,7 +17,7 @@
namespace Cake\View\Helper;
use Cake\Core\App;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\I18n\Number;
use Cake\View\Helper;
use Cake\View\View;
@@ -58,7 +58,7 @@ class NumberHelper extends Helper
*
* @param \Cake\View\View $view The View this helper is being attached to.
* @param array $config Configuration settings for the helper
- * @throws \Cake\Core\Exception\Exception When the engine class could not be found.
+ * @throws \Cake\Core\Exception\CakeException When the engine class could not be found.
*/
public function __construct(View $view, array $config = [])
{
@@ -69,7 +69,7 @@ public function __construct(View $view, array $config = [])
/** @psalm-var class-string<\Cake\I18n\Number>|null $engineClass */
$engineClass = App::className($config['engine'], 'Utility');
if ($engineClass === null) {
- throw new Exception(sprintf('Class for %s could not be found', $config['engine']));
+ throw new CakeException(sprintf('Class for %s could not be found', $config['engine']));
}
$this->_engine = new $engineClass($config);
@@ -84,7 +84,7 @@ public function __construct(View $view, array $config = [])
*/
public function __call(string $method, array $params)
{
- return call_user_func_array([$this->_engine, $method], $params);
+ return $this->_engine->{$method}(...$params);
}
/**
@@ -222,9 +222,14 @@ public function formatDelta($value, array $options = []): string
* if $currency argument is not provided. If boolean false is passed, it will clear the
* currently stored value. Null reads the current default.
* @return string|null Currency
+ * @deprecated 3.9.0 Use setDefaultCurrency()/getDefaultCurrency() instead.
*/
public function defaultCurrency($currency): ?string
{
+ deprecationWarning(
+ 'NumberHelper::defaultCurrency() is deprecated. Use setDefaultCurrency() and getDefaultCurrency() instead.'
+ );
+
return $this->_engine->defaultCurrency($currency);
}
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/PaginatorHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/PaginatorHelper.php
index 2f954b6fa..0c9591a19 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/PaginatorHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/PaginatorHelper.php
@@ -43,7 +43,7 @@ class PaginatorHelper extends Helper
*
* @var array
*/
- public $helpers = ['Url', 'Number', 'Html', 'Form'];
+ protected $helpers = ['Url', 'Number', 'Html', 'Form'];
/**
* Default config for this class
@@ -260,7 +260,7 @@ public function sortDir(?string $model = null, array $options = []): string
$options = $this->params($model);
}
- if (isset($options['direction'])) {
+ if (!empty($options['direction'])) {
$dir = strtolower($options['direction']);
}
@@ -567,20 +567,18 @@ public function generateUrlParams(array $options = [], ?string $model = null, ar
) {
$options['sort'] = $options['direction'] = null;
}
-
+ $baseUrl = $this->_config['options']['url'] ?? [];
if (!empty($paging['scope'])) {
$scope = $paging['scope'];
- $currentParams = $this->_config['options']['url'];
-
- if (isset($currentParams['?'][$scope]) && is_array($currentParams['?'][$scope])) {
- $options += $currentParams['?'][$scope];
+ if (isset($baseUrl['?'][$scope]) && is_array($baseUrl['?'][$scope])) {
+ $options += $baseUrl['?'][$scope];
+ unset($baseUrl['?'][$scope]);
}
$options = [$scope => $options];
}
- if (!empty($this->_config['options']['url'])) {
- $key = implode('.', array_filter(['options.url', Hash::get($paging, 'scope', null)]));
- $url = Hash::merge($url, Hash::get($this->_config, $key, []));
+ if (!empty($baseUrl)) {
+ $url = Hash::merge($url, $baseUrl);
}
if (!isset($url['?'])) {
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/TextHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/TextHelper.php
index b4d19e1f8..b6cdbab7a 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/TextHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/TextHelper.php
@@ -17,7 +17,7 @@
namespace Cake\View\Helper;
use Cake\Core\App;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Utility\Security;
use Cake\Utility\Text;
use Cake\View\Helper;
@@ -39,7 +39,7 @@ class TextHelper extends Helper
*
* @var array
*/
- public $helpers = ['Html'];
+ protected $helpers = ['Html'];
/**
* Default config for this class
@@ -75,7 +75,7 @@ class TextHelper extends Helper
*
* @param \Cake\View\View $view the view object the helper is attached to.
* @param array $config Settings array Settings array
- * @throws \Cake\Core\Exception\Exception when the engine class could not be found.
+ * @throws \Cake\Core\Exception\CakeException when the engine class could not be found.
*/
public function __construct(View $view, array $config = [])
{
@@ -86,7 +86,7 @@ public function __construct(View $view, array $config = [])
/** @psalm-var class-string<\Cake\Utility\Text>|null $engineClass */
$engineClass = App::className($config['engine'], 'Utility');
if ($engineClass === null) {
- throw new Exception(sprintf('Class for %s could not be found', $config['engine']));
+ throw new CakeException(sprintf('Class for %s could not be found', $config['engine']));
}
$this->_engine = new $engineClass($config);
@@ -101,7 +101,7 @@ public function __construct(View $view, array $config = [])
*/
public function __call(string $method, array $params)
{
- return call_user_func_array([$this->_engine, $method], $params);
+ return $this->_engine->{$method}(...$params);
}
/**
@@ -397,6 +397,32 @@ public function toList(array $list, ?string $and = null, string $separator = ',
return $this->_engine->toList($list, $and, $separator);
}
+ /**
+ * Returns a string with all spaces converted to dashes (by default),
+ * characters transliterated to ASCII characters, and non word characters removed.
+ *
+ * ### Options:
+ *
+ * - `replacement`: Replacement string. Default '-'.
+ * - `transliteratorId`: A valid transliterator id string.
+ * If `null` (default) the transliterator (identifier) set via
+ * `Text::setTransliteratorId()` or `Text::setTransliterator()` will be used.
+ * If `false` no transliteration will be done, only non words will be removed.
+ * - `preserve`: Specific non-word character to preserve. Default `null`.
+ * For e.g. this option can be set to '.' to generate clean file names.
+ *
+ * @param string $string the string you want to slug
+ * @param array|string $options If string it will be use as replacement character
+ * or an array of options.
+ * @return string
+ * @see \Cake\Utility\Text::setTransliterator()
+ * @see \Cake\Utility\Text::setTransliteratorId()
+ */
+ public function slug(string $string, $options = []): string
+ {
+ return $this->_engine->slug($string, $options);
+ }
+
/**
* Event listeners.
*
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/TimeHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/TimeHelper.php
index 614c7d9c1..693ddf874 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/TimeHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/TimeHelper.php
@@ -351,7 +351,7 @@ public function gmt($string = null): string
*
* This method is an alias for TimeHelper::i18nFormat().
*
- * @param int|string|\DateTimeInterface $date UNIX timestamp, strtotime() valid string
+ * @param int|string|\DateTimeInterface|null $date UNIX timestamp, strtotime() valid string
* or DateTime object (or a date format string).
* @param int|string|null $format date format string (or a UNIX timestamp,
* strtotime() valid string or DateTime object).
diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/UrlHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/UrlHelper.php
index f232222eb..a13d61140 100644
--- a/app/vendor/cakephp/cakephp/src/View/Helper/UrlHelper.php
+++ b/app/vendor/cakephp/cakephp/src/View/Helper/UrlHelper.php
@@ -16,6 +16,8 @@
*/
namespace Cake\View\Helper;
+use Cake\Core\App;
+use Cake\Core\Exception\CakeException;
use Cake\Routing\Asset;
use Cake\Routing\Router;
use Cake\View\Helper;
@@ -25,6 +27,43 @@
*/
class UrlHelper extends Helper
{
+ /**
+ * Default config for this class
+ *
+ * @var array
+ */
+ protected $_defaultConfig = [
+ 'assetUrlClassName' => Asset::class,
+ ];
+
+ /**
+ * Asset URL engine class name
+ *
+ * @var string
+ * @psalm-var class-string<\Cake\Routing\Asset>
+ */
+ protected $_assetUrlClassName;
+
+ /**
+ * Check proper configuration
+ *
+ * @param array $config The configuration settings provided to this helper.
+ * @return void
+ */
+ public function initialize(array $config): void
+ {
+ parent::initialize($config);
+ $engineClassConfig = $this->getConfig('assetUrlClassName');
+
+ /** @psalm-var class-string<\Cake\Routing\Asset>|null $engineClass */
+ $engineClass = App::className($engineClassConfig, 'Routing');
+ if ($engineClass === null) {
+ throw new CakeException(sprintf('Class for %s could not be found', $engineClassConfig));
+ }
+
+ $this->_assetUrlClassName = $engineClass;
+ }
+
/**
* Returns a URL based on provided parameters.
*
@@ -57,6 +96,27 @@ public function build($url = null, array $options = []): string
return $url;
}
+ /**
+ * Returns a URL from a route path string.
+ *
+ * ### Options:
+ *
+ * - `escape`: If false, the URL will be returned unescaped, do only use if it is manually
+ * escaped afterwards before being displayed.
+ * - `fullBase`: If true, the full base URL will be prepended to the result
+ *
+ * @param string $path Cake-relative route path.
+ * @param array $params An array specifying any additional parameters.
+ * Can be also any special parameters supported by `Router::url()`.
+ * @param array $options Array of options.
+ * @return string Full translated URL with base path.
+ * @see \Cake\Routing\Router::pathUrl()
+ */
+ public function buildFromPath(string $path, array $params = [], array $options = []): string
+ {
+ return $this->build(['_path' => $path] + $params, $options);
+ }
+
/**
* Generates URL for given image file.
*
@@ -78,7 +138,7 @@ public function image(string $path, array $options = []): string
{
$options += ['theme' => $this->_View->getTheme()];
- return h(Asset::imageUrl($path, $options));
+ return h($this->_assetUrlClassName::imageUrl($path, $options));
}
/**
@@ -103,7 +163,7 @@ public function css(string $path, array $options = []): string
{
$options += ['theme' => $this->_View->getTheme()];
- return h(Asset::cssUrl($path, $options));
+ return h($this->_assetUrlClassName::cssUrl($path, $options));
}
/**
@@ -128,7 +188,7 @@ public function script(string $path, array $options = []): string
{
$options += ['theme' => $this->_View->getTheme()];
- return h(Asset::scriptUrl($path, $options));
+ return h($this->_assetUrlClassName::scriptUrl($path, $options));
}
/**
@@ -157,7 +217,7 @@ public function assetUrl(string $path, array $options = []): string
{
$options += ['theme' => $this->_View->getTheme()];
- return h(Asset::url($path, $options));
+ return h($this->_assetUrlClassName::url($path, $options));
}
/**
@@ -171,7 +231,7 @@ public function assetUrl(string $path, array $options = []): string
*/
public function assetTimestamp(string $path, $timestamp = null): string
{
- return h(Asset::assetTimestamp($path, $timestamp));
+ return h($this->_assetUrlClassName::assetTimestamp($path, $timestamp));
}
/**
@@ -184,7 +244,7 @@ public function webroot(string $file): string
{
$options = ['theme' => $this->_View->getTheme()];
- return h(Asset::webroot($file, $options));
+ return h($this->_assetUrlClassName::webroot($file, $options));
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/View/HelperRegistry.php b/app/vendor/cakephp/cakephp/src/View/HelperRegistry.php
index e6ba25d37..bb122741a 100644
--- a/app/vendor/cakephp/cakephp/src/View/HelperRegistry.php
+++ b/app/vendor/cakephp/cakephp/src/View/HelperRegistry.php
@@ -143,16 +143,16 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void
*
* @param string $class The class to create.
* @param string $alias The alias of the loaded helper.
- * @param array $settings An array of settings to use for the helper.
+ * @param array $config An array of settings to use for the helper.
* @return \Cake\View\Helper The constructed helper class.
* @psalm-suppress MoreSpecificImplementedParamType
*/
- protected function _create($class, string $alias, array $settings): Helper
+ protected function _create($class, string $alias, array $config): Helper
{
/** @var \Cake\View\Helper $instance */
- $instance = new $class($this->_View, $settings);
+ $instance = new $class($this->_View, $config);
- $enable = $settings['enabled'] ?? true;
+ $enable = $config['enabled'] ?? true;
if ($enable) {
$this->getEventManager()->on($instance);
}
diff --git a/app/vendor/cakephp/cakephp/src/View/JsonView.php b/app/vendor/cakephp/cakephp/src/View/JsonView.php
index 7f79d958d..71248a67e 100644
--- a/app/vendor/cakephp/cakephp/src/View/JsonView.php
+++ b/app/vendor/cakephp/cakephp/src/View/JsonView.php
@@ -49,7 +49,7 @@
* You can also set `'serialize'` to a string or array to serialize only the
* specified view variables.
*
- * If you don't set the `serialize` opton, you will need a view template.
+ * If you don't set the `serialize` option, you will need a view template.
* You can use extended views to provide layout-like functionality.
*
* You can also enable JSONP support by setting `jsonp` option to true or a
@@ -59,7 +59,7 @@
class JsonView extends SerializedView
{
/**
- * JSON layouts are located in the json sub directory of `Layouts/`
+ * JSON layouts are located in the JSON sub directory of `Layouts/`
*
* @var string
*/
@@ -94,8 +94,7 @@ class JsonView extends SerializedView
* - Setting it to a string value, uses the provided query string parameter
* for finding the JSONP callback name.
*
- * @var array
- * @pslam-var array{serialize:string|bool|null, jsonOptions: int|null, jsonp: bool|string|null}
+ * @var array
*/
protected $_defaultConfig = [
'serialize' => null,
diff --git a/app/vendor/cakephp/cakephp/src/View/SerializedView.php b/app/vendor/cakephp/cakephp/src/View/SerializedView.php
index 383180a16..a8a83bf04 100644
--- a/app/vendor/cakephp/cakephp/src/View/SerializedView.php
+++ b/app/vendor/cakephp/cakephp/src/View/SerializedView.php
@@ -16,9 +16,6 @@
*/
namespace Cake\View;
-use Cake\Event\EventManager;
-use Cake\Http\Response;
-use Cake\Http\ServerRequest;
use Cake\View\Exception\SerializationFailureException;
use Exception;
use TypeError;
@@ -45,32 +42,19 @@ abstract class SerializedView extends View
* names. If true all view variables will be serialized. If null or false
* normal view template will be rendered.
*
- * @var array
- * @psalm-var array{serialize:string|bool|null}
+ * @var array
*/
protected $_defaultConfig = [
'serialize' => null,
];
/**
- * Constructor
- *
- * @param \Cake\Http\ServerRequest|null $request Request instance.
- * @param \Cake\Http\Response|null $response Response instance.
- * @param \Cake\Event\EventManager|null $eventManager EventManager instance.
- * @param array $viewOptions An array of view options
+ * @inheritDoc
*/
- public function __construct(
- ?ServerRequest $request = null,
- ?Response $response = null,
- ?EventManager $eventManager = null,
- array $viewOptions = []
- ) {
- if ($response) {
- $response = $response->withType($this->_responseType);
- }
-
- parent::__construct($request, $response, $eventManager, $viewOptions);
+ public function initialize(): void
+ {
+ parent::initialize();
+ $this->setResponse($this->getResponse()->withType($this->_responseType));
}
/**
diff --git a/app/vendor/cakephp/cakephp/src/View/StringTemplate.php b/app/vendor/cakephp/cakephp/src/View/StringTemplate.php
index aebb4d8d6..a0cae660e 100644
--- a/app/vendor/cakephp/cakephp/src/View/StringTemplate.php
+++ b/app/vendor/cakephp/cakephp/src/View/StringTemplate.php
@@ -17,7 +17,7 @@
namespace Cake\View;
use Cake\Core\Configure\Engine\PhpConfig;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\InstanceConfigTrait;
use Cake\Utility\Hash;
use RuntimeException;
@@ -203,7 +203,7 @@ protected function _compileTemplates(array $templates = []): void
public function load(string $file): void
{
if ($file === '') {
- throw new Exception('String template filename cannot be an empty string');
+ throw new CakeException('String template filename cannot be an empty string');
}
$loader = new PhpConfig();
diff --git a/app/vendor/cakephp/cakephp/src/View/StringTemplateTrait.php b/app/vendor/cakephp/cakephp/src/View/StringTemplateTrait.php
index 03ef78611..2529801f6 100644
--- a/app/vendor/cakephp/cakephp/src/View/StringTemplateTrait.php
+++ b/app/vendor/cakephp/cakephp/src/View/StringTemplateTrait.php
@@ -29,7 +29,7 @@ trait StringTemplateTrait
/**
* StringTemplate instance.
*
- * @var \Cake\View\StringTemplate
+ * @var \Cake\View\StringTemplate|null
*/
protected $_templater;
diff --git a/app/vendor/cakephp/cakephp/src/View/View.php b/app/vendor/cakephp/cakephp/src/View/View.php
index 6df6626fd..11796b3d9 100644
--- a/app/vendor/cakephp/cakephp/src/View/View.php
+++ b/app/vendor/cakephp/cakephp/src/View/View.php
@@ -34,6 +34,7 @@
use InvalidArgumentException;
use LogicException;
use RuntimeException;
+use Throwable;
/**
* View, the V in the MVC triad. View interacts with Helpers and view variables passed
@@ -120,11 +121,11 @@ class View implements EventDispatcherInterface
*
* @var string
*/
- protected $templatePath;
+ protected $templatePath = '';
/**
* The name of the template file to render. The name specified
- * is the filename in /templates/ without the .php extension.
+ * is the filename in `templates//` without the .php extension.
*
* @var string
*/
@@ -132,7 +133,7 @@ class View implements EventDispatcherInterface
/**
* The name of the layout file to render the template inside of. The name specified
- * is the filename of the layout in /templates/Layout without the .php
+ * is the filename of the layout in `templates/layout/` without the .php
* extension.
*
* @var string
@@ -222,7 +223,7 @@ class View implements EventDispatcherInterface
/**
* Default custom config options.
*
- * @var string[]
+ * @var array
*/
protected $_defaultConfig = [];
@@ -526,7 +527,7 @@ public function setTheme(?string $theme)
/**
* Get the name of the template file to render. The name specified is the
- * filename in /templates/ without the .php extension.
+ * filename in `templates//` without the .php extension.
*
* @return string
*/
@@ -537,7 +538,7 @@ public function getTemplate(): string
/**
* Set the name of the template file to render. The name specified is the
- * filename in /templates/ without the .php extension.
+ * filename in `templates//` without the .php extension.
*
* @param string $name Template file name to set.
* @return $this
@@ -551,7 +552,7 @@ public function setTemplate(string $name)
/**
* Get the name of the layout file to render the template inside of.
- * The name specified is the filename of the layout in /templates/Layout
+ * The name specified is the filename of the layout in `templates/layout/`
* without the .php extension.
*
* @return string
@@ -563,7 +564,7 @@ public function getLayout(): string
/**
* Set the name of the layout file to render the template inside of.
- * The name specified is the filename of the layout in /templates/Layout
+ * The name specified is the filename of the layout in `templates/layout/`
* without the .php extension.
*
* @param string $name Layout file name to set.
@@ -615,7 +616,7 @@ public function getConfig(?string $key = null, $default = null)
* This realizes the concept of Elements, (or "partial layouts") and the $params array is used to send
* data to be used in the element. Elements can be cached improving performance by using the `cache` option.
*
- * @param string $name Name of template file in the /templates/Element/ folder,
+ * @param string $name Name of template file in the `templates/element/` folder,
* or `MyPlugin.template` to use the template element from MyPlugin. If the element
* is not found in the plugin, the normal view path cascade will be searched.
* @param array $data Array of data to be made available to the rendered view (i.e. the Element)
@@ -655,9 +656,9 @@ public function element(string $name, array $data = [], array $options = []): st
}
if (empty($options['ignoreMissing'])) {
- [$plugin] = $this->pluginSplit($name, $pluginCheck);
+ [$plugin, $elementName] = $this->pluginSplit($name, $pluginCheck);
$paths = iterator_to_array($this->getElementPaths($plugin));
- throw new MissingElementException($name . $this->_ext, $paths);
+ throw new MissingElementException([$name . $this->_ext, $elementName . $this->_ext], $paths);
}
return '';
@@ -687,8 +688,20 @@ public function cache(callable $block, array $options = []): string
if ($result) {
return $result;
}
+
+ $bufferLevel = ob_get_level();
ob_start();
- $block();
+
+ try {
+ $block();
+ } catch (Throwable $exception) {
+ while (ob_get_level() > $bufferLevel) {
+ ob_end_clean();
+ }
+
+ throw $exception;
+ }
+
$result = ob_get_clean();
Cache::write($options['key'], $result, $options['config']);
@@ -699,7 +712,7 @@ public function cache(callable $block, array $options = []): string
/**
* Checks if an element exists
*
- * @param string $name Name of template file in the /templates/Element/ folder,
+ * @param string $name Name of template file in the `templates/element/` folder,
* or `MyPlugin.template` to check the template element from MyPlugin. If the element
* is not found in the plugin, the normal view path cascade will be searched.
* @return bool Success
@@ -722,14 +735,14 @@ public function elementExists(string $name): bool
*
* If View::$autoLayout is set to `false`, the template will be returned bare.
*
- * Template and layout names can point to plugin templates/layouts. Using the `Plugin.template` syntax
- * a plugin template/layout can be used instead of the app ones. If the chosen plugin is not found
+ * Template and layout names can point to plugin templates or layouts. Using the `Plugin.template` syntax
+ * a plugin template/layout/ can be used instead of the app ones. If the chosen plugin is not found
* the template will be located along the regular view path cascade.
*
* @param string|null $template Name of template file to use
* @param string|false|null $layout Layout to use. False to disable.
* @return string Rendered content.
- * @throws \Cake\Core\Exception\Exception If there is an error in the view.
+ * @throws \Cake\Core\Exception\CakeException If there is an error in the view.
* @triggers View.beforeRender $this, [$templateFileName]
* @triggers View.afterRender $this, [$templateFileName]
*/
@@ -779,7 +792,7 @@ public function render(?string $template = null, $layout = null): string
* @param string $content Content to render in a template, wrapped by the surrounding layout.
* @param string|null $layout Layout name
* @return string Rendered output.
- * @throws \Cake\Core\Exception\Exception if there is an error in the view.
+ * @throws \Cake\Core\Exception\CakeException if there is an error in the view.
* @triggers View.beforeLayout $this, [$layoutFileName]
* @triggers View.afterLayout $this, [$layoutFileName]
*/
@@ -795,7 +808,7 @@ public function renderLayout(string $content, ?string $layout = null): string
$title = $this->Blocks->get('title');
if ($title === '') {
- $title = Inflector::humanize(str_replace(DIRECTORY_SEPARATOR, '/', (string)$this->templatePath));
+ $title = Inflector::humanize(str_replace(DIRECTORY_SEPARATOR, '/', $this->templatePath));
$this->Blocks->set('title', $title);
}
@@ -1155,9 +1168,19 @@ protected function _render(string $templateFile, array $data = []): string
protected function _evaluate(string $templateFile, array $dataForView): string
{
extract($dataForView);
+
+ $bufferLevel = ob_get_level();
ob_start();
- include func_get_arg(0);
+ try {
+ include func_get_arg(0);
+ } catch (Throwable $exception) {
+ while (ob_get_level() > $bufferLevel) {
+ ob_end_clean();
+ }
+
+ throw $exception;
+ }
return ob_get_clean();
}
@@ -1316,14 +1339,14 @@ protected function _getTemplateFileName(?string $name = null): string
} elseif (!$plugin || $this->templatePath !== $this->name) {
$name = $templatePath . $subDir . $name;
} else {
- $name = DIRECTORY_SEPARATOR . $subDir . $name;
+ $name = $subDir . $name;
}
}
$name .= $this->_ext;
$paths = $this->_paths($plugin);
foreach ($paths as $path) {
- if (file_exists($path . $name)) {
+ if (is_file($path . $name)) {
return $this->_checkFilePath($path . $name, $path);
}
}
@@ -1417,7 +1440,7 @@ protected function _getLayoutFileName(?string $name = null): string
$name .= $this->_ext;
foreach ($this->getLayoutPaths($plugin) as $path) {
- if (file_exists($path . $name)) {
+ if (is_file($path . $name)) {
return $this->_checkFilePath($path . $name, $path);
}
}
@@ -1460,7 +1483,7 @@ protected function _getElementFileName(string $name, bool $pluginCheck = true)
$name .= $this->_ext;
foreach ($this->getElementPaths($plugin) as $path) {
- if (file_exists($path . $name)) {
+ if (is_file($path . $name)) {
return $path . $name;
}
}
diff --git a/app/vendor/cakephp/cakephp/src/View/ViewBlock.php b/app/vendor/cakephp/cakephp/src/View/ViewBlock.php
index 0ab142108..ec6eb3184 100644
--- a/app/vendor/cakephp/cakephp/src/View/ViewBlock.php
+++ b/app/vendor/cakephp/cakephp/src/View/ViewBlock.php
@@ -16,7 +16,7 @@
*/
namespace Cake\View;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
/**
* ViewBlock implements the concept of Blocks or Slots in the View layer.
@@ -82,13 +82,13 @@ class ViewBlock
* @param string $mode If ViewBlock::OVERRIDE existing content will be overridden by new content.
* If ViewBlock::APPEND content will be appended to existing content.
* If ViewBlock::PREPEND it will be prepended.
- * @throws \Cake\Core\Exception\Exception When starting a block twice
+ * @throws \Cake\Core\Exception\CakeException When starting a block twice
* @return void
*/
public function start(string $name, string $mode = ViewBlock::OVERRIDE): void
{
if (array_key_exists($name, $this->_active)) {
- throw new Exception(sprintf("A view block with the name '%s' is already/still open.", $name));
+ throw new CakeException(sprintf("A view block with the name '%s' is already/still open.", $name));
}
$this->_active[$name] = $mode;
ob_start();
diff --git a/app/vendor/cakephp/cakephp/src/View/ViewBuilder.php b/app/vendor/cakephp/cakephp/src/View/ViewBuilder.php
index 61d47c018..a36fa37b5 100644
--- a/app/vendor/cakephp/cakephp/src/View/ViewBuilder.php
+++ b/app/vendor/cakephp/cakephp/src/View/ViewBuilder.php
@@ -301,6 +301,25 @@ public function getPlugin(): ?string
return $this->_plugin;
}
+ /**
+ * Adds a helper to use.
+ *
+ * @param string $helper Helper to use.
+ * @param array $options Options.
+ * @return $this
+ * @since 4.1.0
+ */
+ public function addHelper(string $helper, array $options = [])
+ {
+ if ($options) {
+ $array = [$helper => $options];
+ } else {
+ $array = [$helper];
+ }
+
+ return $this->setHelpers($array);
+ }
+
/**
* Sets the helpers to use.
*
@@ -354,7 +373,7 @@ public function getTheme(): ?string
/**
* Sets the name of the view file to render. The name specified is the
- * filename in /templates/ without the .php extension.
+ * filename in `templates//` without the .php extension.
*
* @param string|null $name View file name to set, or null to remove the template name.
* @return $this
@@ -368,7 +387,7 @@ public function setTemplate(?string $name)
/**
* Gets the name of the view file to render. The name specified is the
- * filename in /templates/ without the .php extension.
+ * filename in `templates//` without the .php extension.
*
* @return string|null
*/
@@ -379,7 +398,7 @@ public function getTemplate(): ?string
/**
* Sets the name of the layout file to render the view inside of.
- * The name specified is the filename of the layout in /templates/Layout
+ * The name specified is the filename of the layout in `templates/layout/`
* without the .php extension.
*
* @param string|null $name Layout file name to set.
@@ -551,7 +570,6 @@ public function build(
];
$data += $this->_options;
- // phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat
/** @var \Cake\View\View */
return new $className($request, $response, $events, $data);
}
diff --git a/app/vendor/cakephp/cakephp/src/View/ViewVarsTrait.php b/app/vendor/cakephp/cakephp/src/View/ViewVarsTrait.php
index 71960f271..fe170bb7a 100644
--- a/app/vendor/cakephp/cakephp/src/View/ViewVarsTrait.php
+++ b/app/vendor/cakephp/cakephp/src/View/ViewVarsTrait.php
@@ -67,6 +67,7 @@ public function createView(?string $viewClass = null): View
}
}
+ /** @psalm-suppress RedundantPropertyInitializationCheck */
return $builder->build(
[],
$this->request ?? null,
diff --git a/app/vendor/cakephp/cakephp/src/View/Widget/DateTimeWidget.php b/app/vendor/cakephp/cakephp/src/View/Widget/DateTimeWidget.php
index be9652355..8715c9c05 100644
--- a/app/vendor/cakephp/cakephp/src/View/Widget/DateTimeWidget.php
+++ b/app/vendor/cakephp/cakephp/src/View/Widget/DateTimeWidget.php
@@ -91,6 +91,13 @@ class DateTimeWidget extends BasicWidget
* - `escape` Set to false to disable escaping on all attributes.
* - `type` A valid HTML date/time input type. Defaults to "datetime-local".
* - `timezone` The timezone the input value should be converted to.
+ * - `step` The "step" attribute. Defaults to `1` for "time" and "datetime-local" type inputs.
+ * You can set it to `null` or `false` to prevent explicit step attribute being added in HTML.
+ * - `format` A `date()` function compatible datetime format string.
+ * By default the widget will use a suitable format based on the input type and
+ * database type for the context. If an explicit format is provided, then no
+ * default value will be set for the `step` attribute, and it needs to be
+ * explicitly set if required.
*
* All other keys will be converted into HTML attributes.
*
@@ -109,16 +116,10 @@ public function render(array $data, ContextInterface $context): string
));
}
- if (!isset($data['step'])) {
- $data['step'] = $this->defaultStep[$data['type']];
-
- if (isset($data['fieldName'])) {
- $data = $this->setStep($data, $context, $data['fieldName']);
- }
- }
+ $data = $this->setStep($data, $context, $data['fieldName'] ?? '');
$data['value'] = $this->formatDateTime($data['val'], $data);
- unset($data['val'], $data['timezone']);
+ unset($data['val'], $data['timezone'], $data['format']);
return $this->_templates->format('input', [
'name' => $data['name'],
@@ -141,6 +142,20 @@ public function render(array $data, ContextInterface $context): string
*/
protected function setStep(array $data, ContextInterface $context, string $fieldName): array
{
+ if (array_key_exists('step', $data)) {
+ return $data;
+ }
+
+ if (isset($data['format'])) {
+ $data['step'] = null;
+ } else {
+ $data['step'] = $this->defaultStep[$data['type']];
+ }
+
+ if (empty($data['fieldName'])) {
+ return $data;
+ }
+
$dbType = $context->type($fieldName);
$fractionalTypes = [
TableSchema::TYPE_DATETIME_FRACTIONAL,
@@ -192,9 +207,18 @@ protected function formatDateTime($value, array $options): string
$dateTime = $dateTime->setTimezone($timezone);
}
- $format = $this->formatMap[$options['type']];
- if ($options['type'] === 'datetime-local' && $options['step'] < 1) {
- $format = 'Y-m-d\TH:i:s.v';
+ if (isset($options['format'])) {
+ $format = $options['format'];
+ } else {
+ $format = $this->formatMap[$options['type']];
+
+ if (
+ $options['type'] === 'datetime-local'
+ && is_numeric($options['step'])
+ && $options['step'] < 1
+ ) {
+ $format = 'Y-m-d\TH:i:s.v';
+ }
}
return $dateTime->format($format);
diff --git a/app/vendor/cakephp/cakephp/src/View/Widget/MultiCheckboxWidget.php b/app/vendor/cakephp/cakephp/src/View/Widget/MultiCheckboxWidget.php
index 7afaacb4c..dbc5e1a9e 100644
--- a/app/vendor/cakephp/cakephp/src/View/Widget/MultiCheckboxWidget.php
+++ b/app/vendor/cakephp/cakephp/src/View/Widget/MultiCheckboxWidget.php
@@ -235,7 +235,7 @@ protected function _renderInput(array $checkbox, ContextInterface $context): str
* Helper method for deciding what options are selected.
*
* @param string $key The key to test.
- * @param array|string|null $selected The selected values.
+ * @param string[]|string|int|false|null $selected The selected values.
* @return bool
*/
protected function _isSelected(string $key, $selected): bool
diff --git a/app/vendor/cakephp/cakephp/src/View/Widget/RadioWidget.php b/app/vendor/cakephp/cakephp/src/View/Widget/RadioWidget.php
index 587dc3244..95f840236 100644
--- a/app/vendor/cakephp/cakephp/src/View/Widget/RadioWidget.php
+++ b/app/vendor/cakephp/cakephp/src/View/Widget/RadioWidget.php
@@ -169,7 +169,7 @@ protected function _renderInput($val, $text, $data, $context): string
if (empty($radio['id'])) {
if (isset($data['id'])) {
- $radio['id'] = $data['id'] . '-' . trim(
+ $radio['id'] = $data['id'] . '-' . rtrim(
$this->_idSuffix((string)$radio['value']),
'-'
);
diff --git a/app/vendor/cakephp/cakephp/src/View/Widget/SelectBoxWidget.php b/app/vendor/cakephp/cakephp/src/View/Widget/SelectBoxWidget.php
index 371a87c39..2ba43947b 100644
--- a/app/vendor/cakephp/cakephp/src/View/Widget/SelectBoxWidget.php
+++ b/app/vendor/cakephp/cakephp/src/View/Widget/SelectBoxWidget.php
@@ -306,7 +306,7 @@ protected function _renderOptions(iterable $options, ?array $disabled, $selected
* Helper method for deciding what options are selected.
*
* @param string $key The key to test.
- * @param string[]|string|false|null $selected The selected values.
+ * @param string[]|string|int|false|null $selected The selected values.
* @return bool
*/
protected function _isSelected(string $key, $selected): bool
diff --git a/app/vendor/cakephp/cakephp/src/View/Widget/YearWidget.php b/app/vendor/cakephp/cakephp/src/View/Widget/YearWidget.php
index 91380028f..cfa633406 100644
--- a/app/vendor/cakephp/cakephp/src/View/Widget/YearWidget.php
+++ b/app/vendor/cakephp/cakephp/src/View/Widget/YearWidget.php
@@ -37,6 +37,8 @@ class YearWidget extends BasicWidget
protected $defaults = [
'name' => '',
'val' => null,
+ 'min' => null,
+ 'max' => null,
'order' => 'desc',
'templateVars' => [],
];
diff --git a/app/vendor/cakephp/cakephp/src/View/XmlView.php b/app/vendor/cakephp/cakephp/src/View/XmlView.php
index 4962cfeaa..3e9969f59 100644
--- a/app/vendor/cakephp/cakephp/src/View/XmlView.php
+++ b/app/vendor/cakephp/cakephp/src/View/XmlView.php
@@ -61,7 +61,7 @@
class XmlView extends SerializedView
{
/**
- * XML layouts are located in the xml sub directory of `Layouts/`
+ * XML layouts are located in the `layouts/xml/` sub directory
*
* @var string
*/
@@ -103,8 +103,7 @@ class XmlView extends SerializedView
* For e.g. 'format' as 'attributes' instead of 'tags'.
* - `rootNode`: Root node name. Defaults to "response".
*
- * @var array
- * @psalm-var array{serialize:string|bool|null, xmlOptions: int|null, rootNode: string|null}
+ * @var array
*/
protected $_defaultConfig = [
'serialize' => null,
@@ -143,6 +142,7 @@ protected function _serialize($serialize): string
$data &&
(!is_array($data) || Hash::numeric(array_keys($data)))
) {
+ /** @psalm-suppress InvalidArrayOffset */
$data = [$rootNode => [$serialize => $data]];
}
}
diff --git a/app/vendor/cakephp/cakephp/src/basics.php b/app/vendor/cakephp/cakephp/src/basics.php
index 80e0adb5a..214dbc831 100644
--- a/app/vendor/cakephp/cakephp/src/basics.php
+++ b/app/vendor/cakephp/cakephp/src/basics.php
@@ -100,11 +100,12 @@ function stackTrace(array $options = []): void
* Works the same way as eval(\Psy\sh());
* psy/psysh must be loaded in your project
*
- * @link http://psysh.org/
* ```
* eval(breakpoint());
* ```
+ *
* @return string|null
+ * @link http://psysh.org/
*/
function breakpoint(): ?string
{
diff --git a/app/vendor/cakephp/cakephp/templates/Error/duplicate_named_route.php b/app/vendor/cakephp/cakephp/templates/Error/duplicate_named_route.php
index 8f19fb9c0..51bc8a31e 100644
--- a/app/vendor/cakephp/cakephp/templates/Error/duplicate_named_route.php
+++ b/app/vendor/cakephp/cakephp/templates/Error/duplicate_named_route.php
@@ -34,28 +34,16 @@
even if the names occur in different routing scopes.
Remove duplicate route names in your route configuration.