From 9b2cd730db29d14ac6e760fb15c4bcac15184ec4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 14 Apr 2024 04:18:29 +0000 Subject: [PATCH] Release v4.5.1 --- .gitignore | 2 - phpunit.xml.dist | 107 ++++++++++-------- system/Autoloader/FileLocator.php | 15 ++- system/BaseModel.php | 8 +- system/CLI/BaseCommand.php | 10 +- system/CodeIgniter.php | 2 +- .../Commands/Housekeeping/ClearDebugbar.php | 2 +- system/Config/BaseService.php | 7 ++ system/Config/Factories.php | 4 +- system/Database/BaseConnection.php | 22 +++- system/Database/Postgre/Connection.php | 9 +- system/Debug/BaseExceptionHandler.php | 10 +- system/Debug/Toolbar.php | 5 +- system/Debug/Toolbar/Collectors/Routes.php | 19 ++++ system/Debug/Toolbar/Views/toolbar.css | 6 +- system/HTTP/CURLRequest.php | 7 +- system/Helpers/form_helper.php | 2 +- system/I18n/Time.php | 35 +++--- system/Model.php | 5 +- system/Test/Interfaces/FabricatorModel.php | 14 ++- system/View/Plugins.php | 14 ++- tests/.htaccess | 6 + tests/index.html | 11 ++ writable/debugbar/.gitkeep | 0 writable/index.html | 11 ++ 25 files changed, 234 insertions(+), 99 deletions(-) create mode 100755 tests/.htaccess create mode 100755 tests/index.html delete mode 100755 writable/debugbar/.gitkeep create mode 100755 writable/index.html diff --git a/.gitignore b/.gitignore index 696da9cb..8071bd3d 100644 --- a/.gitignore +++ b/.gitignore @@ -124,5 +124,3 @@ nb-configuration.xml /results/ /phpunit*.xml -/.phpunit.*.cache - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0235b8a7..dea94087 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,48 +1,63 @@ - - - - - - - - - - - - ./tests - - - - - - - - - - - - - - - - - - - - - ./app - - - ./app/Views - ./app/Config/Routes.php - - + + + + + + + + + + + + ./tests + + + + + + + + + + ./app + + + ./app/Views + ./app/Config/Routes.php + + + + + + + + + + + + + + diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 97da0fa2..14b9a136 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -28,6 +28,13 @@ class FileLocator implements FileLocatorInterface */ protected $autoloader; + /** + * List of classnames that did not exist. + * + * @var list + */ + private array $invalidClassnames = []; + public function __construct(Autoloader $autoloader) { $this->autoloader = $autoloader; @@ -288,14 +295,20 @@ public function findQualifiedNameFromPath(string $path) ), '\\' ); - // Remove the file extension (.php) $className = mb_substr($className, 0, -4); + if (in_array($className, $this->invalidClassnames, true)) { + continue; + } + // Check if this exists if (class_exists($className)) { return $className; } + + // If the class does not exist, it is an invalid classname. + $this->invalidClassnames[] = $className; } } diff --git a/system/BaseModel.php b/system/BaseModel.php index 5d08ec59..9b8bb70e 100644 --- a/system/BaseModel.php +++ b/system/BaseModel.php @@ -926,6 +926,9 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch $row = (array) $row; } + // Convert any Time instances to appropriate $dateFormat + $row = $this->timeToString($row); + // Validate every row. if (! $this->skipValidation && ! $this->validate($row)) { // Restore $cleanValidationRules @@ -1845,8 +1848,6 @@ protected function transformDataToArray($row, string $type): array $row = $this->converter->toDataSource($row); } elseif ($row instanceof Entity) { $row = $this->converter->extract($row, $onlyChanged); - // Convert any Time instances to appropriate $dateFormat - $row = $this->timeToString($row); } elseif (is_object($row)) { $row = $this->converter->extract($row, $onlyChanged); } @@ -1870,7 +1871,8 @@ protected function transformDataToArray($row, string $type): array throw DataException::forEmptyDataset($type); } - return $row; + // Convert any Time instances to appropriate $dateFormat + return $this->timeToString($row); } /** diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php index 1b273846..86ba504f 100644 --- a/system/CLI/BaseCommand.php +++ b/system/CLI/BaseCommand.php @@ -108,6 +108,8 @@ abstract public function run(array $params); /** * Can be used by a command to run other commands. * + * @param array $params + * * @return int|void * * @throws ReflectionException @@ -140,7 +142,7 @@ public function showHelp() { CLI::write(lang('CLI.helpUsage'), 'yellow'); - if (! empty($this->usage)) { + if (isset($this->usage)) { $usage = $this->usage; } else { $usage = $this->name; @@ -152,7 +154,7 @@ public function showHelp() CLI::write($this->setPad($usage, 0, 0, 2)); - if (! empty($this->description)) { + if (isset($this->description)) { CLI::newLine(); CLI::write(lang('CLI.helpDescription'), 'yellow'); CLI::write($this->setPad($this->description, 0, 0, 2)); @@ -194,6 +196,8 @@ public function setPad(string $item, int $max, int $extra = 2, int $indent = 0): /** * Get pad for $key => $value array output * + * @param array $array + * * @deprecated Use setPad() instead. * * @codeCoverageIgnore @@ -212,7 +216,7 @@ public function getPad(array $array, int $pad): int /** * Makes it simple to access our protected properties. * - * @return array|Commands|LoggerInterface|string|null + * @return array|Commands|LoggerInterface|string|null */ public function __get(string $key) { diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 7fdade26..17582280 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.0'; + public const CI_VERSION = '4.5.1'; /** * App startup time. diff --git a/system/Commands/Housekeeping/ClearDebugbar.php b/system/Commands/Housekeeping/ClearDebugbar.php index 2a5c9fa4..dd49b24a 100644 --- a/system/Commands/Housekeeping/ClearDebugbar.php +++ b/system/Commands/Housekeeping/ClearDebugbar.php @@ -57,7 +57,7 @@ public function run(array $params) { helper('filesystem'); - if (! delete_files(WRITEPATH . 'debugbar')) { + if (! delete_files(WRITEPATH . 'debugbar', false, true)) { // @codeCoverageIgnoreStart CLI::error('Error deleting the debugbar JSON files.'); CLI::newLine(); diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index 2f8df2a3..cd770dc6 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -389,8 +389,15 @@ protected static function buildServicesCache(): void $locator = static::locator(); $files = $locator->search('Config/Services'); + $systemPath = static::autoloader()->getNamespace('CodeIgniter')[0]; + // Get instances of all service classes and cache them locally. foreach ($files as $file) { + // Does not search `CodeIgniter` namespace to prevent from loading twice. + if (str_starts_with($file, $systemPath)) { + continue; + } + $classname = $locator->findQualifiedNameFromPath($file); if ($classname === false) { diff --git a/system/Config/Factories.php b/system/Config/Factories.php index f8102118..d98664a2 100644 --- a/system/Config/Factories.php +++ b/system/Config/Factories.php @@ -180,7 +180,9 @@ public static function get(string $component, string $alias): ?object if (isset(self::$aliases[$component][$alias])) { $class = self::$aliases[$component][$alias]; - return self::$instances[$component][$class]; + if (isset(self::$instances[$component][$class])) { + return self::$instances[$component][$class]; + } } return self::__callStatic($component, [$alias]); diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index e0bd7bb5..b597c6d6 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -17,6 +17,7 @@ use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Events\Events; use stdClass; +use Stringable; use Throwable; /** @@ -1309,12 +1310,15 @@ public function escape($str) return array_map($this->escape(...), $str); } - /** @psalm-suppress NoValue I don't know why ERROR. */ - if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) { + if ($str instanceof Stringable) { if ($str instanceof RawSql) { return $str->__toString(); } + $str = (string) $str; + } + + if (is_string($str)) { return "'" . $this->escapeString($str) . "'"; } @@ -1328,8 +1332,8 @@ public function escape($str) /** * Escape String * - * @param list|string $str Input string - * @param bool $like Whether or not the string will be used in a LIKE condition + * @param list|string|Stringable $str Input string + * @param bool $like Whether the string will be used in a LIKE condition * * @return list|string */ @@ -1343,6 +1347,14 @@ public function escapeString($str, bool $like = false) return $str; } + if ($str instanceof Stringable) { + if ($str instanceof RawSql) { + return $str->__toString(); + } + + $str = (string) $str; + } + $str = $this->_escapeString($str); // escape LIKE condition wildcards @@ -1371,7 +1383,7 @@ public function escapeString($str, bool $like = false) * Calls the individual driver for platform * specific escaping for LIKE conditions * - * @param list|string $str + * @param list|string|Stringable $str * * @return list|string */ diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 45e30eb6..bce4209c 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -20,6 +20,7 @@ use PgSql\Connection as PgSqlConnection; use PgSql\Result as PgSqlResult; use stdClass; +use Stringable; /** * Connection for Postgre @@ -233,12 +234,15 @@ public function escape($str) $this->initialize(); } - /** @psalm-suppress NoValue I don't know why ERROR. */ - if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) { + if ($str instanceof Stringable) { if ($str instanceof RawSql) { return $str->__toString(); } + $str = (string) $str; + } + + if (is_string($str)) { return pg_escape_literal($this->connID, $str); } @@ -246,7 +250,6 @@ public function escape($str) return $str ? 'TRUE' : 'FALSE'; } - /** @psalm-suppress NoValue I don't know why ERROR. */ return parent::escape($str); } diff --git a/system/Debug/BaseExceptionHandler.php b/system/Debug/BaseExceptionHandler.php index dbccdcd0..4305265d 100644 --- a/system/Debug/BaseExceptionHandler.php +++ b/system/Debug/BaseExceptionHandler.php @@ -245,8 +245,14 @@ protected static function highlightFile(string $file, int $lineNumber, int $line */ protected function render(Throwable $exception, int $statusCode, $viewFile = null): void { - if (empty($viewFile) || ! is_file($viewFile)) { - echo 'The error view files were not found. Cannot render exception trace.'; + if ($viewFile === null) { + echo 'The error view file was not specified. Cannot display error view.'; + + exit(1); + } + + if (! is_file($viewFile)) { + echo 'The error view file "' . $viewFile . '" was not found. Cannot display error view.'; exit(1); } diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index c631e416..11501622 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -365,7 +365,7 @@ protected function roundTo(float $number, int $increments = 5): float } /** - * Prepare for debugging.. + * Prepare for debugging. * * @return void */ @@ -378,6 +378,7 @@ public function prepare(?RequestInterface $request = null, ?ResponseInterface $r $app = service('codeigniter'); $request ??= service('request'); + /** @var ResponseInterface $response */ $response ??= service('response'); // Disable the toolbar for downloads @@ -433,7 +434,7 @@ public function prepare(?RequestInterface $request = null, ?ResponseInterface $r . $kintScript . PHP_EOL; - if (str_contains($response->getBody(), '')) { + if (str_contains((string) $response->getBody(), '')) { $response->setBody( preg_replace( '//', diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index 737c86bf..b6862dce 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -51,6 +51,25 @@ class Routes extends BaseCollector /** * Returns the data of this collector to be formatted in the toolbar * + * @return array{ + * matchedRoute: array + * }>, + * routes: list + * } + * * @throws ReflectionException */ public function display(): array diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css index 4154f4c5..2e165b82 100644 --- a/system/Debug/Toolbar/Views/toolbar.css +++ b/system/Debug/Toolbar/Views/toolbar.css @@ -13,8 +13,8 @@ z-index: 10000; height: 36px; width: 36px; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; clear: both; text-align: center; cursor: pointer; @@ -52,6 +52,8 @@ display: flex; font-weight: normal; margin: 0 0 0 auto; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; } #debug-bar h1 svg { width: 16px; diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 72519591..4b1c9c62 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -107,6 +107,8 @@ class CURLRequest extends OutgoingRequest * - baseURI * - timeout * - any other request options to use as defaults. + * + * @param array $options */ public function __construct(App $config, URI $uri, ?ResponseInterface $response = null, array $options = []) { @@ -116,7 +118,10 @@ public function __construct(App $config, URI $uri, ?ResponseInterface $response parent::__construct(Method::GET, $uri); - $this->responseOrig = $response ?? new Response(config(App::class)); + $this->responseOrig = $response ?? new Response($config); + // Remove the default Content-Type header. + $this->responseOrig->removeHeader('Content-Type'); + $this->baseURI = $uri->useRawQueryString(); $this->defaultOptions = $options; diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index 1e3ea4a9..2e5b9f7c 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -31,7 +31,7 @@ function form_open(string $action = '', $attributes = [], array $hidden = []): s { // If no action is provided then set to the current url if ($action === '') { - $action = current_url(true); + $action = (string) current_url(true); } // If an action is not a full URL then turn it into one elseif (! str_contains($action, '://')) { // If an action has {locale} diff --git a/system/I18n/Time.php b/system/I18n/Time.php index 2c6f3c4f..90647947 100644 --- a/system/I18n/Time.php +++ b/system/I18n/Time.php @@ -14,6 +14,7 @@ namespace CodeIgniter\I18n; use DateTimeImmutable; +use Stringable; /** * A localized date/time package inspired @@ -21,26 +22,26 @@ * * Requires the intl PHP extension. * - * @property int $age read-only - * @property string $day read-only - * @property string $dayOfWeek read-only - * @property string $dayOfYear read-only - * @property bool $dst read-only - * @property string $hour read-only - * @property bool $local read-only - * @property string $minute read-only - * @property string $month read-only - * @property string $quarter read-only - * @property string $second read-only - * @property int $timestamp read-only - * @property bool $utc read-only - * @property string $weekOfMonth read-only - * @property string $weekOfYear read-only - * @property string $year read-only + * @property-read int $age + * @property-read string $day + * @property-read string $dayOfWeek + * @property-read string $dayOfYear + * @property-read bool $dst + * @property-read string $hour + * @property-read bool $local + * @property-read string $minute + * @property-read string $month + * @property-read string $quarter + * @property-read string $second + * @property-read int $timestamp + * @property-read bool $utc + * @property-read string $weekOfMonth + * @property-read string $weekOfYear + * @property-read string $year * * @see \CodeIgniter\I18n\TimeTest */ -class Time extends DateTimeImmutable +class Time extends DateTimeImmutable implements Stringable { use TimeTrait; } diff --git a/system/Model.php b/system/Model.php index 61f4350f..b3ecfc65 100644 --- a/system/Model.php +++ b/system/Model.php @@ -40,7 +40,7 @@ * - allow intermingling calls to the builder * - removes the need to use Result object directly in most cases * - * @property BaseConnection $db + * @property-read BaseConnection $db * * @method $this groupBy($by, ?bool $escape = null) * @method $this groupEnd() @@ -123,7 +123,8 @@ class Model extends BaseModel * so that we can capture it (not the builder) * and ensure it gets validated first. * - * @var array + * @var array{escape: array, data: array}|array{} + * @phpstan-var array{escape: array, data: row_array}|array{} */ protected $tempData = []; diff --git a/system/Test/Interfaces/FabricatorModel.php b/system/Test/Interfaces/FabricatorModel.php index d4a4c270..a5860e22 100644 --- a/system/Test/Interfaces/FabricatorModel.php +++ b/system/Test/Interfaces/FabricatorModel.php @@ -13,6 +13,7 @@ namespace CodeIgniter\Test\Interfaces; +use CodeIgniter\BaseModel; use Faker\Generator; use ReflectionException; @@ -27,6 +28,8 @@ * @property string $returnType * @property string $primaryKey * @property string $dateFormat + * + * @phpstan-import-type row_array from BaseModel */ interface FabricatorModel { @@ -34,9 +37,9 @@ interface FabricatorModel * Fetches the row of database from $this->table with a primary key * matching $id. * - * @param array|mixed|null $id One primary key or an array of primary keys + * @param int|list|string|null $id One primary key or an array of primary keys * - * @return array|object|null The resulting row of data, or null. + * @phpstan-return ($id is int|string ? row_array|object|null : list) */ public function find($id = null); @@ -44,14 +47,15 @@ public function find($id = null); * Inserts data into the current table. If an object is provided, * it will attempt to convert it to an array. * - * @param array|object $data - * @param bool $returnID Whether insert ID should be returned or not. + * @param array|object|null $row + * @phpstan-param row_array|object|null $row + * @param bool $returnID Whether insert ID should be returned or not. * * @return bool|int|string * * @throws ReflectionException */ - public function insert($data = null, bool $returnID = true); + public function insert($row = null, bool $returnID = true); /** * The following properties and methods are optional, but if present should diff --git a/system/View/Plugins.php b/system/View/Plugins.php index da147d09..7142e44c 100644 --- a/system/View/Plugins.php +++ b/system/View/Plugins.php @@ -42,6 +42,8 @@ public static function previousURL() /** * Wrap helper function to use as view plugin. + * + * @param array{email?: string, title?: string, attributes?: array|object|string} $params */ public static function mailto(array $params = []): string { @@ -54,6 +56,8 @@ public static function mailto(array $params = []): string /** * Wrap helper function to use as view plugin. + * + * @param array{email?: string, title?: string, attributes?: array|object|string} $params */ public static function safeMailto(array $params = []): string { @@ -66,6 +70,8 @@ public static function safeMailto(array $params = []): string /** * Wrap helper function to use as view plugin. + * + * @param array|list $params */ public static function lang(array $params = []): string { @@ -76,8 +82,10 @@ public static function lang(array $params = []): string /** * Wrap helper function to use as view plugin. + * + * @param array{field?: string} $params */ - public static function ValidationErrors(array $params = []): string + public static function validationErrors(array $params = []): string { $validator = service('validation'); if ($params === []) { @@ -90,6 +98,8 @@ public static function ValidationErrors(array $params = []): string /** * Wrap helper function to use as view plugin. * + * @param list $params + * * @return false|string */ public static function route(array $params = []) @@ -99,6 +109,8 @@ public static function route(array $params = []) /** * Wrap helper function to use as view plugin. + * + * @param list $params */ public static function siteURL(array $params = []): string { diff --git a/tests/.htaccess b/tests/.htaccess new file mode 100755 index 00000000..3462048a --- /dev/null +++ b/tests/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/tests/index.html b/tests/index.html new file mode 100755 index 00000000..b702fbc3 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/writable/debugbar/.gitkeep b/writable/debugbar/.gitkeep deleted file mode 100755 index e69de29b..00000000 diff --git a/writable/index.html b/writable/index.html new file mode 100755 index 00000000..b702fbc3 --- /dev/null +++ b/writable/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + +