From f65a2cff3a572c58c8ab9c8f61bdba904362b549 Mon Sep 17 00:00:00 2001 From: MGatner Date: Thu, 16 Jun 2022 13:53:25 +0000 Subject: [PATCH] Release v4.2.1 --- app/Config/Mimes.php | 19 +++++------ composer.json | 2 +- system/Cache/Handlers/MemcachedHandler.php | 2 -- system/CodeIgniter.php | 6 ++-- .../AutoRouterImproved/AutoRouteCollector.php | 2 +- system/Cookie/Cookie.php | 4 ++- system/Database/BaseBuilder.php | 2 +- system/Database/BaseConnection.php | 19 ++++++++++- system/Database/MigrationRunner.php | 4 +++ system/Debug/Toolbar/Views/toolbar.css | 4 --- system/Email/Email.php | 6 ++-- system/Files/File.php | 7 +++- system/HTTP/CLIRequest.php | 2 +- system/HTTP/IncomingRequest.php | 2 +- system/HTTP/ResponseTrait.php | 5 ++- system/Helpers/cookie_helper.php | 21 +++++++++--- system/I18n/Time.php | 6 ++-- system/Router/AutoRouterImproved.php | 6 +--- system/Router/RouteCollection.php | 2 +- system/Session/Handlers/BaseHandler.php | 33 ++++++++++++++----- system/Session/Session.php | 4 +-- system/Test/CIUnitTestCase.php | 5 +++ system/Test/DatabaseTestTrait.php | 18 ++++++++++ 23 files changed, 123 insertions(+), 58 deletions(-) diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index a1bb458a..884e76bc 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -102,8 +102,6 @@ class Mimes ], 'pptx' => [ 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/x-zip', - 'application/zip', ], 'wbxml' => 'application/wbxml', 'wmlc' => 'application/wmlc', @@ -512,20 +510,19 @@ public static function guessExtensionFromType(string $type, ?string $proposedExt $proposedExtension = trim(strtolower($proposedExtension ?? '')); - if ($proposedExtension !== '') { - if (array_key_exists($proposedExtension, static::$mimes) && in_array($type, is_string(static::$mimes[$proposedExtension]) ? [static::$mimes[$proposedExtension]] : static::$mimes[$proposedExtension], true)) { - // The detected mime type matches with the proposed extension. - return $proposedExtension; - } - - // An extension was proposed, but the media type does not match the mime type list. - return null; + if ( + $proposedExtension !== '' + && array_key_exists($proposedExtension, static::$mimes) + && in_array($type, (array) static::$mimes[$proposedExtension], true) + ) { + // The detected mime type matches with the proposed extension. + return $proposedExtension; } // Reverse check the mime type list if no extension was proposed. // This search is order sensitive! foreach (static::$mimes as $ext => $types) { - if ((is_string($types) && $types === $type) || (is_array($types) && in_array($type, $types, true))) { + if (in_array($type, (array) $types, true)) { return $ext; } } diff --git a/composer.json b/composer.json index 03d39657..95ea6080 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "mikey179/vfsstream": "^1.6", "nexusphp/cs-config": "^3.3", "phpunit/phpunit": "^9.1", - "predis/predis": "^1.1" + "predis/predis": "^1.1 || ^2.0" }, "suggest": { "ext-fileinfo": "Improves mime type detection for files" diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index f240a5d0..b317652d 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -115,8 +115,6 @@ public function initialize() } else { throw new CriticalError('Cache: Not support Memcache(d) extension.'); } - } catch (CriticalError $e) { - throw $e; } catch (Exception $e) { throw new CriticalError('Cache: Memcache(d) connection refused (' . $e->getMessage() . ').'); } diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 82b08e31..a4a05233 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -47,7 +47,7 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - public const CI_VERSION = '4.2.0'; + public const CI_VERSION = '4.2.1'; private const MIN_PHP_VERSION = '7.4'; @@ -256,9 +256,7 @@ protected function initializeKint() require_once SYSTEMPATH . 'ThirdParty/Kint/init.php'; } - /** - * Config\Kint - */ + /** @var \Config\Kint $config */ $config = config(KintConfig::class); Kint::$depth_limit = $config->maxDepth; diff --git a/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php b/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php index 0df2e6bf..e7597014 100644 --- a/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php +++ b/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php @@ -65,7 +65,7 @@ public function get(): array foreach ($finder->find() as $class) { // Exclude controllers in Defined Routes. - if (in_array($class, $this->protectedControllers, true)) { + if (in_array('\\' . $class, $this->protectedControllers, true)) { continue; } diff --git a/system/Cookie/Cookie.php b/system/Cookie/Cookie.php index e40d0a12..22c01a7d 100644 --- a/system/Cookie/Cookie.php +++ b/system/Cookie/Cookie.php @@ -119,6 +119,8 @@ class Cookie implements ArrayAccess, CloneableCookieInterface * Set the default attributes to a Cookie instance by injecting * the values from the `CookieConfig` config or an array. * + * This method is called from Response::__construct(). + * * @param array|CookieConfig $config * * @return array The old defaults array. Useful for resetting. @@ -209,7 +211,7 @@ final public function __construct(string $name, string $value = '', array $optio } // to preserve backward compatibility with array-based cookies in previous CI versions - $prefix = $options['prefix'] ?: self::$defaults['prefix']; + $prefix = ($options['prefix'] === '') ? self::$defaults['prefix'] : $options['prefix']; $path = $options['path'] ?: self::$defaults['path']; $domain = $options['domain'] ?: self::$defaults['domain']; diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index ca983691..2d54b90f 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1923,7 +1923,7 @@ protected function validateInsert(): bool { if (empty($this->QBSet)) { if (CI_DEBUG) { - throw new DatabaseException('You must use the "set" method to update an entry.'); + throw new DatabaseException('You must use the "set" method to insert an entry.'); } return false; // @codeCoverageIgnore diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 9d7e69bf..0e103a51 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -14,6 +14,7 @@ use Closure; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Events\Events; +use Exception; use stdClass; use Throwable; @@ -603,7 +604,14 @@ public function query(string $sql, $binds = null, bool $setEscapeFlags = true, s $this->lastQuery = $query; // Run the query for real - if (! $this->pretend && false === ($this->resultID = $this->simpleQuery($query->getQuery()))) { + try { + $exception = null; + $this->resultID = $this->simpleQuery($query->getQuery()); + } catch (Exception $exception) { + $this->resultID = false; + } + + if (! $this->pretend && $this->resultID === false) { $query->setDuration($startTime, $startTime); // This will trigger a rollback if transactions are being used @@ -626,6 +634,15 @@ public function query(string $sql, $binds = null, bool $setEscapeFlags = true, s } } + if (! $this->pretend) { + // Let others do something with this query. + Events::trigger('DBQuery', $query); + } + + if ($exception !== null) { + throw $exception; + } + return false; } diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index e92d31b0..2c71189d 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -402,6 +402,10 @@ public function findMigrations(): array $migrations = []; foreach ($namespaces as $namespace) { + if (ENVIRONMENT !== 'testing' && $namespace === 'Tests\Support') { + continue; + } + foreach ($this->findNamespaceMigrations($namespace) as $migration) { $migrations[$migration->uid] = $migration; } diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css index 11a3301a..4cb379cb 100644 --- a/system/Debug/Toolbar/Views/toolbar.css +++ b/system/Debug/Toolbar/Views/toolbar.css @@ -303,7 +303,6 @@ #debug-bar .ci-label img { margin: unset; } - .hide-sm { display: none !important; } @@ -432,7 +431,6 @@ #debug-icon a:visited { color: #DD8615; } - #debug-bar { background-color: #252525; color: #DFDFDF; @@ -526,11 +524,9 @@ #debug-bar .timeline .timer { background-color: #DD8615; } - .debug-view.show-view { border-color: #DD8615; } - .debug-view-path { background-color: #FDC894; color: #434343; diff --git a/system/Email/Email.php b/system/Email/Email.php index 966c08e6..753dfe7d 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -1046,10 +1046,8 @@ public function wordWrap($str, $charlim = null) $output .= $line . $this->newline; } - if ($unwrap) { - foreach ($unwrap as $key => $val) { - $output = str_replace('{{unwrapped' . $key . '}}', $val, $output); - } + foreach ($unwrap as $key => $val) { + $output = str_replace('{{unwrapped' . $key . '}}', $val, $output); } return $output; diff --git a/system/Files/File.php b/system/Files/File.php index b3bdb47b..4d4067d4 100644 --- a/system/Files/File.php +++ b/system/Files/File.php @@ -89,7 +89,12 @@ public function getSizeByUnit(string $unit = 'b') */ public function guessExtension(): ?string { - return Mimes::guessExtensionFromType($this->getMimeType()); + // naively get the path extension using pathinfo + $pathinfo = pathinfo($this->getRealPath() ?: $this->__toString()) + ['extension' => '']; + + $proposedExtension = $pathinfo['extension']; + + return Mimes::guessExtensionFromType($this->getMimeType(), $proposedExtension); } /** diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php index 5f2c70b1..0563be28 100644 --- a/system/HTTP/CLIRequest.php +++ b/system/HTTP/CLIRequest.php @@ -212,6 +212,6 @@ protected function parseCommand() */ public function isCLI(): bool { - return true; + return is_cli(); } } diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 8bd5f33e..d37f498e 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -347,7 +347,7 @@ public function negotiate(string $type, array $supported, bool $strictMatch = fa */ public function isCLI(): bool { - return false; + return is_cli(); } /** diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 0e945b62..f8dd174b 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -543,7 +543,7 @@ public function redirect(string $uri, string $method = 'auto', ?int $code = null * @param string $expire Cookie expiration time in seconds * @param string $domain Cookie domain (e.g.: '.yourdomain.com') * @param string $path Cookie path (default: '/') - * @param string $prefix Cookie name prefix + * @param string $prefix Cookie name prefix ('': the default prefix) * @param bool $secure Whether to only transfer cookies via SSL * @param bool $httponly Whether only make the cookie accessible via HTTP (no javascript) * @param string|null $samesite @@ -618,6 +618,9 @@ public function hasCookie(string $name, ?string $value = null, string $prefix = /** * Returns the cookie * + * @param string $prefix Cookie prefix. + * '': the default prefix + * * @return Cookie|Cookie[]|null */ public function getCookie(?string $name = null, string $prefix = '') diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index 51a746c5..5537de9c 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -10,6 +10,7 @@ */ use Config\App; +use Config\Cookie; use Config\Services; //============================================================================= @@ -28,7 +29,7 @@ * @param string $expire The number of seconds until expiration * @param string $domain For site-wide cookie. Usually: .yourdomain.com * @param string $path The cookie path - * @param string $prefix The cookie prefix + * @param string $prefix The cookie prefix ('': the default prefix) * @param bool $secure True makes the cookie secure * @param bool $httpOnly True makes the cookie accessible via http(s) only (no javascript) * @param string|null $sameSite The cookie SameSite value @@ -55,15 +56,25 @@ function set_cookie( /** * Fetch an item from the $_COOKIE array * - * @param string $index + * @param string $index + * @param string|null $prefix Cookie name prefix. + * '': the prefix in Config\Cookie + * null: no prefix * - * @return mixed + * @return array|string|null * * @see \CodeIgniter\HTTP\IncomingRequest::getCookie() */ - function get_cookie($index, bool $xssClean = false) + function get_cookie($index, bool $xssClean = false, ?string $prefix = '') { - $prefix = isset($_COOKIE[$index]) ? '' : config(App::class)->cookiePrefix; + if ($prefix === '') { + /** @var Cookie|null $cookie */ + $cookie = config('Cookie'); + + // @TODO Remove Config\App fallback when deprecated `App` members are removed. + $prefix = $cookie instanceof Cookie ? $cookie->prefix : config(App::class)->cookiePrefix; + } + $request = Services::request(); $filter = $xssClean ? FILTER_SANITIZE_FULL_SPECIAL_CHARS : FILTER_DEFAULT; diff --git a/system/I18n/Time.php b/system/I18n/Time.php index 64b4d350..659a0a25 100644 --- a/system/I18n/Time.php +++ b/system/I18n/Time.php @@ -82,7 +82,7 @@ public function __construct(?string $time = null, $timezone = null, ?string $loc // If a test instance has been provided, use it instead. if ($time === '' && static::$testNow instanceof self) { $timezone = $timezone ?: static::$testNow->getTimezone(); - $time = (string) static::$testNow->toDateTimeString(); + $time = static::$testNow->format('Y-m-d H:i:s'); } $timezone = $timezone ?: date_default_timezone_get(); @@ -1005,7 +1005,7 @@ public function isAfter($testTime, ?string $timezone = null): bool */ public function humanize() { - $now = IntlCalendar::fromDateTime(self::now($this->timezone)->toDateTimeString()); + $now = IntlCalendar::fromDateTime(self::now($this->timezone)); $time = $this->getCalendar()->getTime(); $years = $now->fieldDifference($time, IntlCalendar::FIELD_YEAR); @@ -1106,7 +1106,7 @@ public function getUTCObject($time, ?string $timezone = null) */ public function getCalendar() { - return IntlCalendar::fromDateTime($this->toDateTimeString()); + return IntlCalendar::fromDateTime($this); } /** diff --git a/system/Router/AutoRouterImproved.php b/system/Router/AutoRouterImproved.php index 7b03e705..39b264ec 100644 --- a/system/Router/AutoRouterImproved.php +++ b/system/Router/AutoRouterImproved.php @@ -251,11 +251,7 @@ private function scanControllers(array $segments): array $c = count($segments); while ($c-- > 0) { - $segmentConvert = ucfirst( - $this->translateURIDashes === true - ? str_replace('-', '_', $segments[0]) - : $segments[0] - ); + $segmentConvert = $this->translateURIDashes(ucfirst($segments[0])); // as soon as we encounter any segment that is not PSR-4 compliant, stop searching if (! $this->isValidSegment($segmentConvert)) { diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 35fe4192..4998a16c 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -1008,7 +1008,7 @@ public function reverseRoute(string $search, ...$params) $namespace = trim($this->defaultNamespace, '\\') . '\\'; if ( substr($search, 0, 1) !== '\\' - || substr($search, 0, strlen($namespace)) !== $namespace + && substr($search, 0, strlen($namespace)) !== $namespace ) { $search = $namespace . $search; } diff --git a/system/Session/Handlers/BaseHandler.php b/system/Session/Handlers/BaseHandler.php index 008cae36..c441aa08 100644 --- a/system/Session/Handlers/BaseHandler.php +++ b/system/Session/Handlers/BaseHandler.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Session\Handlers; use Config\App as AppConfig; +use Config\Cookie as CookieConfig; use Psr\Log\LoggerAwareTrait; use SessionHandlerInterface; @@ -39,6 +40,9 @@ abstract class BaseHandler implements SessionHandlerInterface /** * Cookie prefix * + * The Config\Cookie::$prefix setting is completely ignored. + * See https://codeigniter4.github.io/CodeIgniter4/libraries/sessions.html#session-preferences + * * @var string */ protected $cookiePrefix = ''; @@ -102,14 +106,27 @@ abstract class BaseHandler implements SessionHandlerInterface public function __construct(AppConfig $config, string $ipAddress) { - $this->cookiePrefix = $config->cookiePrefix; - $this->cookieDomain = $config->cookieDomain; - $this->cookiePath = $config->cookiePath; - $this->cookieSecure = $config->cookieSecure; - $this->cookieName = $config->sessionCookieName; - $this->matchIP = $config->sessionMatchIP; - $this->savePath = $config->sessionSavePath; - $this->ipAddress = $ipAddress; + /** @var CookieConfig|null $cookie */ + $cookie = config('Cookie'); + + if ($cookie instanceof CookieConfig) { + // Session cookies have no prefix. + $this->cookieDomain = $cookie->domain; + $this->cookiePath = $cookie->path; + $this->cookieSecure = $cookie->secure; + } else { + // @TODO Remove this fallback when deprecated `App` members are removed. + // `Config/Cookie.php` is absence + // Session cookies have no prefix. + $this->cookieDomain = $config->cookieDomain; + $this->cookiePath = $config->cookiePath; + $this->cookieSecure = $config->cookieSecure; + } + + $this->cookieName = $config->sessionCookieName; + $this->matchIP = $config->sessionMatchIP; + $this->savePath = $config->sessionSavePath; + $this->ipAddress = $ipAddress; } /** diff --git a/system/Session/Session.php b/system/Session/Session.php index 0b27b03f..965edf88 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -187,7 +187,7 @@ public function __construct(SessionHandlerInterface $driver, App $config) /** @var CookieConfig $cookie */ $cookie = config('Cookie'); - $this->cookie = new Cookie($this->sessionCookieName, '', [ + $this->cookie = (new Cookie($this->sessionCookieName, '', [ 'expires' => $this->sessionExpiration === 0 ? 0 : time() + $this->sessionExpiration, 'path' => $cookie->path ?? $config->cookiePath, 'domain' => $cookie->domain ?? $config->cookieDomain, @@ -195,7 +195,7 @@ public function __construct(SessionHandlerInterface $driver, App $config) 'httponly' => true, // for security 'samesite' => $cookie->samesite ?? $config->cookieSameSite ?? Cookie::SAMESITE_LAX, 'raw' => $cookie->raw ?? false, - ]); + ]))->withPrefix(''); // Cookie prefix should be ignored. helper('array'); } diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index ba3c81b0..b5a105c6 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -45,6 +45,9 @@ abstract class CIUnitTestCase extends TestCase /** * Methods to run during setUp. * + * WARNING: Do not override unless you know exactly what you are doing. + * This property may be deprecated in the future. + * * @var array of methods */ protected $setUpMethods = [ @@ -57,6 +60,8 @@ abstract class CIUnitTestCase extends TestCase /** * Methods to run during tearDown. * + * WARNING: This property may be deprecated in the future. + * * @var array of methods */ protected $tearDownMethods = []; diff --git a/system/Test/DatabaseTestTrait.php b/system/Test/DatabaseTestTrait.php index d15a5fb4..539627e2 100644 --- a/system/Test/DatabaseTestTrait.php +++ b/system/Test/DatabaseTestTrait.php @@ -323,4 +323,22 @@ public function seeNumRecords(int $expected, string $table, array $where) $this->assertEquals($expected, $count, 'Wrong number of matching rows in database.'); } + + /** + * Sets $DBDebug to false. + * + * WARNING: this value will persist! take care to roll it back. + */ + protected function disableDBDebug(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + } + + /** + * Sets $DBDebug to true. + */ + protected function enableDBDebug(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', true); + } }