Array with 2 indexes. 0 => namespace, 1 => classname.
+ */
+ function namespaceSplit(string $class): array
+ {
+ $pos = strrpos($class, '\\');
+ if ($pos === false) {
+ return ['', $class];
+ }
+
+ return [substr($class, 0, $pos), substr($class, $pos + 1)];
+ }
+
+}
+
+if (!function_exists('pr')) {
+ /**
+ * print_r() convenience function.
+ *
+ * In terminals this will act similar to using print_r() directly, when not run on CLI
+ * print_r() will also wrap `` tags around the output of given variable. Similar to debug().
+ *
+ * This function returns the same variable that was passed.
+ *
+ * @param mixed $var Variable to print out.
+ * @return mixed the same $var that was passed to this function
+ * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pr
+ * @see debug()
+ */
+ function pr($var)
+ {
+ if (!Configure::read('debug')) {
+ return $var;
+ }
+
+ $template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '%s
' : "\n%s\n\n";
+ printf($template, trim(print_r($var, true)));
+
+ return $var;
+ }
+
+}
+
+if (!function_exists('pj')) {
+ /**
+ * JSON pretty print convenience function.
+ *
+ * In terminals this will act similar to using json_encode() with JSON_PRETTY_PRINT directly, when not run on CLI
+ * will also wrap `` tags around the output of given variable. Similar to pr().
+ *
+ * This function returns the same variable that was passed.
+ *
+ * @param mixed $var Variable to print out.
+ * @return mixed the same $var that was passed to this function
+ * @see pr()
+ * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pj
+ */
+ function pj($var)
+ {
+ if (!Configure::read('debug')) {
+ return $var;
+ }
+
+ $template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '%s
' : "\n%s\n\n";
+ printf($template, trim(json_encode($var, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)));
+
+ return $var;
+ }
+
+}
+
+if (!function_exists('env')) {
+ /**
+ * Gets an environment variable from available sources, and provides emulation
+ * for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on
+ * IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom
+ * environment information.
+ *
+ * @param string $key Environment variable name.
+ * @param string|bool|null $default Specify a default value in case the environment variable is not defined.
+ * @return string|bool|null Environment variable setting.
+ * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#env
+ */
+ function env(string $key, $default = null)
+ {
+ if ($key === 'HTTPS') {
+ if (isset($_SERVER['HTTPS'])) {
+ return !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
+ }
+
+ return strpos((string)env('SCRIPT_URI'), 'https://') === 0;
+ }
+
+ if ($key === 'SCRIPT_NAME' && env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) {
+ $key = 'SCRIPT_URL';
+ }
+
+ /** @var string|null $val */
+ $val = $_SERVER[$key] ?? $_ENV[$key] ?? null;
+ if ($val == null && getenv($key) !== false) {
+ /** @var string|false $val */
+ $val = getenv($key);
+ }
+
+ if ($key === 'REMOTE_ADDR' && $val === env('SERVER_ADDR')) {
+ $addr = env('HTTP_PC_REMOTE_ADDR');
+ if ($addr !== null) {
+ $val = $addr;
+ }
+ }
+
+ if ($val !== null) {
+ return $val;
+ }
+
+ switch ($key) {
+ case 'DOCUMENT_ROOT':
+ $name = (string)env('SCRIPT_NAME');
+ $filename = (string)env('SCRIPT_FILENAME');
+ $offset = 0;
+ if (!strpos($name, '.php')) {
+ $offset = 4;
+ }
+
+ return substr($filename, 0, -(strlen($name) + $offset));
+ case 'PHP_SELF':
+ return str_replace((string)env('DOCUMENT_ROOT'), '', (string)env('SCRIPT_FILENAME'));
+ case 'CGI_MODE':
+ return PHP_SAPI === 'cgi';
+ }
+
+ return $default;
+ }
+
+}
+
+if (!function_exists('triggerWarning')) {
+ /**
+ * Triggers an E_USER_WARNING.
+ *
+ * @param string $message The warning message.
+ * @return void
+ */
+ function triggerWarning(string $message): void
+ {
+ $trace = debug_backtrace();
+ if (isset($trace[1])) {
+ $frame = $trace[1];
+ $frame += ['file' => '[internal]', 'line' => '??'];
+ $message = sprintf(
+ '%s - %s, line: %s',
+ $message,
+ $frame['file'],
+ $frame['line']
+ );
+ }
+ trigger_error($message, E_USER_WARNING);
+ }
+}
+
+if (!function_exists('deprecationWarning')) {
+ /**
+ * Helper method for outputting deprecation warnings
+ *
+ * @param string $message The message to output as a deprecation warning.
+ * @param int $stackFrame The stack frame to include in the error. Defaults to 1
+ * as that should point to application/plugin code.
+ * @return void
+ */
+ function deprecationWarning(string $message, int $stackFrame = 1): void
+ {
+ if (!(error_reporting() & E_USER_DEPRECATED)) {
+ return;
+ }
+
+ $trace = debug_backtrace();
+ if (isset($trace[$stackFrame])) {
+ $frame = $trace[$stackFrame];
+ $frame += ['file' => '[internal]', 'line' => '??'];
+
+ // Assuming we're installed in vendor/cakephp/cakephp/src/Core/functions.php
+ $root = dirname(__DIR__, 5);
+ if (defined('ROOT')) {
+ $root = ROOT;
+ }
+ $relative = str_replace(DIRECTORY_SEPARATOR, '/', substr($frame['file'], strlen($root) + 1));
+ $patterns = (array)Configure::read('Error.ignoredDeprecationPaths');
+ foreach ($patterns as $pattern) {
+ $pattern = str_replace(DIRECTORY_SEPARATOR, '/', $pattern);
+ if (fnmatch($pattern, $relative)) {
+ return;
+ }
+ }
+
+ $message = sprintf(
+ "%s\n%s, line: %s\n" .
+ 'You can disable all deprecation warnings by setting `Error.errorLevel` to ' .
+ '`E_ALL & ~E_USER_DEPRECATED`. Adding `%s` to `Error.ignoredDeprecationPaths` ' .
+ 'in your `config/app.php` config will mute deprecations from that file only.',
+ $message,
+ $frame['file'],
+ $frame['line'],
+ $relative
+ );
+ }
+
+ static $errors = [];
+ $checksum = md5($message);
+ $duplicate = (bool)Configure::read('Error.allowDuplicateDeprecations', false);
+ if (isset($errors[$checksum]) && !$duplicate) {
+ return;
+ }
+ if (!$duplicate) {
+ $errors[$checksum] = true;
+ }
+
+ trigger_error($message, E_USER_DEPRECATED);
+ }
+}
+
+if (!function_exists('getTypeName')) {
+ /**
+ * Returns the objects class or var type of it's not an object
+ *
+ * @param mixed $var Variable to check
+ * @return string Returns the class name or variable type
+ */
+ function getTypeName($var): string
+ {
+ return is_object($var) ? get_class($var) : gettype($var);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Connection.php b/app/vendor/cakephp/cakephp/src/Database/Connection.php
new file mode 100644
index 000000000..92a54ae8d
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Connection.php
@@ -0,0 +1,989 @@
+
+ */
+ protected $_config;
+
+ /**
+ * Driver object, responsible for creating the real connection
+ * and provide specific SQL dialect.
+ *
+ * @var \Cake\Database\DriverInterface
+ */
+ protected $_driver;
+
+ /**
+ * Contains how many nested transactions have been started.
+ *
+ * @var int
+ */
+ protected $_transactionLevel = 0;
+
+ /**
+ * Whether a transaction is active in this connection.
+ *
+ * @var bool
+ */
+ protected $_transactionStarted = false;
+
+ /**
+ * Whether this connection can and should use savepoints for nested
+ * transactions.
+ *
+ * @var bool
+ */
+ protected $_useSavePoints = false;
+
+ /**
+ * Whether to log queries generated during this connection.
+ *
+ * @var bool
+ */
+ protected $_logQueries = false;
+
+ /**
+ * Logger object instance.
+ *
+ * @var \Psr\Log\LoggerInterface|null
+ */
+ protected $_logger;
+
+ /**
+ * Cacher object instance.
+ *
+ * @var \Psr\SimpleCache\CacheInterface|null
+ */
+ protected $cacher;
+
+ /**
+ * The schema collection object
+ *
+ * @var \Cake\Database\Schema\CollectionInterface|null
+ */
+ protected $_schemaCollection;
+
+ /**
+ * NestedTransactionRollbackException object instance, will be stored if
+ * the rollback method is called in some nested transaction.
+ *
+ * @var \Cake\Database\Exception\NestedTransactionRollbackException|null
+ */
+ protected $nestedTransactionRollbackException;
+
+ /**
+ * Constructor.
+ *
+ * ### Available options:
+ *
+ * - `driver` Sort name or FCQN for driver.
+ * - `log` Boolean indicating whether to use query logging.
+ * - `name` Connection name.
+ * - `cacheMetaData` Boolean indicating whether metadata (datasource schemas) should be cached.
+ * If set to a string it will be used as the name of cache config to use.
+ * - `cacheKeyPrefix` Custom prefix to use when generation cache keys. Defaults to connection name.
+ *
+ * @param array $config Configuration array.
+ */
+ public function __construct(array $config)
+ {
+ $this->_config = $config;
+
+ $driverConfig = array_diff_key($config, array_flip([
+ 'name',
+ 'driver',
+ 'log',
+ 'cacheMetaData',
+ 'cacheKeyPrefix',
+ ]));
+ $this->_driver = $this->createDriver($config['driver'] ?? '', $driverConfig);
+
+ if (!empty($config['log'])) {
+ $this->enableQueryLogging((bool)$config['log']);
+ }
+ }
+
+ /**
+ * Destructor
+ *
+ * Disconnects the driver to release the connection.
+ */
+ public function __destruct()
+ {
+ if ($this->_transactionStarted && class_exists(Log::class)) {
+ Log::warning('The connection is going to be closed but there is an active transaction.');
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function config(): array
+ {
+ return $this->_config;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function configName(): string
+ {
+ return $this->_config['name'] ?? '';
+ }
+
+ /**
+ * Sets the driver instance. If a string is passed it will be treated
+ * as a class name and will be instantiated.
+ *
+ * @param \Cake\Database\DriverInterface|string $driver The driver instance to use.
+ * @param array $config Config for a new driver.
+ * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing.
+ * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing.
+ * @return $this
+ * @deprecated 4.4.0 Setting the driver is deprecated. Use the connection config instead.
+ */
+ public function setDriver($driver, $config = [])
+ {
+ deprecationWarning('Setting the driver is deprecated. Use the connection config instead.');
+
+ $this->_driver = $this->createDriver($driver, $config);
+
+ return $this;
+ }
+
+ /**
+ * Creates driver from name, class name or instance.
+ *
+ * @param \Cake\Database\DriverInterface|string $name Driver name, class name or instance.
+ * @param array $config Driver config if $name is not an instance.
+ * @return \Cake\Database\DriverInterface
+ * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing.
+ * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing.
+ */
+ protected function createDriver($name, array $config): DriverInterface
+ {
+ $driver = $name;
+ if (is_string($driver)) {
+ /** @psalm-var class-string<\Cake\Database\DriverInterface>|null $className */
+ $className = App::className($driver, 'Database/Driver');
+ if ($className === null) {
+ throw new MissingDriverException(['driver' => $driver, 'connection' => $this->configName()]);
+ }
+ $driver = new $className($config);
+ }
+
+ if (!$driver->enabled()) {
+ throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $this->configName()]);
+ }
+
+ return $driver;
+ }
+
+ /**
+ * Get the retry wrapper object that is allows recovery from server disconnects
+ * while performing certain database actions, such as executing a query.
+ *
+ * @return \Cake\Core\Retry\CommandRetry The retry wrapper
+ */
+ public function getDisconnectRetry(): CommandRetry
+ {
+ return new CommandRetry(new ReconnectStrategy($this));
+ }
+
+ /**
+ * Gets the driver instance.
+ *
+ * @return \Cake\Database\DriverInterface
+ */
+ public function getDriver(): DriverInterface
+ {
+ return $this->_driver;
+ }
+
+ /**
+ * Connects to the configured database.
+ *
+ * @throws \Cake\Database\Exception\MissingConnectionException If database connection could not be established.
+ * @return bool true, if the connection was already established or the attempt was successful.
+ */
+ public function connect(): bool
+ {
+ try {
+ return $this->_driver->connect();
+ } catch (MissingConnectionException $e) {
+ throw $e;
+ } catch (Throwable $e) {
+ throw new MissingConnectionException(
+ [
+ 'driver' => App::shortName(get_class($this->_driver), 'Database/Driver'),
+ 'reason' => $e->getMessage(),
+ ],
+ null,
+ $e
+ );
+ }
+ }
+
+ /**
+ * Disconnects from database server.
+ *
+ * @return void
+ */
+ public function disconnect(): void
+ {
+ $this->_driver->disconnect();
+ }
+
+ /**
+ * Returns whether connection to database server was already established.
+ *
+ * @return bool
+ */
+ public function isConnected(): bool
+ {
+ return $this->_driver->isConnected();
+ }
+
+ /**
+ * Prepares a SQL statement to be executed.
+ *
+ * @param \Cake\Database\Query|string $query The SQL to convert into a prepared statement.
+ * @return \Cake\Database\StatementInterface
+ */
+ public function prepare($query): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($query) {
+ $statement = $this->_driver->prepare($query);
+
+ if ($this->_logQueries) {
+ $statement = $this->_newLogger($statement);
+ }
+
+ return $statement;
+ });
+ }
+
+ /**
+ * Executes a query using $params for interpolating values and $types as a hint for each
+ * those params.
+ *
+ * @param string $sql SQL to be executed and interpolated with $params
+ * @param array $params list or associative array of params to be interpolated in $sql as values
+ * @param array $types list or associative array of types to be used for casting values in query
+ * @return \Cake\Database\StatementInterface executed statement
+ */
+ public function execute(string $sql, array $params = [], array $types = []): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($sql, $params, $types) {
+ $statement = $this->prepare($sql);
+ if (!empty($params)) {
+ $statement->bind($params, $types);
+ }
+ $statement->execute();
+
+ return $statement;
+ });
+ }
+
+ /**
+ * Compiles a Query object into a SQL string according to the dialect for this
+ * connection's driver
+ *
+ * @param \Cake\Database\Query $query The query to be compiled
+ * @param \Cake\Database\ValueBinder $binder Value binder
+ * @return string
+ */
+ public function compileQuery(Query $query, ValueBinder $binder): string
+ {
+ return $this->getDriver()->compileQuery($query, $binder)[1];
+ }
+
+ /**
+ * Executes the provided query after compiling it for the specific driver
+ * dialect and returns the executed Statement object.
+ *
+ * @param \Cake\Database\Query $query The query to be executed
+ * @return \Cake\Database\StatementInterface executed statement
+ */
+ public function run(Query $query): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($query) {
+ $statement = $this->prepare($query);
+ $query->getValueBinder()->attachTo($statement);
+ $statement->execute();
+
+ return $statement;
+ });
+ }
+
+ /**
+ * Executes a SQL statement and returns the Statement object as result.
+ *
+ * @param string $sql The SQL query to execute.
+ * @return \Cake\Database\StatementInterface
+ */
+ public function query(string $sql): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($sql) {
+ $statement = $this->prepare($sql);
+ $statement->execute();
+
+ return $statement;
+ });
+ }
+
+ /**
+ * Create a new Query instance for this connection.
+ *
+ * @return \Cake\Database\Query
+ */
+ public function newQuery(): Query
+ {
+ return new Query($this);
+ }
+
+ /**
+ * Sets a Schema\Collection object for this connection.
+ *
+ * @param \Cake\Database\Schema\CollectionInterface $collection The schema collection object
+ * @return $this
+ */
+ public function setSchemaCollection(SchemaCollectionInterface $collection)
+ {
+ $this->_schemaCollection = $collection;
+
+ return $this;
+ }
+
+ /**
+ * Gets a Schema\Collection object for this connection.
+ *
+ * @return \Cake\Database\Schema\CollectionInterface
+ */
+ public function getSchemaCollection(): SchemaCollectionInterface
+ {
+ if ($this->_schemaCollection !== null) {
+ return $this->_schemaCollection;
+ }
+
+ if (!empty($this->_config['cacheMetadata'])) {
+ return $this->_schemaCollection = new CachedCollection(
+ new SchemaCollection($this),
+ empty($this->_config['cacheKeyPrefix']) ? $this->configName() : $this->_config['cacheKeyPrefix'],
+ $this->getCacher()
+ );
+ }
+
+ return $this->_schemaCollection = new SchemaCollection($this);
+ }
+
+ /**
+ * Executes an INSERT query on the specified table.
+ *
+ * @param string $table the table to insert values in
+ * @param array $values values to be inserted
+ * @param array $types Array containing the types to be used for casting
+ * @return \Cake\Database\StatementInterface
+ */
+ public function insert(string $table, array $values, array $types = []): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($table, $values, $types) {
+ $columns = array_keys($values);
+
+ return $this->newQuery()->insert($columns, $types)
+ ->into($table)
+ ->values($values)
+ ->execute();
+ });
+ }
+
+ /**
+ * Executes an UPDATE statement on the specified table.
+ *
+ * @param string $table the table to update rows from
+ * @param array $values values to be updated
+ * @param array $conditions conditions to be set for update statement
+ * @param array $types list of associative array containing the types to be used for casting
+ * @return \Cake\Database\StatementInterface
+ */
+ public function update(string $table, array $values, array $conditions = [], array $types = []): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($table, $values, $conditions, $types) {
+ return $this->newQuery()->update($table)
+ ->set($values, $types)
+ ->where($conditions, $types)
+ ->execute();
+ });
+ }
+
+ /**
+ * Executes a DELETE statement on the specified table.
+ *
+ * @param string $table the table to delete rows from
+ * @param array $conditions conditions to be set for delete statement
+ * @param array $types list of associative array containing the types to be used for casting
+ * @return \Cake\Database\StatementInterface
+ */
+ public function delete(string $table, array $conditions = [], array $types = []): StatementInterface
+ {
+ return $this->getDisconnectRetry()->run(function () use ($table, $conditions, $types) {
+ return $this->newQuery()->delete($table)
+ ->where($conditions, $types)
+ ->execute();
+ });
+ }
+
+ /**
+ * Starts a new transaction.
+ *
+ * @return void
+ */
+ public function begin(): void
+ {
+ if (!$this->_transactionStarted) {
+ if ($this->_logQueries) {
+ $this->log('BEGIN');
+ }
+
+ $this->getDisconnectRetry()->run(function (): void {
+ $this->_driver->beginTransaction();
+ });
+
+ $this->_transactionLevel = 0;
+ $this->_transactionStarted = true;
+ $this->nestedTransactionRollbackException = null;
+
+ return;
+ }
+
+ $this->_transactionLevel++;
+ if ($this->isSavePointsEnabled()) {
+ $this->createSavePoint((string)$this->_transactionLevel);
+ }
+ }
+
+ /**
+ * Commits current transaction.
+ *
+ * @return bool true on success, false otherwise
+ */
+ public function commit(): bool
+ {
+ if (!$this->_transactionStarted) {
+ return false;
+ }
+
+ if ($this->_transactionLevel === 0) {
+ if ($this->wasNestedTransactionRolledback()) {
+ /** @var \Cake\Database\Exception\NestedTransactionRollbackException $e */
+ $e = $this->nestedTransactionRollbackException;
+ $this->nestedTransactionRollbackException = null;
+ throw $e;
+ }
+
+ $this->_transactionStarted = false;
+ $this->nestedTransactionRollbackException = null;
+ if ($this->_logQueries) {
+ $this->log('COMMIT');
+ }
+
+ return $this->_driver->commitTransaction();
+ }
+ if ($this->isSavePointsEnabled()) {
+ $this->releaseSavePoint((string)$this->_transactionLevel);
+ }
+
+ $this->_transactionLevel--;
+
+ return true;
+ }
+
+ /**
+ * Rollback current transaction.
+ *
+ * @param bool|null $toBeginning Whether the transaction should be rolled back to the
+ * beginning of it. Defaults to false if using savepoints, or true if not.
+ * @return bool
+ */
+ public function rollback(?bool $toBeginning = null): bool
+ {
+ if (!$this->_transactionStarted) {
+ return false;
+ }
+
+ $useSavePoint = $this->isSavePointsEnabled();
+ if ($toBeginning === null) {
+ $toBeginning = !$useSavePoint;
+ }
+ if ($this->_transactionLevel === 0 || $toBeginning) {
+ $this->_transactionLevel = 0;
+ $this->_transactionStarted = false;
+ $this->nestedTransactionRollbackException = null;
+ if ($this->_logQueries) {
+ $this->log('ROLLBACK');
+ }
+ $this->_driver->rollbackTransaction();
+
+ return true;
+ }
+
+ $savePoint = $this->_transactionLevel--;
+ if ($useSavePoint) {
+ $this->rollbackSavepoint($savePoint);
+ } elseif ($this->nestedTransactionRollbackException === null) {
+ $this->nestedTransactionRollbackException = new NestedTransactionRollbackException();
+ }
+
+ return true;
+ }
+
+ /**
+ * Enables/disables the usage of savepoints, enables only if driver the allows it.
+ *
+ * If you are trying to enable this feature, make sure you check
+ * `isSavePointsEnabled()` to verify that savepoints were enabled successfully.
+ *
+ * @param bool $enable Whether save points should be used.
+ * @return $this
+ */
+ public function enableSavePoints(bool $enable = true)
+ {
+ if ($enable === false) {
+ $this->_useSavePoints = false;
+ } else {
+ $this->_useSavePoints = $this->_driver->supports(DriverInterface::FEATURE_SAVEPOINT);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Disables the usage of savepoints.
+ *
+ * @return $this
+ */
+ public function disableSavePoints()
+ {
+ $this->_useSavePoints = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns whether this connection is using savepoints for nested transactions
+ *
+ * @return bool true if enabled, false otherwise
+ */
+ public function isSavePointsEnabled(): bool
+ {
+ return $this->_useSavePoints;
+ }
+
+ /**
+ * Creates a new save point for nested transactions.
+ *
+ * @param string|int $name Save point name or id
+ * @return void
+ */
+ public function createSavePoint($name): void
+ {
+ $this->execute($this->_driver->savePointSQL($name))->closeCursor();
+ }
+
+ /**
+ * Releases a save point by its name.
+ *
+ * @param string|int $name Save point name or id
+ * @return void
+ */
+ public function releaseSavePoint($name): void
+ {
+ $sql = $this->_driver->releaseSavePointSQL($name);
+ if ($sql) {
+ $this->execute($sql)->closeCursor();
+ }
+ }
+
+ /**
+ * Rollback a save point by its name.
+ *
+ * @param string|int $name Save point name or id
+ * @return void
+ */
+ public function rollbackSavepoint($name): void
+ {
+ $this->execute($this->_driver->rollbackSavePointSQL($name))->closeCursor();
+ }
+
+ /**
+ * Run driver specific SQL to disable foreign key checks.
+ *
+ * @return void
+ */
+ public function disableForeignKeys(): void
+ {
+ $this->getDisconnectRetry()->run(function (): void {
+ $this->execute($this->_driver->disableForeignKeySQL())->closeCursor();
+ });
+ }
+
+ /**
+ * Run driver specific SQL to enable foreign key checks.
+ *
+ * @return void
+ */
+ public function enableForeignKeys(): void
+ {
+ $this->getDisconnectRetry()->run(function (): void {
+ $this->execute($this->_driver->enableForeignKeySQL())->closeCursor();
+ });
+ }
+
+ /**
+ * Returns whether the driver supports adding or dropping constraints
+ * to already created tables.
+ *
+ * @return bool true if driver supports dynamic constraints
+ * @deprecated 4.3.0 Fixtures no longer dynamically drop and create constraints.
+ */
+ public function supportsDynamicConstraints(): bool
+ {
+ return $this->_driver->supportsDynamicConstraints();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function transactional(callable $callback)
+ {
+ $this->begin();
+
+ try {
+ $result = $callback($this);
+ } catch (Throwable $e) {
+ $this->rollback(false);
+ throw $e;
+ }
+
+ if ($result === false) {
+ $this->rollback(false);
+
+ return false;
+ }
+
+ try {
+ $this->commit();
+ } catch (NestedTransactionRollbackException $e) {
+ $this->rollback(false);
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns whether some nested transaction has been already rolled back.
+ *
+ * @return bool
+ */
+ protected function wasNestedTransactionRolledback(): bool
+ {
+ return $this->nestedTransactionRollbackException instanceof NestedTransactionRollbackException;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function disableConstraints(callable $callback)
+ {
+ return $this->getDisconnectRetry()->run(function () use ($callback) {
+ $this->disableForeignKeys();
+
+ try {
+ $result = $callback($this);
+ } finally {
+ $this->enableForeignKeys();
+ }
+
+ return $result;
+ });
+ }
+
+ /**
+ * Checks if a transaction is running.
+ *
+ * @return bool True if a transaction is running else false.
+ */
+ public function inTransaction(): bool
+ {
+ return $this->_transactionStarted;
+ }
+
+ /**
+ * Quotes value to be used safely in database query.
+ *
+ * This uses `PDO::quote()` and requires `supportsQuoting()` to work.
+ *
+ * @param mixed $value The value to quote.
+ * @param \Cake\Database\TypeInterface|string|int $type Type to be used for determining kind of quoting to perform
+ * @return string Quoted value
+ */
+ public function quote($value, $type = 'string'): string
+ {
+ [$value, $type] = $this->cast($value, $type);
+
+ return $this->_driver->quote($value, $type);
+ }
+
+ /**
+ * Checks if using `quote()` is supported.
+ *
+ * This is not required to use `quoteIdentifier()`.
+ *
+ * @return bool
+ */
+ public function supportsQuoting(): bool
+ {
+ return $this->_driver->supports(DriverInterface::FEATURE_QUOTE);
+ }
+
+ /**
+ * Quotes a database identifier (a column name, table name, etc..) to
+ * be used safely in queries without the risk of using reserved words.
+ *
+ * This does not require `supportsQuoting()` to work.
+ *
+ * @param string $identifier The identifier to quote.
+ * @return string
+ */
+ public function quoteIdentifier(string $identifier): string
+ {
+ return $this->_driver->quoteIdentifier($identifier);
+ }
+
+ /**
+ * Enables or disables metadata caching for this connection
+ *
+ * Changing this setting will not modify existing schema collections objects.
+ *
+ * @param string|bool $cache Either boolean false to disable metadata caching, or
+ * true to use `_cake_model_` or the name of the cache config to use.
+ * @return void
+ */
+ public function cacheMetadata($cache): void
+ {
+ $this->_schemaCollection = null;
+ $this->_config['cacheMetadata'] = $cache;
+ if (is_string($cache)) {
+ $this->cacher = null;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setCacher(CacheInterface $cacher)
+ {
+ $this->cacher = $cacher;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCacher(): CacheInterface
+ {
+ if ($this->cacher !== null) {
+ return $this->cacher;
+ }
+
+ $configName = $this->_config['cacheMetadata'] ?? '_cake_model_';
+ if (!is_string($configName)) {
+ $configName = '_cake_model_';
+ }
+
+ if (!class_exists(Cache::class)) {
+ throw new RuntimeException(
+ 'To use caching you must either set a cacher using Connection::setCacher()' .
+ ' or require the cakephp/cache package in your composer config.'
+ );
+ }
+
+ return $this->cacher = Cache::pool($configName);
+ }
+
+ /**
+ * Enable/disable query logging
+ *
+ * @param bool $enable Enable/disable query logging
+ * @return $this
+ */
+ public function enableQueryLogging(bool $enable = true)
+ {
+ $this->_logQueries = $enable;
+
+ return $this;
+ }
+
+ /**
+ * Disable query logging
+ *
+ * @return $this
+ */
+ public function disableQueryLogging()
+ {
+ $this->_logQueries = false;
+
+ return $this;
+ }
+
+ /**
+ * Check if query logging is enabled.
+ *
+ * @return bool
+ */
+ public function isQueryLoggingEnabled(): bool
+ {
+ return $this->_logQueries;
+ }
+
+ /**
+ * Sets a logger
+ *
+ * @param \Psr\Log\LoggerInterface $logger Logger object
+ * @return $this
+ * @psalm-suppress ImplementedReturnTypeMismatch
+ */
+ public function setLogger(LoggerInterface $logger)
+ {
+ $this->_logger = $logger;
+
+ return $this;
+ }
+
+ /**
+ * Gets the logger object
+ *
+ * @return \Psr\Log\LoggerInterface logger instance
+ */
+ public function getLogger(): LoggerInterface
+ {
+ if ($this->_logger !== null) {
+ return $this->_logger;
+ }
+
+ if (!class_exists(BaseLog::class)) {
+ throw new RuntimeException(
+ 'For logging you must either set a logger using Connection::setLogger()' .
+ ' or require the cakephp/log package in your composer config.'
+ );
+ }
+
+ return $this->_logger = new QueryLogger(['connection' => $this->configName()]);
+ }
+
+ /**
+ * Logs a Query string using the configured logger object.
+ *
+ * @param string $sql string to be logged
+ * @return void
+ */
+ public function log(string $sql): void
+ {
+ $query = new LoggedQuery();
+ $query->query = $sql;
+ $this->getLogger()->debug((string)$query, ['query' => $query]);
+ }
+
+ /**
+ * Returns a new statement object that will log the activity
+ * for the passed original statement instance.
+ *
+ * @param \Cake\Database\StatementInterface $statement the instance to be decorated
+ * @return \Cake\Database\Log\LoggingStatement
+ */
+ protected function _newLogger(StatementInterface $statement): LoggingStatement
+ {
+ $log = new LoggingStatement($statement, $this->_driver);
+ $log->setLogger($this->getLogger());
+
+ return $log;
+ }
+
+ /**
+ * Returns an array that can be used to describe the internal state of this
+ * object.
+ *
+ * @return array
+ */
+ public function __debugInfo(): array
+ {
+ $secrets = [
+ 'password' => '*****',
+ 'username' => '*****',
+ 'host' => '*****',
+ 'database' => '*****',
+ 'port' => '*****',
+ ];
+ $replace = array_intersect_key($secrets, $this->_config);
+ $config = $replace + $this->_config;
+
+ return [
+ 'config' => $config,
+ 'driver' => $this->_driver,
+ 'transactionLevel' => $this->_transactionLevel,
+ 'transactionStarted' => $this->_transactionStarted,
+ 'useSavePoints' => $this->_useSavePoints,
+ 'logQueries' => $this->_logQueries,
+ 'logger' => $this->_logger,
+ ];
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php b/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php
new file mode 100644
index 000000000..f1fe3c161
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php
@@ -0,0 +1,49 @@
+ DB-specific error codes that allow connect retry
+ */
+ protected const RETRY_ERROR_CODES = [];
+
+ /**
+ * Instance of PDO.
+ *
+ * @var \PDO
+ */
+ protected $_connection;
+
+ /**
+ * Configuration data.
+ *
+ * @var array
+ */
+ protected $_config;
+
+ /**
+ * Base configuration that is merged into the user
+ * supplied configuration data.
+ *
+ * @var array
+ */
+ protected $_baseConfig = [];
+
+ /**
+ * Indicates whether the driver is doing automatic identifier quoting
+ * for all queries
+ *
+ * @var bool
+ */
+ protected $_autoQuoting = false;
+
+ /**
+ * The server version
+ *
+ * @var string|null
+ */
+ protected $_version;
+
+ /**
+ * The last number of connection retry attempts.
+ *
+ * @var int
+ */
+ protected $connectRetries = 0;
+
+ /**
+ * Constructor
+ *
+ * @param array $config The configuration for the driver.
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(array $config = [])
+ {
+ if (empty($config['username']) && !empty($config['login'])) {
+ throw new InvalidArgumentException(
+ 'Please pass "username" instead of "login" for connecting to the database'
+ );
+ }
+ $config += $this->_baseConfig;
+ $this->_config = $config;
+ if (!empty($config['quoteIdentifiers'])) {
+ $this->enableAutoQuoting();
+ }
+ }
+
+ /**
+ * Establishes a connection to the database server
+ *
+ * @param string $dsn A Driver-specific PDO-DSN
+ * @param array $config configuration to be used for creating connection
+ * @return bool true on success
+ */
+ protected function _connect(string $dsn, array $config): bool
+ {
+ $action = function () use ($dsn, $config) {
+ $this->setConnection(new PDO(
+ $dsn,
+ $config['username'] ?: null,
+ $config['password'] ?: null,
+ $config['flags']
+ ));
+ };
+
+ $retry = new CommandRetry(new ErrorCodeWaitStrategy(static::RETRY_ERROR_CODES, 5), 4);
+ try {
+ $retry->run($action);
+ } catch (PDOException $e) {
+ throw new MissingConnectionException(
+ [
+ 'driver' => App::shortName(static::class, 'Database/Driver'),
+ 'reason' => $e->getMessage(),
+ ],
+ null,
+ $e
+ );
+ } finally {
+ $this->connectRetries = $retry->getRetries();
+ }
+
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ abstract public function connect(): bool;
+
+ /**
+ * @inheritDoc
+ */
+ public function disconnect(): void
+ {
+ /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
+ $this->_connection = null;
+ $this->_version = null;
+ }
+
+ /**
+ * Returns connected server version.
+ *
+ * @return string
+ */
+ public function version(): string
+ {
+ if ($this->_version === null) {
+ $this->connect();
+ $this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
+ }
+
+ return $this->_version;
+ }
+
+ /**
+ * Get the internal PDO connection instance.
+ *
+ * @return \PDO
+ */
+ public function getConnection()
+ {
+ if ($this->_connection === null) {
+ throw new MissingConnectionException([
+ 'driver' => App::shortName(static::class, 'Database/Driver'),
+ 'reason' => 'Unknown',
+ ]);
+ }
+
+ return $this->_connection;
+ }
+
+ /**
+ * Set the internal PDO connection instance.
+ *
+ * @param \PDO $connection PDO instance.
+ * @return $this
+ * @psalm-suppress MoreSpecificImplementedParamType
+ */
+ public function setConnection($connection)
+ {
+ $this->_connection = $connection;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ abstract public function enabled(): bool;
+
+ /**
+ * @inheritDoc
+ */
+ public function prepare($query): StatementInterface
+ {
+ $this->connect();
+ $statement = $this->_connection->prepare($query instanceof Query ? $query->sql() : $query);
+
+ return new PDOStatement($statement, $this);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function beginTransaction(): bool
+ {
+ $this->connect();
+ if ($this->_connection->inTransaction()) {
+ return true;
+ }
+
+ return $this->_connection->beginTransaction();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function commitTransaction(): bool
+ {
+ $this->connect();
+ if (!$this->_connection->inTransaction()) {
+ return false;
+ }
+
+ return $this->_connection->commit();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function rollbackTransaction(): bool
+ {
+ $this->connect();
+ if (!$this->_connection->inTransaction()) {
+ return false;
+ }
+
+ return $this->_connection->rollBack();
+ }
+
+ /**
+ * Returns whether a transaction is active for connection.
+ *
+ * @return bool
+ */
+ public function inTransaction(): bool
+ {
+ $this->connect();
+
+ return $this->_connection->inTransaction();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsSavePoints(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_SAVEPOINT);
+ }
+
+ /**
+ * Returns true if the server supports common table expressions.
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead
+ */
+ public function supportsCTEs(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_CTE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function quote($value, $type = PDO::PARAM_STR): string
+ {
+ $this->connect();
+
+ return $this->_connection->quote((string)$value, $type);
+ }
+
+ /**
+ * Checks if the driver supports quoting, as PDO_ODBC does not support it.
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead
+ */
+ public function supportsQuoting(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_QUOTE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ abstract public function queryTranslator(string $type): Closure;
+
+ /**
+ * @inheritDoc
+ */
+ abstract public function schemaDialect(): SchemaDialect;
+
+ /**
+ * @inheritDoc
+ */
+ abstract public function quoteIdentifier(string $identifier): string;
+
+ /**
+ * @inheritDoc
+ */
+ public function schemaValue($value): string
+ {
+ if ($value === null) {
+ return 'NULL';
+ }
+ if ($value === false) {
+ return 'FALSE';
+ }
+ if ($value === true) {
+ return 'TRUE';
+ }
+ if (is_float($value)) {
+ return str_replace(',', '.', (string)$value);
+ }
+ /** @psalm-suppress InvalidArgument */
+ if (
+ (
+ is_int($value) ||
+ $value === '0'
+ ) ||
+ (
+ is_numeric($value) &&
+ strpos($value, ',') === false &&
+ substr($value, 0, 1) !== '0' &&
+ strpos($value, 'e') === false
+ )
+ ) {
+ return (string)$value;
+ }
+
+ return $this->_connection->quote((string)$value, PDO::PARAM_STR);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function schema(): string
+ {
+ return $this->_config['schema'];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function lastInsertId(?string $table = null, ?string $column = null)
+ {
+ $this->connect();
+
+ if ($this->_connection instanceof PDO) {
+ return $this->_connection->lastInsertId($table);
+ }
+
+ return $this->_connection->lastInsertId($table);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isConnected(): bool
+ {
+ if ($this->_connection === null) {
+ $connected = false;
+ } else {
+ try {
+ $connected = (bool)$this->_connection->query('SELECT 1');
+ } catch (PDOException $e) {
+ $connected = false;
+ }
+ }
+
+ return $connected;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function enableAutoQuoting(bool $enable = true)
+ {
+ $this->_autoQuoting = $enable;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function disableAutoQuoting()
+ {
+ $this->_autoQuoting = false;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isAutoQuotingEnabled(): bool
+ {
+ return $this->_autoQuoting;
+ }
+
+ /**
+ * Returns whether the driver supports the feature.
+ *
+ * Defaults to true for FEATURE_QUOTE and FEATURE_SAVEPOINT.
+ *
+ * @param string $feature Driver feature name
+ * @return bool
+ */
+ public function supports(string $feature): bool
+ {
+ switch ($feature) {
+ case static::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION:
+ case static::FEATURE_QUOTE:
+ case static::FEATURE_SAVEPOINT:
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function compileQuery(Query $query, ValueBinder $binder): array
+ {
+ $processor = $this->newCompiler();
+ $translator = $this->queryTranslator($query->type());
+ $query = $translator($query);
+
+ return [$query, $processor->compile($query, $binder)];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function newCompiler(): QueryCompiler
+ {
+ return new QueryCompiler();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function newTableSchema(string $table, array $columns = []): TableSchema
+ {
+ $className = TableSchema::class;
+ if (isset($this->_config['tableSchema'])) {
+ /** @var class-string<\Cake\Database\Schema\TableSchema> $className */
+ $className = $this->_config['tableSchema'];
+ }
+
+ return new $className($table, $columns);
+ }
+
+ /**
+ * Returns the maximum alias length allowed.
+ * This can be different from the maximum identifier length for columns.
+ *
+ * @return int|null Maximum alias length or null if no limit
+ */
+ public function getMaxAliasLength(): ?int
+ {
+ return static::MAX_ALIAS_LENGTH;
+ }
+
+ /**
+ * Returns the number of connection retry attempts made.
+ *
+ * @return int
+ */
+ public function getConnectRetries(): int
+ {
+ return $this->connectRetries;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an array that can be used to describe the internal state of this
+ * object.
+ *
+ * @return array
+ */
+ public function __debugInfo(): array
+ {
+ return [
+ 'connected' => $this->_connection !== null,
+ ];
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php
new file mode 100644
index 000000000..68f46ff74
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php
@@ -0,0 +1,344 @@
+
+ */
+ protected $_baseConfig = [
+ 'persistent' => true,
+ 'host' => 'localhost',
+ 'username' => 'root',
+ 'password' => '',
+ 'database' => 'cake',
+ 'port' => '3306',
+ 'flags' => [],
+ 'encoding' => 'utf8mb4',
+ 'timezone' => null,
+ 'init' => [],
+ ];
+
+ /**
+ * The schema dialect for this driver
+ *
+ * @var \Cake\Database\Schema\MysqlSchemaDialect|null
+ */
+ protected $_schemaDialect;
+
+ /**
+ * String used to start a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_startQuote = '`';
+
+ /**
+ * String used to end a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_endQuote = '`';
+
+ /**
+ * Server type.
+ *
+ * If the underlying server is MariaDB, its value will get set to `'mariadb'`
+ * after `version()` method is called.
+ *
+ * @var string
+ */
+ protected $serverType = self::SERVER_TYPE_MYSQL;
+
+ /**
+ * Mapping of feature to db server version for feature availability checks.
+ *
+ * @var array>
+ */
+ protected $featureVersions = [
+ 'mysql' => [
+ 'json' => '5.7.0',
+ 'cte' => '8.0.0',
+ 'window' => '8.0.0',
+ ],
+ 'mariadb' => [
+ 'json' => '10.2.7',
+ 'cte' => '10.2.1',
+ 'window' => '10.2.0',
+ ],
+ ];
+
+ /**
+ * Establishes a connection to the database server
+ *
+ * @return bool true on success
+ */
+ public function connect(): bool
+ {
+ if ($this->_connection) {
+ return true;
+ }
+ $config = $this->_config;
+
+ if ($config['timezone'] === 'UTC') {
+ $config['timezone'] = '+0:00';
+ }
+
+ if (!empty($config['timezone'])) {
+ $config['init'][] = sprintf("SET time_zone = '%s'", $config['timezone']);
+ }
+
+ $config['flags'] += [
+ PDO::ATTR_PERSISTENT => $config['persistent'],
+ PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ ];
+
+ if (!empty($config['ssl_key']) && !empty($config['ssl_cert'])) {
+ $config['flags'][PDO::MYSQL_ATTR_SSL_KEY] = $config['ssl_key'];
+ $config['flags'][PDO::MYSQL_ATTR_SSL_CERT] = $config['ssl_cert'];
+ }
+ if (!empty($config['ssl_ca'])) {
+ $config['flags'][PDO::MYSQL_ATTR_SSL_CA] = $config['ssl_ca'];
+ }
+
+ if (empty($config['unix_socket'])) {
+ $dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
+ } else {
+ $dsn = "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
+ }
+
+ if (!empty($config['encoding'])) {
+ $dsn .= ";charset={$config['encoding']}";
+ }
+
+ $this->_connect($dsn, $config);
+
+ if (!empty($config['init'])) {
+ $connection = $this->getConnection();
+ foreach ((array)$config['init'] as $command) {
+ $connection->exec($command);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns whether php is able to use this driver for connecting to database
+ *
+ * @return bool true if it is valid to use this driver
+ */
+ public function enabled(): bool
+ {
+ return in_array('mysql', PDO::getAvailableDrivers(), true);
+ }
+
+ /**
+ * Prepares a sql statement to be executed
+ *
+ * @param \Cake\Database\Query|string $query The query to prepare.
+ * @return \Cake\Database\StatementInterface
+ */
+ public function prepare($query): StatementInterface
+ {
+ $this->connect();
+ $isObject = $query instanceof Query;
+ /**
+ * @psalm-suppress PossiblyInvalidMethodCall
+ * @psalm-suppress PossiblyInvalidArgument
+ */
+ $statement = $this->_connection->prepare($isObject ? $query->sql() : $query);
+ $result = new MysqlStatement($statement, $this);
+ /** @psalm-suppress PossiblyInvalidMethodCall */
+ if ($isObject && $query->isBufferedResultsEnabled() === false) {
+ $result->bufferResults(false);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function schemaDialect(): SchemaDialect
+ {
+ if ($this->_schemaDialect === null) {
+ $this->_schemaDialect = new MysqlSchemaDialect($this);
+ }
+
+ return $this->_schemaDialect;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function schema(): string
+ {
+ return $this->_config['database'];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function disableForeignKeySQL(): string
+ {
+ return 'SET foreign_key_checks = 0';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function enableForeignKeySQL(): string
+ {
+ return 'SET foreign_key_checks = 1';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supports(string $feature): bool
+ {
+ switch ($feature) {
+ case static::FEATURE_CTE:
+ case static::FEATURE_JSON:
+ case static::FEATURE_WINDOW:
+ return version_compare(
+ $this->version(),
+ $this->featureVersions[$this->serverType][$feature],
+ '>='
+ );
+ }
+
+ return parent::supports($feature);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsDynamicConstraints(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Returns true if the connected server is MariaDB.
+ *
+ * @return bool
+ */
+ public function isMariadb(): bool
+ {
+ $this->version();
+
+ return $this->serverType === static::SERVER_TYPE_MARIADB;
+ }
+
+ /**
+ * Returns connected server version.
+ *
+ * @return string
+ */
+ public function version(): string
+ {
+ if ($this->_version === null) {
+ $this->connect();
+ $this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
+
+ if (strpos($this->_version, 'MariaDB') !== false) {
+ $this->serverType = static::SERVER_TYPE_MARIADB;
+ preg_match('/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.*-MariaDB[^:]*)/', $this->_version, $matches);
+ $this->_version = $matches[1];
+ }
+ }
+
+ return $this->_version;
+ }
+
+ /**
+ * Returns true if the server supports common table expressions.
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead
+ */
+ public function supportsCTEs(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_CTE);
+ }
+
+ /**
+ * Returns true if the server supports native JSON columns
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_JSON)` instead
+ */
+ public function supportsNativeJson(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_JSON);
+ }
+
+ /**
+ * Returns true if the connected server supports window functions.
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead
+ */
+ public function supportsWindowFunctions(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_WINDOW);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php
new file mode 100644
index 000000000..2a32264c0
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php
@@ -0,0 +1,348 @@
+
+ */
+ protected $_baseConfig = [
+ 'persistent' => true,
+ 'host' => 'localhost',
+ 'username' => 'root',
+ 'password' => '',
+ 'database' => 'cake',
+ 'schema' => 'public',
+ 'port' => 5432,
+ 'encoding' => 'utf8',
+ 'timezone' => null,
+ 'flags' => [],
+ 'init' => [],
+ ];
+
+ /**
+ * The schema dialect class for this driver
+ *
+ * @var \Cake\Database\Schema\PostgresSchemaDialect|null
+ */
+ protected $_schemaDialect;
+
+ /**
+ * String used to start a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_startQuote = '"';
+
+ /**
+ * String used to end a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_endQuote = '"';
+
+ /**
+ * Establishes a connection to the database server
+ *
+ * @return bool true on success
+ */
+ public function connect(): bool
+ {
+ if ($this->_connection) {
+ return true;
+ }
+ $config = $this->_config;
+ $config['flags'] += [
+ PDO::ATTR_PERSISTENT => $config['persistent'],
+ PDO::ATTR_EMULATE_PREPARES => false,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ ];
+ if (empty($config['unix_socket'])) {
+ $dsn = "pgsql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
+ } else {
+ $dsn = "pgsql:dbname={$config['database']}";
+ }
+
+ $this->_connect($dsn, $config);
+ $this->_connection = $connection = $this->getConnection();
+ if (!empty($config['encoding'])) {
+ $this->setEncoding($config['encoding']);
+ }
+
+ if (!empty($config['schema'])) {
+ $this->setSchema($config['schema']);
+ }
+
+ if (!empty($config['timezone'])) {
+ $config['init'][] = sprintf('SET timezone = %s', $connection->quote($config['timezone']));
+ }
+
+ foreach ($config['init'] as $command) {
+ $connection->exec($command);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns whether php is able to use this driver for connecting to database
+ *
+ * @return bool true if it is valid to use this driver
+ */
+ public function enabled(): bool
+ {
+ return in_array('pgsql', PDO::getAvailableDrivers(), true);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function schemaDialect(): SchemaDialect
+ {
+ if ($this->_schemaDialect === null) {
+ $this->_schemaDialect = new PostgresSchemaDialect($this);
+ }
+
+ return $this->_schemaDialect;
+ }
+
+ /**
+ * Sets connection encoding
+ *
+ * @param string $encoding The encoding to use.
+ * @return void
+ */
+ public function setEncoding(string $encoding): void
+ {
+ $this->connect();
+ $this->_connection->exec('SET NAMES ' . $this->_connection->quote($encoding));
+ }
+
+ /**
+ * Sets connection default schema, if any relation defined in a query is not fully qualified
+ * postgres will fallback to looking the relation into defined default schema
+ *
+ * @param string $schema The schema names to set `search_path` to.
+ * @return void
+ */
+ public function setSchema(string $schema): void
+ {
+ $this->connect();
+ $this->_connection->exec('SET search_path TO ' . $this->_connection->quote($schema));
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function disableForeignKeySQL(): string
+ {
+ return 'SET CONSTRAINTS ALL DEFERRED';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function enableForeignKeySQL(): string
+ {
+ return 'SET CONSTRAINTS ALL IMMEDIATE';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supports(string $feature): bool
+ {
+ switch ($feature) {
+ case static::FEATURE_CTE:
+ case static::FEATURE_JSON:
+ case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS:
+ case static::FEATURE_WINDOW:
+ return true;
+
+ case static::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION:
+ return false;
+ }
+
+ return parent::supports($feature);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsDynamicConstraints(): bool
+ {
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _transformDistinct(Query $query): Query
+ {
+ return $query;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _insertQueryTranslator(Query $query): Query
+ {
+ if (!$query->clause('epilog')) {
+ $query->epilog('RETURNING *');
+ }
+
+ return $query;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _expressionTranslators(): array
+ {
+ return [
+ IdentifierExpression::class => '_transformIdentifierExpression',
+ FunctionExpression::class => '_transformFunctionExpression',
+ StringExpression::class => '_transformStringExpression',
+ ];
+ }
+
+ /**
+ * Changes identifer expression into postgresql format.
+ *
+ * @param \Cake\Database\Expression\IdentifierExpression $expression The expression to tranform.
+ * @return void
+ */
+ protected function _transformIdentifierExpression(IdentifierExpression $expression): void
+ {
+ $collation = $expression->getCollation();
+ if ($collation) {
+ // use trim() to work around expression being transformed multiple times
+ $expression->setCollation('"' . trim($collation, '"') . '"');
+ }
+ }
+
+ /**
+ * Receives a FunctionExpression and changes it so that it conforms to this
+ * SQL dialect.
+ *
+ * @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert
+ * to postgres SQL.
+ * @return void
+ */
+ protected function _transformFunctionExpression(FunctionExpression $expression): void
+ {
+ switch ($expression->getName()) {
+ case 'CONCAT':
+ // CONCAT function is expressed as exp1 || exp2
+ $expression->setName('')->setConjunction(' ||');
+ break;
+ case 'DATEDIFF':
+ $expression
+ ->setName('')
+ ->setConjunction('-')
+ ->iterateParts(function ($p) {
+ if (is_string($p)) {
+ $p = ['value' => [$p => 'literal'], 'type' => null];
+ } else {
+ $p['value'] = [$p['value']];
+ }
+
+ return new FunctionExpression('DATE', $p['value'], [$p['type']]);
+ });
+ break;
+ case 'CURRENT_DATE':
+ $time = new FunctionExpression('LOCALTIMESTAMP', [' 0 ' => 'literal']);
+ $expression->setName('CAST')->setConjunction(' AS ')->add([$time, 'date' => 'literal']);
+ break;
+ case 'CURRENT_TIME':
+ $time = new FunctionExpression('LOCALTIMESTAMP', [' 0 ' => 'literal']);
+ $expression->setName('CAST')->setConjunction(' AS ')->add([$time, 'time' => 'literal']);
+ break;
+ case 'NOW':
+ $expression->setName('LOCALTIMESTAMP')->add([' 0 ' => 'literal']);
+ break;
+ case 'RAND':
+ $expression->setName('RANDOM');
+ break;
+ case 'DATE_ADD':
+ $expression
+ ->setName('')
+ ->setConjunction(' + INTERVAL')
+ ->iterateParts(function ($p, $key) {
+ if ($key === 1) {
+ $p = sprintf("'%s'", $p);
+ }
+
+ return $p;
+ });
+ break;
+ case 'DAYOFWEEK':
+ $expression
+ ->setName('EXTRACT')
+ ->setConjunction(' ')
+ ->add(['DOW FROM' => 'literal'], [], true)
+ ->add([') + (1' => 'literal']); // Postgres starts on index 0 but Sunday should be 1
+ break;
+ }
+ }
+
+ /**
+ * Changes string expression into postgresql format.
+ *
+ * @param \Cake\Database\Expression\StringExpression $expression The string expression to tranform.
+ * @return void
+ */
+ protected function _transformStringExpression(StringExpression $expression): void
+ {
+ // use trim() to work around expression being transformed multiple times
+ $expression->setCollation('"' . trim($expression->getCollation(), '"') . '"');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \Cake\Database\PostgresCompiler
+ */
+ public function newCompiler(): QueryCompiler
+ {
+ return new PostgresCompiler();
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/SqlDialectTrait.php b/app/vendor/cakephp/cakephp/src/Database/Driver/SqlDialectTrait.php
new file mode 100644
index 000000000..7bcabda6b
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/SqlDialectTrait.php
@@ -0,0 +1,308 @@
+_startQuote . $identifier . $this->_endQuote;
+ }
+
+ // string.string
+ if (preg_match('/^[\w-]+\.[^ \*]*$/u', $identifier)) {
+ $items = explode('.', $identifier);
+
+ return $this->_startQuote . implode($this->_endQuote . '.' . $this->_startQuote, $items) . $this->_endQuote;
+ }
+
+ // string.*
+ if (preg_match('/^[\w-]+\.\*$/u', $identifier)) {
+ return $this->_startQuote . str_replace('.*', $this->_endQuote . '.*', $identifier);
+ }
+
+ // Functions
+ if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) {
+ return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
+ }
+
+ // Alias.field AS thing
+ if (preg_match('/^([\w-]+(\.[\w\s-]+|\(.*\))*)\s+AS\s*([\w-]+)$/ui', $identifier, $matches)) {
+ return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]);
+ }
+
+ // string.string with spaces
+ if (preg_match('/^([\w-]+\.[\w][\w\s-]*[\w])(.*)/u', $identifier, $matches)) {
+ $items = explode('.', $matches[1]);
+ $field = implode($this->_endQuote . '.' . $this->_startQuote, $items);
+
+ return $this->_startQuote . $field . $this->_endQuote . $matches[2];
+ }
+
+ if (preg_match('/^[\w\s-]*[\w-]+/u', $identifier)) {
+ return $this->_startQuote . $identifier . $this->_endQuote;
+ }
+
+ return $identifier;
+ }
+
+ /**
+ * Returns a callable function that will be used to transform a passed Query object.
+ * This function, in turn, will return an instance of a Query object that has been
+ * transformed to accommodate any specificities of the SQL dialect in use.
+ *
+ * @param string $type the type of query to be transformed
+ * (select, insert, update, delete)
+ * @return \Closure
+ */
+ public function queryTranslator(string $type): Closure
+ {
+ return function ($query) use ($type) {
+ if ($this->isAutoQuotingEnabled()) {
+ $query = (new IdentifierQuoter($this))->quote($query);
+ }
+
+ /** @var \Cake\ORM\Query $query */
+ $query = $this->{'_' . $type . 'QueryTranslator'}($query);
+ $translators = $this->_expressionTranslators();
+ if (!$translators) {
+ return $query;
+ }
+
+ $query->traverseExpressions(function ($expression) use ($translators, $query): void {
+ foreach ($translators as $class => $method) {
+ if ($expression instanceof $class) {
+ $this->{$method}($expression, $query);
+ }
+ }
+ });
+
+ return $query;
+ };
+ }
+
+ /**
+ * Returns an associative array of methods that will transform Expression
+ * objects to conform with the specific SQL dialect. Keys are class names
+ * and values a method in this class.
+ *
+ * @psalm-return array
+ * @return array
+ */
+ protected function _expressionTranslators(): array
+ {
+ return [];
+ }
+
+ /**
+ * Apply translation steps to select queries.
+ *
+ * @param \Cake\Database\Query $query The query to translate
+ * @return \Cake\Database\Query The modified query
+ */
+ protected function _selectQueryTranslator(Query $query): Query
+ {
+ return $this->_transformDistinct($query);
+ }
+
+ /**
+ * Returns the passed query after rewriting the DISTINCT clause, so that drivers
+ * that do not support the "ON" part can provide the actual way it should be done
+ *
+ * @param \Cake\Database\Query $query The query to be transformed
+ * @return \Cake\Database\Query
+ */
+ protected function _transformDistinct(Query $query): Query
+ {
+ if (is_array($query->clause('distinct'))) {
+ $query->group($query->clause('distinct'), true);
+ $query->distinct(false);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Apply translation steps to delete queries.
+ *
+ * Chops out aliases on delete query conditions as most database dialects do not
+ * support aliases in delete queries. This also removes aliases
+ * in table names as they frequently don't work either.
+ *
+ * We are intentionally not supporting deletes with joins as they have even poorer support.
+ *
+ * @param \Cake\Database\Query $query The query to translate
+ * @return \Cake\Database\Query The modified query
+ */
+ protected function _deleteQueryTranslator(Query $query): Query
+ {
+ $hadAlias = false;
+ $tables = [];
+ foreach ($query->clause('from') as $alias => $table) {
+ if (is_string($alias)) {
+ $hadAlias = true;
+ }
+ $tables[] = $table;
+ }
+ if ($hadAlias) {
+ $query->from($tables, true);
+ }
+
+ if (!$hadAlias) {
+ return $query;
+ }
+
+ return $this->_removeAliasesFromConditions($query);
+ }
+
+ /**
+ * Apply translation steps to update queries.
+ *
+ * Chops out aliases on update query conditions as not all database dialects do support
+ * aliases in update queries.
+ *
+ * Just like for delete queries, joins are currently not supported for update queries.
+ *
+ * @param \Cake\Database\Query $query The query to translate
+ * @return \Cake\Database\Query The modified query
+ */
+ protected function _updateQueryTranslator(Query $query): Query
+ {
+ return $this->_removeAliasesFromConditions($query);
+ }
+
+ /**
+ * Removes aliases from the `WHERE` clause of a query.
+ *
+ * @param \Cake\Database\Query $query The query to process.
+ * @return \Cake\Database\Query The modified query.
+ * @throws \RuntimeException In case the processed query contains any joins, as removing
+ * aliases from the conditions can break references to the joined tables.
+ */
+ protected function _removeAliasesFromConditions(Query $query): Query
+ {
+ if ($query->clause('join')) {
+ throw new RuntimeException(
+ 'Aliases are being removed from conditions for UPDATE/DELETE queries, ' .
+ 'this can break references to joined tables.'
+ );
+ }
+
+ $conditions = $query->clause('where');
+ if ($conditions) {
+ $conditions->traverse(function ($expression) {
+ if ($expression instanceof ComparisonExpression) {
+ $field = $expression->getField();
+ if (
+ is_string($field) &&
+ strpos($field, '.') !== false
+ ) {
+ [, $unaliasedField] = explode('.', $field, 2);
+ $expression->setField($unaliasedField);
+ }
+
+ return $expression;
+ }
+
+ if ($expression instanceof IdentifierExpression) {
+ $identifier = $expression->getIdentifier();
+ if (strpos($identifier, '.') !== false) {
+ [, $unaliasedIdentifier] = explode('.', $identifier, 2);
+ $expression->setIdentifier($unaliasedIdentifier);
+ }
+
+ return $expression;
+ }
+
+ return $expression;
+ });
+ }
+
+ return $query;
+ }
+
+ /**
+ * Apply translation steps to insert queries.
+ *
+ * @param \Cake\Database\Query $query The query to translate
+ * @return \Cake\Database\Query The modified query
+ */
+ protected function _insertQueryTranslator(Query $query): Query
+ {
+ return $query;
+ }
+
+ /**
+ * Returns a SQL snippet for creating a new transaction savepoint
+ *
+ * @param string|int $name save point name
+ * @return string
+ */
+ public function savePointSQL($name): string
+ {
+ return 'SAVEPOINT LEVEL' . $name;
+ }
+
+ /**
+ * Returns a SQL snippet for releasing a previously created save point
+ *
+ * @param string|int $name save point name
+ * @return string
+ */
+ public function releaseSavePointSQL($name): string
+ {
+ return 'RELEASE SAVEPOINT LEVEL' . $name;
+ }
+
+ /**
+ * Returns a SQL snippet for rollbacking a previously created save point
+ *
+ * @param string|int $name save point name
+ * @return string
+ */
+ public function rollbackSavePointSQL($name): string
+ {
+ return 'ROLLBACK TO SAVEPOINT LEVEL' . $name;
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php
new file mode 100644
index 000000000..6ca955856
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php
@@ -0,0 +1,384 @@
+
+ */
+ protected $_baseConfig = [
+ 'persistent' => false,
+ 'username' => null,
+ 'password' => null,
+ 'database' => ':memory:',
+ 'encoding' => 'utf8',
+ 'mask' => 0644,
+ 'cache' => null,
+ 'mode' => null,
+ 'flags' => [],
+ 'init' => [],
+ ];
+
+ /**
+ * The schema dialect class for this driver
+ *
+ * @var \Cake\Database\Schema\SqliteSchemaDialect|null
+ */
+ protected $_schemaDialect;
+
+ /**
+ * Whether the connected server supports window functions.
+ *
+ * @var bool|null
+ */
+ protected $_supportsWindowFunctions;
+
+ /**
+ * String used to start a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_startQuote = '"';
+
+ /**
+ * String used to end a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_endQuote = '"';
+
+ /**
+ * Mapping of date parts.
+ *
+ * @var array
+ */
+ protected $_dateParts = [
+ 'day' => 'd',
+ 'hour' => 'H',
+ 'month' => 'm',
+ 'minute' => 'M',
+ 'second' => 'S',
+ 'week' => 'W',
+ 'year' => 'Y',
+ ];
+
+ /**
+ * Mapping of feature to db server version for feature availability checks.
+ *
+ * @var array
+ */
+ protected $featureVersions = [
+ 'cte' => '3.8.3',
+ 'window' => '3.28.0',
+ ];
+
+ /**
+ * Establishes a connection to the database server
+ *
+ * @return bool true on success
+ */
+ public function connect(): bool
+ {
+ if ($this->_connection) {
+ return true;
+ }
+ $config = $this->_config;
+ $config['flags'] += [
+ PDO::ATTR_PERSISTENT => $config['persistent'],
+ PDO::ATTR_EMULATE_PREPARES => false,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ ];
+ if (!is_string($config['database']) || $config['database'] === '') {
+ $name = $config['name'] ?? 'unknown';
+ throw new InvalidArgumentException(
+ "The `database` key for the `{$name}` SQLite connection needs to be a non-empty string."
+ );
+ }
+
+ $chmodFile = false;
+ if ($config['database'] !== ':memory:' && $config['mode'] !== 'memory') {
+ $chmodFile = !file_exists($config['database']);
+ }
+
+ $params = [];
+ if ($config['cache']) {
+ $params[] = 'cache=' . $config['cache'];
+ }
+ if ($config['mode']) {
+ $params[] = 'mode=' . $config['mode'];
+ }
+
+ if ($params) {
+ if (PHP_VERSION_ID < 80100) {
+ throw new RuntimeException('SQLite URI support requires PHP 8.1.');
+ }
+ $dsn = 'sqlite:file:' . $config['database'] . '?' . implode('&', $params);
+ } else {
+ $dsn = 'sqlite:' . $config['database'];
+ }
+
+ $this->_connect($dsn, $config);
+ if ($chmodFile) {
+ // phpcs:disable
+ @chmod($config['database'], $config['mask']);
+ // phpcs:enable
+ }
+
+ if (!empty($config['init'])) {
+ foreach ((array)$config['init'] as $command) {
+ $this->getConnection()->exec($command);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns whether php is able to use this driver for connecting to database
+ *
+ * @return bool true if it is valid to use this driver
+ */
+ public function enabled(): bool
+ {
+ return in_array('sqlite', PDO::getAvailableDrivers(), true);
+ }
+
+ /**
+ * Prepares a sql statement to be executed
+ *
+ * @param \Cake\Database\Query|string $query The query to prepare.
+ * @return \Cake\Database\StatementInterface
+ */
+ public function prepare($query): StatementInterface
+ {
+ $this->connect();
+ $isObject = $query instanceof Query;
+ /**
+ * @psalm-suppress PossiblyInvalidMethodCall
+ * @psalm-suppress PossiblyInvalidArgument
+ */
+ $statement = $this->_connection->prepare($isObject ? $query->sql() : $query);
+ $result = new SqliteStatement(new PDOStatement($statement, $this), $this);
+ /** @psalm-suppress PossiblyInvalidMethodCall */
+ if ($isObject && $query->isBufferedResultsEnabled() === false) {
+ $result->bufferResults(false);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function disableForeignKeySQL(): string
+ {
+ return 'PRAGMA foreign_keys = OFF';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function enableForeignKeySQL(): string
+ {
+ return 'PRAGMA foreign_keys = ON';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supports(string $feature): bool
+ {
+ switch ($feature) {
+ case static::FEATURE_CTE:
+ case static::FEATURE_WINDOW:
+ return version_compare(
+ $this->version(),
+ $this->featureVersions[$feature],
+ '>='
+ );
+
+ case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS:
+ return true;
+ }
+
+ return parent::supports($feature);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsDynamicConstraints(): bool
+ {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function schemaDialect(): SchemaDialect
+ {
+ if ($this->_schemaDialect === null) {
+ $this->_schemaDialect = new SqliteSchemaDialect($this);
+ }
+
+ return $this->_schemaDialect;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function newCompiler(): QueryCompiler
+ {
+ return new SqliteCompiler();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _expressionTranslators(): array
+ {
+ return [
+ FunctionExpression::class => '_transformFunctionExpression',
+ TupleComparison::class => '_transformTupleComparison',
+ ];
+ }
+
+ /**
+ * Receives a FunctionExpression and changes it so that it conforms to this
+ * SQL dialect.
+ *
+ * @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL.
+ * @return void
+ */
+ protected function _transformFunctionExpression(FunctionExpression $expression): void
+ {
+ switch ($expression->getName()) {
+ case 'CONCAT':
+ // CONCAT function is expressed as exp1 || exp2
+ $expression->setName('')->setConjunction(' ||');
+ break;
+ case 'DATEDIFF':
+ $expression
+ ->setName('ROUND')
+ ->setConjunction('-')
+ ->iterateParts(function ($p) {
+ return new FunctionExpression('JULIANDAY', [$p['value']], [$p['type']]);
+ });
+ break;
+ case 'NOW':
+ $expression->setName('DATETIME')->add(["'now'" => 'literal']);
+ break;
+ case 'RAND':
+ $expression
+ ->setName('ABS')
+ ->add(['RANDOM() % 1' => 'literal'], [], true);
+ break;
+ case 'CURRENT_DATE':
+ $expression->setName('DATE')->add(["'now'" => 'literal']);
+ break;
+ case 'CURRENT_TIME':
+ $expression->setName('TIME')->add(["'now'" => 'literal']);
+ break;
+ case 'EXTRACT':
+ $expression
+ ->setName('STRFTIME')
+ ->setConjunction(' ,')
+ ->iterateParts(function ($p, $key) {
+ if ($key === 0) {
+ $value = rtrim(strtolower($p), 's');
+ if (isset($this->_dateParts[$value])) {
+ $p = ['value' => '%' . $this->_dateParts[$value], 'type' => null];
+ }
+ }
+
+ return $p;
+ });
+ break;
+ case 'DATE_ADD':
+ $expression
+ ->setName('DATE')
+ ->setConjunction(',')
+ ->iterateParts(function ($p, $key) {
+ if ($key === 1) {
+ $p = ['value' => $p, 'type' => null];
+ }
+
+ return $p;
+ });
+ break;
+ case 'DAYOFWEEK':
+ $expression
+ ->setName('STRFTIME')
+ ->setConjunction(' ')
+ ->add(["'%w', " => 'literal'], [], true)
+ ->add([') + (1' => 'literal']); // Sqlite starts on index 0 but Sunday should be 1
+ break;
+ }
+ }
+
+ /**
+ * Returns true if the server supports common table expressions.
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead
+ */
+ public function supportsCTEs(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_CTE);
+ }
+
+ /**
+ * Returns true if the connected server supports window functions.
+ *
+ * @return bool
+ * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead
+ */
+ public function supportsWindowFunctions(): bool
+ {
+ deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
+
+ return $this->supports(static::FEATURE_WINDOW);
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php
new file mode 100644
index 000000000..a0dc2ec58
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php
@@ -0,0 +1,569 @@
+
+ */
+ protected $_baseConfig = [
+ 'host' => 'localhost\SQLEXPRESS',
+ 'username' => '',
+ 'password' => '',
+ 'database' => 'cake',
+ 'port' => '',
+ // PDO::SQLSRV_ENCODING_UTF8
+ 'encoding' => 65001,
+ 'flags' => [],
+ 'init' => [],
+ 'settings' => [],
+ 'attributes' => [],
+ 'app' => null,
+ 'connectionPooling' => null,
+ 'failoverPartner' => null,
+ 'loginTimeout' => null,
+ 'multiSubnetFailover' => null,
+ 'encrypt' => null,
+ 'trustServerCertificate' => null,
+ ];
+
+ /**
+ * The schema dialect class for this driver
+ *
+ * @var \Cake\Database\Schema\SqlserverSchemaDialect|null
+ */
+ protected $_schemaDialect;
+
+ /**
+ * String used to start a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_startQuote = '[';
+
+ /**
+ * String used to end a database identifier quoting to make it safe
+ *
+ * @var string
+ */
+ protected $_endQuote = ']';
+
+ /**
+ * Establishes a connection to the database server.
+ *
+ * Please note that the PDO::ATTR_PERSISTENT attribute is not supported by
+ * the SQL Server PHP PDO drivers. As a result you cannot use the
+ * persistent config option when connecting to a SQL Server (for more
+ * information see: https://github.com/Microsoft/msphpsql/issues/65).
+ *
+ * @throws \InvalidArgumentException if an unsupported setting is in the driver config
+ * @return bool true on success
+ */
+ public function connect(): bool
+ {
+ if ($this->_connection) {
+ return true;
+ }
+ $config = $this->_config;
+
+ if (isset($config['persistent']) && $config['persistent']) {
+ throw new InvalidArgumentException(
+ 'Config setting "persistent" cannot be set to true, '
+ . 'as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT'
+ );
+ }
+
+ $config['flags'] += [
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ ];
+
+ if (!empty($config['encoding'])) {
+ $config['flags'][PDO::SQLSRV_ATTR_ENCODING] = $config['encoding'];
+ }
+ $port = '';
+ if ($config['port']) {
+ $port = ',' . $config['port'];
+ }
+
+ $dsn = "sqlsrv:Server={$config['host']}{$port};Database={$config['database']};MultipleActiveResultSets=false";
+ if ($config['app'] !== null) {
+ $dsn .= ";APP={$config['app']}";
+ }
+ if ($config['connectionPooling'] !== null) {
+ $dsn .= ";ConnectionPooling={$config['connectionPooling']}";
+ }
+ if ($config['failoverPartner'] !== null) {
+ $dsn .= ";Failover_Partner={$config['failoverPartner']}";
+ }
+ if ($config['loginTimeout'] !== null) {
+ $dsn .= ";LoginTimeout={$config['loginTimeout']}";
+ }
+ if ($config['multiSubnetFailover'] !== null) {
+ $dsn .= ";MultiSubnetFailover={$config['multiSubnetFailover']}";
+ }
+ if ($config['encrypt'] !== null) {
+ $dsn .= ";Encrypt={$config['encrypt']}";
+ }
+ if ($config['trustServerCertificate'] !== null) {
+ $dsn .= ";TrustServerCertificate={$config['trustServerCertificate']}";
+ }
+ $this->_connect($dsn, $config);
+
+ $connection = $this->getConnection();
+ if (!empty($config['init'])) {
+ foreach ((array)$config['init'] as $command) {
+ $connection->exec($command);
+ }
+ }
+ if (!empty($config['settings']) && is_array($config['settings'])) {
+ foreach ($config['settings'] as $key => $value) {
+ $connection->exec("SET {$key} {$value}");
+ }
+ }
+ if (!empty($config['attributes']) && is_array($config['attributes'])) {
+ foreach ($config['attributes'] as $key => $value) {
+ $connection->setAttribute($key, $value);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns whether PHP is able to use this driver for connecting to database
+ *
+ * @return bool true if it is valid to use this driver
+ */
+ public function enabled(): bool
+ {
+ return in_array('sqlsrv', PDO::getAvailableDrivers(), true);
+ }
+
+ /**
+ * Prepares a sql statement to be executed
+ *
+ * @param \Cake\Database\Query|string $query The query to prepare.
+ * @return \Cake\Database\StatementInterface
+ */
+ public function prepare($query): StatementInterface
+ {
+ $this->connect();
+
+ $sql = $query;
+ $options = [
+ PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
+ PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,
+ ];
+ if ($query instanceof Query) {
+ $sql = $query->sql();
+ if (count($query->getValueBinder()->bindings()) > 2100) {
+ throw new InvalidArgumentException(
+ 'Exceeded maximum number of parameters (2100) for prepared statements in Sql Server. ' .
+ 'This is probably due to a very large WHERE IN () clause which generates a parameter ' .
+ 'for each value in the array. ' .
+ 'If using an Association, try changing the `strategy` from select to subquery.'
+ );
+ }
+
+ if (!$query->isBufferedResultsEnabled()) {
+ $options = [];
+ }
+ }
+
+ /** @psalm-suppress PossiblyInvalidArgument */
+ $statement = $this->_connection->prepare($sql, $options);
+
+ return new SqlserverStatement($statement, $this);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function savePointSQL($name): string
+ {
+ return 'SAVE TRANSACTION t' . $name;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function releaseSavePointSQL($name): string
+ {
+ // SQLServer has no release save point operation.
+ return '';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function rollbackSavePointSQL($name): string
+ {
+ return 'ROLLBACK TRANSACTION t' . $name;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function disableForeignKeySQL(): string
+ {
+ return 'EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function enableForeignKeySQL(): string
+ {
+ return 'EXEC sp_MSforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supports(string $feature): bool
+ {
+ switch ($feature) {
+ case static::FEATURE_CTE:
+ case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS:
+ case static::FEATURE_WINDOW:
+ return true;
+
+ case static::FEATURE_QUOTE:
+ $this->connect();
+
+ return $this->_connection->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'odbc';
+ }
+
+ return parent::supports($feature);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsDynamicConstraints(): bool
+ {
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function schemaDialect(): SchemaDialect
+ {
+ if ($this->_schemaDialect === null) {
+ $this->_schemaDialect = new SqlserverSchemaDialect($this);
+ }
+
+ return $this->_schemaDialect;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \Cake\Database\SqlserverCompiler
+ */
+ public function newCompiler(): QueryCompiler
+ {
+ return new SqlserverCompiler();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _selectQueryTranslator(Query $query): Query
+ {
+ $limit = $query->clause('limit');
+ $offset = $query->clause('offset');
+
+ if ($limit && $offset === null) {
+ $query->modifier(['_auto_top_' => sprintf('TOP %d', $limit)]);
+ }
+
+ if ($offset !== null && !$query->clause('order')) {
+ $query->order($query->newExpr()->add('(SELECT NULL)'));
+ }
+
+ if ($this->version() < 11 && $offset !== null) {
+ return $this->_pagingSubquery($query, $limit, $offset);
+ }
+
+ return $this->_transformDistinct($query);
+ }
+
+ /**
+ * Generate a paging subquery for older versions of SQLserver.
+ *
+ * Prior to SQLServer 2012 there was no equivalent to LIMIT OFFSET, so a subquery must
+ * be used.
+ *
+ * @param \Cake\Database\Query $original The query to wrap in a subquery.
+ * @param int|null $limit The number of rows to fetch.
+ * @param int|null $offset The number of rows to offset.
+ * @return \Cake\Database\Query Modified query object.
+ */
+ protected function _pagingSubquery(Query $original, ?int $limit, ?int $offset): Query
+ {
+ $field = '_cake_paging_._cake_page_rownum_';
+
+ if ($original->clause('order')) {
+ // SQL server does not support column aliases in OVER clauses. But
+ // the only practical way to specify the use of calculated columns
+ // is with their alias. So substitute the select SQL in place of
+ // any column aliases for those entries in the order clause.
+ $select = $original->clause('select');
+ $order = new OrderByExpression();
+ $original
+ ->clause('order')
+ ->iterateParts(function ($direction, $orderBy) use ($select, $order) {
+ $key = $orderBy;
+ if (
+ isset($select[$orderBy]) &&
+ $select[$orderBy] instanceof ExpressionInterface
+ ) {
+ $order->add(new OrderClauseExpression($select[$orderBy], $direction));
+ } else {
+ $order->add([$key => $direction]);
+ }
+
+ // Leave original order clause unchanged.
+ return $orderBy;
+ });
+ } else {
+ $order = new OrderByExpression('(SELECT NULL)');
+ }
+
+ $query = clone $original;
+ $query->select([
+ '_cake_page_rownum_' => new UnaryExpression('ROW_NUMBER() OVER', $order),
+ ])->limit(null)
+ ->offset(null)
+ ->order([], true);
+
+ $outer = new Query($query->getConnection());
+ $outer->select('*')
+ ->from(['_cake_paging_' => $query]);
+
+ if ($offset) {
+ $outer->where(["$field > " . $offset]);
+ }
+ if ($limit) {
+ $value = (int)$offset + $limit;
+ $outer->where(["$field <= $value"]);
+ }
+
+ // Decorate the original query as that is what the
+ // end developer will be calling execute() on originally.
+ $original->decorateResults(function ($row) {
+ if (isset($row['_cake_page_rownum_'])) {
+ unset($row['_cake_page_rownum_']);
+ }
+
+ return $row;
+ });
+
+ return $outer;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _transformDistinct(Query $query): Query
+ {
+ if (!is_array($query->clause('distinct'))) {
+ return $query;
+ }
+
+ $original = $query;
+ $query = clone $original;
+
+ $distinct = $query->clause('distinct');
+ $query->distinct(false);
+
+ $order = new OrderByExpression($distinct);
+ $query
+ ->select(function ($q) use ($distinct, $order) {
+ $over = $q->newExpr('ROW_NUMBER() OVER')
+ ->add('(PARTITION BY')
+ ->add($q->newExpr()->add($distinct)->setConjunction(','))
+ ->add($order)
+ ->add(')')
+ ->setConjunction(' ');
+
+ return [
+ '_cake_distinct_pivot_' => $over,
+ ];
+ })
+ ->limit(null)
+ ->offset(null)
+ ->order([], true);
+
+ $outer = new Query($query->getConnection());
+ $outer->select('*')
+ ->from(['_cake_distinct_' => $query])
+ ->where(['_cake_distinct_pivot_' => 1]);
+
+ // Decorate the original query as that is what the
+ // end developer will be calling execute() on originally.
+ $original->decorateResults(function ($row) {
+ if (isset($row['_cake_distinct_pivot_'])) {
+ unset($row['_cake_distinct_pivot_']);
+ }
+
+ return $row;
+ });
+
+ return $outer;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function _expressionTranslators(): array
+ {
+ return [
+ FunctionExpression::class => '_transformFunctionExpression',
+ TupleComparison::class => '_transformTupleComparison',
+ ];
+ }
+
+ /**
+ * Receives a FunctionExpression and changes it so that it conforms to this
+ * SQL dialect.
+ *
+ * @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL.
+ * @return void
+ */
+ protected function _transformFunctionExpression(FunctionExpression $expression): void
+ {
+ switch ($expression->getName()) {
+ case 'CONCAT':
+ // CONCAT function is expressed as exp1 + exp2
+ $expression->setName('')->setConjunction(' +');
+ break;
+ case 'DATEDIFF':
+ /** @var bool $hasDay */
+ $hasDay = false;
+ $visitor = function ($value) use (&$hasDay) {
+ if ($value === 'day') {
+ $hasDay = true;
+ }
+
+ return $value;
+ };
+ $expression->iterateParts($visitor);
+
+ if (!$hasDay) {
+ $expression->add(['day' => 'literal'], [], true);
+ }
+ break;
+ case 'CURRENT_DATE':
+ $time = new FunctionExpression('GETUTCDATE');
+ $expression->setName('CONVERT')->add(['date' => 'literal', $time]);
+ break;
+ case 'CURRENT_TIME':
+ $time = new FunctionExpression('GETUTCDATE');
+ $expression->setName('CONVERT')->add(['time' => 'literal', $time]);
+ break;
+ case 'NOW':
+ $expression->setName('GETUTCDATE');
+ break;
+ case 'EXTRACT':
+ $expression->setName('DATEPART')->setConjunction(' ,');
+ break;
+ case 'DATE_ADD':
+ $params = [];
+ $visitor = function ($p, $key) use (&$params) {
+ if ($key === 0) {
+ $params[2] = $p;
+ } else {
+ $valueUnit = explode(' ', $p);
+ $params[0] = rtrim($valueUnit[1], 's');
+ $params[1] = $valueUnit[0];
+ }
+
+ return $p;
+ };
+ $manipulator = function ($p, $key) use (&$params) {
+ return $params[$key];
+ };
+
+ $expression
+ ->setName('DATEADD')
+ ->setConjunction(',')
+ ->iterateParts($visitor)
+ ->iterateParts($manipulator)
+ ->add([$params[2] => 'literal']);
+ break;
+ case 'DAYOFWEEK':
+ $expression
+ ->setName('DATEPART')
+ ->setConjunction(' ')
+ ->add(['weekday, ' => 'literal'], [], true);
+ break;
+ case 'SUBSTR':
+ $expression->setName('SUBSTRING');
+ if (count($expression) < 4) {
+ $params = [];
+ $expression
+ ->iterateParts(function ($p) use (&$params) {
+ return $params[] = $p;
+ })
+ ->add([new FunctionExpression('LEN', [$params[0]]), ['string']]);
+ }
+
+ break;
+ }
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php b/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php
new file mode 100644
index 000000000..e94b268b5
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php
@@ -0,0 +1,114 @@
+getField();
+
+ if (!is_array($fields)) {
+ return;
+ }
+
+ $operator = strtoupper($expression->getOperator());
+ if (!in_array($operator, ['IN', '='])) {
+ throw new RuntimeException(
+ sprintf(
+ 'Tuple comparison transform only supports the `IN` and `=` operators, `%s` given.',
+ $operator
+ )
+ );
+ }
+
+ $value = $expression->getValue();
+ $true = new QueryExpression('1');
+
+ if ($value instanceof Query) {
+ $selected = array_values($value->clause('select'));
+ foreach ($fields as $i => $field) {
+ $value->andWhere([$field => new IdentifierExpression($selected[$i])]);
+ }
+ $value->select($true, true);
+ $expression->setField($true);
+ $expression->setOperator('=');
+
+ return;
+ }
+
+ $type = $expression->getType();
+ if ($type) {
+ /** @var array $typeMap */
+ $typeMap = array_combine($fields, $type) ?: [];
+ } else {
+ $typeMap = [];
+ }
+
+ $surrogate = $query->getConnection()
+ ->newQuery()
+ ->select($true);
+
+ if (!is_array(current($value))) {
+ $value = [$value];
+ }
+
+ $conditions = ['OR' => []];
+ foreach ($value as $tuple) {
+ $item = [];
+ foreach (array_values($tuple) as $i => $value2) {
+ $item[] = [$fields[$i] => $value2];
+ }
+ $conditions['OR'][] = $item;
+ }
+ $surrogate->where($conditions, $typeMap);
+
+ $expression->setField($true);
+ $expression->setValue($surrogate);
+ $expression->setOperator('=');
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/DriverInterface.php b/app/vendor/cakephp/cakephp/src/Database/DriverInterface.php
new file mode 100644
index 000000000..e2ccc4d31
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/DriverInterface.php
@@ -0,0 +1,334 @@
+ $types Associative array of type names used to bind values to query
+ * @return $this
+ * @see \Cake\Database\Query::where()
+ */
+ public function filter($conditions, array $types = [])
+ {
+ if ($this->filter === null) {
+ $this->filter = new QueryExpression();
+ }
+
+ if ($conditions instanceof Closure) {
+ $conditions = $conditions(new QueryExpression());
+ }
+
+ $this->filter->add($conditions, $types);
+
+ return $this;
+ }
+
+ /**
+ * Adds an empty `OVER()` window expression or a named window epression.
+ *
+ * @param string|null $name Window name
+ * @return $this
+ */
+ public function over(?string $name = null)
+ {
+ if ($this->window === null) {
+ $this->window = new WindowExpression();
+ }
+ if ($name) {
+ // Set name manually in case this was chained from FunctionsBuilder wrapper
+ $this->window->name($name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function partition($partitions)
+ {
+ $this->over();
+ $this->window->partition($partitions);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function order($fields)
+ {
+ $this->over();
+ $this->window->order($fields);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function range($start, $end = 0)
+ {
+ $this->over();
+ $this->window->range($start, $end);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function rows(?int $start, ?int $end = 0)
+ {
+ $this->over();
+ $this->window->rows($start, $end);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function groups(?int $start, ?int $end = 0)
+ {
+ $this->over();
+ $this->window->groups($start, $end);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function frame(
+ string $type,
+ $startOffset,
+ string $startDirection,
+ $endOffset,
+ string $endDirection
+ ) {
+ $this->over();
+ $this->window->frame($type, $startOffset, $startDirection, $endOffset, $endDirection);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function excludeCurrent()
+ {
+ $this->over();
+ $this->window->excludeCurrent();
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function excludeGroup()
+ {
+ $this->over();
+ $this->window->excludeGroup();
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function excludeTies()
+ {
+ $this->over();
+ $this->window->excludeTies();
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function sql(ValueBinder $binder): string
+ {
+ $sql = parent::sql($binder);
+ if ($this->filter !== null) {
+ $sql .= ' FILTER (WHERE ' . $this->filter->sql($binder) . ')';
+ }
+ if ($this->window !== null) {
+ if ($this->window->isNamedOnly()) {
+ $sql .= ' OVER ' . $this->window->sql($binder);
+ } else {
+ $sql .= ' OVER (' . $this->window->sql($binder) . ')';
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function traverse(Closure $callback)
+ {
+ parent::traverse($callback);
+ if ($this->filter !== null) {
+ $callback($this->filter);
+ $this->filter->traverse($callback);
+ }
+ if ($this->window !== null) {
+ $callback($this->window);
+ $this->window->traverse($callback);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function count(): int
+ {
+ $count = parent::count();
+ if ($this->window !== null) {
+ $count = $count + 1;
+ }
+
+ return $count;
+ }
+
+ /**
+ * Clone this object and its subtree of expressions.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ parent::__clone();
+ if ($this->filter !== null) {
+ $this->filter = clone $this->filter;
+ }
+ if ($this->window !== null) {
+ $this->window = clone $this->window;
+ }
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php
new file mode 100644
index 000000000..346614789
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php
@@ -0,0 +1,144 @@
+_castToExpression($from, $type);
+ $to = $this->_castToExpression($to, $type);
+ }
+
+ $this->_field = $field;
+ $this->_from = $from;
+ $this->_to = $to;
+ $this->_type = $type;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function sql(ValueBinder $binder): string
+ {
+ $parts = [
+ 'from' => $this->_from,
+ 'to' => $this->_to,
+ ];
+
+ /** @var \Cake\Database\ExpressionInterface|string $field */
+ $field = $this->_field;
+ if ($field instanceof ExpressionInterface) {
+ $field = $field->sql($binder);
+ }
+
+ foreach ($parts as $name => $part) {
+ if ($part instanceof ExpressionInterface) {
+ $parts[$name] = $part->sql($binder);
+ continue;
+ }
+ $parts[$name] = $this->_bindValue($part, $binder, $this->_type);
+ }
+
+ return sprintf('%s BETWEEN %s AND %s', $field, $parts['from'], $parts['to']);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function traverse(Closure $callback)
+ {
+ foreach ([$this->_field, $this->_from, $this->_to] as $part) {
+ if ($part instanceof ExpressionInterface) {
+ $callback($part);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Registers a value in the placeholder generator and returns the generated placeholder
+ *
+ * @param mixed $value The value to bind
+ * @param \Cake\Database\ValueBinder $binder The value binder to use
+ * @param string $type The type of $value
+ * @return string generated placeholder
+ */
+ protected function _bindValue($value, $binder, $type): string
+ {
+ $placeholder = $binder->placeholder('c');
+ $binder->bind($placeholder, $value, $type);
+
+ return $placeholder;
+ }
+
+ /**
+ * Do a deep clone of this expression.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ foreach (['_field', '_from', '_to'] as $part) {
+ if ($this->{$part} instanceof ExpressionInterface) {
+ $this->{$part} = clone $this->{$part};
+ }
+ }
+ }
+}
diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpression.php
new file mode 100644
index 000000000..864a3bcce
--- /dev/null
+++ b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpression.php
@@ -0,0 +1,251 @@
+ :value"
+ *
+ * @var array
+ */
+ protected $_conditions = [];
+
+ /**
+ * Values that are associated with the conditions in the $_conditions array.
+ * Each value represents the 'true' value for the condition with the corresponding key.
+ *
+ * @var array
+ */
+ protected $_values = [];
+
+ /**
+ * The `ELSE` value for the case statement. If null then no `ELSE` will be included.
+ *
+ * @var \Cake\Database\ExpressionInterface|array|string|null
+ */
+ protected $_elseValue;
+
+ /**
+ * Constructs the case expression
+ *
+ * @param \Cake\Database\ExpressionInterface|array $conditions The conditions to test. Must be a ExpressionInterface
+ * instance, or an array of ExpressionInterface instances.
+ * @param \Cake\Database\ExpressionInterface|array $values Associative array of values to be associated with the
+ * conditions passed in $conditions. If there are more $values than $conditions,
+ * the last $value is used as the `ELSE` value.
+ * @param array $types Associative array of types to be associated with the values
+ * passed in $values
+ */
+ public function __construct($conditions = [], $values = [], $types = [])
+ {
+ $conditions = is_array($conditions) ? $conditions : [$conditions];
+ $values = is_array($values) ? $values : [$values];
+ $types = is_array($types) ? $types : [$types];
+
+ if (!empty($conditions)) {
+ $this->add($conditions, $values, $types);
+ }
+
+ if (count($values) > count($conditions)) {
+ end($values);
+ $key = key($values);
+ $this->elseValue($values[$key], $types[$key] ?? null);
+ }
+ }
+
+ /**
+ * Adds one or more conditions and their respective true values to the case object.
+ * Conditions must be a one dimensional array or a QueryExpression.
+ * The trueValues must be a similar structure, but may contain a string value.
+ *
+ * @param \Cake\Database\ExpressionInterface|array $conditions Must be a ExpressionInterface instance,
+ * or an array of ExpressionInterface instances.
+ * @param \Cake\Database\ExpressionInterface|array $values Associative array of values of each condition
+ * @param array $types Associative array of types to be associated with the values
+ * @return $this
+ */
+ public function add($conditions = [], $values = [], $types = [])
+ {
+ $conditions = is_array($conditions) ? $conditions : [$conditions];
+ $values = is_array($values) ? $values : [$values];
+ $types = is_array($types) ? $types : [$types];
+
+ $this->_addExpressions($conditions, $values, $types);
+
+ return $this;
+ }
+
+ /**
+ * Iterates over the passed in conditions and ensures that there is a matching true value for each.
+ * If no matching true value, then it is defaulted to '1'.
+ *
+ * @param array $conditions Array of ExpressionInterface instances.
+ * @param array $values Associative array of values of each condition
+ * @param array