diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index fecd1d78b317..1b547fb76884 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -42,10 +42,15 @@ jobs: matrix: php-versions: ['7.3', '7.4', '8.0'] db-platforms: ['MySQLi', 'Postgre', 'SQLite3', 'SQLSRV'] + mysql-versions: ['5.7'] + include: + - php-versions: 7.4 + db-platforms: MySQLi + mysql-versions: 8.0 services: mysql: - image: mysql:5.7 + image: mysql:${{ matrix.mysql-versions }} env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: test @@ -100,9 +105,8 @@ jobs: - name: Install latest ImageMagick run: | - sudo apt-get remove ghostscript - sudo apt-get update - sudo apt-get install -y imagemagick ghostscript-x gsfonts + sudo apt-get install --reinstall libgs9-common fonts-noto-mono libgs9:amd64 libijs-0.35:amd64 fonts-urw-base35 ghostscript poppler-data libjbig2dec0:amd64 gsfonts libopenjp2-7:amd64 fonts-droid-fallback ttf-dejavu-core + sudo apt-get install -y imagemagick - name: Get composer cache directory id: composercache @@ -133,7 +137,7 @@ jobs: DB: ${{ matrix.db-platforms }} TERM: xterm-256color - - if: matrix.php-versions == '7.4' + - if: github.repository_owner == 'codeigniter4' && matrix.php-versions == '7.4' name: Run Coveralls run: | composer global require php-coveralls/php-coveralls:^2.4 @@ -144,6 +148,7 @@ jobs: COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} coveralls-finish: + if: github.repository_owner == 'codeigniter4' needs: [tests] runs-on: ubuntu-20.04 steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cf6ad44a0b..86777d7fccab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,63 @@ [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.2...HEAD) -## [v4.1.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.2) (2021-05-17) +**Fixed bugs:** + +- Bug: Error using SQLITE3 strftime in CodeIgniter 4.1.2 [\#4760](https://github.com/codeigniter4/CodeIgniter4/issues/4760) +- Bug: Caching something through cron, is not accessible in the web application [\#4751](https://github.com/codeigniter4/CodeIgniter4/issues/4751) +- Bug: SQLite Drop Column [\#4746](https://github.com/codeigniter4/CodeIgniter4/issues/4746) +- Bug: CURL Class - BaseURI options notworking [\#4713](https://github.com/codeigniter4/CodeIgniter4/issues/4713) +- Bug: autorouting [\#4711](https://github.com/codeigniter4/CodeIgniter4/issues/4711) +- Bug: curlrequest not using baseURI on localhost [\#4707](https://github.com/codeigniter4/CodeIgniter4/issues/4707) +- Bug: cli not working with cron [\#4699](https://github.com/codeigniter4/CodeIgniter4/issues/4699) + +**Closed issues:** + +- Bug: Class 'Locale' not found [\#4775](https://github.com/codeigniter4/CodeIgniter4/issues/4775) +- Bug: deprecated notice on CodeIgniter\HTTP\RequestInterface::getMethod\(\) [\#4717](https://github.com/codeigniter4/CodeIgniter4/issues/4717) +- Allow to join models between primary keys and foreign keys [\#4714](https://github.com/codeigniter4/CodeIgniter4/issues/4714) +- DateTime::\_\_construct\(\): Failed to parse time string \(\) at position 0 \(�\): Unexpected character [\#4708](https://github.com/codeigniter4/CodeIgniter4/issues/4708) +- Bug: Query Builder breaks with SQL function LENGTH\(\) and column name "row" [\#4687](https://github.com/codeigniter4/CodeIgniter4/issues/4687) + +**Merged pull requests:** + +- Expand Query named binds recognition [\#4769](https://github.com/codeigniter4/CodeIgniter4/pull/4769) ([paulbalandan](https://github.com/paulbalandan)) +- \[Rector\] Remove @var from class constant [\#4766](https://github.com/codeigniter4/CodeIgniter4/pull/4766) ([samsonasik](https://github.com/samsonasik)) +- Set WarningsReturnAsErrors = 0 before connection [\#4762](https://github.com/codeigniter4/CodeIgniter4/pull/4762) ([obelisk-services](https://github.com/obelisk-services)) +- \[Rector\] Apply Rector: VarConstantCommentRector [\#4759](https://github.com/codeigniter4/CodeIgniter4/pull/4759) ([samsonasik](https://github.com/samsonasik)) +- \[Autoloader\] include\_once is not needed on Autoloader::loadClass\(\) with no namespace [\#4756](https://github.com/codeigniter4/CodeIgniter4/pull/4756) ([samsonasik](https://github.com/samsonasik)) +- Fix imagemagick build [\#4755](https://github.com/codeigniter4/CodeIgniter4/pull/4755) ([michalsn](https://github.com/michalsn)) +- \[Rector\] Apply Rector: MoveVariableDeclarationNearReferenceRector [\#4752](https://github.com/codeigniter4/CodeIgniter4/pull/4752) ([samsonasik](https://github.com/samsonasik)) +- SQLite3 "nullable" [\#4749](https://github.com/codeigniter4/CodeIgniter4/pull/4749) ([MGatner](https://github.com/MGatner)) +- Remove $response variable at ControllerResponse::\_\_construct\(\) as never defined [\#4747](https://github.com/codeigniter4/CodeIgniter4/pull/4747) ([samsonasik](https://github.com/samsonasik)) +- Use variable for Config/Paths config to reduce repetitive definition [\#4745](https://github.com/codeigniter4/CodeIgniter4/pull/4745) ([samsonasik](https://github.com/samsonasik)) +- \[Rector\] Apply Rector : ListToArrayDestructRector [\#4743](https://github.com/codeigniter4/CodeIgniter4/pull/4743) ([samsonasik](https://github.com/samsonasik)) +- Add default TTL [\#4742](https://github.com/codeigniter4/CodeIgniter4/pull/4742) ([MGatner](https://github.com/MGatner)) +- update return sample of `dot array\_search\(\)` [\#4740](https://github.com/codeigniter4/CodeIgniter4/pull/4740) ([totoprayogo1916](https://github.com/totoprayogo1916)) +- Additional check for `$argv` variable when detecting CLI [\#4739](https://github.com/codeigniter4/CodeIgniter4/pull/4739) ([paulbalandan](https://github.com/paulbalandan)) +- Ensure variable declarations [\#4737](https://github.com/codeigniter4/CodeIgniter4/pull/4737) ([jeromegamez](https://github.com/jeromegamez)) +- Fix setting of value in Cookie's flag attributes [\#4736](https://github.com/codeigniter4/CodeIgniter4/pull/4736) ([paulbalandan](https://github.com/paulbalandan)) +- Add missing imports [\#4735](https://github.com/codeigniter4/CodeIgniter4/pull/4735) ([jeromegamez](https://github.com/jeromegamez)) +- Add environment spark command [\#4734](https://github.com/codeigniter4/CodeIgniter4/pull/4734) ([paulbalandan](https://github.com/paulbalandan)) +- Remove explicit condition that is always true [\#4731](https://github.com/codeigniter4/CodeIgniter4/pull/4731) ([jeromegamez](https://github.com/jeromegamez)) +- Deduplicate code [\#4730](https://github.com/codeigniter4/CodeIgniter4/pull/4730) ([jeromegamez](https://github.com/jeromegamez)) +- Replace `isset\(\)` with the `??` null coalesce operator [\#4729](https://github.com/codeigniter4/CodeIgniter4/pull/4729) ([jeromegamez](https://github.com/jeromegamez)) +- Remove unused imports [\#4728](https://github.com/codeigniter4/CodeIgniter4/pull/4728) ([jeromegamez](https://github.com/jeromegamez)) +- Fix truncated SCRIPT\_NAME [\#4726](https://github.com/codeigniter4/CodeIgniter4/pull/4726) ([MGatner](https://github.com/MGatner)) +- Expand CLI detection [\#4725](https://github.com/codeigniter4/CodeIgniter4/pull/4725) ([paulbalandan](https://github.com/paulbalandan)) +- \[Rector\] Add custom Rector Rule: RemoveErrorSuppressInTryCatchStmtsRector rector rule [\#4724](https://github.com/codeigniter4/CodeIgniter4/pull/4724) ([samsonasik](https://github.com/samsonasik)) +- Test with MySQL 8 [\#4721](https://github.com/codeigniter4/CodeIgniter4/pull/4721) ([jeromegamez](https://github.com/jeromegamez)) +- Replace URI string casts [\#4716](https://github.com/codeigniter4/CodeIgniter4/pull/4716) ([MGatner](https://github.com/MGatner)) +- Format URI directly [\#4715](https://github.com/codeigniter4/CodeIgniter4/pull/4715) ([MGatner](https://github.com/MGatner)) +- Additional File functions [\#4712](https://github.com/codeigniter4/CodeIgniter4/pull/4712) ([MGatner](https://github.com/MGatner)) +- Remove unused private rowOffset property in Database/SQLSRV/Result.php [\#4709](https://github.com/codeigniter4/CodeIgniter4/pull/4709) ([samsonasik](https://github.com/samsonasik)) +- Check for configured instead of hard-coded database in DbUtilsTest [\#4705](https://github.com/codeigniter4/CodeIgniter4/pull/4705) ([jeromegamez](https://github.com/jeromegamez)) +- Revert UG margins [\#4704](https://github.com/codeigniter4/CodeIgniter4/pull/4704) ([MGatner](https://github.com/MGatner)) +- Create .git/hooks directory if not already present [\#4703](https://github.com/codeigniter4/CodeIgniter4/pull/4703) ([jeromegamez](https://github.com/jeromegamez)) +- Annotate specifically designed slow tests with custom limits [\#4698](https://github.com/codeigniter4/CodeIgniter4/pull/4698) ([paulbalandan](https://github.com/paulbalandan)) +- Cache robustness [\#4697](https://github.com/codeigniter4/CodeIgniter4/pull/4697) ([MGatner](https://github.com/MGatner)) + +## [v4.1.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.2) (2021-05-18) [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.1...v4.1.2) diff --git a/admin/RELEASE.md b/admin/RELEASE.md index 9b39c2558c31..822bd1f5f154 100644 --- a/admin/RELEASE.md +++ b/admin/RELEASE.md @@ -57,13 +57,14 @@ See the changelog: https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHA * Build the HTML version of the User Guide: `make html` * Build the ePub version of the User Guide: `make epub` * Switch to the **userguide** repo and create a new branch `release-4.x.x` -* Copy the contents of **CodeIgniter4/user_guide_src/build/html** into **docs/** +* Replace **docs/** with **CodeIgniter4/user_guide_src/build/html** +* Ensure the file **docs/.nojekyll** exists or GitHub Pages will ignore folders with an underscore prefix * Copy **CodeIgniter4/user_guide_src/build/epub/CodeIgniter.epub** to **./CodeIgniter4.x.x.epub** * Commit the changes with "Update for 4.x.x" and push to origin * Create a new PR from `release-4.x.x` to `develop`: * Title: "Update for 4.x.x" * Description: blank -* Merge the PR then fast-forward `develop` to catch the merge commit +* Merge the PR * Create a new Release: * Version: "v4.x.x" * Title: "CodeIgniter 4.x.x User Guide" @@ -74,7 +75,7 @@ See the changelog: https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHA Currently the User Guide on the website has to be updated manually. Visit Jim's user home where the served directory **codeigniter.com** exists. Copy the latest **docs** folder from -the User Guide repo into **public** and updated the symlink. +the User Guide repo to **public/userguide4** and browse to the website to make sure it works. ## Announcement diff --git a/admin/setup.sh b/admin/setup.sh index 3170716c53ef..86254e8e8889 100644 --- a/admin/setup.sh +++ b/admin/setup.sh @@ -2,5 +2,6 @@ # Install a pre-commit hook that # automatically runs phpcs to fix styles +mkdir -p .git/hooks cp admin/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit diff --git a/app/Config/Cache.php b/app/Config/Cache.php index a3b3bbcf895d..e5711a35fbca 100644 --- a/app/Config/Cache.php +++ b/app/Config/Cache.php @@ -82,6 +82,21 @@ class Cache extends BaseConfig */ public $prefix = ''; + /** + * -------------------------------------------------------------------------- + * Default TTL + * -------------------------------------------------------------------------- + * + * The default number of seconds to save items when none is specified. + * + * WARNING: This is not used by framework handlers where 60 seconds is + * hard-coded, but may be useful to projects and modules. This will replace + * the hard-coded value in a future release. + * + * @var integer + */ + public $ttl = 60; + /** * -------------------------------------------------------------------------- * File settings diff --git a/composer.json b/composer.json index 5c5816819c65..ef2e937e7dce 100644 --- a/composer.json +++ b/composer.json @@ -19,10 +19,10 @@ "fakerphp/faker": "^1.9", "mikey179/vfsstream": "^1.6", "nexusphp/tachycardia": "^1.0", - "phpstan/phpstan": "0.12.86", + "phpstan/phpstan": "0.12.88", "phpunit/phpunit": "^9.1", "predis/predis": "^1.1", - "rector/rector": "0.11.2", + "rector/rector": "0.11.8", "squizlabs/php_codesniffer": "^3.3", "symplify/package-builder": "^9.3" }, diff --git a/contributing/internals.rst b/contributing/internals.rst index 2ef86d0b563c..ba7f908f88e9 100644 --- a/contributing/internals.rst +++ b/contributing/internals.rst @@ -2,18 +2,18 @@ CodeIgniter Internals Overview ############################## -This guide should help contributors understand how the core of the framework works, -and what needs to be done when creating new functionality. Specifically, it +This guide should help contributors understand how the core of the framework works, +and what needs to be done when creating new functionality. Specifically, it details the information needed to create new packages for the core. Dependencies ============ -All packages should be designed to be completely isolated from the rest of the -packages, if possible. This will allow them to be used in projects outside of CodeIgniter. -Basically, this means that any dependencies should be kept to a minimum. +All packages should be designed to be completely isolated from the rest of the +packages, if possible. This will allow them to be used in projects outside of CodeIgniter. +Basically, this means that any dependencies should be kept to a minimum. Any dependencies must be able to be passed into the constructor. If you do need to use one -of the other core packages, you can create that in the constructor using the +of the other core packages, you can create that in the constructor using the Services class, as long as you provide a way for dependencies to override that:: public function __construct(Foo $foo=null) @@ -27,7 +27,7 @@ Type hinting ============ PHP7 provides the ability to `type hint `_ -method parameters and return types. Use it where possible. Return type hinting +method parameters and return types. Use it where possible. Return type hinting is not always practical, but do try to make it work. At this time, we are not using strict type hinting. @@ -35,9 +35,9 @@ At this time, we are not using strict type hinting. Abstractions ============ -The amount of abstraction required to implement a solution should be the minimal -amount required. Every layer of abstraction brings additional levels of technical -debt and unnecessary complexity. That said, don't be afraid to use it when it's +The amount of abstraction required to implement a solution should be the minimal +amount required. Every layer of abstraction brings additional levels of technical +debt and unnecessary complexity. That said, don't be afraid to use it when it's needed and can help things. * Don't create a new container class when an array will do just fine. @@ -46,35 +46,35 @@ needed and can help things. Testing ======= -Any new packages submitted to the framework must be accompanied by unit tests. +Any new packages submitted to the framework must be accompanied by unit tests. The target is 80%+ code coverage of all classes within the package. * Test only public methods, not protected and private unless the method really needs it due to complexity. * Don't just test that the method works, but test for all fail states, thrown exceptions, and other pathways through your code. -You should be aware of the extra assertions that we have made, provisions for -accessing private properties for testing, and mock services. +You should be aware of the extra assertions that we have made, provisions for +accessing private properties for testing, and mock services. We have also made a **CITestStreamFilter** to capture test output. -Do check out similar tests in ``tests/system/``, and read the "Testing" section +Do check out similar tests in ``tests/system/``, and read the "Testing" section in the user guide, before you dig in to your own. -Some testing needs to be done in a separate process, in order to setup the -PHP globals to mimic test situations properly. See +Some testing needs to be done in a separate process, in order to setup the +PHP globals to mimic test situations properly. See ``tests/system/HTTP/ResponseSendTest`` for an example of this. Namespaces and Files ==================== -All new packages should live under the ``CodeIgniter`` namespace. +All new packages should live under the ``CodeIgniter`` namespace. The package itself will need its own sub-namespace that collects all related files into one grouping, like ``CodeIgniter\HTTP``. -Files MUST be named the same as the class they hold, and they must match the -:doc:`Style Guide <./styleguide.rst>`, meaning CamelCase class and file names. -They should be in their own directory that matches the sub-namespace under the +Files MUST be named the same as the class they hold, and they must match the +:doc:`Style Guide <./styleguide.rst>`, meaning CamelCase class and file names. +They should be in their own directory that matches the sub-namespace under the **system** directory. -Take the Router class as an example. The Router lives in the ``CodeIgniter\Router`` +Take the Router class as an example. The Router lives in the ``CodeIgniter\Router`` namespace. Its two main classes, **RouteCollection** and **Router**, are in the files **system/Router/RouteCollection.php** and **system/Router/Router.php** respectively. @@ -82,10 +82,10 @@ namespace. Its two main classes, Interfaces ---------- -Most base classes should have an interface defined for them. +Most base classes should have an interface defined for them. At the very least this allows them to be easily mocked -and passed to other classes as a dependency, without breaking the type-hinting. -The interface names should match the name of the class with "Interface" appended +and passed to other classes as a dependency, without breaking the type-hinting. +The interface names should match the name of the class with "Interface" appended to it, like ``RouteCollectionInterface``. The Router package mentioned above includes the @@ -95,8 +95,8 @@ interfaces to provide the abstractions for the two classes in the package. Handlers -------- -When a package supports multiple "drivers", the convention is to place them in -a **Handlers** directory, and name the child classes as Handlers. +When a package supports multiple "drivers", the convention is to place them in +a **Handlers** directory, and name the child classes as Handlers. You will often find that creating a ``BaseHandler``, that the child classes can extend, to be beneficial in keeping the code DRY. @@ -105,27 +105,27 @@ See the Log and Session packages for examples. Configuration ============= -Should the package require user-configurable settings, you should create a new -file just for that package under **app/Config**. +Should the package require user-configurable settings, you should create a new +file just for that package under **app/Config**. The file name should generally match the package name. Autoloader ========== -All files within the package should be added to **system/Config/AutoloadConfig.php**, -in the "classmap" property. This is only used for core framework files, and helps +All files within the package should be added to **system/Config/AutoloadConfig.php**, +in the "classmap" property. This is only used for core framework files, and helps to minimize file system scans and keep performance high. Command-Line Support ==================== -CodeIgniter has never been known for it's strong CLI support. However, if your -package could benefit from it, create a new file under **system/Commands**. +CodeIgniter has never been known for it's strong CLI support. However, if your +package could benefit from it, create a new file under **system/Commands**. The class contained within is simply a controller that is intended for CLI -usage only. The ``index()`` method should provide a list of available commands +usage only. The ``index()`` method should provide a list of available commands provided by that package. -Routes must be added to **system/Config/Routes.php** using the ``cli()`` method +Routes must be added to **system/Config/Routes.php** using the ``cli()`` method to ensure it is not accessible through the browser, but is restricted to the CLI only. See the **MigrationsCommand** file for an example. @@ -133,6 +133,16 @@ See the **MigrationsCommand** file for an example. Documentation ============= -All packages must contain appropriate documentation that matches the tone and -style of the rest of the user guide. In most cases, the top portion of the package's +All packages must contain appropriate documentation that matches the tone and +style of the rest of the user guide. In most cases, the top portion of the package's page should be treated in tutorial fashion, while the second half would be a class reference. + +Modification of the ``env`` file +================================ + +CodeIgniter is shipped with a template ``env`` file to support adding secrets too sensitive to +be stored in a version control system. Contributors adding new entries to the env file should +always ensure that these entries are commented, i.e., starting with a hash (``#``). This is +because we have spark commands that actually copy the template file to a ``.env`` file (which +is actually the live version actually read by CodeIgniter for secrets) if the latter is missing. +As much as possible, we do not want settings to go live unexpectedly without the user's knowledge. diff --git a/public/index.php b/public/index.php index cd60baeb11eb..77373025f96d 100644 --- a/public/index.php +++ b/public/index.php @@ -17,8 +17,9 @@ // Load our paths config file // This is the line that might need to be changed, depending on your folder structure. -require realpath(FCPATH . '../app/Config/Paths.php') ?: FCPATH . '../app/Config/Paths.php'; +$pathsConfig = FCPATH . '../app/Config/Paths.php'; // ^^^ Change this if you move your application folder +require realpath($pathsConfig) ?: $pathsConfig; $paths = new Config\Paths(); diff --git a/rector.php b/rector.php index 591e4e6784cf..3df26ace9d06 100644 --- a/rector.php +++ b/rector.php @@ -11,6 +11,7 @@ use Rector\CodeQuality\Rector\If_\SimplifyIfReturnBoolRector; use Rector\CodeQuality\Rector\Return_\SimplifyUselessVariableRector; use Rector\CodeQuality\Rector\Ternary\UnnecessaryTernaryExpressionRector; +use Rector\CodeQualityStrict\Rector\Variable\MoveVariableDeclarationNearReferenceRector; use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector; use Rector\Core\Configuration\Option; use Rector\Core\ValueObject\PhpVersion; @@ -22,11 +23,15 @@ use Rector\EarlyReturn\Rector\If_\ChangeIfElseValueAssignToEarlyReturnRector; use Rector\EarlyReturn\Rector\If_\RemoveAlwaysElseRector; use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector; +use Rector\Php70\Rector\Ternary\TernaryToNullCoalescingRector; +use Rector\Php71\Rector\List_\ListToArrayDestructRector; use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector; use Rector\Set\ValueObject\SetList; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Utils\Rector\PassStrictParameterToFunctionParameterRector; +use Utils\Rector\RemoveErrorSuppressInTryCatchStmtsRector; +use Utils\Rector\RemoveVarTagFromClassConstantRector; use Utils\Rector\UnderscoreToCamelCaseVariableNameRector; return static function (ContainerConfigurator $containerConfigurator): void { @@ -84,4 +89,9 @@ $services->set(ChangeArrayPushToArrayAssignRector::class); $services->set(UnnecessaryTernaryExpressionRector::class); $services->set(RemoveUnusedPrivatePropertyRector::class); + $services->set(RemoveErrorSuppressInTryCatchStmtsRector::class); + $services->set(TernaryToNullCoalescingRector::class); + $services->set(ListToArrayDestructRector::class); + $services->set(MoveVariableDeclarationNearReferenceRector::class); + $services->set(RemoveVarTagFromClassConstantRector::class); }; diff --git a/spark b/spark index c4ad645a366b..f62aeddb2978 100755 --- a/spark +++ b/spark @@ -33,8 +33,9 @@ if (strpos(PHP_SAPI, 'cgi') === 0) define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); // Load our paths config file -require realpath('app/Config/Paths.php') ?: 'app/Config/Paths.php'; +$pathsConfig = 'app/Config/Paths.php'; // ^^^ Change this line if you move your application folder +require realpath($pathsConfig) ?: $pathsConfig; $paths = new Config\Paths(); diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 273be08c52d6..162d898394a2 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -22,17 +22,17 @@ * An autoloader that uses both PSR4 autoloading, and traditional classmaps. * * Given a foo-bar package of classes in the file system at the following paths: - *``` + * ``` * /path/to/packages/foo-bar/ * /src * Baz.php # Foo\Bar\Baz * Qux/ * Quux.php # Foo\Bar\Qux\Quux - *``` + * ``` * you can add the path to the configuration array that is passed in the constructor. * The Config array consists of 2 primary keys, both of which are associative arrays: * 'psr4', and 'classmap'. - *``` + * ``` * $Config = [ * 'psr4' => [ * 'Foo\Bar' => '/path/to/packages/foo-bar' @@ -41,9 +41,9 @@ * 'MyClass' => '/path/to/class/file.php' * ] * ]; - *``` + * ``` * Example: - *``` + * ``` * register(); - *``` + * ``` */ class Autoloader { @@ -258,15 +258,6 @@ protected function loadInNamespace(string $class) { if (strpos($class, '\\') === false) { - $class = 'Config\\' . $class; - $filePath = APPPATH . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; - $filename = $this->includeFile($filePath); - - if ($filename) - { - return $filename; - } - return false; } @@ -353,7 +344,9 @@ protected function discoverComposerNamespaces() return; } - /** @var ClassLoader $composer */ + /** + * @var ClassLoader $composer + */ $composer = include COMPOSER_PATH; $paths = $composer->getPrefixesPsr4(); $classes = $composer->getClassMap(); diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 1f311adf01f6..a477f71be151 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -983,7 +983,7 @@ public static function getOption(string $name) // If the option didn't have a value, simply return TRUE // so they know it was set, otherwise return the actual value. - $val = static::$options[$name] === null ? true : static::$options[$name]; + $val = static::$options[$name] ?? true; return $val; } diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index 91afb212e0e4..c67b2180a643 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -285,35 +285,13 @@ public function getMetaData(string $key) { $key = static::validateKey($key, $this->prefix); - if (! is_file($this->path . $key)) + if (false === $data = $this->getItem($key)) { return false; // This will return null in a future release } - $data = @unserialize(file_get_contents($this->path . $key)); - - if (! is_array($data) || ! isset($data['ttl'])) - { - return false; // This will return null in a future release - } - - // Consider expired items as missing - $expire = $data['time'] + $data['ttl']; - - // @phpstan-ignore-next-line - if ($data['ttl'] > 0 && time() > $expire) - { - // If the file is still there then remove it - if (is_file($this->path . $key)) - { - unlink($this->path . $key); - } - - return false; // This will return null in a future release - } - return [ - 'expire' => $expire, + 'expire' => $data['time'] + $data['ttl'], 'mtime' => filemtime($this->path . $key), 'data' => $data['data'], ]; @@ -348,15 +326,19 @@ protected function getItem(string $filename) return false; } - $data = unserialize(file_get_contents($this->path . $filename)); + $data = @unserialize(file_get_contents($this->path . $filename)); + if (! is_array($data) || ! isset($data['ttl'])) + { + return false; + } // @phpstan-ignore-next-line if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl']) { - // If the file is still there then remove it + // If the file is still there then try to remove it if (is_file($this->path . $filename)) { - unlink($this->path . $filename); + @unlink($this->path . $filename); } return false; diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index 7452330a83c4..e6a0ce2007c0 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -351,7 +351,7 @@ public function getMetaData(string $key) return false; // This will return null in a future release } - list($data, $time, $limit) = $stored; + [$data, $time, $limit] = $stored; // Calculate the remaining time to live from the original limit $ttl = time() - $time - $limit; diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 56a01dcd9344..92bb010f6bd0 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -44,11 +44,8 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - const CI_VERSION = '4.1.2'; + const CI_VERSION = '4.1.3'; - /** - * @var string - */ private const MIN_PHP_VERSION = '7.3'; /** @@ -474,7 +471,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // Save our current URI as the previous URI in the session // for safer, more accurate use with `previous_url()` helper function. - $this->storePreviousURL((string) current_url(true)); + $this->storePreviousURL(current_url(true)); unset($uri); @@ -1080,7 +1077,7 @@ public function storePreviousURL($uri) if (isset($_SESSION)) { - $_SESSION['_ci_previous_url'] = (string) $uri; + $_SESSION['_ci_previous_url'] = URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } } diff --git a/system/Commands/Generators/ValidationGenerator.php b/system/Commands/Generators/ValidationGenerator.php index 13fe3e2d0fc1..9a79d47f09fe 100644 --- a/system/Commands/Generators/ValidationGenerator.php +++ b/system/Commands/Generators/ValidationGenerator.php @@ -12,7 +12,6 @@ namespace CodeIgniter\Commands\Generators; use CodeIgniter\CLI\BaseCommand; -use CodeIgniter\CLI\CLI; use CodeIgniter\CLI\GeneratorTrait; /** diff --git a/system/Commands/Utilities/Environment.php b/system/Commands/Utilities/Environment.php new file mode 100644 index 000000000000..1fa4331cce87 --- /dev/null +++ b/system/Commands/Utilities/Environment.php @@ -0,0 +1,160 @@ +]'; + + /** + * The Command's arguments + * + * @var array + */ + protected $arguments = [ + 'environment' => '[Optional] The new environment to set. If none is provided, this will print the current environment.', + ]; + + /** + * The Command's options + * + * @var array + */ + protected $options = []; + + /** + * Allowed values for environment. `testing` is excluded + * since spark won't work on it. + * + * @var array + */ + private static $knownTypes = [ + 'production', + 'development', + ]; + + /** + * @inheritDoc + * + * @param array $params + * + * @return void + */ + public function run(array $params) + { + if ($params === []) + { + CLI::write(sprintf('Your environment is currently set as %s.', CLI::color($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, 'green'))); + CLI::newLine(); + + return; + } + + $env = strtolower(array_shift($params)); + + if ($env === 'testing') + { + CLI::error('The "testing" environment is reserved for PHPUnit testing.', 'light_gray', 'red'); + CLI::error('You will not be able to run spark under a "testing" environment.', 'light_gray', 'red'); + CLI::newLine(); + + return; + } + + if (! in_array($env, self::$knownTypes, true)) + { + CLI::error(sprintf('Invalid environment type "%s". Expected one of "%s".', $env, implode('" and "', self::$knownTypes)), 'light_gray', 'red'); + CLI::newLine(); + + return; + } + + if (! $this->writeNewEnvironmentToEnvFile($env)) + { + CLI::error('Error in writing new environment to .env file.', 'light_gray', 'red'); + CLI::newLine(); + + return; + } + + // force DotEnv to reload the new environment + // however we cannot redefine the ENVIRONMENT constant + putenv('CI_ENVIRONMENT'); + unset($_ENV['CI_ENVIRONMENT'], $_SERVER['CI_ENVIRONMENT']); + (new DotEnv(ROOTPATH))->load(); + + CLI::write(sprintf('Environment is successfully changed to "%s".', $env), 'green'); + CLI::write('The ENVIRONMENT constant will be changed in the next script execution.'); + CLI::newLine(); + } + + /** + * @see https://regex101.com/r/4sSORp/1 for the regex in action + * + * @param string $newEnv + * + * @return boolean + */ + private function writeNewEnvironmentToEnvFile(string $newEnv): bool + { + $baseEnv = ROOTPATH . 'env'; + $envFile = ROOTPATH . '.env'; + + if (! is_file($envFile)) + { + if (! is_file($baseEnv)) + { + CLI::write('Both default shipped `env` file and custom `.env` are missing.', 'yellow'); + CLI::write('It is impossible to write the new environment type.', 'yellow'); + CLI::newLine(); + + return false; + } + + copy($baseEnv, $envFile); + } + + $pattern = preg_quote($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, '/'); + $pattern = sprintf('/^[#\s]*CI_ENVIRONMENT[=\s]+%s$/m', $pattern); + + return file_put_contents( + $envFile, + preg_replace($pattern, "\nCI_ENVIRONMENT = {$newEnv}", file_get_contents($envFile), -1, $count) + ) !== false && $count > 0; + } +} diff --git a/system/Common.php b/system/Common.php index acdc66823db2..f975ab3afa06 100644 --- a/system/Common.php +++ b/system/Common.php @@ -740,15 +740,36 @@ function helper($filenames) if (! function_exists('is_cli')) { /** - * Is CLI? - * - * Test to see if a request was made from the command line. + * Check if PHP was invoked from the command line. * * @return boolean + * + * @codeCoverageIgnore Cannot be tested fully as PHPUnit always run in CLI */ function is_cli(): bool { - return (PHP_SAPI === 'cli' || defined('STDIN')); + if (PHP_SAPI === 'cli') + { + return true; + } + + if (defined('STDIN')) + { + return true; + } + + if (stristr(PHP_SAPI, 'cgi') && getenv('TERM')) + { + return true; + } + + if (! isset($_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']) && isset($_SERVER['argv']) && count($_SERVER['argv']) > 0) + { + return true; + } + + // if source of request is from CLI, the `$_SERVER` array will not populate this key + return ! isset($_SERVER['REQUEST_METHOD']); } } @@ -1278,14 +1299,16 @@ function view_cell(string $library, $params = null, int $ttl = 0, string $cacheN * * @see https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/helpers.php */ -// @codeCoverageIgnoreStart if (! function_exists('class_basename')) { /** * Get the class "basename" of the given object / class. * - * @param string|object $class + * @param string|object $class + * * @return string + * + * @codeCoverageIgnore */ function class_basename($class) { @@ -1300,8 +1323,11 @@ function class_basename($class) /** * Returns all traits used by a class, its parent classes and trait of their traits. * - * @param object|string $class + * @param object|string $class + * * @return array + * + * @codeCoverageIgnore */ function class_uses_recursive($class) { @@ -1327,8 +1353,11 @@ function class_uses_recursive($class) /** * Returns all traits used by a trait and its traits. * - * @param string $trait + * @param string $trait + * * @return array + * + * @codeCoverageIgnore */ function trait_uses_recursive($trait) { @@ -1342,4 +1371,3 @@ function trait_uses_recursive($trait) return $traits; } } -// @codeCoverageIgnoreEnd diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index 75140f05571d..54374add5c65 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -90,7 +90,7 @@ public function parse(): ?array // If there is an equal sign, then we know we are assigning a variable. if (strpos($line, '=') !== false) { - list($name, $value) = $this->normaliseVariable($line); + [$name, $value] = $this->normaliseVariable($line); $vars[$name] = $value; $this->setVariable($name, $value); } @@ -143,7 +143,7 @@ public function normaliseVariable(string $name, string $value = ''): array // Split our compound string into its parts. if (strpos($name, '=') !== false) { - list($name, $value) = explode('=', $name, 2); + [$name, $value] = explode('=', $name, 2); } $name = trim($name); diff --git a/system/Config/Factories.php b/system/Config/Factories.php index 9222e5e71ead..22a9a27b84a2 100644 --- a/system/Config/Factories.php +++ b/system/Config/Factories.php @@ -23,7 +23,7 @@ * instantiation checks. * * @method static Model models(...$arguments) - * @method static \Config\BaseConfig config(...$arguments) + * @method static BaseConfig config(...$arguments) */ class Factories { diff --git a/system/Cookie/Cookie.php b/system/Cookie/Cookie.php index f6ebf37a9847..614f327d64e7 100644 --- a/system/Cookie/Cookie.php +++ b/system/Cookie/Cookie.php @@ -184,7 +184,7 @@ public static function fromHeaderString(string $cookie, bool $raw = false) { if (strpos($part, '=') !== false) { - list($attr, $val) = explode('=', $part); + [$attr, $val] = explode('=', $part); } else { @@ -220,15 +220,19 @@ final public function __construct(string $name, string $value = '', array $optio unset($options['max-age']); } - // to retain backward compatibility with previous versions' fallback - $prefix = $options['prefix'] ?: self::$defaults['prefix']; - $path = $options['path'] ?: self::$defaults['path']; - $domain = $options['domain'] ?: self::$defaults['domain']; - $secure = $options['secure'] ?: self::$defaults['secure']; - $httponly = $options['httponly'] ?: self::$defaults['httponly']; + // to preserve backward compatibility with array-based cookies in previous CI versions + $prefix = $options['prefix'] ?: self::$defaults['prefix']; + $path = $options['path'] ?: self::$defaults['path']; + $domain = $options['domain'] ?: self::$defaults['domain']; + + // empty string SameSite should use the default for browsers $samesite = $options['samesite'] ?: self::$defaults['samesite']; - $this->validateName($name, $options['raw']); + $raw = $options['raw']; + $secure = $options['secure']; + $httponly = $options['httponly']; + + $this->validateName($name, $raw); $this->validatePrefix($prefix, $secure, $path, $domain); $this->validateSameSite($samesite, $secure); @@ -241,7 +245,7 @@ final public function __construct(string $name, string $value = '', array $optio $this->secure = $secure; $this->httponly = $httponly; $this->samesite = ucfirst(strtolower($samesite)); - $this->raw = $options['raw']; + $this->raw = $raw; } //========================================================================= diff --git a/system/Cookie/CookieInterface.php b/system/Cookie/CookieInterface.php index 1dddb13a1426..550323fe89aa 100644 --- a/system/Cookie/CookieInterface.php +++ b/system/Cookie/CookieInterface.php @@ -29,24 +29,18 @@ interface CookieInterface * Cookies are not sent on normal cross-site subrequests (for example to * load images or frames into a third party site), but are sent when a * user is navigating to the origin site (i.e. when following a link). - * - * @var string */ public const SAMESITE_LAX = 'lax'; /** * Cookies will only be sent in a first-party context and not be sent * along with requests initiated by third party websites. - * - * @var string */ public const SAMESITE_STRICT = 'strict'; /** * RFC 6265 allowed values for the "SameSite" attribute. * - * @var string[] - * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite */ public const ALLOWED_SAMESITE_VALUES = [ @@ -58,8 +52,6 @@ interface CookieInterface /** * Expires date format. * - * @var string - * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date * @see https://tools.ietf.org/html/rfc7231#section-7.1.1.2 */ diff --git a/system/Database/Forge.php b/system/Database/Forge.php index a8975f17fe22..f2e2ee511067 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -964,8 +964,8 @@ protected function _processFields(bool $createTable = false): array $field = [ 'name' => $key, - 'new_name' => isset($attributes['NAME']) ? $attributes['NAME'] : null, - 'type' => isset($attributes['TYPE']) ? $attributes['TYPE'] : null, + 'new_name' => $attributes['NAME'] ?? null, + 'type' => $attributes['TYPE'] ?? null, 'length' => '', 'unsigned' => '', 'null' => '', diff --git a/system/Database/MySQLi/Result.php b/system/Database/MySQLi/Result.php index 5488212530bd..4be7fc9f96af 100644 --- a/system/Database/MySQLi/Result.php +++ b/system/Database/MySQLi/Result.php @@ -98,7 +98,7 @@ public function getFieldData(): array $retVal[$i] = new stdClass(); $retVal[$i]->name = $data->name; $retVal[$i]->type = $data->type; - $retVal[$i]->type_name = in_array($data->type, [1, 247], true) ? 'char' : (isset($dataTypes[$data->type]) ? $dataTypes[$data->type] : null); + $retVal[$i]->type_name = in_array($data->type, [1, 247], true) ? 'char' : ($dataTypes[$data->type] ?? null); $retVal[$i]->max_length = $data->max_length; $retVal[$i]->primary_key = (int) ($data->flags & 2); $retVal[$i]->length = $data->length; diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index d77bcb25d8c1..f23d6dc9f685 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -310,7 +310,8 @@ protected function _update(string $table, array $values): string */ protected function _updateBatch(string $table, array $values, string $index): string { - $ids = []; + $ids = []; + $final = []; foreach ($values as $val) { $ids[] = $val[$index]; @@ -319,13 +320,15 @@ protected function _updateBatch(string $table, array $values, string $index): st { if ($field !== $index) { + $final[$field] = $final[$field] ?? []; + $final[$field][] = "WHEN {$val[$index]} THEN {$val[$field]}"; } } } $cases = ''; - foreach ($final as $k => $v) // @phpstan-ignore-line + foreach ($final as $k => $v) { $cases .= "{$k} = (CASE {$index}\n" . implode("\n", $v) @@ -383,11 +386,11 @@ protected function _truncate(string $table): string * * @see https://www.postgresql.org/docs/9.2/static/functions-matching.html * - * @param string|null $prefix - * @param string $column - * @param string|null $not - * @param string $bind - * @param boolean $insensitiveSearch + * @param string|null $prefix + * @param string $column + * @param string|null $not + * @param string $bind + * @param boolean $insensitiveSearch * * @return string $like_statement */ diff --git a/system/Database/Query.php b/system/Database/Query.php index e548dfc189d1..ba84ac47e055 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -315,13 +315,13 @@ public function getOriginalQuery(): string * * @return void * - * @see https://regex101.com/r/EUEhay/1 Test + * @see https://regex101.com/r/EUEhay/4 */ protected function compileBinds() { $sql = $this->finalQueryString; - $hasNamedBinds = preg_match('/:[a-z\d.)_(]+:/i', $sql) === 1; + $hasNamedBinds = preg_match('/:((?!=).+):/', $sql) === 1; if (empty($this->binds) || empty($this->bindMarker) diff --git a/system/Database/SQLSRV/Connection.php b/system/Database/SQLSRV/Connection.php index c7b6b1da28e8..0e039a005669 100755 --- a/system/Database/SQLSRV/Connection.php +++ b/system/Database/SQLSRV/Connection.php @@ -11,7 +11,6 @@ namespace CodeIgniter\Database\SQLSRV; -use CodeIgniter\Database\Query; use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; use Exception; @@ -130,12 +129,11 @@ public function connect(bool $persistent = false) unset($connection['UID'], $connection['PWD']); } + sqlsrv_configure('WarningsReturnAsErrors', 0); $this->connID = sqlsrv_connect($this->hostname, $connection); if ($this->connID !== false) { - sqlsrv_configure('WarningsReturnAsErrors', 0); - // Determine how identifiers are escaped $query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi'); $query = $query->getResultObject(); diff --git a/system/Database/SQLSRV/Result.php b/system/Database/SQLSRV/Result.php index b3ae7f975393..f5e771e5e614 100755 --- a/system/Database/SQLSRV/Result.php +++ b/system/Database/SQLSRV/Result.php @@ -20,13 +20,6 @@ */ class Result extends BaseResult { - /** - * Row offset - * - * @var integer - */ - private $rowOffset = 0; - //-------------------------------------------------------------------- /** @@ -106,7 +99,7 @@ public function getFieldData(): array $retVal[$i] = new stdClass(); $retVal[$i]->name = $field['Name']; $retVal[$i]->type = $field['Type']; - $retVal[$i]->type_name = isset($dataTypes[$field['Type']]) ? $dataTypes[$field['Type']] : null; + $retVal[$i]->type_name = $dataTypes[$field['Type']] ?? null; $retVal[$i]->max_length = $field['Size']; } diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index 3bdf1fa8d18a..4547ad7c5937 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -11,7 +11,6 @@ namespace CodeIgniter\Database\SQLite3; -use CodeIgniter\Database\Query; use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; use ErrorException; diff --git a/system/Database/SQLite3/Result.php b/system/Database/SQLite3/Result.php index 7d1948b0bb18..bd07eacff21f 100644 --- a/system/Database/SQLite3/Result.php +++ b/system/Database/SQLite3/Result.php @@ -76,7 +76,7 @@ public function getFieldData(): array $retVal[$i]->name = $this->resultID->columnName($i); // @phpstan-ignore-line $type = $this->resultID->columnType($i); // @phpstan-ignore-line $retVal[$i]->type = $type; - $retVal[$i]->type_name = isset($dataTypes[$type]) ? $dataTypes[$type] : null; + $retVal[$i]->type_name = $dataTypes[$type] ?? null; $retVal[$i]->max_length = null; $retVal[$i]->length = null; } diff --git a/system/Database/SQLite3/Table.php b/system/Database/SQLite3/Table.php index 296810a121d4..1183f4a2ef01 100644 --- a/system/Database/SQLite3/Table.php +++ b/system/Database/SQLite3/Table.php @@ -301,12 +301,8 @@ protected function copyData() foreach ($this->fields as $name => $details) { - $newFields[] = isset($details['new_name']) - // Are we modifying the column? - ? $details['new_name'] - : $name; - - $exFields[] = $name; + $newFields[] = $details['new_name'] ?? $name; + $exFields[] = $name; } $exFields = implode(', ', $exFields); @@ -336,9 +332,9 @@ protected function formatFields($fields) foreach ($fields as $field) { $return[$field->name] = [ - 'type' => $field->type, - 'default' => $field->default, - 'nullable' => $field->nullable, + 'type' => $field->type, + 'default' => $field->default, + 'null' => $field->nullable, ]; if ($field->primary_key) diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php index 4e9b2a7b266f..6d32702f40e4 100644 --- a/system/Database/Seeder.php +++ b/system/Database/Seeder.php @@ -127,25 +127,16 @@ public static function faker(): ?Generator */ public function call(string $class) { - if (empty($class)) + $class = trim($class); + + if ($class === '') { throw new InvalidArgumentException('No seeder was specified.'); } - $path = str_replace('.php', '', $class) . '.php'; - - // If we have namespaced class, simply try to load it. - if (strpos($class, '\\') !== false) + if (strpos($class, '\\') === false) { - /** - * @var Seeder - */ - $seeder = new $class($this->config); - } - // Otherwise, try to load the class manually. - else - { - $path = $this->seedPath . $path; + $path = $this->seedPath . str_replace('.php', '', $class) . '.php'; if (! is_file($path)) { @@ -160,14 +151,13 @@ public function call(string $class) { require_once $path; } - - /** - * @var Seeder - */ - $seeder = new $class($this->config); // @codeCoverageIgnoreEnd } + /** + * @var Seeder + */ + $seeder = new $class($this->config); $seeder->setSilent($this->silent)->run(); unset($seeder); diff --git a/system/Email/Email.php b/system/Email/Email.php index a3c3b274da9a..36720ba99c59 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -1272,6 +1272,8 @@ protected function buildMessage() return; case 'html': + $boundary = uniqid('B_ALT_', true); + if ($this->sendMultipart === false) { $hdr .= 'Content-Type: text/html; charset=' @@ -1280,8 +1282,6 @@ protected function buildMessage() } else { - $boundary = uniqid('B_ALT_', true); - $hdr .= 'Content-Type: multipart/alternative; boundary="' . $boundary . '"'; $body .= $this->getMimeMessage() . $this->newline . $this->newline . '--' . $boundary . $this->newline @@ -1306,7 +1306,7 @@ protected function buildMessage() if ($this->sendMultipart !== false) { - $this->finalBody .= '--' . $boundary . '--'; // @phpstan-ignore-line + $this->finalBody .= '--' . $boundary . '--'; } return; @@ -1432,7 +1432,7 @@ protected function appendAttachments(&$body, $boundary, $multipart = null) { continue; } - $name = isset($attachment['name'][1]) ? $attachment['name'][1] : basename($attachment['name'][0]); + $name = $attachment['name'][1] ?? basename($attachment['name'][0]); $body .= '--' . $boundary . $this->newline . 'Content-Type: ' . $attachment['type'] . '; name="' . $name . '"' . $this->newline . 'Content-Disposition: ' . $attachment['disposition'] . ';' . $this->newline @@ -2184,13 +2184,16 @@ protected function sendCommand($cmd, $data = '') $this->sendData('QUIT'); $resp = 221; break; + + default: + $resp = null; } $reply = $this->getSMTPData(); $this->debugMessage[] = '
' . $cmd . ': ' . $reply . '
'; - if ((int) static::substr($reply, 0, 3) !== $resp) // @phpstan-ignore-line + if ($resp === null || ((int) static::substr($reply, 0, 3) !== $resp)) { $this->setErrorMessage(lang('Email.SMTPError', [$reply])); @@ -2278,6 +2281,8 @@ protected function sendData($data) { $data .= $this->newline; + $result = null; + for ($written = $timestamp = 0, $length = static::strlen($data); $written < $length; $written += $result) { if (($result = fwrite($this->SMTPConnect, static::substr($data, $written))) === false) @@ -2307,7 +2312,7 @@ protected function sendData($data) $timestamp = 0; } - if ($result === false) // @phpstan-ignore-line + if (! is_int($result)) { $this->setErrorMessage(lang('Email.SMTPDataFailure', [$data])); diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 0e96943a3bef..1ba881fd78c9 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -358,7 +358,7 @@ public function enableFilter(string $name, string $when = 'before') // Get parameters and clean name if (strpos($name, ':') !== false) { - list($name, $params) = explode(':', $name); + [$name, $params] = explode(':', $name); $params = explode(',', $params); array_walk($params, function (&$item) { diff --git a/system/Format/XMLFormatter.php b/system/Format/XMLFormatter.php index 1e46f6ab70b8..249f916c4308 100644 --- a/system/Format/XMLFormatter.php +++ b/system/Format/XMLFormatter.php @@ -63,15 +63,15 @@ protected function arrayToXML(array $data, &$output) { foreach ($data as $key => $value) { + $key = $this->normalizeXMLTag($key); + if (is_array($value)) { - $key = $this->normalizeXMLTag($key); $subnode = $output->addChild("$key"); $this->arrayToXML($value, $subnode); } else { - $key = $this->normalizeXMLTag($key); $output->addChild("$key", htmlspecialchars("$value")); } } diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 35fb535e5c8e..55797e06c646 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -347,7 +347,8 @@ protected function prepareURL(string $url): string $uri = $this->baseURI->resolveRelativeURI($url); - return (string) $uri; + // Create the string instead of casting to prevent baseURL muddling + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 560b6523e86c..9d3e72277ab5 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -14,7 +14,6 @@ use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\HTTP\Files\FileCollection; use CodeIgniter\HTTP\Files\UploadedFile; -use CodeIgniter\HTTP\URI; use Config\App; use Config\Services; use InvalidArgumentException; @@ -270,7 +269,7 @@ protected function parseRequestURI(): string foreach (explode('/', $_SERVER['SCRIPT_NAME']) as $i => $segment) { // If these segments are not the same then we're done - if ($segment !== $segments[$i]) + if (! isset($segments[$i]) || $segment !== $segments[$i]) { break; } diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 4dea868d8a25..fb2608f3239e 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -391,8 +391,8 @@ public function matchTypes(array $acceptable, array $supported): bool { // PHPDocumentor v2 cannot parse yet the shorter list syntax, // causing no API generation for the file. - list($aType, $aSubType) = explode('/', $acceptable['value']); - list($sType, $sSubType) = explode('/', $supported['value']); + [$aType, $aSubType] = explode('/', $acceptable['value']); + [$sType, $sSubType] = explode('/', $supported['value']); // If the types don't match, we're done. if ($aType !== $sType) diff --git a/system/HTTP/RedirectResponse.php b/system/HTTP/RedirectResponse.php index 50061c8925b0..4f31e901e1fe 100644 --- a/system/HTTP/RedirectResponse.php +++ b/system/HTTP/RedirectResponse.php @@ -36,7 +36,7 @@ public function to(string $uri, int $code = null, string $method = 'auto') // for better security. if (strpos($uri, 'http') !== 0) { - $uri = (string) current_url(true)->resolveRelativeURI($uri); + $uri = site_url($uri); } return $this->redirect($uri, $method, $code); diff --git a/system/HTTP/RequestTrait.php b/system/HTTP/RequestTrait.php index 0f4de37f9ba8..2c9a0f963e9f 100644 --- a/system/HTTP/RequestTrait.php +++ b/system/HTTP/RequestTrait.php @@ -60,7 +60,7 @@ public function getIPAddress(): string /** * @deprecated $this->proxyIPs property will be removed in the future */ - $proxyIPs = isset($this->proxyIPs) ? $this->proxyIPs : config('App')->proxyIPs; + $proxyIPs = $this->proxyIPs ?? config('App')->proxyIPs; if (! empty($proxyIPs) && ! is_array($proxyIPs)) { $proxyIPs = explode(',', str_replace(' ', '', $proxyIPs)); diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php index bfa493b99987..c1f2ed0e5bf3 100644 --- a/system/HTTP/ResponseInterface.php +++ b/system/HTTP/ResponseInterface.php @@ -38,32 +38,29 @@ interface ResponseInterface * From https://en.wikipedia.org/wiki/List_of_HTTP_status_codes */ // Informational - const HTTP_CONTINUE = 100; - const HTTP_SWITCHING_PROTOCOLS = 101; - const HTTP_PROCESSING = 102; - const HTTP_EARLY_HINTS = 103; - // Success - const HTTP_OK = 200; - const HTTP_CREATED = 201; - const HTTP_ACCEPTED = 202; - const HTTP_NONAUTHORITATIVE_INFORMATION = 203; - const HTTP_NO_CONTENT = 204; - const HTTP_RESET_CONTENT = 205; - const HTTP_PARTIAL_CONTENT = 206; - const HTTP_MULTI_STATUS = 207; - const HTTP_ALREADY_REPORTED = 208; - const HTTP_IM_USED = 226; - // Redirection - const HTTP_MULTIPLE_CHOICES = 300; - const HTTP_MOVED_PERMANENTLY = 301; - const HTTP_FOUND = 302; - const HTTP_SEE_OTHER = 303; - const HTTP_NOT_MODIFIED = 304; - const HTTP_USE_PROXY = 305; - const HTTP_SWITCH_PROXY = 306; - const HTTP_TEMPORARY_REDIRECT = 307; - const HTTP_PERMANENT_REDIRECT = 308; - // Client Error + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; + const HTTP_EARLY_HINTS = 103; + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NONAUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; + const HTTP_ALREADY_REPORTED = 208; + const HTTP_IM_USED = 226; + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_SWITCH_PROXY = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENT_REDIRECT = 308; const HTTP_BAD_REQUEST = 400; const HTTP_UNAUTHORIZED = 401; const HTTP_PAYMENT_REQUIRED = 402; @@ -94,7 +91,6 @@ interface ResponseInterface const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; const HTTP_CLIENT_CLOSED_REQUEST = 499; - // Server Error const HTTP_INTERNAL_SERVER_ERROR = 500; const HTTP_NOT_IMPLEMENTED = 501; const HTTP_BAD_GATEWAY = 502; diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 4eb1f39f0e73..3fc44343f73a 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -21,15 +21,11 @@ class URI { /** * Sub-delimiters used in query strings and fragments. - * - * @const string */ const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; /** * Unreserved characters used in paths, query strings, and fragments. - * - * @const string */ const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php index ddbbcf5dcccb..57355370b55d 100644 --- a/system/Helpers/filesystem_helper.php +++ b/system/Helpers/filesystem_helper.php @@ -75,6 +75,59 @@ function directory_map(string $sourceDir, int $directoryDepth = 0, bool $hidden // ------------------------------------------------------------------------ +if (! function_exists('directory_mirror')) +{ + /** + * Recursively copies the files and directories of the origin directory + * into the target directory, i.e. "mirror" its contents. + * + * @param string $originDir + * @param string $targetDir + * @param boolean $overwrite Whether individual files overwrite on collision + * + * @return void + * + * @throws InvalidArgumentException + */ + function directory_mirror(string $originDir, string $targetDir, bool $overwrite = true): void + { + if (! is_dir($originDir = rtrim($originDir, '\\/'))) + { + throw new InvalidArgumentException(sprintf('The origin directory "%s" was not found.', $originDir)); + } + + if (! is_dir($targetDir = rtrim($targetDir, '\\/'))) + { + @mkdir($targetDir, 0755, true); + } + + $dirLen = strlen($originDir); + + /** + * @var SplFileInfo $file + */ + foreach (new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($originDir, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::SELF_FIRST + ) as $file) + { + $origin = $file->getPathname(); + $target = $targetDir . substr($origin, $dirLen); + + if ($file->isDir()) + { + mkdir($target, 0755); + } + elseif (! is_file($target) || ($overwrite && is_file($target))) + { + copy($origin, $target); + } + } + } +} + +// ------------------------------------------------------------------------ + if (! function_exists('write_file')) { /** @@ -159,12 +212,12 @@ function delete_files(string $path, bool $delDir = false, bool $htdocs = false, $isDir = $object->isDir(); if ($isDir && $delDir) { - @rmdir($object->getPathname()); + rmdir($object->getPathname()); continue; } if (! $isDir) { - @unlink($object->getPathname()); + unlink($object->getPathname()); } } } @@ -264,7 +317,7 @@ function get_dir_file_info(string $sourceDir, bool $topLevelOnly = true, bool $r try { - $fp = @opendir($sourceDir); { + $fp = opendir($sourceDir); { // reset the array and make sure $source_dir has a trailing slash on the initial call if ($recursion === false) { @@ -321,6 +374,8 @@ function get_file_info(string $file, $returnedValues = ['name', 'server_path', ' return null; } + $fileInfo = []; + if (is_string($returnedValues)) { $returnedValues = explode(',', $returnedValues); @@ -356,7 +411,7 @@ function get_file_info(string $file, $returnedValues = ['name', 'server_path', ' } } - return $fileInfo; // @phpstan-ignore-line + return $fileInfo; } } @@ -448,6 +503,24 @@ function octal_permissions(int $perms): string // ------------------------------------------------------------------------ +if (! function_exists('same_file')) +{ + /** + * Checks if two files both exist and have identical hashes + * + * @param string $file1 + * @param string $file2 + * + * @return boolean Same or not + */ + function same_file(string $file1, string $file2): bool + { + return is_file($file1) && is_file($file2) && md5_file($file1) === md5_file($file2); + } +} + +// ------------------------------------------------------------------------ + if (! function_exists('set_realpath')) { /** diff --git a/system/Helpers/test_helper.php b/system/Helpers/test_helper.php index c6f9bebe7577..e051d9d44eda 100644 --- a/system/Helpers/test_helper.php +++ b/system/Helpers/test_helper.php @@ -9,6 +9,7 @@ * file that was distributed with this source code. */ +use CodeIgniter\Model; use CodeIgniter\Test\Fabricator; /** diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php index eebe3ca17dd8..0e3baed7e434 100755 --- a/system/Helpers/text_helper.php +++ b/system/Helpers/text_helper.php @@ -122,7 +122,7 @@ function ascii_to_entities(string $str): string */ if (count($temp) === 1) { - $out .= '&#' . array_shift($temp) . ';'; + $out .= '&#' . array_shift($temp) . ';'; $count = 1; } @@ -140,9 +140,9 @@ function ascii_to_entities(string $str): string if (count($temp) === $count) { $number = ($count === 3) ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) : (($temp[0] % 32) * 64) + ($temp[1] % 64); - $out .= '&#' . $number . ';'; - $count = 1; - $temp = []; + $out .= '&#' . $number . ';'; + $count = 1; + $temp = []; } // If this is the last iteration, just output whatever we have elseif ($i === $s) @@ -486,7 +486,7 @@ function word_wrap(string $str, int $charlim = 76): string } // Trim the word down $temp .= mb_substr($line, 0, $charlim - 1); - $line = mb_substr($line, $charlim - 1); + $line = mb_substr($line, $charlim - 1); } // If $temp contains data it means we had to split up an over-length @@ -811,14 +811,14 @@ function excerpt(string $text, string $phrase = null, int $radius = 100, string $phrasePos = stripos($text, $phrase); $phraseLen = strlen($phrase); } - elseif (! isset($phrase)) + else { $phrasePos = $radius / 2; $phraseLen = 1; } - $pre = explode(' ', substr($text, 0, $phrasePos)); // @phpstan-ignore-line - $pos = explode(' ', substr($text, $phrasePos + $phraseLen)); // @phpstan-ignore-line + $pre = explode(' ', substr($text, 0, $phrasePos)); + $pos = explode(' ', substr($text, $phrasePos + $phraseLen)); $prev = ' '; $post = ' '; diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index f58045ae73a7..2424a98c0291 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -205,20 +205,9 @@ function previous_url(bool $returnObject = false) */ function uri_string(bool $relative = false): string { - $request = Services::request(); - $uri = $request->uri; - - // An absolute path is equivalent to getPath() - if (! $relative) - { - return $uri->getPath(); - } - - // Remove the baseURL from the entire URL - $url = (string) $uri->__toString(); - $baseURL = rtrim($request->config->baseURL, '/ ') . '/'; - - return substr($url, strlen($baseURL)); + return $relative + ? ltrim(Services::request()->getPath(), '/') + : Services::request()->getUri()->getPath(); } } diff --git a/system/I18n/Time.php b/system/I18n/Time.php index 87697c673488..c50f8f4691b4 100644 --- a/system/I18n/Time.php +++ b/system/I18n/Time.php @@ -758,7 +758,7 @@ public function setSecond($value) */ protected function setValue(string $name, $value) { - list($year, $month, $day, $hour, $minute, $second) = explode('-', $this->format('Y-n-j-G-i-s')); + [$year, $month, $day, $hour, $minute, $second] = explode('-', $this->format('Y-n-j-G-i-s')); $$name = $value; return Time::create( diff --git a/system/Images/Handlers/BaseHandler.php b/system/Images/Handlers/BaseHandler.php index 9eb007a6c647..b721bce21a0f 100644 --- a/system/Images/Handlers/BaseHandler.php +++ b/system/Images/Handlers/BaseHandler.php @@ -638,14 +638,14 @@ public function fit(int $width, int $height = null, string $position = 'center') $origWidth = $this->image()->origWidth; $origHeight = $this->image()->origHeight; - list($cropWidth, $cropHeight) = $this->calcAspectRatio($width, $height, $origWidth, $origHeight); + [$cropWidth, $cropHeight] = $this->calcAspectRatio($width, $height, $origWidth, $origHeight); if (is_null($height)) { $height = ceil(($width / $cropWidth) * $cropHeight); } - list($x, $y) = $this->calcCropCoords($cropWidth, $cropHeight, $origWidth, $origHeight, $position); + [$x, $y] = $this->calcCropCoords($cropWidth, $cropHeight, $origWidth, $origHeight, $position); return $this->crop($cropWidth, $cropHeight, $x, $y) ->resize($width, $height); diff --git a/system/Images/Handlers/ImageMagickHandler.php b/system/Images/Handlers/ImageMagickHandler.php index b43863aa64ec..53dc66e97fbc 100644 --- a/system/Images/Handlers/ImageMagickHandler.php +++ b/system/Images/Handlers/ImageMagickHandler.php @@ -234,10 +234,11 @@ protected function process(string $action, int $quality = 100): array $this->config->libraryPath = rtrim($this->config->libraryPath, '/') . '/convert'; } - $cmd = $this->config->libraryPath; + $cmd = $this->config->libraryPath; $cmd .= $action === '-version' ? ' ' . $action : ' -quality ' . $quality . ' ' . $action; $retval = 1; + $output = []; // exec() might be disabled if (function_usable('exec')) { @@ -250,7 +251,7 @@ protected function process(string $action, int $quality = 100): array throw ImageException::forImageProcessFailed(); } - return $output; // @phpstan-ignore-line + return $output; } //-------------------------------------------------------------------- @@ -449,7 +450,7 @@ protected function _text(string $text, array $options = []) // Color if (isset($options['color'])) { - list($r, $g, $b) = sscanf("#{$options['color']}", '#%02x%02x%02x'); + [$r, $g, $b] = sscanf("#{$options['color']}", '#%02x%02x%02x'); $cmd .= " -fill 'rgba({$r},{$g},{$b},{$options['opacity']})'"; } diff --git a/system/Language/Language.php b/system/Language/Language.php index cb66922cd339..be66b8aab5b5 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -115,15 +115,15 @@ public function getLine(string $line, array $args = []) // Parse out the file name and the actual alias. // Will load the language file and strings. - list($file, $parsedLine) = $this->parseLine($line, $this->locale); + [$file, $parsedLine] = $this->parseLine($line, $this->locale); $output = $this->getTranslationOutput($this->locale, $file, $parsedLine); if ($output === null && strpos($this->locale, '-')) { - list($locale) = explode('-', $this->locale, 2); + [$locale] = explode('-', $this->locale, 2); - list($file, $parsedLine) = $this->parseLine($line, $locale); + [$file, $parsedLine] = $this->parseLine($line, $locale); $output = $this->getTranslationOutput($locale, $file, $parsedLine); } @@ -131,7 +131,7 @@ public function getLine(string $line, array $args = []) // if still not found, try English if ($output === null) { - list($file, $parsedLine) = $this->parseLine($line, 'en'); + [$file, $parsedLine] = $this->parseLine($line, 'en'); $output = $this->getTranslationOutput('en', $file, $parsedLine); } diff --git a/system/Log/Handlers/ChromeLoggerHandler.php b/system/Log/Handlers/ChromeLoggerHandler.php index b248072aa829..21a1bd71f04c 100644 --- a/system/Log/Handlers/ChromeLoggerHandler.php +++ b/system/Log/Handlers/ChromeLoggerHandler.php @@ -26,8 +26,6 @@ class ChromeLoggerHandler extends BaseHandler { /** * Version of this library - for ChromeLogger use. - * - * @var float */ const VERSION = 1.0; @@ -87,9 +85,7 @@ public function __construct(array $config = []) { parent::__construct($config); - $request = Services::request(null, true); - - $this->json['request_uri'] = (string) $request->uri; + $this->json['request_uri'] = current_url(); } //-------------------------------------------------------------------- diff --git a/system/Log/Handlers/FileHandler.php b/system/Log/Handlers/FileHandler.php index 8b608b3692fe..362758f07581 100644 --- a/system/Log/Handlers/FileHandler.php +++ b/system/Log/Handlers/FileHandler.php @@ -112,6 +112,8 @@ public function handle($level, $message): bool flock($fp, LOCK_EX); + $result = null; + for ($written = 0, $length = strlen($msg); $written < $length; $written += $result) { if (($result = fwrite($fp, substr($msg, $written))) === false) @@ -131,7 +133,7 @@ public function handle($level, $message): bool chmod($filepath, $this->filePermissions); } - return is_int($result); // @phpstan-ignore-line + return is_int($result); } //-------------------------------------------------------------------- diff --git a/system/Log/Logger.php b/system/Log/Logger.php index d3054ef0f6ed..c457545e75cf 100644 --- a/system/Log/Logger.php +++ b/system/Log/Logger.php @@ -409,7 +409,7 @@ protected function interpolate($message, array $context = []) // Allow us to log the file/line that we are logging from if (strpos($message, '{file}') !== false) { - list($file, $line) = $this->determineFile(); + [$file, $line] = $this->determineFile(); $replace['{file}'] = $file; $replace['{line}'] = $line; diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php index 3220d127e7e5..7b15b317e62f 100644 --- a/system/Pager/Pager.php +++ b/system/Pager/Pager.php @@ -147,12 +147,11 @@ public function makeLinks(int $page, ?int $perPage, int $total, string $template */ protected function displayLinks(string $group, string $template): string { - $pager = new PagerRenderer($this->getDetails($group)); - if (! array_key_exists($template, $this->config->templates)) { throw PagerException::forInvalidTemplate($template); } + $pager = new PagerRenderer($this->getDetails($group)); return $this->view->setVar('pager', $pager) ->render($this->config->templates[$template]); @@ -377,7 +376,7 @@ public function getPageURI(int $page = null, string $group = 'default', bool $re $uri->setQueryArray($query); } - return $returnObject === true ? $uri : (string) $uri; + return $returnObject === true ? $uri : URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- diff --git a/system/Pager/PagerRenderer.php b/system/Pager/PagerRenderer.php index 37f289e9affe..f1a45992b3a3 100644 --- a/system/Pager/PagerRenderer.php +++ b/system/Pager/PagerRenderer.php @@ -156,7 +156,7 @@ public function getPrevious() $uri->setSegment($this->segment, $this->first - 1); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- @@ -200,7 +200,7 @@ public function getNext() $uri->setSegment($this->segment, $this->last + 1); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- @@ -223,7 +223,7 @@ public function getFirst(): string $uri->setSegment($this->segment, 1); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- @@ -246,7 +246,7 @@ public function getLast(): string $uri->setSegment($this->segment, $this->pageCount); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- @@ -269,7 +269,7 @@ public function getCurrent(): string $uri->setSegment($this->segment, $this->current); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- @@ -290,8 +290,9 @@ public function links(): array for ($i = $this->first; $i <= $this->last; $i ++) { + $uri = $this->segment === 0 ? $uri->addQuery($this->pageSelector, $i) : $uri->setSegment($this->segment, $i); $links[] = [ - 'uri' => (string) ($this->segment === 0 ? $uri->addQuery($this->pageSelector, $i) : $uri->setSegment($this->segment, $i)), + 'uri' => URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()), 'title' => (int) $i, 'active' => ($i === $this->current), ]; @@ -359,7 +360,7 @@ public function getPreviousPage() $uri->setSegment($this->segment, $this->current - 1); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } //-------------------------------------------------------------------- @@ -401,7 +402,7 @@ public function getNextPage() $uri->setSegment($this->segment, $this->current + 1); } - return (string) $uri; + return URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath(), $uri->getQuery(), $uri->getFragment()); } /** diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 4cd4f207c173..b7d586e48907 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -13,7 +13,6 @@ use Closure; use CodeIgniter\Autoloader\FileLocator; -use CodeIgniter\HTTP\Request; use CodeIgniter\Router\Exceptions\RouterException; use Config\Modules; use Config\Services; diff --git a/system/Router/Router.php b/system/Router/Router.php index 1980bda6e40e..dcadd8b832e2 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -716,7 +716,7 @@ protected function setRequest(array $segments = []) return; } - list($controller, $method) = array_pad(explode('::', $segments[0]), 2, null); + [$controller, $method] = array_pad(explode('::', $segments[0]), 2, null); $this->controller = $controller; diff --git a/system/Session/Handlers/FileHandler.php b/system/Session/Handlers/FileHandler.php index 17ee5bf46671..dc28cd4f9822 100644 --- a/system/Session/Handlers/FileHandler.php +++ b/system/Session/Handlers/FileHandler.php @@ -240,6 +240,8 @@ public function write($sessionID, $sessionData): bool if (($length = strlen($sessionData)) > 0) { + $result = null; + for ($written = 0; $written < $length; $written += $result) { if (($result = fwrite($this->fileHandle, substr($sessionData, $written))) === false) @@ -248,7 +250,7 @@ public function write($sessionID, $sessionData): bool } } - if (! is_int($result)) // @phpstan-ignore-line + if (! is_int($result)) { $this->fingerprint = md5(substr($sessionData, 0, $written)); $this->logger->error('Session: Unable to write data.'); diff --git a/system/Session/Session.php b/system/Session/Session.php index e6cab173b263..5532dc7d48fa 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -187,7 +187,9 @@ public function __construct(SessionHandlerInterface $driver, App $config) $this->cookieSecure = $config->cookieSecure ?? $this->cookieSecure; $this->cookieSameSite = $config->cookieSameSite ?? $this->cookieSameSite; - /** @var CookieConfig */ + /** + * @var CookieConfig + */ $cookie = config('Cookie'); $this->cookie = new Cookie($this->sessionCookieName, '', [ @@ -512,7 +514,7 @@ public function set($data, $value = null) */ public function get(string $key = null) { - if (! empty($key) && (! is_null($value = isset($_SESSION[$key]) ? $_SESSION[$key] : null) || ! is_null($value = dot_array_search($key, $_SESSION ?? [])))) + if (! empty($key) && (! is_null($value = $_SESSION[$key] ?? null) || ! is_null($value = dot_array_search($key, $_SESSION ?? [])))) { return $value; } diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index 95fea0843fba..c651aaa8d81d 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -14,6 +14,7 @@ use CodeIgniter\CodeIgniter; use CodeIgniter\Config\Factories; use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\MigrationRunner; use CodeIgniter\Database\Seeder; use CodeIgniter\Events\Events; use CodeIgniter\Router\RouteCollection; diff --git a/system/Test/ControllerResponse.php b/system/Test/ControllerResponse.php index 8e7cea612875..2365c35ed069 100644 --- a/system/Test/ControllerResponse.php +++ b/system/Test/ControllerResponse.php @@ -46,7 +46,7 @@ class ControllerResponse extends TestResponse */ public function __construct() { - parent::__construct($response ?? Services::response()); + parent::__construct(Services::response()); $this->dom = &$this->domParser; } diff --git a/system/Test/ControllerTestTrait.php b/system/Test/ControllerTestTrait.php index 83a520e7bc05..47810e519df3 100644 --- a/system/Test/ControllerTestTrait.php +++ b/system/Test/ControllerTestTrait.php @@ -19,6 +19,7 @@ use Config\App; use Config\Services; use InvalidArgumentException; +use Psr\Log\LoggerInterface; use Throwable; /** diff --git a/system/Test/ControllerTester.php b/system/Test/ControllerTester.php index 078d26093547..391873ac436f 100644 --- a/system/Test/ControllerTester.php +++ b/system/Test/ControllerTester.php @@ -18,6 +18,7 @@ use Config\App; use Config\Services; use InvalidArgumentException; +use Psr\Log\LoggerInterface; use Throwable; /** diff --git a/system/Test/DOMParser.php b/system/Test/DOMParser.php index ab8616756d34..4938fdfed0b7 100644 --- a/system/Test/DOMParser.php +++ b/system/Test/DOMParser.php @@ -296,7 +296,7 @@ public function parseSelector(string $selector) // ID? if ($pos = strpos($selector, '#') !== false) { - list($tag, $id) = explode('#', $selector); + [$tag, $id] = explode('#', $selector); } // Attribute elseif (strpos($selector, '[') !== false && strpos($selector, ']') !== false) @@ -311,7 +311,7 @@ public function parseSelector(string $selector) $text = explode(',', $text); $text = trim(array_shift($text)); - list($name, $value) = explode('=', $text); + [$name, $value] = explode('=', $text); $name = trim($name); $value = trim($value); $attr = [$name => trim($value, '] ')]; @@ -319,7 +319,7 @@ public function parseSelector(string $selector) // Class? elseif ($pos = strpos($selector, '.') !== false) { - list($tag, $class) = explode('.', $selector); + [$tag, $class] = explode('.', $selector); } // Otherwise, assume the entire string is our tag else diff --git a/system/Test/DatabaseTestTrait.php b/system/Test/DatabaseTestTrait.php index 0d2949af4806..2c8ba23a795d 100644 --- a/system/Test/DatabaseTestTrait.php +++ b/system/Test/DatabaseTestTrait.php @@ -12,10 +12,7 @@ namespace CodeIgniter\Test; use CodeIgniter\Database\BaseBuilder; -use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; -use CodeIgniter\Database\MigrationRunner; -use CodeIgniter\Database\Seeder; use CodeIgniter\Test\Constraints\SeeInDatabase; use Config\Database; use Config\Migrations; diff --git a/system/Test/Fabricator.php b/system/Test/Fabricator.php index ef2294e0acbf..885f2ef42e10 100644 --- a/system/Test/Fabricator.php +++ b/system/Test/Fabricator.php @@ -43,7 +43,7 @@ class Fabricator /** * Model instance (can be non-framework if it follows framework design) * - * @var CodeIgniter\Model|object + * @var Model|object */ protected $model; diff --git a/system/Test/Mock/MockServices.php b/system/Test/Mock/MockServices.php index 3fa1a142aea0..58fac0c1ad68 100644 --- a/system/Test/Mock/MockServices.php +++ b/system/Test/Mock/MockServices.php @@ -11,7 +11,7 @@ namespace CodeIgniter\Test\Mock; -use \CodeIgniter\Config\BaseService; +use CodeIgniter\Config\BaseService; use CodeIgniter\Autoloader\FileLocator; class MockServices extends BaseService diff --git a/system/Test/TestResponse.php b/system/Test/TestResponse.php index 4c7a655457c8..a1bacdaacd2c 100644 --- a/system/Test/TestResponse.php +++ b/system/Test/TestResponse.php @@ -16,7 +16,6 @@ use CodeIgniter\HTTP\ResponseInterface; use Config\Services; use Exception; -use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; /** diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index 44e77a5502a1..9f9e23d0762c 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -130,7 +130,7 @@ public function greater_than_equal_to(string $str = null, string $min): bool public function is_not_unique(string $str = null, string $field, array $data): bool { // Grab any data for exclusion of a single row. - list($field, $whereField, $whereValue) = array_pad(explode(',', $field), 3, null); + [$field, $whereField, $whereValue] = array_pad(explode(',', $field), 3, null); // Break the table and field apart sscanf($field, '%[^.].%[^.]', $table, $field); @@ -186,7 +186,7 @@ public function in_list(string $value = null, string $list): bool public function is_unique(string $str = null, string $field, array $data): bool { // Grab any data for exclusion of a single row. - list($field, $ignoreField, $ignoreValue) = array_pad(explode(',', $field), 3, null); + [$field, $ignoreField, $ignoreValue] = array_pad(explode(',', $field), 3, null); // Break the table and field apart sscanf($field, '%[^.].%[^.]', $table, $field); diff --git a/system/View/Cell.php b/system/View/Cell.php index acb58de77735..8f753e68bae8 100644 --- a/system/View/Cell.php +++ b/system/View/Cell.php @@ -78,7 +78,7 @@ public function __construct(CacheInterface $cache) */ public function render(string $library, $params = null, int $ttl = 0, string $cacheName = null): string { - list($class, $method) = $this->determineClass($library); + [$class, $method] = $this->determineClass($library); // Is it cached? $cacheName = ! empty($cacheName) @@ -194,7 +194,7 @@ public function prepareParams($params) { if (! empty($p)) { - list($key, $val) = explode('=', $p); + [$key, $val] = explode('=', $p); $newParams[trim($key)] = trim($val, ', '); } } @@ -228,7 +228,7 @@ protected function determineClass(string $library): array // by default, so convert any double colons. $library = str_replace('::', ':', $library); - list($class, $method) = explode(':', $library); + [$class, $method] = explode(':', $library); if (empty($class)) { diff --git a/system/View/Parser.php b/system/View/Parser.php index 9bfb937067ca..b3cef4b4a1c1 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -101,16 +101,13 @@ public function render(string $view, array $options = null, bool $saveData = nul $fileExt = pathinfo($view, PATHINFO_EXTENSION); $view = empty($fileExt) ? $view . '.php' : $view; // allow Views as .html, .tpl, etc (from CI3) + $cacheName = $options['cache_name'] ?? str_replace('.php', '', $view); + // Was it cached? - if (isset($options['cache'])) + if (isset($options['cache']) && ($output = cache($cacheName))) { - $cacheName = $options['cache_name'] ?? str_replace('.php', '', $view); - - if ($output = cache($cacheName)) - { - $this->logPerformance($start, microtime(true), $view); - return $output; - } + $this->logPerformance($start, microtime(true), $view); + return $output; } $file = $this->viewPath . $view; @@ -143,7 +140,7 @@ public function render(string $view, array $options = null, bool $saveData = nul // Should we cache? if (isset($options['cache'])) { - cache()->save($cacheName, $output, (int) $options['cache']); // @phpstan-ignore-line + cache()->save($cacheName, $output, (int) $options['cache']); } $this->tempData = null; return $output; diff --git a/system/View/Table.php b/system/View/Table.php index aa1ae3659e24..ec10e75c79e7 100644 --- a/system/View/Table.php +++ b/system/View/Table.php @@ -320,7 +320,7 @@ public function generate($tableData = null) } } - $out .= $temp . (isset($heading['data']) ? $heading['data'] : '') . $this->template['heading_cell_end']; + $out .= $temp . ($heading['data'] ?? '') . $this->template['heading_cell_end']; } $out .= $this->template['heading_row_end'] . $this->newline . $this->template['thead_close'] . $this->newline; @@ -352,7 +352,7 @@ public function generate($tableData = null) } } - $cell = isset($cell['data']) ? $cell['data'] : ''; + $cell = $cell['data'] ?? ''; $out .= $temp; if ($cell === '' || $cell === null) @@ -394,7 +394,7 @@ public function generate($tableData = null) } } - $out .= $temp . (isset($footing['data']) ? $footing['data'] : '') . $this->template['footing_cell_end']; + $out .= $temp . ($footing['data'] ?? '') . $this->template['footing_cell_end']; } $out .= $this->template['footing_row_end'] . $this->newline . $this->template['tfoot_close'] . $this->newline; diff --git a/tests/system/Autoloader/AutoloaderTest.php b/tests/system/Autoloader/AutoloaderTest.php index e8476fabdbf9..c96f60adac58 100644 --- a/tests/system/Autoloader/AutoloaderTest.php +++ b/tests/system/Autoloader/AutoloaderTest.php @@ -172,19 +172,9 @@ public function testRemoveNamespace() $this->assertFalse((bool) $this->loader->loadClass('My\App\AutoloaderTest')); } - public function testloadClassConfigFound() + public function testloadClassNonNamespaced() { - $this->loader->addNamespace('Config', APPPATH . 'Config'); - $this->assertSame( - APPPATH . 'Config' . DIRECTORY_SEPARATOR . 'Modules.php', - $this->loader->loadClass('Modules') - ); - } - - public function testloadClassConfigNotFound() - { - $this->loader->addNamespace('Config', APPPATH . 'Config'); - $this->assertFalse($this->loader->loadClass('NotFound')); + $this->assertFalse($this->loader->loadClass('Modules')); } //-------------------------------------------------------------------- diff --git a/tests/system/CLI/CLITest.php b/tests/system/CLI/CLITest.php index 3b67821d229f..85b2ded4f007 100644 --- a/tests/system/CLI/CLITest.php +++ b/tests/system/CLI/CLITest.php @@ -1,10 +1,12 @@ -assertInstanceOf(FileHandler::class, $this->fileHandler); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testGet() { $this->fileHandler->save(self::$key1, 'value', 2); @@ -106,6 +111,12 @@ public function testGet() $this->assertNull($this->fileHandler->get(self::$key1)); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testRemember() { $this->fileHandler->remember(self::$key1, 2, function () { @@ -295,8 +306,6 @@ public function modeProvider() ]; } - //-------------------------------------------------------------------- - public function testFileHandler() { $fileHandler = new BaseTestFileHandler(); @@ -316,7 +325,6 @@ public function testFileHandler() final class BaseTestFileHandler extends FileHandler { - private static $directory = 'FileHandler'; private $config; @@ -344,5 +352,4 @@ public function getFileInfoTest() 'fileperms', ]); } - } diff --git a/tests/system/Cache/Handlers/MemcachedHandlerTest.php b/tests/system/Cache/Handlers/MemcachedHandlerTest.php index ff3ab1027942..82232c3061ae 100644 --- a/tests/system/Cache/Handlers/MemcachedHandlerTest.php +++ b/tests/system/Cache/Handlers/MemcachedHandlerTest.php @@ -7,7 +7,7 @@ use Config\Cache; use Exception; -class MemcachedHandlerTest extends CIUnitTestCase +final class MemcachedHandlerTest extends CIUnitTestCase { private $memcachedHandler; private static $key1 = 'key1'; @@ -32,10 +32,6 @@ protected function setUp(): void $this->config = new Cache(); $this->memcachedHandler = new MemcachedHandler($this->config); - if (! $this->memcachedHandler->isSupported()) - { - $this->markTestSkipped('Not support memcached and memcache'); - } $this->memcachedHandler->initialize(); } @@ -53,6 +49,12 @@ public function testNew() $this->assertInstanceOf(MemcachedHandler::class, $this->memcachedHandler); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testGet() { $this->memcachedHandler->save(self::$key1, 'value', 2); @@ -64,6 +66,12 @@ public function testGet() $this->assertNull($this->memcachedHandler->get(self::$key1)); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testRemember() { $this->memcachedHandler->remember(self::$key1, 2, function () { diff --git a/tests/system/Cache/Handlers/PredisHandlerTest.php b/tests/system/Cache/Handlers/PredisHandlerTest.php index 960098b76121..23e294ddb18a 100644 --- a/tests/system/Cache/Handlers/PredisHandlerTest.php +++ b/tests/system/Cache/Handlers/PredisHandlerTest.php @@ -6,7 +6,7 @@ use CodeIgniter\Test\CIUnitTestCase; use Config\Cache; -class PredisHandlerTest extends CIUnitTestCase +final class PredisHandlerTest extends CIUnitTestCase { private $PredisHandler; private static $key1 = 'key1'; @@ -32,10 +32,6 @@ protected function setUp(): void $this->config = new Cache(); $this->PredisHandler = new PredisHandler($this->config); - if (! $this->PredisHandler->isSupported()) - { - $this->markTestSkipped('Not support Predis'); - } $this->PredisHandler->initialize(); } @@ -61,6 +57,12 @@ public function testDestruct() $this->assertInstanceOf(PRedisHandler::class, $this->PredisHandler); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testGet() { $this->PredisHandler->save(self::$key1, 'value', 2); @@ -72,6 +74,12 @@ public function testGet() $this->assertNull($this->PredisHandler->get(self::$key1)); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testRemember() { $this->PredisHandler->remember(self::$key1, 2, function () { diff --git a/tests/system/Cache/Handlers/RedisHandlerTest.php b/tests/system/Cache/Handlers/RedisHandlerTest.php index a2ab6a463780..36cab2d0c0d4 100644 --- a/tests/system/Cache/Handlers/RedisHandlerTest.php +++ b/tests/system/Cache/Handlers/RedisHandlerTest.php @@ -6,7 +6,7 @@ use CodeIgniter\Test\CIUnitTestCase; use Config\Cache; -class RedisHandlerTest extends CIUnitTestCase +final class RedisHandlerTest extends CIUnitTestCase { private $redisHandler; private static $key1 = 'key1'; @@ -32,10 +32,6 @@ protected function setUp(): void $this->config = new Cache(); $this->redisHandler = new RedisHandler($this->config); - if (! $this->redisHandler->isSupported()) - { - $this->markTestSkipped('Not support redis'); - } $this->redisHandler->initialize(); } @@ -61,6 +57,12 @@ public function testDestruct() $this->assertInstanceOf(RedisHandler::class, $this->redisHandler); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testGet() { $this->redisHandler->save(self::$key1, 'value', 2); @@ -72,6 +74,12 @@ public function testGet() $this->assertNull($this->redisHandler->get(self::$key1)); } + /** + * This test waits for 3 seconds before last assertion so this + * is naturally a "slow" test on the perspective of the default limit. + * + * @timeLimit 3.5 + */ public function testRemember() { $this->redisHandler->remember(self::$key1, 2, function () { diff --git a/tests/system/Commands/EnvironmentCommandTest.php b/tests/system/Commands/EnvironmentCommandTest.php new file mode 100644 index 000000000000..0f30f98868be --- /dev/null +++ b/tests/system/Commands/EnvironmentCommandTest.php @@ -0,0 +1,86 @@ +streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter'); + $this->streamFilter = stream_filter_append(STDERR, 'CITestStreamFilter'); + + if (is_file($this->envPath)) + { + rename($this->envPath, $this->backupEnvPath); + } + } + + protected function tearDown(): void + { + parent::tearDown(); + stream_filter_remove($this->streamFilter); + + if (is_file($this->envPath)) + { + unlink($this->envPath); + } + + if (is_file($this->backupEnvPath)) + { + rename($this->backupEnvPath, $this->envPath); + } + + $_SERVER['CI_ENVIRONMENT'] = $_ENV['CI_ENVIRONMENT'] = ENVIRONMENT; + } + + public function testUsingCommandWithNoArgumentsGivesCurrentEnvironment(): void + { + command('env'); + $this->assertStringContainsString('testing', CITestStreamFilter::$buffer); + $this->assertStringContainsString(ENVIRONMENT, CITestStreamFilter::$buffer); + } + + public function testProvidingTestingAsEnvGivesErrorMessage(): void + { + command('env testing'); + $this->assertStringContainsString('The "testing" environment is reserved for PHPUnit testing.', CITestStreamFilter::$buffer); + } + + public function testProvidingUnknownEnvGivesErrorMessage(): void + { + command('env foobar'); + $this->assertStringContainsString('Invalid environment type "foobar".', CITestStreamFilter::$buffer); + } + + public function testDefaultShippedEnvIsMissing() + { + rename(ROOTPATH . 'env', ROOTPATH . 'lostenv'); + command('env development'); + rename(ROOTPATH . 'lostenv', ROOTPATH . 'env'); + + $this->assertStringContainsString('Both default shipped', CITestStreamFilter::$buffer); + $this->assertStringContainsString('It is impossible to write the new environment type.', CITestStreamFilter::$buffer); + } + + public function testSettingNewEnvIsSuccess(): void + { + // default env file has `production` env in it + $_SERVER['CI_ENVIRONMENT'] = 'production'; + command('env development'); + + $this->assertStringContainsString('Environment is successfully changed to', CITestStreamFilter::$buffer); + $this->assertStringContainsString('CI_ENVIRONMENT = development', file_get_contents($this->envPath)); + } +} diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php index 0a92d5a01642..62aa4182ac60 100644 --- a/tests/system/CommonFunctionsTest.php +++ b/tests/system/CommonFunctionsTest.php @@ -507,4 +507,10 @@ public function testHelperLoadsOnce() $this->assertFalse($exception); Services::reset(); } + + public function testIsCli() + { + $this->assertIsBool(is_cli()); + $this->assertTrue(is_cli()); + } } diff --git a/tests/system/Database/Live/DbUtilsTest.php b/tests/system/Database/Live/DbUtilsTest.php index eff9d5abf8f8..1d3b6a9d083b 100644 --- a/tests/system/Database/Live/DbUtilsTest.php +++ b/tests/system/Database/Live/DbUtilsTest.php @@ -79,7 +79,7 @@ public function testUtilsListDatabases() { $databases = $util->listDatabases(); - $this->assertTrue(in_array('test', $databases, true)); + $this->assertTrue(in_array($this->db->getDatabase(), $databases, true)); } elseif ($this->db->DBDriver === 'SQLite3') { @@ -98,7 +98,7 @@ public function testUtilsDatabaseExist() if (in_array($this->db->DBDriver, ['MySQLi', 'Postgre', 'SQLSRV'], true)) { - $exist = $util->databaseExists('test'); + $exist = $util->databaseExists($this->db->getDatabase()); $this->assertTrue($exist); } @@ -107,7 +107,7 @@ public function testUtilsDatabaseExist() $this->expectException(DatabaseException::class); $this->expectExceptionMessage('Unsupported feature of the database platform you are using.'); - $util->databaseExists('test'); + $util->databaseExists($this->db->getDatabase()); } } diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 9383b46af2bb..c7ea8fc4510b 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -249,8 +249,16 @@ public function testCreateTableWithArrayFieldConstraints() public function testCreateTableWithNullableFieldsGivesNullDataType(): void { $this->forge->addField([ - 'id' => ['type' => 'INT', 'constraint' => 11, 'auto_increment' => true], - 'name' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true], + 'id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'auto_increment' => true, + ], + 'name' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => true, + ], ]); $createTable = $this->getPrivateMethodInvoker($this->forge, '_createTable'); @@ -620,7 +628,13 @@ public function testAddFields() $this->assertEquals($fieldsData[0]->type, 'int'); $this->assertEquals($fieldsData[1]->type, 'varchar'); - $this->assertEquals($fieldsData[0]->max_length, 11); + if (version_compare($this->db->getVersion(), '8.0.17', '<')) + { + // As of MySQL 8.0.17, the display width attribute for integer data types + // is deprecated and is not reported back anymore. + // @see https://dev.mysql.com/doc/refman/8.0/en/numeric-type-attributes.html + $this->assertEquals($fieldsData[0]->max_length, 11); + } $this->assertNull($fieldsData[0]->default); $this->assertNull($fieldsData[1]->default); diff --git a/tests/system/Database/Live/SQLite/AlterTableTest.php b/tests/system/Database/Live/SQLite/AlterTableTest.php index 8d8f5387329e..05842cbdf53c 100644 --- a/tests/system/Database/Live/SQLite/AlterTableTest.php +++ b/tests/system/Database/Live/SQLite/AlterTableTest.php @@ -83,17 +83,17 @@ public function testFromTableFillsDetails() $this->assertCount(4, $fields); $this->assertTrue(array_key_exists('id', $fields)); $this->assertNull($fields['id']['default']); - $this->assertTrue($fields['id']['nullable']); + $this->assertTrue($fields['id']['null']); $this->assertEquals('integer', strtolower($fields['id']['type'])); $this->assertTrue(array_key_exists('name', $fields)); $this->assertNull($fields['name']['default']); - $this->assertFalse($fields['name']['nullable']); + $this->assertFalse($fields['name']['null']); $this->assertEquals('varchar', strtolower($fields['name']['type'])); $this->assertTrue(array_key_exists('email', $fields)); $this->assertNull($fields['email']['default']); - $this->assertTrue($fields['email']['nullable']); + $this->assertTrue($fields['email']['null']); $this->assertEquals('varchar', strtolower($fields['email']['type'])); $keys = $this->getPrivateProperty($this->table, 'keys'); diff --git a/tests/system/Debug/TimerTest.php b/tests/system/Debug/TimerTest.php index 34ec2c1e572a..d637c27a35e7 100644 --- a/tests/system/Debug/TimerTest.php +++ b/tests/system/Debug/TimerTest.php @@ -1,27 +1,17 @@ assertGreaterThanOrEqual(1.0, $timers['test1']['duration']); } - //-------------------------------------------------------------------- - + /** + * @timeLimit 1.5 + */ public function testAutoCalcsTimerEnd() { $timer = new Timer(); @@ -59,8 +50,9 @@ public function testAutoCalcsTimerEnd() $this->assertGreaterThanOrEqual(1.0, $timers['test1']['duration']); } - //-------------------------------------------------------------------- - + /** + * @timeLimit 1.5 + */ public function testElapsedTimeGivesSameResultAsTimersArray() { $timer = new Timer(); @@ -76,8 +68,6 @@ public function testElapsedTimeGivesSameResultAsTimersArray() $this->assertEquals($expected, $timer->getElapsedTime('test1')); } - //-------------------------------------------------------------------- - public function testThrowsExceptionStoppingNonTimer() { $this->expectException('RunTimeException'); @@ -87,8 +77,6 @@ public function testThrowsExceptionStoppingNonTimer() $timer->stop('test1'); } - //-------------------------------------------------------------------- - public function testLongExecutionTime() { $timer = new Timer(); @@ -96,8 +84,6 @@ public function testLongExecutionTime() $this->assertCloseEnough(11 * 60, $timer->getElapsedTime('longjohn')); } - //-------------------------------------------------------------------- - public function testLongExecutionTimeThroughCommonFunc() { $timer = new Timer(); @@ -105,8 +91,9 @@ public function testLongExecutionTimeThroughCommonFunc() $this->assertCloseEnough(11 * 60, $timer->getElapsedTime('longjohn')); } - //-------------------------------------------------------------------- - + /** + * @timeLimit 1.5 + */ public function testCommonStartStop() { timer('test1'); @@ -116,14 +103,10 @@ public function testCommonStartStop() $this->assertGreaterThanOrEqual(1.0, timer()->getElapsedTime('test1')); } - //-------------------------------------------------------------------- - public function testReturnsNullGettingElapsedTimeOfNonTimer() { $timer = new Timer(); $this->assertNull($timer->getElapsedTime('test1')); } - - //-------------------------------------------------------------------- } diff --git a/tests/system/HTTP/CURLRequestTest.php b/tests/system/HTTP/CURLRequestTest.php index e1ccd5fb2f43..003b8298acc7 100644 --- a/tests/system/HTTP/CURLRequestTest.php +++ b/tests/system/HTTP/CURLRequestTest.php @@ -33,6 +33,24 @@ protected function getRequest(array $options = []) //-------------------------------------------------------------------- + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/4707 + */ + public function testPrepareURLIgnoresAppConfig() + { + config('App')->baseURL = 'http://example.com/fruit/'; + + $request = $this->getRequest([ + 'base_uri' => 'http://example.com/v1/', + ]); + + $method = $this->getPrivateMethodInvoker($request, 'prepareURL'); + + $this->assertEquals('http://example.com/v1/bananas', $method('bananas')); + } + + //-------------------------------------------------------------------- + /** * @see https://github.com/codeigniter4/CodeIgniter4/issues/1029 */ diff --git a/tests/system/HTTP/RedirectResponseTest.php b/tests/system/HTTP/RedirectResponseTest.php index 3e5c211127ef..a154b6fce3f1 100644 --- a/tests/system/HTTP/RedirectResponseTest.php +++ b/tests/system/HTTP/RedirectResponseTest.php @@ -90,7 +90,7 @@ public function testRedirectRelativeConvertsToFullURI() $response = $response->to('/foo'); $this->assertTrue($response->hasHeader('Location')); - $this->assertEquals('http://example.com/foo', $response->getHeaderLine('Location')); + $this->assertEquals('http://example.com/index.php/foo', $response->getHeaderLine('Location')); } //-------------------------------------------------------------------- diff --git a/tests/system/HTTP/ResponseCookieTest.php b/tests/system/HTTP/ResponseCookieTest.php index 16a90316f8c0..aaa92a57871a 100644 --- a/tests/system/HTTP/ResponseCookieTest.php +++ b/tests/system/HTTP/ResponseCookieTest.php @@ -117,9 +117,9 @@ public function testCookieHTTPOnly() $response->setCookie('foo', 'bar'); $cookie = $response->getCookie('foo'); - $this->assertTrue($cookie->isHTTPOnly()); + $this->assertFalse($cookie->isHTTPOnly()); - $response->setCookie(['name' => 'bee', 'value' => 'bop', 'httponly' => false]); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'httponly' => true]); $cookie = $response->getCookie('bee'); $this->assertTrue($cookie->isHTTPOnly()); } diff --git a/tests/system/Helpers/FilesystemHelperTest.php b/tests/system/Helpers/FilesystemHelperTest.php index a30df24a197e..324e670b4f14 100644 --- a/tests/system/Helpers/FilesystemHelperTest.php +++ b/tests/system/Helpers/FilesystemHelperTest.php @@ -6,6 +6,9 @@ use InvalidArgumentException; use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; +use org\bovigo\vfs\visitor\vfsStreamStructureVisitor; + class FilesystemHelperTest extends CIUnitTestCase { @@ -104,6 +107,58 @@ public function testDirectoryMapHandlesNotfound() //-------------------------------------------------------------------- + public function testDirectoryMirror() + { + $this->assertTrue(function_exists('directory_mirror')); + + // Create a subdirectory + $this->structure['foo']['bam'] = ['zab' => 'A deep file']; + + $vfs = vfsStream::setup('root', null, $this->structure); + $root = rtrim(vfsStream::url('root') . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + directory_mirror($root . 'foo', $root . 'boo'); + + $this->assertFileExists($root . 'boo/bar'); + $this->assertFileExists($root . 'boo/bam/zab'); + } + + public function testDirectoryMirrorOverwrites() + { + $this->assertTrue(function_exists('directory_mirror')); + + // Create duplicate files + $this->structure['foo']['far'] = 'all your base'; + $this->structure['foo']['faz'] = 'are belong to us'; + + $vfs = vfsStream::setup('root', null, $this->structure); + $root = rtrim(vfsStream::url('root') . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + directory_mirror($root . 'foo', $root . 'boo', true); + $result = file_get_contents($root . 'boo/faz'); + + $this->assertEquals($this->structure['foo']['faz'], $result); + } + + public function testDirectoryMirrorNotOverwrites() + { + $this->assertTrue(function_exists('directory_mirror')); + + // Create duplicate files + $this->structure['foo']['far'] = 'all your base'; + $this->structure['foo']['faz'] = 'are belong to us'; + + $vfs = vfsStream::setup('root', null, $this->structure); + $root = rtrim(vfsStream::url('root') . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + directory_mirror($root . 'foo', $root . 'boo', false); + $result = file_get_contents($root . 'boo/faz'); + + $this->assertEquals($this->structure['boo']['faz'], $result); + } + + //-------------------------------------------------------------------- + public function testWriteFileSuccess() { $vfs = vfsStream::setup('root'); @@ -379,6 +434,49 @@ public function testGetFileNotThereInfo() } //-------------------------------------------------------------------- + + public function testSameFileSame() + { + $file1 = SUPPORTPATH . 'Files/able/apple.php'; + $file2 = SUPPORTPATH . 'Files/able/apple.php'; + + $this->assertTrue(same_file($file1, $file2)); + } + + public function testSameFileIdentical() + { + $file1 = SUPPORTPATH . 'Files/able/apple.php'; + $file2 = SUPPORTPATH . 'Files/baker/banana.php'; + + $this->assertTrue(same_file($file1, $file2)); + } + + public function testSameFileDifferent() + { + $file1 = SUPPORTPATH . 'Files/able/apple.php'; + $file2 = SUPPORTPATH . 'Images/ci-logo.gif'; + + $this->assertFalse(same_file($file1, $file2)); + } + + public function testSameFileOrder() + { + $file1 = SUPPORTPATH . 'Files/able/apple.php'; + $file2 = SUPPORTPATH . 'Images/ci-logo.gif'; + + $this->assertFalse(same_file($file2, $file1)); + } + + public function testSameFileDirectory() + { + $file1 = SUPPORTPATH . 'Files/able/apple.php'; + $file2 = SUPPORTPATH . 'Images/'; + + $this->assertFalse(same_file($file1, $file2)); + } + + //-------------------------------------------------------------------- + public function testOctalPermissions() { $this->assertEquals('777', octal_permissions(0777)); diff --git a/tests/system/Helpers/URLHelper/CurrentUrlTest.php b/tests/system/Helpers/URLHelper/CurrentUrlTest.php index 895f8318ecbd..44e5a260211e 100644 --- a/tests/system/Helpers/URLHelper/CurrentUrlTest.php +++ b/tests/system/Helpers/URLHelper/CurrentUrlTest.php @@ -153,7 +153,6 @@ public function testUriStringAbsolute() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('/assets/image.jpg', uri_string()); } @@ -167,7 +166,6 @@ public function testUriStringRelative() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('assets/image.jpg', uri_string(true)); } @@ -182,7 +180,6 @@ public function testUriStringNoTrailingSlashAbsolute() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('/assets/image.jpg', uri_string()); } @@ -197,7 +194,6 @@ public function testUriStringNoTrailingSlashRelative() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('assets/image.jpg', uri_string(true)); } @@ -208,7 +204,6 @@ public function testUriStringEmptyAbsolute() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('/', uri_string()); } @@ -219,7 +214,6 @@ public function testUriStringEmptyRelative() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('', uri_string(true)); } @@ -234,15 +228,14 @@ public function testUriStringSubfolderAbsolute() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('/subfolder/assets/image.jpg', uri_string()); } public function testUriStringSubfolderRelative() { $_SERVER['HTTP_HOST'] = 'example.com'; - $_SERVER['REQUEST_URI'] = '/assets/image.jpg'; $_SERVER['REQUEST_URI'] = '/subfolder/assets/image.jpg'; + $_SERVER['SCRIPT_NAME'] = '/subfolder/index.php'; $this->config->baseURL = 'http://example.com/subfolder/'; $request = Services::request($this->config); @@ -250,7 +243,6 @@ public function testUriStringSubfolderRelative() Services::injectMock('request', $request); - $url = current_url(); $this->assertEquals('assets/image.jpg', uri_string(true)); } @@ -304,7 +296,8 @@ public function urlIsProvider() */ public function testUrlIs(string $currentPath, string $testPath, bool $expected) { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/' . $currentPath; $request = Services::request(); $request->uri = new URI('http://example.com/' . $currentPath); @@ -319,6 +312,7 @@ public function testUrlIs(string $currentPath, string $testPath, bool $expected) public function testUrlIsNoIndex(string $currentPath, string $testPath, bool $expected) { $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/' . $currentPath; $this->config->indexPage = ''; $request = Services::request($this->config); @@ -333,8 +327,10 @@ public function testUrlIsNoIndex(string $currentPath, string $testPath, bool $ex */ public function testUrlIsWithSubfolder(string $currentPath, string $testPath, bool $expected) { - $_SERVER['HTTP_HOST'] = 'example.com'; - $this->config->baseURL = 'http://example.com/subfolder/'; + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/' . $currentPath; + $_SERVER['SCRIPT_NAME'] = '/subfolder/index.php'; + $this->config->baseURL = 'http://example.com/subfolder/'; $request = Services::request($this->config); $request->uri = new URI('http://example.com/subfolder/' . $currentPath); diff --git a/user_guide_src/source/_static/css/citheme.css b/user_guide_src/source/_static/css/citheme.css index 733c5db71979..146b7d377686 100644 --- a/user_guide_src/source/_static/css/citheme.css +++ b/user_guide_src/source/_static/css/citheme.css @@ -95,20 +95,6 @@ body, p, legend { background-size: 200px; } -/* -- Breakpoint for large screens (> 900px) --------- */ -@media (min-width: 900px) and (max-width: 1100px) { - .wy-nav-content { - max-width: 1620px; - } -} - -@media (min-width: 1100px) { - .wy-nav-content { - margin: 0 auto; - max-width: 1620px; - } -} - /* Titles ------------------------------------------------------------------- */ h1, h2, h3, h4, h5, h6 { diff --git a/user_guide_src/source/changelogs/index.rst b/user_guide_src/source/changelogs/index.rst index 51e2e2c0ebc9..038d259e4189 100644 --- a/user_guide_src/source/changelogs/index.rst +++ b/user_guide_src/source/changelogs/index.rst @@ -12,6 +12,7 @@ See all the changes. .. toctree:: :titlesonly: + v4.1.4 v4.1.3 v4.1.2 v4.1.1 diff --git a/user_guide_src/source/changelogs/v4.1.3.rst b/user_guide_src/source/changelogs/v4.1.3.rst index 379371ced12c..d041b784a9b9 100644 --- a/user_guide_src/source/changelogs/v4.1.3.rst +++ b/user_guide_src/source/changelogs/v4.1.3.rst @@ -1,6 +1,25 @@ Version 4.1.3 ============= -Release Date: Not released +Release Date: June 6, 2021 **4.1.3 release of CodeIgniter4** + +Enhancements: + +- New functions in the File Helper: ``directory_mirror()`` and ``same_file()`` +- Implemented NexusPHP's ``Tachycardia`` for slow test identification +- Added a new ``$ttl`` option to ``Cache`` config for future use + +Changes: + +- Added MySQL 8.0 to the test matrix +- Improved environment detection from ``$_SERVER`` +- Numerous sweeping code improvements via Rector and analysis + +Bugs Fixed: + +- Fixed a bug where ``CURLRequest`` would try to use a project URI instead of its base +- Fixed a bug where CLI mode was not detected under ``cgi-fcgi`` +- Fixed a logic bug in Cookie construction +- Fixed numerous issues in SQLite3's ``Forge`` class related to an incorrect attribute name diff --git a/user_guide_src/source/changelogs/v4.1.4.rst b/user_guide_src/source/changelogs/v4.1.4.rst new file mode 100644 index 000000000000..e431b242c06b --- /dev/null +++ b/user_guide_src/source/changelogs/v4.1.4.rst @@ -0,0 +1,6 @@ +Version 4.1.4 +============= + +Release Date: Not released + +**4.1.4 release of CodeIgniter4** diff --git a/user_guide_src/source/conf.py b/user_guide_src/source/conf.py index c18880de0297..63fd4dcf46bc 100644 --- a/user_guide_src/source/conf.py +++ b/user_guide_src/source/conf.py @@ -24,7 +24,7 @@ version = '4.1' # The full version, including alpha/beta/rc tags. -release = '4.1.2' +release = '4.1.3' # -- General configuration --------------------------------------------------- diff --git a/user_guide_src/source/general/managing_apps.rst b/user_guide_src/source/general/managing_apps.rst index 3f412c8fa886..c1781e032fa0 100644 --- a/user_guide_src/source/general/managing_apps.rst +++ b/user_guide_src/source/general/managing_apps.rst @@ -23,14 +23,14 @@ they can find the ``Paths`` configuration file: - ``/spark`` runs command line apps; the path is specified on or about line 36:: - require realpath('app/Config/Paths.php') ?: 'app/Config/Paths.php'; - // ^^^ Change this if you move your application folder + $pathsConfig = 'app/Config/Paths.php'; + // ^^^ Change this line if you move your application folder - ``/public/index.php`` is the front controller for your webapp; the config path is specified on or about line 20:: - require realpath(FCPATH . '../app/Config/Paths.php') ?: FCPATH . '../app/Config/Paths.php'; + $pathsConfig = FCPATH . '../app/Config/Paths.php'; // ^^^ Change this if you move your application folder diff --git a/user_guide_src/source/helpers/array_helper.rst b/user_guide_src/source/helpers/array_helper.rst index c8b77c464474..3ccdee76dd6d 100644 --- a/user_guide_src/source/helpers/array_helper.rst +++ b/user_guide_src/source/helpers/array_helper.rst @@ -70,7 +70,7 @@ The following functions are available: // Returns: 23 $barBaz = dot_array_search('foo.bar\.baz', $data); - // Returns: 42 + // Returns: 43 $fooBar = dot_array_search('foo\.bar.baz', $data); diff --git a/user_guide_src/source/helpers/filesystem_helper.rst b/user_guide_src/source/helpers/filesystem_helper.rst index 3d55afec0cad..5cfffae21f33 100644 --- a/user_guide_src/source/helpers/filesystem_helper.rst +++ b/user_guide_src/source/helpers/filesystem_helper.rst @@ -26,7 +26,7 @@ The following functions are available: :param string $source_dir: Path to the source directory :param int $directory_depth: Depth of directories to traverse (0 = fully recursive, 1 = current dir, etc) - :param bool $hidden: Whether to include hidden directories + :param bool $hidden: Whether to include hidden paths :returns: An array of files :rtype: array @@ -42,8 +42,9 @@ The following functions are available: $map = directory_map('./mydirectory/', 1); - By default, hidden files will not be included in the returned array. To - override this behavior, you may set a third parameter to true (boolean):: + By default, hidden files will not be included in the returned array and + hidden directories will be skipped. To override this behavior, you may + set a third parameter to true (boolean):: $map = directory_map('./mydirectory/', FALSE, TRUE); @@ -79,6 +80,28 @@ The following functions are available: If no results are found, this will return an empty array. +.. php:function:: directory_mirror($original, $target[, $overwrite = true]) + + :param string $original: Original source directory + :param string $target: Target destination directory + :param bool $overwrite: Whether individual files overwrite on collision + + Recursively copies the files and directories of the origin directory + into the target directory, i.e. "mirror" its contents. + + Example:: + + try + {      + directory_mirror($uploadedImages, FCPATH . 'images/'); + } + catch (Throwable $e) + {      + echo 'Failed to export uploads!'; + } + + You can optionally change the overwrite behavior via the third parameter. + .. php:function:: write_file($path, $data[, $mode = 'wb']) :param string $path: File path @@ -216,6 +239,19 @@ The following functions are available: echo octal_permissions(fileperms('./index.php')); // 644 +.. php:function:: same_file($file1, $file2) + + :param string $file1: Path to the first file + :param string $file2: Path to the second file + :returns: Whether both files exist with identical hashes + :rtype: boolean + + Compares two files to see if they are the same (based on their MD5 hash). + + :: + + echo same_file($newFile, $oldFile) ? 'Same!' : 'Different!'; + .. php:function:: set_realpath($path[, $check_existence = FALSE]) :param string $path: Path diff --git a/user_guide_src/source/installation/upgrade_413.rst b/user_guide_src/source/installation/upgrade_413.rst new file mode 100644 index 000000000000..9be19f1542ab --- /dev/null +++ b/user_guide_src/source/installation/upgrade_413.rst @@ -0,0 +1,20 @@ +############################# +Upgrading from 4.1.1 to 4.1.2 +############################# + +**Cache TTL** + +There is a new value in **app/Config/Cache.php**: ``$ttl``. This is not used by framework +handlers where 60 seconds is hard-coded, but may be useful to projects and modules. +In a future release this value will replace the hard-coded version, so either leave this as +``60`` or stop relying on the hard-coded version. + +Project Files +============= + +Only a few files in the project space (root, app, public, writable) received updates. Due to +these files being outside of the system scope they will not be changed without your intervention. +The following files received changes and it is recommended that you merge the updated versions with your application: + +* ``app/Config/Cache.php`` +* ``spark`` diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst index c3d897c79363..333f0a0f8e66 100644 --- a/user_guide_src/source/installation/upgrading.rst +++ b/user_guide_src/source/installation/upgrading.rst @@ -8,6 +8,7 @@ upgrading from. .. toctree:: :titlesonly: + Upgrading from 4.1.2 to 4.1.3 Upgrading from 4.1.1 to 4.1.2 Upgrading from 4.0.5 to 4.1.0 or 4.1.1 Upgrading from 4.0.4 to 4.0.5 diff --git a/user_guide_src/source/libraries/caching.rst b/user_guide_src/source/libraries/caching.rst index 058dc824645c..87d213839f5f 100644 --- a/user_guide_src/source/libraries/caching.rst +++ b/user_guide_src/source/libraries/caching.rst @@ -59,6 +59,12 @@ more complex, multi-server setups. If you have more than one application using the same cache storage, you can add a custom prefix string here that is prepended to all key names. +**$ttl** + +The default number of seconds to save items when none is specified. +WARNING: This is not used by framework handlers where 60 seconds is hard-coded, but may be useful +to projects and modules. This will replace the hard-coded value in a future release. + **$file** This is an array of settings specific to the ``File`` handler to determine how it should save the cache files. diff --git a/user_guide_src/source/libraries/cookies.rst b/user_guide_src/source/libraries/cookies.rst index 2558681de17b..e935c56bb1e8 100644 --- a/user_guide_src/source/libraries/cookies.rst +++ b/user_guide_src/source/libraries/cookies.rst @@ -33,7 +33,7 @@ There are currently four (4) ways to create a new ``Cookie`` value object. use CodeIgniter\Cookie\Cookie; use DateTime; - // Throw the constructor + // Using the constructor $cookie = new Cookie( 'remember_token', 'f699c7fd18a8e082d0228932f3acd40e1ef5ef92efcedda32842a211d62f0aa6', @@ -79,7 +79,7 @@ instance or an array of defaults to the static ``Cookie::setDefaults()`` method. use CodeIgniter\Cookie\Cookie; use Config\Cookie as CookieConfig; - // pass in an App instance before constructing a Cookie class + // pass in an Config\Cookie instance before constructing a Cookie class Cookie::setDefaults(new CookieConfig()); $cookie = new Cookie('login_token'); @@ -456,11 +456,11 @@ Class Reference .. php:staticmethod:: setDefaults([$config = []]) - :param App|array $config: The configuration array or instance + :param \Config\Cookie|array $config: The configuration array or instance :rtype: array :returns: The old defaults - Set the default attributes to a Cookie instance by injecting the values from the ``App`` config or an array. + Set the default attributes to a Cookie instance by injecting the values from the ``\Config\Cookie`` config or an array. .. php:staticmethod:: fromHeaderString(string $header[, bool $raw = false]) diff --git a/user_guide_src/source/libraries/uri.rst b/user_guide_src/source/libraries/uri.rst index 69ba98d0b240..649685db71ec 100644 --- a/user_guide_src/source/libraries/uri.rst +++ b/user_guide_src/source/libraries/uri.rst @@ -61,6 +61,11 @@ using the URI class' static ``createURIString()`` method:: // Creates: http://exmample.com/some/path?foo=bar#first-heading echo URI::createURIString('http', 'example.com', 'some/path', 'foo=bar', 'first-heading'); +.. important:: When ``URI`` is cast to a string, it will attempt to adjust project URLs to the + settings defined in ``Config\App``. If you need the exact, unaltered string representation + then use ``URI::createURIString()`` instead. + + ============= The URI Parts ============= diff --git a/utils/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php b/utils/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php new file mode 100644 index 000000000000..67d153e68e24 --- /dev/null +++ b/utils/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php @@ -0,0 +1,68 @@ +betterNodeFinder->findParentType($node, TryCatch::class); + + // not in try catch + if (! $tryCatch instanceof TryCatch) + { + return null; + } + + $inStmts = (bool) $this->betterNodeFinder->findFirst((array) $tryCatch->stmts, static function (Node $n) use ($node) : bool { + return $n === $node; + }); + + // not in stmts, means it in catch or finally + if (! $inStmts) + { + return null; + } + + // in try { ... } stmts + return $node->expr; + } +} diff --git a/utils/Rector/RemoveVarTagFromClassConstantRector.php b/utils/Rector/RemoveVarTagFromClassConstantRector.php new file mode 100644 index 000000000000..0a4689fac17a --- /dev/null +++ b/utils/Rector/RemoveVarTagFromClassConstantRector.php @@ -0,0 +1,60 @@ +phpDocInfoFactory->createFromNodeOrEmpty($node); + $varTagValueNode = $phpDocInfo->getVarTagValueNode(); + if (! $varTagValueNode instanceof VarTagValueNode) + { + return null; + } + + $phpDocInfo->removeByType(VarTagValueNode::class); + return $node; + } +} diff --git a/utils/Rector/UnderscoreToCamelCaseVariableNameRector.php b/utils/Rector/UnderscoreToCamelCaseVariableNameRector.php index 5ba205ce43c2..0d670b617f26 100644 --- a/utils/Rector/UnderscoreToCamelCaseVariableNameRector.php +++ b/utils/Rector/UnderscoreToCamelCaseVariableNameRector.php @@ -24,7 +24,6 @@ final class UnderscoreToCamelCaseVariableNameRector extends AbstractRector { /** - * @var string * @see https://regex101.com/r/OtFn8I/1 */ private const PARAM_NAME_REGEX = '#(?@param\s.*\s+\$)(?%s)#ms';