From 163f11101a49d7ab70287f6163e5b647a0e26fa4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 10 Jun 2024 08:02:36 +0000 Subject: [PATCH] Release v4.5.2 --- README.md | 8 ++-- app/Config/DocTypes.php | 0 app/Config/Exceptions.php | 8 ++-- preload.php | 22 +++------ spark | 2 +- system/Boot.php | 13 ++++++ system/CLI/CLI.php | 17 ++++--- system/CLI/GeneratorTrait.php | 16 +++++-- system/CodeIgniter.php | 2 +- system/Commands/Encryption/GenerateKey.php | 4 ++ system/Config/Filters.php | 2 + system/DataCaster/DataCaster.php | 2 +- system/Database/BaseBuilder.php | 27 ++++++++--- system/Database/BaseConnection.php | 16 +++++-- system/Database/MySQLi/Connection.php | 2 + system/Database/Postgre/Connection.php | 15 +++++-- system/Database/SQLSRV/Builder.php | 24 ++++++++++ system/Database/SQLSRV/Connection.php | 2 + system/Database/SQLSRV/Forge.php | 0 system/Database/SQLSRV/PreparedQuery.php | 0 system/Database/SQLSRV/Result.php | 0 system/Database/SQLSRV/Utils.php | 0 system/Database/SQLite3/Connection.php | 2 + system/Email/Email.php | 6 +-- system/Files/FileCollection.php | 2 +- system/HTTP/IncomingRequest.php | 0 system/HTTP/ResponseTrait.php | 2 +- system/Helpers/cookie_helper.php | 0 system/Helpers/html_helper.php | 0 system/Helpers/inflector_helper.php | 0 system/Helpers/number_helper.php | 3 +- system/Helpers/text_helper.php | 0 system/Language/en/Security.php | 1 + system/Model.php | 43 +++++++++++------- system/Security/CheckPhpIni.php | 5 ++- .../Security/Exceptions/SecurityException.php | 10 +++++ system/Test/Mock/MockCache.php | 3 +- system/Test/Mock/MockConnection.php | 2 + system/Test/Mock/MockResponse.php | 0 system/Test/PhpStreamWrapper.php | 2 +- .../ThirdParty/Kint/Parser/FsPathPlugin.php | 10 ++++- .../ThirdParty/Kint/Parser/MysqliPlugin.php | 3 +- system/ThirdParty/Kint/Zval/BlobValue.php | 4 +- .../SplFileInfoRepresentation.php | 2 +- system/ThirdParty/Kint/init.php | 2 +- system/View/Cell.php | 13 +++--- system/View/Filters.php | 2 +- system/View/Parser.php | 30 ++++++++++--- system/View/RendererInterface.php | 19 ++++---- system/View/Table.php | 29 ++++++------ system/View/View.php | 45 ++++++++++--------- tests/.htaccess | 0 tests/index.html | 0 writable/.htaccess | 0 writable/cache/index.html | 0 writable/index.html | 0 writable/logs/index.html | 0 writable/session/index.html | 0 writable/uploads/index.html | 0 59 files changed, 285 insertions(+), 137 deletions(-) mode change 100755 => 100644 app/Config/DocTypes.php mode change 100755 => 100644 system/Database/SQLSRV/Builder.php mode change 100755 => 100644 system/Database/SQLSRV/Connection.php mode change 100755 => 100644 system/Database/SQLSRV/Forge.php mode change 100755 => 100644 system/Database/SQLSRV/PreparedQuery.php mode change 100755 => 100644 system/Database/SQLSRV/Result.php mode change 100755 => 100644 system/Database/SQLSRV/Utils.php mode change 100755 => 100644 system/HTTP/IncomingRequest.php mode change 100755 => 100644 system/Helpers/cookie_helper.php mode change 100755 => 100644 system/Helpers/html_helper.php mode change 100755 => 100644 system/Helpers/inflector_helper.php mode change 100755 => 100644 system/Helpers/text_helper.php mode change 100755 => 100644 system/Test/Mock/MockResponse.php mode change 100755 => 100644 tests/.htaccess mode change 100755 => 100644 tests/index.html mode change 100755 => 100644 writable/.htaccess mode change 100755 => 100644 writable/cache/index.html mode change 100755 => 100644 writable/index.html mode change 100755 => 100644 writable/logs/index.html mode change 100755 => 100644 writable/session/index.html mode change 100755 => 100644 writable/uploads/index.html diff --git a/README.md b/README.md index 32668f5a..a23783ac 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ PHP version 8.1 or higher is required, with the following extensions installed: - [mbstring](http://php.net/manual/en/mbstring.installation.php) > [!WARNING] -> The end of life date for PHP 7.4 was November 28, 2022. -> The end of life date for PHP 8.0 was November 26, 2023. -> If you are still using PHP 7.4 or 8.0, you should upgrade immediately. -> The end of life date for PHP 8.1 will be November 25, 2024. +> - The end of life date for PHP 7.4 was November 28, 2022. +> - The end of life date for PHP 8.0 was November 26, 2023. +> - If you are still using PHP 7.4 or 8.0, you should upgrade immediately. +> - The end of life date for PHP 8.1 will be December 31, 2025. Additionally, make sure that the following extensions are enabled in your PHP: diff --git a/app/Config/DocTypes.php b/app/Config/DocTypes.php old mode 100755 new mode 100644 diff --git a/app/Config/Exceptions.php b/app/Config/Exceptions.php index c240675e..4e339634 100644 --- a/app/Config/Exceptions.php +++ b/app/Config/Exceptions.php @@ -60,12 +60,10 @@ class Exceptions extends BaseConfig /** * -------------------------------------------------------------------------- - * LOG DEPRECATIONS INSTEAD OF THROWING? + * WHETHER TO THROW AN EXCEPTION ON DEPRECATED ERRORS * -------------------------------------------------------------------------- - * By default, CodeIgniter converts deprecations into exceptions. Also, - * starting in PHP 8.1 will cause a lot of deprecated usage warnings. - * Use this option to temporarily cease the warnings and instead log those. - * This option also works for user deprecations. + * If set to `true`, DEPRECATED errors are only logged and no exceptions are + * thrown. This option also works for user deprecations. */ public bool $logDeprecations = true; diff --git a/preload.php b/preload.php index 2fa69938..75d86f5c 100644 --- a/preload.php +++ b/preload.php @@ -29,19 +29,6 @@ // Path to the front controller define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); -/** - * See https://www.php.net/manual/en/function.str-contains.php#126277 - */ -if (! function_exists('str_contains')) { - /** - * Polyfill of str_contains() - */ - function str_contains(string $haystack, string $needle): bool - { - return empty($needle) || strpos($haystack, $needle) !== false; - } -} - class preload { /** @@ -51,6 +38,7 @@ class preload [ 'include' => __DIR__ . '/vendor/codeigniter4/framework/system', // Change this path if using manual installation 'exclude' => [ + '/system/bootstrap.php', // Not needed if you don't use them. '/system/Database/OCI8/', '/system/Database/Postgre/', @@ -77,16 +65,18 @@ public function __construct() $this->loadAutoloader(); } - private function loadAutoloader() + private function loadAutoloader(): void { $paths = new Config\Paths(); - require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; + require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'Boot.php'; + + CodeIgniter\Boot::preload($paths); } /** * Load PHP files. */ - public function load() + public function load(): void { foreach ($this->paths as $path) { $directory = new RecursiveDirectoryIterator($path['include']); diff --git a/spark b/spark index a56fbc1b..992d044c 100755 --- a/spark +++ b/spark @@ -25,7 +25,7 @@ */ // Refuse to run when called from php-cgi -if (strpos(PHP_SAPI, 'cgi') === 0) { +if (str_starts_with(PHP_SAPI, 'cgi')) { exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n"); } diff --git a/system/Boot.php b/system/Boot.php index 8a769bbb..54468a5c 100644 --- a/system/Boot.php +++ b/system/Boot.php @@ -122,6 +122,19 @@ public static function bootTest(Paths $paths): void static::autoloadHelpers(); } + /** + * Used by `preload.php` + */ + public static function preload(Paths $paths): void + { + static::definePathConstants($paths); + static::loadConstants(); + static::defineEnvironment(); + static::loadEnvironmentBootstrap($paths, false); + + static::loadAutoloader(); + } + /** * Load environment settings from .env files into $_SERVER and $_ENV */ diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 865241b7..7badf180 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -258,7 +258,8 @@ public static function prompt(string $field, $options = null, $validation = null static::fwrite(STDOUT, $field . (trim($field) !== '' ? ' ' : '') . $extraOutput . ': '); // Read the input from keyboard. - $input = trim(static::$io->input()) ?: (string) $default; + $input = trim(static::$io->input()); + $input = ($input === '') ? (string) $default : $input; if ($validation !== []) { while (! static::validate('"' . trim($field) . '"', $input, $validation)) { @@ -330,7 +331,9 @@ public static function promptByMultipleKeys(string $text, array $options): array CLI::write($text); CLI::printKeysAndValues($options); CLI::newLine(); - $input = static::prompt($extraOutput) ?: 0; // 0 is default + + $input = static::prompt($extraOutput); + $input = ($input === '') ? '0' : $input; // 0 is default // validation while (true) { @@ -343,13 +346,15 @@ public static function promptByMultipleKeys(string $text, array $options): array // find max from input $maxInput = max($inputToArray); - // return the prompt again if $input contain(s) non-numeric charachter, except a comma. - // And if max from $options less than max from input - // it is mean user tried to access null value in $options + // return the prompt again if $input contain(s) non-numeric character, except a comma. + // And if max from $options less than max from input, + // it means user tried to access null value in $options if (! $pattern || $maxOptions < $maxInput) { static::error('Please select correctly.'); CLI::newLine(); - $input = static::prompt($extraOutput) ?: 0; + + $input = static::prompt($extraOutput); + $input = ($input === '') ? '0' : $input; } else { break; } diff --git a/system/CLI/GeneratorTrait.php b/system/CLI/GeneratorTrait.php index 5640a7ae..258f1237 100644 --- a/system/CLI/GeneratorTrait.php +++ b/system/CLI/GeneratorTrait.php @@ -96,13 +96,15 @@ trait GeneratorTrait * * @internal * - * @var array + * @var array */ private $params = []; /** * Execute the command. * + * @param array $params + * * @deprecated use generateClass() instead */ protected function execute(array $params): void @@ -112,6 +114,8 @@ protected function execute(array $params): void /** * Generates a class file from an existing template. + * + * @param array $params */ protected function generateClass(array $params): void { @@ -134,7 +138,8 @@ protected function generateClass(array $params): void /** * Generate a view file from an existing template. * - * @param string $view namespaced view name that is generated + * @param string $view namespaced view name that is generated + * @param array $params */ protected function generateView(string $view, array $params): void { @@ -331,6 +336,8 @@ private function normalizeInputClassName(): string /** * Gets the generator view as defined in the `Config\Generators::$views`, * with fallback to `$template` when the defined view does not exist. + * + * @param array $data */ protected function renderTemplate(array $data = []): string { @@ -352,7 +359,10 @@ protected function renderTemplate(array $data = []): string /** * Performs pseudo-variables contained within view file. * - * @param string $class namespaced classname or namespaced view. + * @param string $class namespaced classname or namespaced view. + * @param list $search + * @param list $replace + * @param array $data * * @return string generated file content */ diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 17582280..65f5dd22 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -56,7 +56,7 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - public const CI_VERSION = '4.5.1'; + public const CI_VERSION = '4.5.2'; /** * App startup time. diff --git a/system/Commands/Encryption/GenerateKey.php b/system/Commands/Encryption/GenerateKey.php index 820ec481..21a582a0 100644 --- a/system/Commands/Encryption/GenerateKey.php +++ b/system/Commands/Encryption/GenerateKey.php @@ -124,6 +124,8 @@ protected function generateRandomKey(string $prefix, int $length): string /** * Sets the new encryption key in your .env file. + * + * @param array $params */ protected function setNewEncryptionKey(string $key, array $params): bool { @@ -139,6 +141,8 @@ protected function setNewEncryptionKey(string $key, array $params): bool /** * Checks whether to overwrite existing encryption key. + * + * @param array $params */ protected function confirmOverwrite(array $params): bool { diff --git a/system/Config/Filters.php b/system/Config/Filters.php index 9096dc7b..562eae8a 100644 --- a/system/Config/Filters.php +++ b/system/Config/Filters.php @@ -102,6 +102,8 @@ class Filters extends BaseConfig * If you use this, you should disable auto-routing because auto-routing * permits any HTTP method to access a controller. Accessing the controller * with a method you don't expect could bypass the filter. + * + * @var array> */ public array $methods = []; diff --git a/system/DataCaster/DataCaster.php b/system/DataCaster/DataCaster.php index 854a0b50..29de3987 100644 --- a/system/DataCaster/DataCaster.php +++ b/system/DataCaster/DataCaster.php @@ -159,7 +159,7 @@ public function castAs(mixed $value, string $field, string $method = 'get'): mix $params = array_map('trim', explode(',', $matches[2])); } - if ($isNullable) { + if ($isNullable && ! $this->strict) { $params[] = 'nullable'; } diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 1f0807cd..f9ca1efd 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -654,6 +654,7 @@ public function join(string $table, $cond, string $type = '', ?bool $escape = nu $cond = ' ON ' . $cond; } else { // Split multiple conditions + // @TODO This does not parse `BETWEEN a AND b` correctly. if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE)) { $conditions = []; $joints = $joints[0]; @@ -676,6 +677,13 @@ public function join(string $table, $cond, string $type = '', ?bool $escape = nu foreach ($conditions as $i => $condition) { $operator = $this->getOperator($condition); + // Workaround for BETWEEN + if ($operator === false) { + $cond .= $joints[$i] . $condition; + + continue; + } + $cond .= $joints[$i]; $cond .= preg_match('/(\(*)?([\[\]\w\.\'-]+)' . preg_quote($operator, '/') . '(.*)/i', $condition, $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $condition; } @@ -1090,7 +1098,7 @@ public function orNotHavingLike($field, string $match = '', string $side = 'both * @used-by notHavingLike() * @used-by orNotHavingLike() * - * @param array|RawSql|string $field + * @param array|RawSql|string $field * * @return $this */ @@ -2376,7 +2384,9 @@ protected function validateInsert(): bool /** * Generates a platform-specific insert string from the supplied data * - * @param string $table Protected table name + * @param string $table Protected table name + * @param list $keys Keys of QBSet + * @param list $unescapedKeys Values of QBSet */ protected function _insert(string $table, array $keys, array $unescapedKeys): string { @@ -2416,7 +2426,9 @@ public function replace(?array $set = null) /** * Generates a platform-specific replace string from the supplied data * - * @param string $table Protected table name + * @param string $table Protected table name + * @param list $keys Keys of QBSet + * @param list $values Values of QBSet */ protected function _replace(string $table, array $keys, array $values): string { @@ -2512,7 +2524,8 @@ public function update($set = null, $where = null, ?int $limit = null): bool /** * Generates a platform-specific update string from the supplied data * - * @param string $table Protected table name + * @param string $table Protected table name + * @param array $values QBSet */ protected function _update(string $table, array $values): string { @@ -2863,9 +2876,9 @@ public function deleteBatch($set = null, $constraints = null, int $batchSize = 1 * * @used-by batchExecute() * - * @param string $table Protected table name - * @param list $keys QBKeys - * @paramst> $values QBSet + * @param string $table Protected table name + * @param list $keys QBKeys + * @param list $values QBSet */ protected function _deleteBatch(string $table, array $keys, array $values): string { diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index b597c6d6..57c37d8a 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -420,7 +420,12 @@ public function initialize() // Connect to the database and set the connection ID $this->connID = $this->connect($this->pConnect); } catch (Throwable $e) { - $connectionErrors[] = sprintf('Main connection [%s]: %s', $this->DBDriver, $e->getMessage()); + $this->connID = false; + $connectionErrors[] = sprintf( + 'Main connection [%s]: %s', + $this->DBDriver, + $e->getMessage() + ); log_message('error', 'Error connecting to the database: ' . $e); } @@ -441,7 +446,12 @@ public function initialize() // Try to connect $this->connID = $this->connect($this->pConnect); } catch (Throwable $e) { - $connectionErrors[] = sprintf('Failover #%d [%s]: %s', ++$index, $this->DBDriver, $e->getMessage()); + $connectionErrors[] = sprintf( + 'Failover #%d [%s]: %s', + ++$index, + $this->DBDriver, + $e->getMessage() + ); log_message('error', 'Error connecting to the database: ' . $e); } @@ -479,7 +489,7 @@ public function close() /** * Platform dependent way method for closing the connection. * - * @return mixed + * @return void */ abstract protected function _close(); diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index c2ef98ad..b25f2e1a 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -246,6 +246,8 @@ public function reconnect() /** * Close the database connection. + * + * @return void */ protected function _close() { diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index bce4209c..c22dc860 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -78,9 +78,14 @@ public function connect(bool $persistent = false) $this->connID = $persistent === true ? pg_pconnect($this->DSN) : pg_connect($this->DSN); if ($this->connID !== false) { - if ($persistent === true && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD && pg_ping($this->connID) === false + if ( + $persistent === true + && pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD + && pg_ping($this->connID) === false ) { - return false; + $error = pg_last_error($this->connID); + + throw new DatabaseException($error); } if (! empty($this->schema)) { @@ -88,7 +93,9 @@ public function connect(bool $persistent = false) } if ($this->setClientEncoding($this->charset) === false) { - return false; + $error = pg_last_error($this->connID); + + throw new DatabaseException($error); } } @@ -146,6 +153,8 @@ public function reconnect() /** * Close the database connection. + * + * @return void */ protected function _close() { diff --git a/system/Database/SQLSRV/Builder.php b/system/Database/SQLSRV/Builder.php old mode 100755 new mode 100644 index a624dced..e2301f7e --- a/system/Database/SQLSRV/Builder.php +++ b/system/Database/SQLSRV/Builder.php @@ -145,6 +145,13 @@ public function join(string $table, $cond, string $type = '', ?bool $escape = nu foreach ($conditions as $i => $condition) { $operator = $this->getOperator($condition); + // Workaround for BETWEEN + if ($operator === false) { + $cond .= $joints[$i] . $condition; + + continue; + } + $cond .= $joints[$i]; $cond .= preg_match('/(\(*)?([\[\]\w\.\'-]+)' . preg_quote($operator, '/') . '(.*)/i', $condition, $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $condition; } @@ -290,6 +297,23 @@ private function getFullName(string $table): string } if ($this->db->escapeChar === '"') { + if (str_contains($table, '.') && ! str_starts_with($table, '.') && ! str_ends_with($table, '.')) { + $dbInfo = explode('.', $table); + $database = $this->db->getDatabase(); + $table = $dbInfo[0]; + + if (count($dbInfo) === 3) { + $database = str_replace('"', '', $dbInfo[0]); + $schema = str_replace('"', '', $dbInfo[1]); + $tableName = str_replace('"', '', $dbInfo[2]); + } else { + $schema = str_replace('"', '', $dbInfo[0]); + $tableName = str_replace('"', '', $dbInfo[1]); + } + + return '"' . $database . '"."' . $schema . '"."' . str_replace('"', '', $tableName) . '"' . $alias; + } + return '"' . $this->db->getDatabase() . '"."' . $this->db->schema . '"."' . str_replace('"', '', $table) . '"' . $alias; } diff --git a/system/Database/SQLSRV/Connection.php b/system/Database/SQLSRV/Connection.php old mode 100755 new mode 100644 index 411470d8..0626a63c --- a/system/Database/SQLSRV/Connection.php +++ b/system/Database/SQLSRV/Connection.php @@ -173,6 +173,8 @@ public function reconnect() /** * Close the database connection. + * + * @return void */ protected function _close() { diff --git a/system/Database/SQLSRV/Forge.php b/system/Database/SQLSRV/Forge.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/PreparedQuery.php b/system/Database/SQLSRV/PreparedQuery.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/Result.php b/system/Database/SQLSRV/Result.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLSRV/Utils.php b/system/Database/SQLSRV/Utils.php old mode 100755 new mode 100644 diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index d15c6421..94543418 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -110,6 +110,8 @@ public function reconnect() /** * Close the database connection. + * + * @return void */ protected function _close() { diff --git a/system/Email/Email.php b/system/Email/Email.php index a686ca0c..b82779eb 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -151,7 +151,7 @@ class Email * * @var string */ - public $charset = 'utf-8'; + public $charset = 'UTF-8'; /** * Alternative message (for HTML messages only) @@ -182,7 +182,7 @@ class Email * * @var string "\r\n" or "\n" */ - public $newline = "\n"; + public $newline = "\r\n"; /** * CRLF character sequence @@ -197,7 +197,7 @@ class Email * * @var string */ - public $CRLF = "\n"; + public $CRLF = "\r\n"; /** * Whether to use Delivery Status Notification. diff --git a/system/Files/FileCollection.php b/system/Files/FileCollection.php index b9456dcc..f1310795 100644 --- a/system/Files/FileCollection.php +++ b/system/Files/FileCollection.php @@ -105,7 +105,7 @@ final protected static function matchFiles(array $files, string $pattern): array ['\#', '\.', '.*', '.'], $pattern ); - $pattern = "#{$pattern}#"; + $pattern = "#\\A{$pattern}\\z#"; } return array_filter($files, static fn ($value) => (bool) preg_match($pattern, basename($value))); diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php old mode 100755 new mode 100644 diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 2d429511..45f07d18 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -670,7 +670,7 @@ private function dispatchCookies(): void foreach ($this->cookieStore->display() as $cookie) { if ($cookie->isSecure() && ! $request->isSecure()) { - throw SecurityException::forDisallowedAction(); + throw SecurityException::forInsecureCookie(); } $name = $cookie->getPrefixedName(); diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php old mode 100755 new mode 100644 diff --git a/system/Helpers/html_helper.php b/system/Helpers/html_helper.php old mode 100755 new mode 100644 diff --git a/system/Helpers/inflector_helper.php b/system/Helpers/inflector_helper.php old mode 100755 new mode 100644 diff --git a/system/Helpers/number_helper.php b/system/Helpers/number_helper.php index 96468ddc..0999d200 100644 --- a/system/Helpers/number_helper.php +++ b/system/Helpers/number_helper.php @@ -82,8 +82,9 @@ function number_to_amount($num, int $precision = 0, ?string $locale = null) // Strip any formatting & ensure numeric input try { // @phpstan-ignore-next-line - $num = 0 + str_replace(',', '', $num); + $num = 0 + str_replace(',', '', (string) $num); } catch (ErrorException) { + // Catch "Warning: A non-numeric value encountered" return false; } diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php old mode 100755 new mode 100644 diff --git a/system/Language/en/Security.php b/system/Language/en/Security.php index 145abaab..fd906e37 100644 --- a/system/Language/en/Security.php +++ b/system/Language/en/Security.php @@ -14,6 +14,7 @@ // Security language settings return [ 'disallowedAction' => 'The action you requested is not allowed.', + 'insecureCookie' => 'Attempted to send a secure cookie over a non-secure connection.', // @deprecated 'invalidSameSite' => 'The SameSite value must be None, Lax, Strict, or a blank string. Given: "{0}"', diff --git a/system/Model.php b/system/Model.php index b3ecfc65..d5df3f86 100644 --- a/system/Model.php +++ b/system/Model.php @@ -174,7 +174,7 @@ public function setTable(string $table) } /** - * Fetches the row of database from $this->table with a primary key + * Fetches the row(s) of database from $this->table with a primary key * matching $id. * This method works only with dbCalls. * @@ -198,8 +198,11 @@ protected function doFind(bool $singleton, $id = null) $builder->where($this->table . '.' . $this->deletedField, null); } + $row = null; + $rows = []; + if (is_array($id)) { - $row = $builder->whereIn($this->table . '.' . $this->primaryKey, $id) + $rows = $builder->whereIn($this->table . '.' . $this->primaryKey, $id) ->get() ->getResult($this->tempReturnType); } elseif ($singleton) { @@ -207,16 +210,32 @@ protected function doFind(bool $singleton, $id = null) ->get() ->getFirstRow($this->tempReturnType); } else { - $row = $builder->get()->getResult($this->tempReturnType); + $rows = $builder->get()->getResult($this->tempReturnType); } if ($useCast) { - $row = $this->convertToReturnType($row, $returnType); - $this->tempReturnType = $returnType; + + if ($singleton) { + if ($row === null) { + return null; + } + + return $this->convertToReturnType($row, $returnType); + } + + foreach ($rows as $i => $row) { + $rows[$i] = $this->convertToReturnType($row, $returnType); + } + + return $rows; } - return $row; + if ($singleton) { + return $row; + } + + return $rows; } /** @@ -230,15 +249,7 @@ protected function doFind(bool $singleton, $id = null) */ protected function doFindColumn(string $columnName) { - $results = $this->select($columnName)->asArray()->find(); - - if ($this->useCasts()) { - foreach ($results as $i => $row) { - $results[$i] = $this->converter->fromDataSource($row); - } - } - - return $results; + return $this->select($columnName)->asArray()->find(); } /** @@ -318,7 +329,7 @@ protected function doFirst() $row = $builder->limit(1, 0)->get()->getFirstRow($this->tempReturnType); - if ($useCast) { + if ($useCast && $row !== null) { $row = $this->convertToReturnType($row, $returnType); $this->tempReturnType = $returnType; diff --git a/system/Security/CheckPhpIni.php b/system/Security/CheckPhpIni.php index 6bbd8619..4cef565a 100644 --- a/system/Security/CheckPhpIni.php +++ b/system/Security/CheckPhpIni.php @@ -141,9 +141,10 @@ public static function checkIni(): array $ini = ini_get_all(); foreach ($items as $key => $values) { + $hasKeyInIni = array_key_exists($key, $ini); $output[$key] = [ - 'global' => $ini[$key]['global_value'], - 'current' => $ini[$key]['local_value'], + 'global' => $hasKeyInIni ? $ini[$key]['global_value'] : 'disabled', + 'current' => $hasKeyInIni ? $ini[$key]['local_value'] : 'disabled', 'recommended' => $values['recommended'] ?? '', 'remark' => $values['remark'] ?? '', ]; diff --git a/system/Security/Exceptions/SecurityException.php b/system/Security/Exceptions/SecurityException.php index 16383fc2..ed1d7682 100644 --- a/system/Security/Exceptions/SecurityException.php +++ b/system/Security/Exceptions/SecurityException.php @@ -20,6 +20,7 @@ class SecurityException extends FrameworkException implements HTTPExceptionInter { /** * Throws when some specific action is not allowed. + * This is used for CSRF protection. * * @return static */ @@ -28,6 +29,15 @@ public static function forDisallowedAction() return new static(lang('Security.disallowedAction'), 403); } + /** + * Throws if a secure cookie is dispatched when the current connection is not + * secure. + */ + public static function forInsecureCookie(): static + { + return new static(lang('Security.insecureCookie')); + } + /** * Throws when the source string contains invalid UTF-8 characters. * diff --git a/system/Test/Mock/MockCache.php b/system/Test/Mock/MockCache.php index df217967..85b5fd9e 100644 --- a/system/Test/Mock/MockCache.php +++ b/system/Test/Mock/MockCache.php @@ -92,11 +92,10 @@ public function remember(string $key, int $ttl, Closure $callback) * @param string $key Cache item name * @param mixed $value the data to save * @param int $ttl Time To Live, in seconds (default 60) - * @param bool $raw Whether to store the raw value. * * @return bool */ - public function save(string $key, $value, int $ttl = 60, bool $raw = false) + public function save(string $key, $value, int $ttl = 60) { if ($this->bypass) { return false; diff --git a/system/Test/Mock/MockConnection.php b/system/Test/Mock/MockConnection.php index 83826c34..020d6e3d 100644 --- a/system/Test/Mock/MockConnection.php +++ b/system/Test/Mock/MockConnection.php @@ -220,6 +220,8 @@ protected function _foreignKeyData(string $table): array /** * Close the connection. + * + * @return void */ protected function _close() { diff --git a/system/Test/Mock/MockResponse.php b/system/Test/Mock/MockResponse.php old mode 100755 new mode 100644 diff --git a/system/Test/PhpStreamWrapper.php b/system/Test/PhpStreamWrapper.php index 980d5424..a6be8dd1 100644 --- a/system/Test/PhpStreamWrapper.php +++ b/system/Test/PhpStreamWrapper.php @@ -46,7 +46,7 @@ public static function restore() stream_wrapper_restore('php'); } - public function stream_open(string $path): bool + public function stream_open(): bool { return true; } diff --git a/system/ThirdParty/Kint/Parser/FsPathPlugin.php b/system/ThirdParty/Kint/Parser/FsPathPlugin.php index 7ec49de1..1a98c6dc 100644 --- a/system/ThirdParty/Kint/Parser/FsPathPlugin.php +++ b/system/ThirdParty/Kint/Parser/FsPathPlugin.php @@ -30,6 +30,7 @@ use Kint\Zval\Representation\SplFileInfoRepresentation; use Kint\Zval\Value; use SplFileInfo; +use TypeError; class FsPathPlugin extends AbstractPlugin { @@ -59,8 +60,13 @@ public function parse(&$var, Value &$o, int $trigger): void return; } - if (!@\file_exists($var)) { - return; + try { + if (!@\file_exists($var)) { + return; + } + } catch (TypeError $e) {// @codeCoverageIgnore + // Only possible in PHP 7 + return; // @codeCoverageIgnore } if (\in_array($var, self::$blacklist, true)) { diff --git a/system/ThirdParty/Kint/Parser/MysqliPlugin.php b/system/ThirdParty/Kint/Parser/MysqliPlugin.php index 90a4abd6..22a23a90 100644 --- a/system/ThirdParty/Kint/Parser/MysqliPlugin.php +++ b/system/ThirdParty/Kint/Parser/MysqliPlugin.php @@ -105,7 +105,8 @@ public function parse(&$var, Value &$o, int $trigger): void foreach ($o->value->contents as $key => $obj) { if (isset($this->connected_readable[$obj->name])) { if (!$connected) { - continue; + // No failed connections after PHP 8.1 + continue; // @codeCoverageIgnore } } elseif (isset($this->empty_readable[$obj->name])) { // No failed connections after PHP 8.1 diff --git a/system/ThirdParty/Kint/Zval/BlobValue.php b/system/ThirdParty/Kint/Zval/BlobValue.php index 5e9f129d..66f60fdb 100644 --- a/system/ThirdParty/Kint/Zval/BlobValue.php +++ b/system/ThirdParty/Kint/Zval/BlobValue.php @@ -182,7 +182,9 @@ public static function detectEncoding(string $string) if (\function_exists('iconv')) { foreach (self::$legacy_encodings as $encoding) { // Iconv detection works by triggering - // "Detected an illegal character in input string" warnings + // "Detected an illegal character in input string" notices + // This notice does not become a TypeError with strict_types + // so we don't have to wrap this in a try catch if (@\iconv($encoding, $encoding, $string) === $string) { return $encoding; } diff --git a/system/ThirdParty/Kint/Zval/Representation/SplFileInfoRepresentation.php b/system/ThirdParty/Kint/Zval/Representation/SplFileInfoRepresentation.php index 6424ee6a..dad9d04c 100644 --- a/system/ThirdParty/Kint/Zval/Representation/SplFileInfoRepresentation.php +++ b/system/ThirdParty/Kint/Zval/Representation/SplFileInfoRepresentation.php @@ -76,7 +76,7 @@ public function __construct(SplFileInfo $fileInfo) } } catch (RuntimeException $e) { if (false === \strpos($e->getMessage(), ' open_basedir ')) { - throw $e; + throw $e; // @codeCoverageIgnore } } diff --git a/system/ThirdParty/Kint/init.php b/system/ThirdParty/Kint/init.php index 039489ab..d682d9a9 100644 --- a/system/ThirdParty/Kint/init.php +++ b/system/ThirdParty/Kint/init.php @@ -51,7 +51,7 @@ if (false !== \ini_get('xdebug.file_link_format')) { Kint::$file_link_format = \ini_get('xdebug.file_link_format'); } -if (isset($_SERVER['DOCUMENT_ROOT'])) { +if (isset($_SERVER['DOCUMENT_ROOT']) && false === \strpos($_SERVER['DOCUMENT_ROOT'], "\0")) { Kint::$app_root_dirs = [ $_SERVER['DOCUMENT_ROOT'] => '', ]; diff --git a/system/View/Cell.php b/system/View/Cell.php index cafe13d1..28b249a6 100644 --- a/system/View/Cell.php +++ b/system/View/Cell.php @@ -68,10 +68,10 @@ public function __construct(CacheInterface $cache) /** * Render a cell, returning its body as a string. * - * @param string $library Cell class and method name. - * @param array|string|null $params Parameters to pass to the method. - * @param int $ttl Number of seconds to cache the cell. - * @param string|null $cacheName Cache item name. + * @param string $library Cell class and method name. + * @param array|string|null $params Parameters to pass to the method. + * @param int $ttl Number of seconds to cache the cell. + * @param string|null $cacheName Cache item name. * * @throws ReflectionException */ @@ -117,9 +117,10 @@ public function render(string $library, $params = null, int $ttl = 0, ?string $c * If a string, it should be in the format "key1=value key2=value". * It will be split and returned as an array. * - * @param array|string|null $params + * @param array|string|null $params + * @phpstan-param array|string|float|null $params * - * @return array + * @return array */ public function prepareParams($params) { diff --git a/system/View/Filters.php b/system/View/Filters.php index 56204c86..dde9ec6d 100644 --- a/system/View/Filters.php +++ b/system/View/Filters.php @@ -67,7 +67,7 @@ public static function date_modify($value, string $adjustment) /** * Returns the given default value if $value is empty or undefined. * - * @param array|bool|float|int|object|resource|string|null $value + * @param bool|float|int|list|object|resource|string|null $value */ public static function default($value, string $default): string { diff --git a/system/View/Parser.php b/system/View/Parser.php index 08525210..bbeb00f7 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -56,7 +56,7 @@ class Parser extends View /** * Stores extracted noparse blocks. * - * @var array + * @var list */ protected $noparseBlocks = []; @@ -72,7 +72,7 @@ class Parser extends View * Stores the context for each data element * when set by `setData` so the context is respected. * - * @var array + * @var array */ protected $dataContexts = []; @@ -99,6 +99,10 @@ public function __construct( * * Parses pseudo-variables contained in the specified template view, * replacing them with any data that has already been set. + * + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. */ public function render(string $view, ?array $options = null, ?bool $saveData = null): string { @@ -159,6 +163,10 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n * * Parses pseudo-variables contained in the specified string, * replacing them with any data that has already been set. + * + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. */ public function renderString(string $template, ?array $options = null, ?bool $saveData = null): string { @@ -190,6 +198,7 @@ public function renderString(string $template, ?array $options = null, ?bool $sa * so that the variable is correctly handled within the * parsing itself, and contexts (including raw) are respected. * + * @param array $data * @param non-empty-string|null $context The context to escape it for. * If 'raw', no escaping will happen. * @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context @@ -222,7 +231,8 @@ public function setData(array $data = [], ?string $context = null): RendererInte * Parses pseudo-variables contained in the specified template, * replacing them with the data in the second param * - * @param array $options Future options + * @param array $data + * @param array $options Future options */ protected function parse(string $template, array $data = [], ?array $options = null): string { @@ -266,6 +276,8 @@ protected function parse(string $template, array $data = [], ?array $options = n /** * Parse a single key/value, extracting it + * + * @return array */ protected function parseSingle(string $key, string $val): array { @@ -280,6 +292,10 @@ protected function parseSingle(string $key, string $val): array * Parse a tag pair * * Parses tag pairs: {some_tag} string... {/some_tag} + * + * @param array $data + * + * @return array */ protected function parsePair(string $variable, array $data, string $template): array { @@ -533,6 +549,8 @@ protected function replaceSingle($pattern, $content, $template, bool $escape = f /** * Callback used during parse() to apply any filters to the value. + * + * @param list $matches */ protected function prepareReplacement(array $matches, string $replace, bool $escape = true): string { @@ -586,6 +604,8 @@ public function shouldAddEscaping(string $key) /** * Given a set of filters, will apply each of the filters in turn * to $replace, and return the modified string. + * + * @param list $filters */ protected function applyFilters(string $replace, array $filters): string { @@ -708,9 +728,9 @@ public function removePlugin(string $alias) * Converts an object to an array, respecting any * toArray() methods on an object. * - * @param array|bool|float|int|object|string|null $value + * @param array|bool|float|int|object|string|null $value * - * @return array|bool|float|int|string|null + * @return array|bool|float|int|string|null */ protected function objectToArray($value) { diff --git a/system/View/RendererInterface.php b/system/View/RendererInterface.php index 62d3d782..5fdf8794 100644 --- a/system/View/RendererInterface.php +++ b/system/View/RendererInterface.php @@ -24,10 +24,10 @@ interface RendererInterface * Builds the output based upon a file name and any * data that has already been set. * - * @param array|null $options Reserved for 3rd-party uses since - * it might be needed to pass additional info - * to other template engines. - * @param bool $saveData Whether to save data for subsequent calls + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param bool $saveData Whether to save data for subsequent calls */ public function render(string $view, ?array $options = null, bool $saveData = false): string; @@ -35,17 +35,18 @@ public function render(string $view, ?array $options = null, bool $saveData = fa * Builds the output based upon a string and any * data that has already been set. * - * @param string $view The view contents - * @param array|null $options Reserved for 3rd-party uses since - * it might be needed to pass additional info - * to other template engines. - * @param bool $saveData Whether to save data for subsequent calls + * @param string $view The view contents + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param bool $saveData Whether to save data for subsequent calls */ public function renderString(string $view, ?array $options = null, bool $saveData = false): string; /** * Sets several pieces of view data at once. * + * @param array $data * @param non-empty-string|null $context The context to escape it for. * If 'raw', no escaping will happen. * @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context diff --git a/system/View/Table.php b/system/View/Table.php index 85770e31..60ccc509 100644 --- a/system/View/Table.php +++ b/system/View/Table.php @@ -27,21 +27,21 @@ class Table /** * Data for table rows * - * @var list|list> + * @var list>|list>> */ public $rows = []; /** * Data for table heading * - * @var array + * @var array */ public $heading = []; /** * Data for table footing * - * @var array + * @var array */ public $footing = []; @@ -62,7 +62,7 @@ class Table /** * Table layout template * - * @var array + * @var array */ public $template; @@ -95,7 +95,7 @@ class Table /** * Set the template from the table config file if it exists * - * @param array $config (default: array()) + * @param array $config (default: array()) */ public function __construct($config = []) { @@ -108,7 +108,8 @@ public function __construct($config = []) /** * Set the template * - * @param array $template + * @param array $template + * @phpstan-param array|string $template * * @return bool */ @@ -157,10 +158,10 @@ public function setFooting() * columns. This allows a single array with many elements to be * displayed in a table that has a fixed column count. * - * @param array $array - * @param int $columnLimit + * @param list $array + * @param int $columnLimit * - * @return array|false + * @return array|false */ public function makeColumns($array = [], $columnLimit = 0) { @@ -260,7 +261,9 @@ public function setSyncRowsWithHeading(bool $orderByKey) * * Ensures a standard associative array format for all cell data * - * @return array|list + * @param array $args + * + * @return array>|list> */ protected function _prepArgs(array $args) { @@ -297,7 +300,7 @@ public function setCaption($caption) /** * Generate the table * - * @param array|BaseResult|null $tableData + * @param array|BaseResult|null $tableData * * @return string */ @@ -472,7 +475,7 @@ protected function _setFromDBResult($object) /** * Set table data from an array * - * @param array $data + * @param array $data * * @return void */ @@ -510,7 +513,7 @@ protected function _compileTemplate() /** * Default Template * - * @return array + * @return array */ protected function _defaultTemplate() { diff --git a/system/View/View.php b/system/View/View.php index cda3d64f..7b6bfea0 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -34,14 +34,14 @@ class View implements RendererInterface /** * Saved Data. * - * @var array + * @var array */ protected $data = []; /** * Data for the variables that are available in the Views. * - * @var array|null + * @var array|null */ protected $tempData; @@ -55,7 +55,7 @@ class View implements RendererInterface /** * Data for rendering including Caching and Debug Toolbar data. * - * @var array + * @var array */ protected $renderVars = []; @@ -86,7 +86,7 @@ class View implements RendererInterface * Cache stats about our performance here, * when CI_DEBUG = true * - * @var array + * @var list */ protected $performanceData = []; @@ -120,7 +120,7 @@ class View implements RendererInterface /** * Holds the sections and their data. * - * @var array + * @var array> */ protected $sections = []; @@ -165,13 +165,13 @@ public function __construct( * - cache Number of seconds to cache for * - cache_name Name to use for cache * - * @param string $view File name of the view source - * @param array|null $options Reserved for 3rd-party uses since - * it might be needed to pass additional info - * to other template engines. - * @param bool|null $saveData If true, saves data for subsequent calls, - * if false, cleans the data after displaying, - * if null, uses the config setting. + * @param string $view File name of the view source + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param bool|null $saveData If true, saves data for subsequent calls, + * if false, cleans the data after displaying, + * if null, uses the config setting. */ public function render(string $view, ?array $options = null, ?bool $saveData = null): string { @@ -306,13 +306,13 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n * data that has already been set. * Cache does not apply, because there is no "key". * - * @param string $view The view contents - * @param array|null $options Reserved for 3rd-party uses since - * it might be needed to pass additional info - * to other template engines. - * @param bool|null $saveData If true, saves data for subsequent calls, - * if false, cleans the data after displaying, - * if null, uses the config setting. + * @param string $view The view contents + * @param array|null $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param bool|null $saveData If true, saves data for subsequent calls, + * if false, cleans the data after displaying, + * if null, uses the config setting. */ public function renderString(string $view, ?array $options = null, ?bool $saveData = null): string { @@ -393,6 +393,8 @@ public function resetData(): RendererInterface /** * Returns the current data that will be displayed in the view. + * + * @return array */ public function getData(): array { @@ -477,7 +479,8 @@ public function renderSection(string $sectionName, bool $saveData = false) /** * Used within layout views to include additional views. * - * @param bool $saveData + * @param array|null $options + * @param bool $saveData */ public function include(string $view, ?array $options = null, $saveData = true): string { @@ -487,6 +490,8 @@ public function include(string $view, ?array $options = null, $saveData = true): /** * Returns the performance data that might have been collected * during the execution. Used primarily in the Debug Toolbar. + * + * @return list */ public function getPerformanceData(): array { diff --git a/tests/.htaccess b/tests/.htaccess old mode 100755 new mode 100644 diff --git a/tests/index.html b/tests/index.html old mode 100755 new mode 100644 diff --git a/writable/.htaccess b/writable/.htaccess old mode 100755 new mode 100644 diff --git a/writable/cache/index.html b/writable/cache/index.html old mode 100755 new mode 100644 diff --git a/writable/index.html b/writable/index.html old mode 100755 new mode 100644 diff --git a/writable/logs/index.html b/writable/logs/index.html old mode 100755 new mode 100644 diff --git a/writable/session/index.html b/writable/session/index.html old mode 100755 new mode 100644 diff --git a/writable/uploads/index.html b/writable/uploads/index.html old mode 100755 new mode 100644