diff --git a/.github/workflows/deploy-apidocs.yml b/.github/workflows/deploy-apidocs.yml index 55480f4fd5a4..41633244cb52 100644 --- a/.github/workflows/deploy-apidocs.yml +++ b/.github/workflows/deploy-apidocs.yml @@ -15,7 +15,7 @@ jobs: build: name: Deploy to api if: github.repository == 'codeigniter4/CodeIgniter4' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Setup credentials diff --git a/.github/workflows/test-autoreview.yml b/.github/workflows/test-autoreview.yml index 7e6ff05c2734..3d86065a407d 100644 --- a/.github/workflows/test-autoreview.yml +++ b/.github/workflows/test-autoreview.yml @@ -14,10 +14,14 @@ on: - '**.php' - .github/workflows/test-autoreview.yml +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: auto-review-tests: name: Automatic Code Review - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout @@ -30,13 +34,12 @@ jobs: coverage: none - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache dependencies uses: actions/cache@v3 with: - path: ${{ steps.composercache.outputs.dir }} + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- diff --git a/.github/workflows/test-coding-standards.yml b/.github/workflows/test-coding-standards.yml index 5fa13728b37d..99e6bd4a3b14 100644 --- a/.github/workflows/test-coding-standards.yml +++ b/.github/workflows/test-coding-standards.yml @@ -12,10 +12,14 @@ on: - 'spark' - '.github/workflows/test-coding-standards.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: lint: name: PHP ${{ matrix.php-version }} Lint with PHP CS Fixer - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -36,13 +40,12 @@ jobs: coverage: none - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache dependencies uses: actions/cache@v3 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-${{ matrix.php-version }}- diff --git a/.github/workflows/test-deptrac.yml b/.github/workflows/test-deptrac.yml index ff8551aa9500..8b9a598885de 100644 --- a/.github/workflows/test-deptrac.yml +++ b/.github/workflows/test-deptrac.yml @@ -24,10 +24,14 @@ on: - 'depfile.yaml' - '.github/workflows/test-deptrac.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: Architectural Inspection - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 @@ -43,16 +47,12 @@ jobs: run: composer validate --strict - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - name: Cache composer dependencies + - name: Cache dependencies uses: actions/cache@v3 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- diff --git a/.github/workflows/test-phpcpd.yml b/.github/workflows/test-phpcpd.yml index 967e49d9ad46..9fe3764dfcb1 100644 --- a/.github/workflows/test-phpcpd.yml +++ b/.github/workflows/test-phpcpd.yml @@ -23,10 +23,14 @@ on: - 'system/**.php' - '.github/workflows/test-phpcpd.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: Duplicate Code Detection - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/test-phpstan.yml b/.github/workflows/test-phpstan.yml index d723983d6d46..c892ef320d63 100644 --- a/.github/workflows/test-phpstan.yml +++ b/.github/workflows/test-phpstan.yml @@ -27,14 +27,16 @@ on: - '**.neon.dist' - '.github/workflows/test-phpstan.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: PHP ${{ matrix.php-versions }} Static Analysis - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false - matrix: - php-versions: ['8.0', '8.1'] steps: - name: Checkout uses: actions/checkout@v3 @@ -42,7 +44,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php-versions }} + php-version: '8.1' extensions: intl - name: Use latest Composer @@ -52,16 +54,12 @@ jobs: run: composer validate --strict - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - name: Cache composer dependencies + - name: Cache dependencies uses: actions/cache@v3 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index 7a7b72513070..253263e78884 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -27,10 +27,21 @@ on: - phpunit.xml.dist - .github/workflows/test-phpunit.yml +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + COVERAGE_PHP_VERSION: '8.1' + NLS_LANG: 'AMERICAN_AMERICA.UTF8' + NLS_DATE_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_TZ_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + jobs: tests: name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: "!contains(github.event.head_commit.message, '[ci skip]')" strategy: @@ -65,7 +76,7 @@ jobs: options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 mssql: - image: mcr.microsoft.com/mssql/server:2019-CU10-ubuntu-20.04 + image: mcr.microsoft.com/mssql/server:2022-latest env: SA_PASSWORD: 1Secure*Password1 ACCEPT_EULA: Y @@ -75,12 +86,18 @@ jobs: options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 oracle: - image: quillbuilduser/oracle-18-xe + image: gvenzl/oracle-xe:21 env: - ORACLE_ALLOW_REMOTE: true + ORACLE_RANDOM_PASSWORD: true + APP_USER: ORACLE + APP_USER_PASSWORD: ORACLE ports: - 1521:1521 - options: --health-cmd="/opt/oracle/product/18c/dbhomeXE/bin/sqlplus -s sys/Oracle18@oracledbxe/XE as sysdba <<< 'SELECT 1 FROM DUAL'" --health-interval=10s --health-timeout=5s --health-retries=3 + options: >- + --health-cmd healthcheck.sh + --health-interval 20s + --health-timeout 10s + --health-retries 10 redis: image: redis @@ -98,28 +115,6 @@ jobs: if: matrix.db-platforms == 'SQLSRV' run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" - - name: Install Oracle InstantClient - if: matrix.db-platforms == 'OCI8' - run: | - sudo apt-get install wget libaio1 alien - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm - sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm - sudo alien oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo dpkg -i oracle-instantclient18.5-basic_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-devel_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-sqlplus_18.5.0.0.0-4_amd64.deb - echo "LD_LIBRARY_PATH=/lib/oracle/18.5/client64/lib/" >> $GITHUB_ENV - echo "NLS_LANG=AMERICAN_AMERICA.UTF8" >> $GITHUB_ENV - echo "C_INCLUDE_PATH=/usr/include/oracle/18.5/client64" >> $GITHUB_ENV - echo 'NLS_DATE_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - echo 'NLS_TIMESTAMP_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - echo 'NLS_TIMESTAMP_TZ_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV - - - name: Create database for Oracle Database - if: matrix.db-platforms == 'OCI8' - run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba - - name: Checkout uses: actions/checkout@v3 @@ -129,25 +124,25 @@ jobs: php-version: ${{ matrix.php-versions }} tools: composer, pecl extensions: imagick, sqlsrv, gd, sqlite3, redis, memcached, oci8, pgsql - coverage: xdebug + coverage: ${{ env.COVERAGE_DRIVER }} env: update: true + COVERAGE_DRIVER: ${{ matrix.php-versions == env.COVERAGE_PHP_VERSION && 'xdebug' || 'none'}} - name: Install latest ImageMagick run: | sudo apt-get update - 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 --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 fonts-dejavu-core sudo apt-get install -y imagemagick sudo apt-get install --fix-broken - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache dependencies uses: actions/cache@v3 with: - path: ${{ steps.composercache.outputs.dir }} + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- @@ -155,19 +150,22 @@ jobs: run: | composer update --ansi --no-interaction composer remove --ansi --dev --unused -W -- rector/rector phpstan/phpstan friendsofphp/php-cs-fixer nexusphp/cs-config codeigniter/coding-standard - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} - - name: Profile slow tests in PHP 8.0 - if: matrix.php-versions == '8.0' + - name: Profile slow tests in PHP ${{ env.COVERAGE_PHP_VERSION }} + if: matrix.php-versions == env.COVERAGE_PHP_VERSION run: echo "TACHYCARDIA_MONITOR_GA=enabled" >> $GITHUB_ENV - name: Compute coverage option uses: actions/github-script@v6 id: phpunit-coverage-option with: - script: 'return "${{ matrix.php-versions }}" == "8.0" ? "" : "--no-coverage"' + script: | + const { COVERAGE_NAME } = process.env + + return "${{ matrix.php-versions }}" == "${{ env.COVERAGE_PHP_VERSION }}" ? `--coverage-php build/cov/coverage-${COVERAGE_NAME}.cov` : "--no-coverage" result-encoding: string + env: + COVERAGE_NAME: php-v${{ env.COVERAGE_PHP_VERSION }}-${{ matrix.db-platforms }} - name: Test with PHPUnit run: script -e -c "vendor/bin/phpunit --color=always --exclude-group=auto-review ${{ steps.phpunit-coverage-option.outputs.result }}" @@ -175,24 +173,61 @@ jobs: DB: ${{ matrix.db-platforms }} TERM: xterm-256color - - name: Run Coveralls - if: github.repository_owner == 'codeigniter4' && matrix.php-versions == '8.0' - run: | - composer global require --ansi php-coveralls/php-coveralls:^2.4 - php-coveralls --coverage_clover=build/logs/clover.xml -v + - name: Upload coverage file + if: matrix.php-versions == env.COVERAGE_PHP_VERSION + uses: actions/upload-artifact@v3 + with: + name: ${{ env.COVERAGE_NAME }} + path: build/cov/coverage-${{ env.COVERAGE_NAME }}.cov + if-no-files-found: error + retention-days: 1 env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} + COVERAGE_NAME: php-v${{ env.COVERAGE_PHP_VERSION }}-${{ matrix.db-platforms }} - coveralls-finish: + coveralls: if: github.repository_owner == 'codeigniter4' - needs: [tests] - runs-on: ubuntu-20.04 + needs: tests + runs-on: ubuntu-22.04 steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@master + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.COVERAGE_PHP_VERSION }} + tools: composer + coverage: xdebug + env: + update: true + + - name: Download coverage files + uses: actions/download-artifact@v3 + with: + path: build/cov + + - name: Display structure of downloaded files + run: ls -R + working-directory: build/cov + + - name: Get composer cache directory + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV + + - name: Cache dependencies + uses: actions/cache@v3 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer update --ansi --no-interaction + + - name: Merge coverage files + run: vendor/bin/phpcov merge --clover build/logs/clover.xml build/cov + + - name: Upload coverage to Coveralls + run: vendor/bin/php-coveralls --verbose --exclude-no-stmt --ansi + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-rector.yml b/.github/workflows/test-rector.yml index 9db9ea63af58..7d8c11d61362 100644 --- a/.github/workflows/test-rector.yml +++ b/.github/workflows/test-rector.yml @@ -29,10 +29,14 @@ on: - composer.json - rector.php +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: PHP ${{ matrix.php-versions }} Analyze code (Rector) on ${{ matrix.paths }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -59,16 +63,12 @@ jobs: run: composer validate --strict - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} + run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - name: Cache composer dependencies + - name: Cache dependencies uses: actions/cache@v3 with: - path: ${{ steps.composer-cache.outputs.dir }} + path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- diff --git a/.github/workflows/test-scss.yml b/.github/workflows/test-scss.yml index 3517a4f26500..797bd3e42ea9 100644 --- a/.github/workflows/test-scss.yml +++ b/.github/workflows/test-scss.yml @@ -19,10 +19,14 @@ on: - '**.css' - '.github/workflows/test-scss.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: name: Compilation of SCSS (Dart Sass) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout diff --git a/.github/workflows/test-userguide.yml b/.github/workflows/test-userguide.yml index e996fcbfefe4..d3cc4c9d25ea 100644 --- a/.github/workflows/test-userguide.yml +++ b/.github/workflows/test-userguide.yml @@ -10,10 +10,14 @@ on: - 'user_guide_src/**' - '.github/workflows/test-userguide.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: syntax_check: name: Check User Guide syntax - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 6a857588f64f..89605c9f92d2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -38,7 +38,11 @@ __DIR__ . '/spark', ]); -$overrides = []; +$overrides = [ + 'no_useless_concat_operator' => [ + 'juggle_simple_strings' => true, + ], +]; $options = [ 'cacheFile' => 'build/.php-cs-fixer.cache', diff --git a/.php-cs-fixer.no-header.php b/.php-cs-fixer.no-header.php index d8241678a771..1425b637706c 100644 --- a/.php-cs-fixer.no-header.php +++ b/.php-cs-fixer.no-header.php @@ -25,12 +25,15 @@ __DIR__ . '/public', ]) ->exclude(['Views/errors/html']) - ->notName('#Logger\.php$#') ->append([ __DIR__ . '/admin/starter/builds', ]); -$overrides = []; +$overrides = [ + 'no_useless_concat_operator' => [ + 'juggle_simple_strings' => true, + ], +]; $options = [ 'cacheFile' => 'build/.php-cs-fixer.no-header.cache', diff --git a/.php-cs-fixer.user-guide.php b/.php-cs-fixer.user-guide.php index 1fa41bc4fb42..7bb88ccc210a 100644 --- a/.php-cs-fixer.user-guide.php +++ b/.php-cs-fixer.user-guide.php @@ -33,6 +33,9 @@ 'php_unit_internal_class' => false, 'no_unused_imports' => false, 'class_attributes_separation' => false, + 'no_useless_concat_operator' => [ + 'juggle_simple_strings' => true, + ], ]; $options = [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 150b0cb5e4bf..65692f144275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [v4.2.8](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.8) (2022-10-30) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.7...v4.2.8) + +### Fixed Bugs +* Fix DotEnv class turning `export` to empty string by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6625 +* Remove unneeded `$logger` property in `Session` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6647 +* fix: Add missing CLIRequest::getCookie() by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6646 +* fix: routes registration bug by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6644 +* Bug: showError in CLI/BaseCommand use hardcoded error view path by @fpoy in https://github.com/codeigniter4/CodeIgniter4/pull/6657 +* fix: getGetPost() and getPostGet() when index is null by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6675 +* fix: add missing methods to BaseConnection by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6712 +* fix: bug that esc() accepts invalid context '0' by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6722 +* fix: [Postgres] reset binds when replace() method is called multiple times in the context by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6728 +* fix: [SQLSRV] _getResult() return object for preparedQuery class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6718 +* Fix error handler callback by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6724 +* bug: Supply mixin for TestResponse by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6756 +* fix: CodeIgniter::run() doesn't respect $returnResponse by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6737 +* Bug: ResponseTest::testSetLastModifiedWithDateTimeObject depends on time by @fpoy in https://github.com/codeigniter4/CodeIgniter4/pull/6683 +* fix: workaround for Faker deprecation errors in PHP 8.2 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6758 +* Add .gitattributes to framework by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/6774 +* Delete admin/module directory by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6775 + ## [v4.2.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.7) (2022-10-06) [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.6...v4.2.7) diff --git a/admin/framework/.gitattributes b/admin/framework/.gitattributes new file mode 100644 index 000000000000..99484cbc3c97 --- /dev/null +++ b/admin/framework/.gitattributes @@ -0,0 +1,3 @@ +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/admin/framework/composer.json b/admin/framework/composer.json index ea5e3ac9faa2..486cdf6787ab 100644 --- a/admin/framework/composer.json +++ b/admin/framework/composer.json @@ -17,7 +17,7 @@ "require-dev": { "codeigniter/coding-standard": "^1.5", "fakerphp/faker": "^1.9", - "friendsofphp/php-cs-fixer": "~3.11.0", + "friendsofphp/php-cs-fixer": "~3.12.0", "mikey179/vfsstream": "^1.6", "nexusphp/cs-config": "^3.6", "phpunit/phpunit": "^9.1", @@ -25,6 +25,8 @@ }, "suggest": { "ext-imagick": "If you use Image class ImageMagickHandler", + "ext-gd": "If you use Image class GDHandler", + "ext-exif": "If you run Image class tests", "ext-simplexml": "If you format XML", "ext-mysqli": "If you use MySQL", "ext-oci8": "If you use Oracle Database", @@ -34,6 +36,8 @@ "ext-memcache": "If you use Cache class MemcachedHandler with Memcache", "ext-memcached": "If you use Cache class MemcachedHandler with Memcached", "ext-redis": "If you use Cache class RedisHandler", + "ext-dom": "If you use TestResponse", + "ext-libxml": "If you use TestResponse", "ext-fileinfo": "Improves mime type detection for files", "ext-readline": "Improves CLI::input() usability" }, diff --git a/admin/module/.gitignore b/admin/module/.gitignore deleted file mode 100644 index e82f5252fbc4..000000000000 --- a/admin/module/.gitignore +++ /dev/null @@ -1,127 +0,0 @@ -#------------------------- -# Operating Specific Junk Files -#------------------------- - -# OS X -.DS_Store -.AppleDouble -.LSOverride - -# OS X Thumbnails -._* - -# Windows image file caches -Thumbs.db -ehthumbs.db -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Linux -*~ - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -#------------------------- -# Environment Files -#------------------------- -# These should never be under version control, -# as it poses a security risk. -.env -.vagrant -Vagrantfile - -#------------------------- -# Temporary Files -#------------------------- -writable/cache/* -!writable/cache/index.html - -writable/logs/* -!writable/logs/index.html - -writable/session/* -!writable/session/index.html - -writable/uploads/* -!writable/uploads/index.html - -writable/debugbar/* - -php_errors.log - -#------------------------- -# User Guide Temp Files -#------------------------- -user_guide_src/build/* -user_guide_src/cilexer/build/* -user_guide_src/cilexer/dist/* -user_guide_src/cilexer/pycilexer.egg-info/* - -#------------------------- -# Test Files -#------------------------- -tests/coverage* - -# Don't save phpunit under version control. -phpunit - -#------------------------- -# Composer -#------------------------- -vendor/ -composer.lock - -#------------------------- -# IDE / Development Files -#------------------------- - -# Modules Testing -_modules/* - -# phpenv local config -.php-version - -# Jetbrains editors (PHPStorm, etc) -.idea/ -*.iml - -# Netbeans -nbproject/ -build/ -nbbuild/ -dist/ -nbdist/ -nbactions.xml -nb-configuration.xml -.nb-gradle/ - -# Sublime Text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache -*.sublime-workspace -*.sublime-project -.phpintel -/api/ - -# Visual Studio Code -.vscode/ - -/results/ -/phpunit*.xml -/.phpunit.*.cache diff --git a/admin/module/composer.json b/admin/module/composer.json deleted file mode 100644 index 172533e9e5f2..000000000000 --- a/admin/module/composer.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "mydomain/mymodule", - "description": "CodeIgniter4 starter module", - "homepage": "https://codeigniter.com", - "license": "MIT", - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "codeigniter4/codeigniter4": "dev-develop", - "fakerphp/faker": "^1.9", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^9.1" - }, - "suggest": { - "ext-fileinfo": "Improves mime type detection for files" - }, - "autoload-dev": { - "psr-4": { - "Tests\\Support\\": "tests/_support" - } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/codeigniter4/codeigniter4" - } - ], - "minimum-stability": "dev", - "scripts": { - "test": "phpunit" - }, - "support": { - "forum": "http://forum.codeigniter.com/", - "source": "https://github.com/codeigniter4/CodeIgniter4", - "slack": "https://codeigniterchat.slack.com" - } -} diff --git a/admin/module/phpunit.xml.dist b/admin/module/phpunit.xml.dist deleted file mode 100644 index 60c32ae056e7..000000000000 --- a/admin/module/phpunit.xml.dist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - ./src - - - ./src/Views - ./src/Config/Routes.php - - - - - - - - - - - ./tests - - - - - - - - - - - - - - - - - - - diff --git a/admin/module/tests/README.md b/admin/module/tests/README.md deleted file mode 100644 index be12d4c954c4..000000000000 --- a/admin/module/tests/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Running Module Tests - -This is the quick-start to CodeIgniter testing. Its intent is to describe what -it takes to set up your module and get it ready to run unit tests. -It is not intended to be a full description of the test features that you can -use to test your module. Those details can be found in the documentation. - -## Resources - -* [CodeIgniter 4 User Guide on Testing](https://codeigniter4.github.io/userguide/testing/index.html) -* [PHPUnit docs](https://phpunit.de/documentation.html) -* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830) - -## Requirements - -It is recommended to use the latest version of PHPUnit. At the time of this -writing we are running version 9.x. Support for this has been built into the -**composer.json** file that ships with CodeIgniter and can easily be installed -via [Composer](https://getcomposer.org/) if you don't already have it installed globally. - -```console -> composer install -``` - -If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer. - -```console -> ln -s ./vendor/bin/phpunit ./phpunit -``` - -You also need to install [XDebug](https://xdebug.org/docs/install) in order -for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage. - -## Setting Up - -A number of the tests use a running database. -In order to set up the database edit the details for the `tests` group in -**phpunit.xml**. -Make sure that you provide a database engine that is currently running on your machine. -More details on a test database setup are in the -[Testing Your Database](https://codeigniter4.github.io/userguide/testing/database.html) section of the documentation. - -## Running the tests - -The entire test suite can be run by simply typing one command-line command from the main directory. - -```console -> ./phpunit -``` - -If you are using Windows, use the following command. - -```console -> vendor\bin\phpunit -``` - -You can limit tests to those within a single test directory by specifying the -directory name after phpunit. - -```console -> ./phpunit tests/Models -``` - -## Generating Code Coverage - -To generate coverage information, including HTML reports you can view in your browser, -you can use the following command: - -```console -> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m -``` - -This runs all of the tests again collecting information about how many lines, -functions, and files are tested. It also reports the percentage of the code that is covered by tests. -It is collected in two formats: a simple text file that provides an overview as well -as a comprehensive collection of HTML files that show the status of every line of code in the project. - -The text file can be found at **tests/coverage.txt**. -The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser. - -## PHPUnit XML Configuration - -The repository has a ``phpunit.xml.dist`` file in the project root that's used for -PHPUnit configuration. This is used to provide a default configuration if you -do not have your own configuration file in the project root. - -The normal practice would be to copy ``phpunit.xml.dist`` to ``phpunit.xml`` -(which is git ignored), and to tailor it as you see fit. -For instance, you might wish to exclude database tests, or automatically generate -HTML code coverage reports. - -## Test Cases - -Every test needs a *test case*, or class that your tests extend. CodeIgniter 4 -provides a few that you may use directly: -* `CodeIgniter\Test\CIUnitTestCase` - for basic tests with no other service needs -* `CodeIgniter\Test\DatabaseTestTrait` - for tests that need database access - -Most of the time you will want to write your own test cases to hold functions and services -common to your test suites. - -## Creating Tests - -All tests go in the **tests/** directory. Each test file is a class that extends a -**Test Case** (see above) and contains methods for the individual tests. These method -names must start with the word "test" and should have descriptive names for precisely what -they are testing: -`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()` - -Writing tests is an art, and there are many resources available to help learn how. -Review the links above and always pay attention to your code coverage. - -### Database Tests - -Tests can include migrating, seeding, and testing against a mock or live1 database. -Be sure to modify the test case (or create your own) to point to your seed and migrations -and include any additional steps to be run before tests in the `setUp()` method. - -1 Note: If you are using database tests that require a live database connection -you will need to rename **phpunit.xml.dist** to **phpunit.xml**, uncomment the database -configuration lines and add your connection details. Prevent **phpunit.xml** from being -tracked in your repo by adding it to **.gitignore**. diff --git a/admin/module/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/admin/module/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php deleted file mode 100644 index 2bbdcfe51994..000000000000 --- a/admin/module/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php +++ /dev/null @@ -1,37 +0,0 @@ -forge->addField('id'); - $this->forge->addField([ - 'name' => ['type' => 'varchar', 'constraint' => 31], - 'uid' => ['type' => 'varchar', 'constraint' => 31], - 'class' => ['type' => 'varchar', 'constraint' => 63], - 'icon' => ['type' => 'varchar', 'constraint' => 31], - 'summary' => ['type' => 'varchar', 'constraint' => 255], - 'created_at' => ['type' => 'datetime', 'null' => true], - 'updated_at' => ['type' => 'datetime', 'null' => true], - 'deleted_at' => ['type' => 'datetime', 'null' => true], - ]); - - $this->forge->addKey('name'); - $this->forge->addKey('uid'); - $this->forge->addKey(['deleted_at', 'id']); - $this->forge->addKey('created_at'); - - $this->forge->createTable('factories'); - } - - public function down() - { - $this->forge->dropTable('factories'); - } -} diff --git a/admin/module/tests/_support/Database/Seeds/ExampleSeeder.php b/admin/module/tests/_support/Database/Seeds/ExampleSeeder.php deleted file mode 100644 index f67bf8fb8cae..000000000000 --- a/admin/module/tests/_support/Database/Seeds/ExampleSeeder.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Test Factory', - 'uid' => 'test001', - 'class' => 'Factories\Tests\NewFactory', - 'icon' => 'fas fa-puzzle-piece', - 'summary' => 'Longer sample text for testing', - ], - [ - 'name' => 'Widget Factory', - 'uid' => 'widget', - 'class' => 'Factories\Tests\WidgetPlant', - 'icon' => 'fas fa-puzzle-piece', - 'summary' => 'Create widgets in your factory', - ], - [ - 'name' => 'Evil Factory', - 'uid' => 'evil-maker', - 'class' => 'Factories\Evil\MyFactory', - 'icon' => 'fas fa-book-dead', - 'summary' => 'Abandon all hope, ye who enter here', - ], - ]; - - $builder = $this->db->table('factories'); - - foreach ($factories as $factory) { - $builder->insert($factory); - } - } -} diff --git a/admin/module/tests/_support/DatabaseTestCase.php b/admin/module/tests/_support/DatabaseTestCase.php deleted file mode 100644 index fd067a585c87..000000000000 --- a/admin/module/tests/_support/DatabaseTestCase.php +++ /dev/null @@ -1,61 +0,0 @@ -mockSession(); - } - - /** - * Pre-loads the mock session driver into $this->session. - * - * @var string - */ - protected function mockSession() - { - $config = config('App'); - $this->session = new MockSession(new ArrayHandler($config, '0.0.0.0'), $config); - \Config\Services::injectMock('session', $this->session); - } -} diff --git a/admin/module/tests/database/ExampleDatabaseTest.php b/admin/module/tests/database/ExampleDatabaseTest.php deleted file mode 100644 index f9edc4d235d8..000000000000 --- a/admin/module/tests/database/ExampleDatabaseTest.php +++ /dev/null @@ -1,45 +0,0 @@ -findAll(); - - // Make sure the count is as expected - $this->assertCount(3, $objects); - } - - public function testSoftDeleteLeavesRow() - { - $model = new ExampleModel(); - $this->setPrivateProperty($model, 'useSoftDeletes', true); - $this->setPrivateProperty($model, 'tempUseSoftDeletes', true); - - $object = $model->first(); - $model->delete($object->id); - - // The model should no longer find it - $this->assertNull($model->find($object->id)); - - // ... but it should still be in the database - $result = $model->builder()->where('id', $object->id)->get()->getResult(); - - $this->assertCount(1, $result); - } -} diff --git a/admin/module/tests/session/ExampleSessionTest.php b/admin/module/tests/session/ExampleSessionTest.php deleted file mode 100644 index 98fe7afa0d77..000000000000 --- a/admin/module/tests/session/ExampleSessionTest.php +++ /dev/null @@ -1,18 +0,0 @@ -set('logged_in', 123); - $this->assertSame(123, $session->get('logged_in')); - } -} diff --git a/admin/module/tests/unit/ExampleTest.php b/admin/module/tests/unit/ExampleTest.php deleted file mode 100644 index 4306ddfe55d5..000000000000 --- a/admin/module/tests/unit/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertTrue($test); - } -} diff --git a/app/Config/Logger.php b/app/Config/Logger.php index fe389d8a2078..743815032280 100644 --- a/app/Config/Logger.php +++ b/app/Config/Logger.php @@ -2,8 +2,8 @@ namespace Config; -use CodeIgniter\Log\Handlers\FileHandler; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Log\Handlers\FileHandler; class Logger extends BaseConfig { diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php index 77e963b2920a..eacd898b4f4f 100644 --- a/app/Views/errors/html/error_exception.php +++ b/app/Views/errors/html/error_exception.php @@ -270,7 +270,7 @@ - getHeaders(); ?> + headers(); ?>

Headers

@@ -318,7 +318,7 @@ - getHeaders(); ?> + headers(); ?> diff --git a/composer.json b/composer.json index 8fca22ded575..da3d66ccda58 100644 --- a/composer.json +++ b/composer.json @@ -17,17 +17,21 @@ "require-dev": { "codeigniter/coding-standard": "^1.5", "fakerphp/faker": "^1.9", - "friendsofphp/php-cs-fixer": "~3.11.0", + "friendsofphp/php-cs-fixer": "~3.12.0", "mikey179/vfsstream": "^1.6", "nexusphp/cs-config": "^3.6", "nexusphp/tachycardia": "^1.0", + "php-coveralls/php-coveralls": "^2.5", "phpstan/phpstan": "^1.7.1", + "phpunit/phpcov": "^8.2", "phpunit/phpunit": "^9.1", "predis/predis": "^1.1 || ^2.0", - "rector/rector": "0.14.5" + "rector/rector": "0.14.6" }, "suggest": { "ext-imagick": "If you use Image class ImageMagickHandler", + "ext-gd": "If you use Image class GDHandler", + "ext-exif": "If you run Image class tests", "ext-simplexml": "If you format XML", "ext-mysqli": "If you use MySQL", "ext-oci8": "If you use Oracle Database", @@ -37,6 +41,8 @@ "ext-memcache": "If you use Cache class MemcachedHandler with Memcache", "ext-memcached": "If you use Cache class MemcachedHandler with Memcached", "ext-redis": "If you use Cache class RedisHandler", + "ext-dom": "If you use TestResponse", + "ext-libxml": "If you use TestResponse", "ext-fileinfo": "Improves mime type detection for files", "ext-readline": "Improves CLI::input() usability" }, diff --git a/contributing/workflow.md b/contributing/workflow.md index 4ea22cce5e22..921c49da1791 100644 --- a/contributing/workflow.md +++ b/contributing/workflow.md @@ -363,28 +363,16 @@ Update your `4.3` branch: > git push origin 4.3 ``` -Create a new branch `feat-ab.new` from the correct branch `4.3`: - -```console -> git switch -c feat-abc.new 4.3 -``` - -Cherry-pick the commits you did: - -```console -> git cherry-pick ... -``` - -Rename the PR branch `feat-abc`: +(Optional) Create a new branch as a backup, just in case: ```console -> git branch -m feat-abc feat-abc.old +> git branch feat-abc.bk feat-abc ``` -Rename the new branch `feat-abc.new` to `feat-abc`. +Rebase your PR branch from `develop` onto `4.3`: ```console -> git branch -m feat-abc.new feat-abc +> git rebase --onto 4.3 develop feat-abc ``` Force push. diff --git a/phpstan-baseline.neon.dist b/phpstan-baseline.neon.dist index 035c15273959..7eb120306dd2 100644 --- a/phpstan-baseline.neon.dist +++ b/phpstan-baseline.neon.dist @@ -100,16 +100,6 @@ parameters: count: 1 path: system/Database/BaseBuilder.php - - - message: "#^Call to an undefined method CodeIgniter\\\\Database\\\\BaseConnection\\:\\:_disableForeignKeyChecks\\(\\)\\.$#" - count: 1 - path: system/Database/BaseConnection.php - - - - message: "#^Call to an undefined method CodeIgniter\\\\Database\\\\BaseConnection\\:\\:_enableForeignKeyChecks\\(\\)\\.$#" - count: 1 - path: system/Database/BaseConnection.php - - message: "#^Call to an undefined method CodeIgniter\\\\Database\\\\QueryInterface\\:\\:getOriginalQuery\\(\\)\\.$#" count: 1 @@ -301,7 +291,7 @@ parameters: path: system/Database/MySQLi/Result.php - - message: "#^Strict comparison using \\=\\=\\= between array and false will always evaluate to false\\.$#" + message: "#^Strict comparison using \\=\\=\\= between array and false will always evaluate to false\\.$#" count: 1 path: system/Database/Postgre/Connection.php @@ -555,11 +545,6 @@ parameters: count: 1 path: system/Helpers/filesystem_helper.php - - - message: "#^Offset 'checked' on array\\{type\\: 'checkbox', name\\: mixed, value\\: string\\} in isset\\(\\) does not exist\\.$#" - count: 1 - path: system/Helpers/form_helper.php - - message: "#^Binary operation \"\\+\" between 0 and string results in an error\\.$#" count: 1 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d4da560a107d..c6d69a55dbe2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -16,7 +16,7 @@ includes: - phpstan-baseline.neon.dist parameters: - phpVersion: 80000 + phpVersion: 80100 tmpDir: build/phpstan level: 5 paths: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 741cd106e080..afa4c489c13c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -64,6 +64,9 @@ false + + true + diff --git a/rector.php b/rector.php index bf0ae9a54030..901917424250 100644 --- a/rector.php +++ b/rector.php @@ -60,7 +60,7 @@ PHPUnitSetList::REMOVE_MOCKS, ]); - $rectorConfig->parallel(); + $rectorConfig->disableParallel(); // paths to refactor; solid alternative to CLI arguments $rectorConfig->paths([__DIR__ . '/app', __DIR__ . '/system', __DIR__ . '/tests', __DIR__ . '/utils']); diff --git a/system/BaseModel.php b/system/BaseModel.php index 5986d7e6a9d5..888d89854f32 100644 --- a/system/BaseModel.php +++ b/system/BaseModel.php @@ -64,6 +64,7 @@ abstract class BaseModel * should be instantiated. * * @var string + * @phpstan-var non-empty-string */ protected $DBGroup; @@ -416,7 +417,7 @@ abstract protected function doDelete($id = null, bool $purge = false); * through soft deletes (deleted = 1) * This methods works only with dbCalls * - * @return bool|mixed + * @return bool|string Returns a string if in test mode. */ abstract protected function doPurgeDeleted(); diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php index e663adb2e251..12a82a7afaa7 100644 --- a/system/CLI/BaseCommand.php +++ b/system/CLI/BaseCommand.php @@ -119,8 +119,9 @@ protected function showError(Throwable $e) { $exception = $e; $message = $e->getMessage(); + $config = config('Exceptions'); - require APPPATH . 'Views/errors/cli/error_exception.php'; + require $config->errorViewPath . '/cli/error_exception.php'; } /** diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 0cc4861be854..0f5303c7ea2f 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -47,7 +47,7 @@ class CodeIgniter /** * The current version of CodeIgniter Framework */ - public const CI_VERSION = '4.2.7'; + public const CI_VERSION = '4.2.8'; /** * App startup time. @@ -151,6 +151,11 @@ class CodeIgniter */ protected ?string $context = null; + /** + * Whether to return Response object or send response. + */ + protected bool $returnResponse = false; + /** * Constructor. */ @@ -291,6 +296,8 @@ protected function initializeKint() */ public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false) { + $this->returnResponse = $returnResponse; + if ($this->context === null) { throw new LogicException('Context must be set before run() is called. If you are upgrading from 4.1.x, you need to merge `public/index.php` and `spark` file from `vendor/codeigniter4/framework`.'); } @@ -309,6 +316,10 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon if ($this->request instanceof IncomingRequest && strtolower($this->request->getMethod()) === 'cli') { $this->response->setStatusCode(405)->setBody('Method Not Allowed'); + if ($this->returnResponse) { + return $this->response; + } + $this->sendResponse(); return; @@ -345,13 +356,22 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon // If the route is a 'redirect' route, it throws // the exception with the $to as the message $this->response->redirect(base_url($e->getMessage()), 'auto', $e->getCode()); + + if ($this->returnResponse) { + return $this->response; + } + $this->sendResponse(); $this->callExit(EXIT_SUCCESS); return; } catch (PageNotFoundException $e) { - $this->display404errors($e); + $return = $this->display404errors($e); + + if ($return instanceof ResponseInterface) { + return $return; + } } } @@ -400,9 +420,13 @@ private function isWeb(): bool * * @throws PageNotFoundException * @throws RedirectException + * + * @deprecated $returnResponse is deprecated. */ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cacheConfig, bool $returnResponse = false) { + $this->returnResponse = $returnResponse; + $routeFilter = $this->tryToRouteIt($routes); $uri = $this->determinePath(); @@ -433,7 +457,8 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // If a ResponseInterface instance is returned then send it back to the client and stop if ($possibleResponse instanceof ResponseInterface) { - return $returnResponse ? $possibleResponse : $possibleResponse->pretend($this->useSafeOutput)->send(); + return $this->returnResponse ? $possibleResponse + : $possibleResponse->pretend($this->useSafeOutput)->send(); } if ($possibleResponse instanceof Request) { @@ -512,7 +537,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache unset($uri); - if (! $returnResponse) { + if (! $this->returnResponse) { $this->sendResponse(); } @@ -910,6 +935,8 @@ protected function runController($class) /** * Displays a 404 Page Not Found error. If set, will try to * call the 404Override controller/method that was set in routing config. + * + * @return ResponseInterface|void */ protected function display404errors(PageNotFoundException $e) { @@ -934,6 +961,10 @@ protected function display404errors(PageNotFoundException $e) $cacheConfig = new Cache(); $this->gatherOutput($cacheConfig, $returned); + if ($this->returnResponse) { + return $this->response; + } + $this->sendResponse(); return; diff --git a/system/Common.php b/system/Common.php index 70bc6105a39a..a4c9d38b5d55 100644 --- a/system/Common.php +++ b/system/Common.php @@ -145,7 +145,10 @@ function command(string $command) $args[] = stripcslashes($match[1]); } else { // @codeCoverageIgnoreStart - throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($command, $cursor, 10))); + throw new InvalidArgumentException(sprintf( + 'Unable to parse input near "... %s ...".', + substr($command, $cursor, 10) + )); // @codeCoverageIgnoreEnd } @@ -357,12 +360,10 @@ function db_connect($db = null, bool $getShared = true) */ function dd(...$vars) { - // @codeCoverageIgnoreStart Kint::$aliases[] = 'dd'; Kint::dump(...$vars); exit; - // @codeCoverageIgnoreEnd } } @@ -414,10 +415,11 @@ function env(string $key, $default = null) * If $data is an array, then it loops over it, escaping each * 'value' of the key/value pairs. * - * Valid context values: html, js, css, url, attr, raw - * * @param array|string $data - * @param string $encoding + * @phpstan-param 'html'|'js'|'css'|'url'|'attr'|'raw' $context + * @param string|null $encoding Current encoding for escaping. + * If not UTF-8, we convert strings from this encoding + * pre-escaping and back to this encoding post-escaping. * * @return array|string * @@ -437,7 +439,7 @@ function esc($data, string $context = 'html', ?string $encoding = null) // Provide a way to NOT escape data since // this could be called automatically by // the View library. - if (empty($context) || $context === 'raw') { + if ($context === 'raw') { return $data; } @@ -493,18 +495,13 @@ function force_https(int $duration = 31_536_000, ?RequestInterface $request = nu } if ((ENVIRONMENT !== 'testing' && (is_cli() || $request->isSecure())) || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'test')) { - // @codeCoverageIgnoreStart - return; - // @codeCoverageIgnoreEnd + return; // @codeCoverageIgnore } // If the session status is active, we should regenerate // the session ID for safety sake. if (ENVIRONMENT !== 'testing' && session_status() === PHP_SESSION_ACTIVE) { - // @codeCoverageIgnoreStart - Services::session(null, true) - ->regenerate(); - // @codeCoverageIgnoreEnd + Services::session(null, true)->regenerate(); // @codeCoverageIgnore } $baseURL = config(App::class)->baseURL; @@ -529,9 +526,7 @@ function force_https(int $duration = 31_536_000, ?RequestInterface $request = nu $response->sendHeaders(); if (ENVIRONMENT !== 'testing') { - // @codeCoverageIgnoreStart - exit(); - // @codeCoverageIgnoreEnd + exit(); // @codeCoverageIgnore } } } @@ -782,7 +777,7 @@ function lang(string $line, array $args = [], ?string $locale = null) * - info * - debug * - * @return mixed + * @return bool */ function log_message(string $level, string $message, array $context = []) { @@ -795,10 +790,7 @@ function log_message(string $level, string $message, array $context = []) return $logger->log($level, $message, $context); } - // @codeCoverageIgnoreStart - return Services::logger(true) - ->log($level, $message, $context); - // @codeCoverageIgnoreEnd + return Services::logger(true)->log($level, $message, $context); // @codeCoverageIgnore } } @@ -823,18 +815,17 @@ function model(string $name, bool $getShared = true, ?ConnectionInterface &$conn * Provides access to "old input" that was set in the session * during a redirect()->withInput(). * - * @param null $default - * @param bool|string $escape + * @param string|null $default + * @param false|string $escape + * @phpstan-param false|'attr'|'css'|'html'|'js'|'raw'|'url' $escape * - * @return mixed|null + * @return array|string|null */ function old(string $key, $default = null, $escape = 'html') { // Ensure the session is loaded if (session_status() === PHP_SESSION_NONE && ENVIRONMENT !== 'testing') { - // @codeCoverageIgnoreStart - session(); - // @codeCoverageIgnoreEnd + session(); // @codeCoverageIgnore } $request = Services::request(); @@ -932,7 +923,8 @@ function route_to(string $method, ...$params) * * @param string $val * - * @return mixed|Session|null + * @return array|bool|float|int|object|Session|string|null + * @phpstan-return ($val is null ? Session : array|bool|float|int|object|string|null) */ function session(?string $val = null) { @@ -1054,7 +1046,7 @@ function slash_item(string $item): ?string * Helper function used to convert a string, array, or object * of attributes to a string. * - * @param mixed $attributes string, array, object + * @param array|object|string $attributes string, array, object that can be cast to array */ function stringify_attributes($attributes, bool $js = false): string { diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index ddc590cd5986..724524e2d590 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -116,7 +116,8 @@ public function normaliseVariable(string $name, string $value = ''): array $value = trim($value); // Sanitize the name - $name = str_replace(['export', '\'', '"'], '', $name); + $name = preg_replace('/^export[ \t]++(\S+)/', '$1', $name); + $name = str_replace(['\'', '"'], '', $name); // Sanitize the value $value = $this->sanitizeValue($value); diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 700219fa3030..3433de491fa6 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1179,7 +1179,7 @@ public function unionAll($union) */ protected function addUnionStatement($union, bool $all = false) { - $this->QBUnion[] = "\n" . 'UNION ' + $this->QBUnion[] = "\nUNION " . ($all ? 'ALL ' : '') . 'SELECT * FROM ' . $this->buildSubquery($union, true, 'uwrp' . (count($this->QBUnion) + 1)); @@ -2350,7 +2350,7 @@ public function getCompiledDelete(bool $reset = true): string * * @param mixed $where * - * @return bool|string + * @return bool|string Returns a string if in test mode. * * @throws DatabaseException */ diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 6d9078dcacb6..1f5009167f2c 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -426,13 +426,6 @@ public function initialize() $this->connectDuration = microtime(true) - $this->connectTime; } - /** - * Connect to the database. - * - * @return mixed - */ - abstract public function connect(bool $persistent = false); - /** * Close the database connection. */ @@ -461,14 +454,6 @@ public function persistentConnect() return $this->connect(true); } - /** - * Keep or establish the connection if no queries have been sent for - * a length of time exceeding the server's idle timeout. - * - * @return mixed - */ - abstract public function reconnect(); - /** * Returns the actual connection object. If both a 'read' and 'write' * connection has been specified, you can pass either term in to @@ -483,13 +468,6 @@ public function getConnection(?string $alias = null) return $this->connID; } - /** - * Select a specific database table to use. - * - * @return mixed - */ - abstract public function setDatabase(string $databaseName); - /** * Returns the name of the current database being used. */ @@ -526,11 +504,6 @@ public function getPlatform(): string return $this->DBDriver; } - /** - * Returns a string containing the version of the database being used. - */ - abstract public function getVersion(): string; - /** * Sets the Table Aliases to use. These are typically * collected during use of the Builder, and set here @@ -1538,21 +1511,35 @@ public function getForeignKeyData(string $table) /** * Disables foreign key checks temporarily. + * + * @return bool */ public function disableForeignKeyChecks() { $sql = $this->_disableForeignKeyChecks(); + if ($sql === '') { + // The feature is not supported. + return false; + } + return $this->query($sql); } /** * Enables foreign key checks temporarily. + * + * @return bool */ public function enableForeignKeyChecks() { $sql = $this->_enableForeignKeyChecks(); + if ($sql === '') { + // The feature is not supported. + return false; + } + return $this->query($sql); } @@ -1647,6 +1634,38 @@ abstract protected function _indexData(string $table): array; */ abstract protected function _foreignKeyData(string $table): array; + /** + * Platform-specific SQL statement to disable foreign key checks. + * + * If this feature is not supported, return empty string. + * + * @TODO This method should be moved to an interface that represents foreign key support. + * + * @return string + * + * @see disableForeignKeyChecks() + */ + protected function _disableForeignKeyChecks() + { + return ''; + } + + /** + * Platform-specific SQL statement to enable foreign key checks. + * + * If this feature is not supported, return empty string. + * + * @TODO This method should be moved to an interface that represents foreign key support. + * + * @return string + * + * @see enableForeignKeyChecks() + */ + protected function _enableForeignKeyChecks() + { + return ''; + } + /** * Accessor for properties if they exist. * diff --git a/system/Database/Config.php b/system/Database/Config.php index f73c93a1169a..eedbbd441465 100644 --- a/system/Database/Config.php +++ b/system/Database/Config.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Database; use CodeIgniter\Config\BaseConfig; +use Config\Database as DbConfig; use InvalidArgumentException; /** @@ -38,8 +39,10 @@ class Config extends BaseConfig /** * Creates the default * - * @param array|string $group The name of the connection group to use, or an array of configuration settings. - * @param bool $getShared Whether to return a shared instance of the connection. + * @param array|BaseConnection|string|null $group The name of the connection group to use, + * or an array of configuration settings. + * @phpstan-param array|BaseConnection|non-empty-string|null $group + * @param bool $getShared Whether to return a shared instance of the connection. * * @return BaseConnection */ @@ -53,16 +56,21 @@ public static function connect($group = null, bool $getShared = true) if (is_array($group)) { $config = $group; $group = 'custom-' . md5(json_encode($config)); - } + } else { + /** @var DbConfig $dbConfig */ + $dbConfig = config('Database'); - $config ??= config('Database'); + if ($group === null) { + $group = (ENVIRONMENT === 'testing') ? 'tests' : $dbConfig->defaultGroup; + } - if (empty($group)) { - $group = ENVIRONMENT === 'testing' ? 'tests' : $config->defaultGroup; - } + assert(is_string($group)); + + if (! isset($dbConfig->{$group})) { + throw new InvalidArgumentException($group . ' is not a valid database connection group.'); + } - if (is_string($group) && ! isset($config->{$group}) && strpos($group, 'custom-') !== 0) { - throw new InvalidArgumentException($group . ' is not a valid database connection group.'); + $config = $dbConfig->{$group}; } if ($getShared && isset(static::$instances[$group])) { @@ -71,13 +79,9 @@ public static function connect($group = null, bool $getShared = true) static::ensureFactory(); - if (isset($config->{$group})) { - $config = $config->{$group}; - } - $connection = static::$factory->load($config, $group); - static::$instances[$group] = &$connection; + static::$instances[$group] = $connection; return $connection; } @@ -122,6 +126,8 @@ public static function utils($group = null) /** * Returns a new instance of the Database Seeder. * + * @phpstan-param null|non-empty-string $group + * * @return Seeder */ public static function seeder(?string $group = null) diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 1555725a1f24..b19cb5209262 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -360,7 +360,7 @@ public function escapeLikeStringDirect($str) // Escape LIKE condition wildcards return str_replace( [$this->likeEscapeChar, '%', '_'], - ['\\' . $this->likeEscapeChar, '\\' . '%', '\\' . '_'], + ['\\' . $this->likeEscapeChar, '\\%', '\\_'], $str ); } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index f7e49edbf91b..b608d603d0ec 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -12,7 +12,6 @@ namespace CodeIgniter\Database\OCI8; use CodeIgniter\Database\BaseConnection; -use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Query; use ErrorException; @@ -21,7 +20,7 @@ /** * Connection for OCI8 */ -class Connection extends BaseConnection implements ConnectionInterface +class Connection extends BaseConnection { /** * Database driver diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 311dacc4045f..467f05b98d3e 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -13,12 +13,11 @@ use BadMethodCallException; use CodeIgniter\Database\BasePreparedQuery; -use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for OCI8 */ -class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface +class PreparedQuery extends BasePreparedQuery { /** * A reference to the db connection to use. diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 1342378733d6..782f5050db0f 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -12,13 +12,12 @@ namespace CodeIgniter\Database\OCI8; use CodeIgniter\Database\BaseResult; -use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity\Entity; /** * Result for OCI8 */ -class Result extends BaseResult implements ResultInterface +class Result extends BaseResult { /** * Gets the number of fields in the result set. diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 453aabe51c38..6f54517d20b1 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -179,6 +179,7 @@ public function replace(?array $set = null) unset($builder); $this->resetWrite(); + $this->binds = []; return $result; } diff --git a/system/Database/SQLSRV/PreparedQuery.php b/system/Database/SQLSRV/PreparedQuery.php index adf0278efca2..576e911e4f60 100755 --- a/system/Database/SQLSRV/PreparedQuery.php +++ b/system/Database/SQLSRV/PreparedQuery.php @@ -27,13 +27,6 @@ class PreparedQuery extends BasePreparedQuery */ protected $parameters = []; - /** - * The result boolean from a sqlsrv_execute. - * - * @var bool - */ - protected $result; - /** * Prepares the query against the database, and saves the connection * info necessary to execute the query later. @@ -68,7 +61,7 @@ public function _prepare(string $sql, array $options = []) /** * Takes a new set of data and runs it against the currently - * prepared query. Upon success, will return a Results object. + * prepared query. */ public function _execute(array $data): bool { @@ -80,19 +73,17 @@ public function _execute(array $data): bool $this->parameters[$key] = $value; } - $this->result = sqlsrv_execute($this->statement); - - return (bool) $this->result; + return sqlsrv_execute($this->statement); } /** - * Returns the result object for the prepared query. + * Returns the statement resource for the prepared query or false when preparing failed. * * @return mixed */ public function _getResult() { - return $this->result; + return $this->statement; } /** diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php index 893de7d56ec4..f51fa417b4ed 100644 --- a/system/Database/Seeder.php +++ b/system/Database/Seeder.php @@ -26,6 +26,7 @@ class Seeder * The name of the database group to use. * * @var string + * @phpstan-var non-empty-string */ protected $DBGroup; diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index 437a47268706..f550c71c785a 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -20,6 +20,7 @@ use Config\Exceptions as ExceptionsConfig; use Config\Paths; use ErrorException; +use Psr\Log\LogLevel; use Throwable; /** @@ -141,11 +142,9 @@ public function exceptionHandler(Throwable $exception) } /** - * Even in PHP7, some errors make it through to the errorHandler, so - * convert these to Exceptions and let the exception handler log it and - * display it. + * The callback to be registered to `set_error_handler()`. * - * This seems to be primarily when a user triggers it with trigger_error(). + * @return bool * * @throws ErrorException * @@ -153,11 +152,45 @@ public function exceptionHandler(Throwable $exception) */ public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null) { - if (! (error_reporting() & $severity)) { - return; + if (error_reporting() & $severity) { + // @TODO Remove if Faker is fixed. + if ($this->isFakerDeprecationError($severity, $message, $file, $line)) { + // Ignore the error. + return true; + } + + throw new ErrorException($message, 0, $severity, $file, $line); + } + + return false; // return false to propagate the error to PHP standard error handler + } + + /** + * Workaround for Faker deprecation errors in PHP 8.2. + * + * @see https://github.com/FakerPHP/Faker/issues/479 + */ + private function isFakerDeprecationError(int $severity, string $message, ?string $file = null, ?int $line = null) + { + if ( + $severity === E_DEPRECATED + && strpos($file, VENDORPATH . 'fakerphp/faker/') !== false + && $message === 'Use of "static" in callables is deprecated' + ) { + log_message( + LogLevel::WARNING, + '[DEPRECATED] {message} in {errFile} on line {errLine}.', + [ + 'message' => $message, + 'errFile' => clean_path($file ?? ''), + 'errLine' => $line ?? 0, + ] + ); + + return true; } - throw new ErrorException($message, 0, $severity, $file, $line); + return false; } /** diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index 257fc6932465..f93bfa0a0f31 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -386,7 +386,7 @@ public function prepare(?RequestInterface $request = null, ?ResponseInterface $r mkdir(WRITEPATH . 'debugbar', 0777); } - write_file(WRITEPATH . 'debugbar/' . 'debugbar_' . $time . '.json', $data, 'w+'); + write_file(WRITEPATH . 'debugbar/debugbar_' . $time . '.json', $data, 'w+'); $format = $response->getHeaderLine('content-type'); diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index f4c629b9f481..30e36a32c65f 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -143,11 +143,11 @@ private function discoverFilters() } /** - * Set the response explicity. + * Set the response explicitly. */ public function setResponse(ResponseInterface $response) { - $this->response = &$response; + $this->response = $response; } /** diff --git a/system/Format/FormatterInterface.php b/system/Format/FormatterInterface.php index f2b211152b87..277299eea2b9 100644 --- a/system/Format/FormatterInterface.php +++ b/system/Format/FormatterInterface.php @@ -21,7 +21,7 @@ interface FormatterInterface * * @param array|string $data * - * @return bool|string + * @return false|string */ public function format($data); } diff --git a/system/Format/XMLFormatter.php b/system/Format/XMLFormatter.php index b32e148a7da9..2a18e388a576 100644 --- a/system/Format/XMLFormatter.php +++ b/system/Format/XMLFormatter.php @@ -25,7 +25,7 @@ class XMLFormatter implements FormatterInterface * * @param mixed $data * - * @return bool|string (XML string | false) + * @return false|string (XML string | false) */ public function format($data) { @@ -34,10 +34,7 @@ public function format($data) // SimpleXML is installed but default // but best to check, and then provide a fallback. if (! extension_loaded('simplexml')) { - // never thrown in travis-ci - // @codeCoverageIgnoreStart - throw FormatException::forMissingExtension(); - // @codeCoverageIgnoreEnd + throw FormatException::forMissingExtension(); // @codeCoverageIgnore } $options = $config->formatterOptions['application/xml'] ?? 0; diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php index e81ae9cabbd9..708fbafbae63 100644 --- a/system/HTTP/CLIRequest.php +++ b/system/HTTP/CLIRequest.php @@ -223,7 +223,7 @@ public function isCLI(): bool * @param int|null $filter A filter name to apply. * @param mixed|null $flags * - * @return null + * @return array|null */ public function getGet($index = null, $filter = null, $flags = null) { @@ -237,7 +237,7 @@ public function getGet($index = null, $filter = null, $flags = null) * @param int|null $filter A filter name to apply * @param mixed $flags * - * @return null + * @return array|null */ public function getPost($index = null, $filter = null, $flags = null) { @@ -251,7 +251,7 @@ public function getPost($index = null, $filter = null, $flags = null) * @param int|null $filter A filter name to apply * @param mixed $flags * - * @return null + * @return array|null */ public function getPostGet($index = null, $filter = null, $flags = null) { @@ -265,13 +265,27 @@ public function getPostGet($index = null, $filter = null, $flags = null) * @param int|null $filter A filter name to apply * @param mixed $flags * - * @return null + * @return array|null */ public function getGetPost($index = null, $filter = null, $flags = null) { return $this->returnNullOrEmptyArray($index); } + /** + * This is a place holder for calls from cookie_helper get_cookie(). + * + * @param array|string|null $index Index for item to be fetched from $_COOKIE + * @param int|null $filter A filter name to be applied + * @param mixed $flags + * + * @return array|null + */ + public function getCookie($index = null, $filter = null, $flags = null) + { + return $this->returnNullOrEmptyArray($index); + } + /** * @param array|string|null $index * diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 08e2d7b4b91a..61492adc4275 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -613,6 +613,9 @@ public function getPost($index = null, $filter = null, $flags = null) */ public function getPostGet($index = null, $filter = null, $flags = null) { + if ($index === null) { + return array_merge($this->getGet($index, $filter, $flags), $this->getPost($index, $filter, $flags)); + } // Use $_POST directly here, since filter_has_var only // checks the initial POST data, not anything that might // have been added since. @@ -630,6 +633,9 @@ public function getPostGet($index = null, $filter = null, $flags = null) */ public function getGetPost($index = null, $filter = null, $flags = null) { + if ($index === null) { + return array_merge($this->getPost($index, $filter, $flags), $this->getGet($index, $filter, $flags)); + } // Use $_GET directly here, since filter_has_var only // checks the initial GET data, not anything that might // have been added since. @@ -665,7 +671,7 @@ public function getUserAgent() * with redirect_with_input(). It first checks for the data in the old * POST data, then the old GET data and finally check for dot arrays * - * @return mixed + * @return array|string|null */ public function getOldInput(string $key) { diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php index e8f9fd3fcf3b..c96bf8b30bbf 100644 --- a/system/HTTP/ResponseInterface.php +++ b/system/HTTP/ResponseInterface.php @@ -202,7 +202,7 @@ public function setJSON($body, bool $unencoded = false); /** * Returns the current body, converted to JSON is it isn't already. * - * @return mixed|string + * @return string|null * * @throws InvalidArgumentException If the body property is not array. */ diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 10a10f817bb4..bd69a41376f8 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -252,7 +252,7 @@ public function setJSON($body, bool $unencoded = false) /** * Returns the current body, converted to JSON is it isn't already. * - * @return mixed|string + * @return string|null * * @throws InvalidArgumentException If the body property is not array. */ @@ -471,7 +471,7 @@ public function sendHeaders() header(sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->getStatusCode(), $this->getReasonPhrase()), true, $this->getStatusCode()); // Send all of our headers - foreach (array_keys($this->getHeaders()) as $name) { + foreach (array_keys($this->headers()) as $name) { header($name . ': ' . $this->getHeaderLine($name), false, $this->getStatusCode()); } diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 5a72efed67d3..9b88cc2a0e0f 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -45,10 +45,15 @@ function dot_array_search(string $index, array $array) */ function _array_search_dot(array $indexes, array $array) { + // If index is empty, returns null. + if ($indexes === []) { + return null; + } + // Grab the current index - $currentIndex = $indexes ? array_shift($indexes) : null; + $currentIndex = array_shift($indexes); - if ((empty($currentIndex) && (int) $currentIndex !== 0) || (! isset($array[$currentIndex]) && $currentIndex !== '*')) { + if (! isset($array[$currentIndex]) && $currentIndex !== '*') { return null; } diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index aaab2a46e0be..f209991746be 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -137,8 +137,8 @@ function form_hidden($name, $value = '', bool $recursing = false): string * Text Input Field. If 'type' is passed in the $type field, it will be * used as the input type, for making 'email', 'phone', etc input fields. * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_input($data = '', string $value = '', $extra = '', string $type = 'text'): string { @@ -158,8 +158,8 @@ function form_input($data = '', string $value = '', $extra = '', string $type = * * Identical to the input function but adds the "password" type * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_password($data = '', string $value = '', $extra = ''): string { @@ -178,8 +178,8 @@ function form_password($data = '', string $value = '', $extra = ''): string * * Identical to the input function but adds the "file" type * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_upload($data = '', string $value = '', $extra = ''): string { @@ -202,8 +202,8 @@ function form_upload($data = '', string $value = '', $extra = ''): string /** * Textarea field * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_textarea($data = '', string $value = '', $extra = ''): string { @@ -238,8 +238,8 @@ function form_textarea($data = '', string $value = '', $extra = ''): string /** * Multi-select menu * - * @param mixed $name - * @param mixed $extra + * @param array|string $name + * @param array|object|string $extra string, array, object that can be cast to array */ function form_multiselect($name = '', array $options = [], array $selected = [], $extra = ''): string { @@ -257,10 +257,10 @@ function form_multiselect($name = '', array $options = [], array $selected = [], /** * Drop-down Menu * - * @param mixed $data - * @param mixed $options - * @param mixed $selected - * @param mixed $extra + * @param array|string $data + * @param array|string $options + * @param array|string $selected + * @param array|object|string $extra string, array, object that can be cast to array */ function form_dropdown($data = '', $options = [], $selected = [], $extra = ''): string { @@ -340,8 +340,8 @@ function form_dropdown($data = '', $options = [], $selected = [], $extra = ''): /** * Checkbox Field * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_checkbox($data = '', string $value = '', bool $checked = false, $extra = ''): string { @@ -353,6 +353,7 @@ function form_checkbox($data = '', string $value = '', bool $checked = false, $e if (is_array($data) && array_key_exists('checked', $data)) { $checked = $data['checked']; + if ($checked === false) { unset($data['checked']); } else { @@ -362,8 +363,6 @@ function form_checkbox($data = '', string $value = '', bool $checked = false, $e if ($checked === true) { $defaults['checked'] = 'checked'; - } elseif (isset($defaults['checked'])) { - unset($defaults['checked']); } return '\n"; @@ -374,8 +373,8 @@ function form_checkbox($data = '', string $value = '', bool $checked = false, $e /** * Radio Button * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_radio($data = '', string $value = '', bool $checked = false, $extra = ''): string { @@ -392,8 +391,8 @@ function form_radio($data = '', string $value = '', bool $checked = false, $extr /** * Submit Button * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_submit($data = '', string $value = '', $extra = ''): string { @@ -405,8 +404,8 @@ function form_submit($data = '', string $value = '', $extra = ''): string /** * Reset Button * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_reset($data = '', string $value = '', $extra = ''): string { @@ -418,8 +417,8 @@ function form_reset($data = '', string $value = '', $extra = ''): string /** * Form Button * - * @param mixed $data - * @param mixed $extra + * @param array|string $data + * @param array|object|string $extra string, array, object that can be cast to array */ function form_button($data = '', string $content = '', $extra = ''): string { @@ -484,13 +483,13 @@ function form_datalist(string $name, string $value, array $options): string $out = form_input($data) . "\n"; - $out .= ""; + $out .= ""; foreach ($options as $option) { - $out .= "' . "\n"); + return $out . ("\n"); } } diff --git a/system/Helpers/html_helper.php b/system/Helpers/html_helper.php index 11491f76910d..6c04a86a56b6 100755 --- a/system/Helpers/html_helper.php +++ b/system/Helpers/html_helper.php @@ -22,7 +22,7 @@ * Generates an HTML unordered list from an single or * multi-dimensional array. * - * @param mixed $attributes HTML attributes string, array, object + * @param array|object|string $attributes HTML attributes string, array, object */ function ul(array $list, $attributes = ''): string { @@ -36,7 +36,7 @@ function ul(array $list, $attributes = ''): string * * Generates an HTML ordered list from an single or multi-dimensional array. * - * @param mixed $attributes HTML attributes string, array, object + * @param array|object|string $attributes HTML attributes string, array, object */ function ol(array $list, $attributes = ''): string { @@ -50,8 +50,8 @@ function ol(array $list, $attributes = ''): string * * Generates an HTML ordered list from an single or multi-dimensional array. * - * @param mixed $list - * @param mixed $attributes string, array, object + * @param array $list + * @param array|object|string $attributes string, array, object */ function _list(string $type = 'ul', $list = [], $attributes = '', int $depth = 0): string { @@ -224,8 +224,8 @@ function script_tag($src = '', bool $indexPage = false): string * * Generates link to a CSS file * - * @param mixed $href Stylesheet href or an array - * @param bool $indexPage should indexPage be added to the CSS path. + * @param array|string $href Stylesheet href or an array + * @param bool $indexPage should indexPage be added to the CSS path. */ function link_tag($href = '', string $rel = 'stylesheet', string $type = 'text/css', string $title = '', string $media = '', bool $indexPage = false, string $hreflang = ''): string { @@ -281,9 +281,9 @@ function link_tag($href = '', string $rel = 'stylesheet', string $type = 'text/c * Generates a video element to embed videos. The video element can * contain one or more video sources * - * @param mixed $src Either a source string or an array of sources - * @param string $unsupportedMessage The message to display if the media tag is not supported by the browser - * @param string $attributes HTML attributes + * @param array|string $src Either a source string or an array of sources + * @param string $unsupportedMessage The message to display if the media tag is not supported by the browser + * @param string $attributes HTML attributes */ function video($src, string $unsupportedMessage = '', string $attributes = '', array $tracks = [], bool $indexPage = false): string { @@ -327,9 +327,9 @@ function video($src, string $unsupportedMessage = '', string $attributes = '', a * * Generates an audio element to embed sounds * - * @param mixed $src Either a source string or an array of sources - * @param string $unsupportedMessage The message to display if the media tag is not supported by the browser. - * @param string $attributes HTML attributes + * @param array|string $src Either a source string or an array of sources + * @param string $unsupportedMessage The message to display if the media tag is not supported by the browser. + * @param string $attributes HTML attributes */ function audio($src, string $unsupportedMessage = '', string $attributes = '', array $tracks = [], bool $indexPage = false): string { diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index 94f430a44b67..f0dcc9d786c8 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -95,8 +95,7 @@ function site_url($relativePath = '', ?string $scheme = null, ?App $config = nul * Returns the base URL as defined by the App config. * Base URLs are trimmed site URLs without the index page. * - * @param mixed $relativePath URI string or array of URI segments - * @param string $scheme + * @param array|string $relativePath URI string or array of URI segments */ function base_url($relativePath = '', ?string $scheme = null): string { @@ -143,7 +142,7 @@ function current_url(bool $returnObject = false, ?IncomingRequest $request = nul * If that's not available, however, we'll use a sanitized url from $_SERVER['HTTP_REFERER'] * which can be set by the user so is untrusted and not set by certain browsers/servers. * - * @return mixed|string|URI + * @return string|URI */ function previous_url(bool $returnObject = false) { @@ -197,10 +196,10 @@ function index_page(?App $altConfig = null): string * * Creates an anchor based on the local URL. * - * @param mixed $uri URI string or array of URI segments - * @param string $title The link title - * @param mixed $attributes Any attributes - * @param App|null $altConfig Alternate configuration to use + * @param array|string $uri URI string or array of URI segments + * @param string $title The link title + * @param array|object|string $attributes Any attributes + * @param App|null $altConfig Alternate configuration to use */ function anchor($uri = '', string $title = '', $attributes = '', ?App $altConfig = null): string { @@ -230,10 +229,10 @@ function anchor($uri = '', string $title = '', $attributes = '', ?App $altConfig * Creates an anchor based on the local URL. The link * opens a new window based on the attributes specified. * - * @param string $uri the URL - * @param string $title the link title - * @param mixed $attributes any attributes - * @param App|null $altConfig Alternate configuration to use + * @param string $uri the URL + * @param string $title the link title + * @param array|false|object|string $attributes any attributes + * @param App|null $altConfig Alternate configuration to use */ function anchor_popup($uri = '', string $title = '', $attributes = false, ?App $altConfig = null): string { @@ -280,9 +279,9 @@ function anchor_popup($uri = '', string $title = '', $attributes = false, ?App $ /** * Mailto Link * - * @param string $email the email address - * @param string $title the link title - * @param mixed $attributes any attributes + * @param string $email the email address + * @param string $title the link title + * @param array|object|string $attributes any attributes */ function mailto(string $email, string $title = '', $attributes = ''): string { @@ -300,9 +299,9 @@ function mailto(string $email, string $title = '', $attributes = ''): string * * Create a spam-protected mailto link written in Javascript * - * @param string $email the email address - * @param string $title the link title - * @param mixed $attributes any attributes + * @param string $email the email address + * @param string $title the link title + * @param array|object|string $attributes any attributes */ function safe_mailto(string $email, string $title = '', $attributes = ''): string { diff --git a/system/Images/Handlers/ImageMagickHandler.php b/system/Images/Handlers/ImageMagickHandler.php index a5b5a89e1cc0..bd7f5b7e7afa 100644 --- a/system/Images/Handlers/ImageMagickHandler.php +++ b/system/Images/Handlers/ImageMagickHandler.php @@ -41,12 +41,9 @@ public function __construct($config = null) { parent::__construct($config); - // We should never see this, so can't test it - // @codeCoverageIgnoreStart if (! (extension_loaded('imagick') || class_exists(Imagick::class))) { - throw ImageException::forMissingExtension('IMAGICK'); + throw ImageException::forMissingExtension('IMAGICK'); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd } /** diff --git a/system/Log/Handlers/ChromeLoggerHandler.php b/system/Log/Handlers/ChromeLoggerHandler.php index 2ab019d8f6fc..b2c6d28fdfb4 100644 --- a/system/Log/Handlers/ChromeLoggerHandler.php +++ b/system/Log/Handlers/ChromeLoggerHandler.php @@ -128,7 +128,7 @@ public function handle($level, $message): bool /** * Converts the object to display nicely in the Chrome Logger UI. * - * @param mixed $object + * @param array|int|object|string $object * * @return array */ diff --git a/system/Log/Logger.php b/system/Log/Logger.php index 515a065647ee..96674c52e6c1 100644 --- a/system/Log/Logger.php +++ b/system/Log/Logger.php @@ -243,7 +243,7 @@ public function debug($message, array $context = []): bool /** * Logs with an arbitrary level. * - * @param mixed $level + * @param string $level * @param string $message */ public function log($level, $message, array $context = []): bool @@ -265,10 +265,6 @@ public function log($level, $message, array $context = []): bool // Parse our placeholders $message = $this->interpolate($message, $context); - if (! is_string($message)) { - $message = print_r($message, true); - } - if ($this->cacheLogs) { $this->logCache[] = [ 'level' => $level, @@ -312,14 +308,14 @@ public function log($level, $message, array $context = []): bool * {file} * {line} * - * @param mixed $message + * @param string $message * - * @return mixed + * @return string */ protected function interpolate($message, array $context = []) { if (! is_string($message)) { - return $message; + return print_r($message, true); } // build a replacement array with braces around the context keys @@ -329,7 +325,7 @@ protected function interpolate($message, array $context = []) // Verify that the 'exception' key is actually an exception // or error, both of which implement the 'Throwable' interface. if ($key === 'exception' && $val instanceof Throwable) { - $val = $val->getMessage() . ' ' . $this->cleanFileNames($val->getFile()) . ':' . $val->getLine(); + $val = $val->getMessage() . ' ' . clean_path($val->getFile()) . ':' . $val->getLine(); } // todo - sanitize input before writing to file? diff --git a/system/Model.php b/system/Model.php index ef5669844ef3..04628bbb2c1a 100644 --- a/system/Model.php +++ b/system/Model.php @@ -406,7 +406,7 @@ protected function doDelete($id = null, bool $purge = false) * through soft deletes (deleted = 1) * This methods works only with dbCalls * - * @return bool|mixed + * @return bool|string Returns a string if in test mode. */ protected function doPurgeDeleted() { diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 66a950e72fee..70a4fdc22311 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -110,7 +110,7 @@ class RouteCollection implements RouteCollectionInterface * verb => [ * routeName => [ * 'route' => [ - * routeKey => handler, + * routeKey(or from) => handler, * ] * ] * ], @@ -133,6 +133,14 @@ class RouteCollection implements RouteCollectionInterface * Array of routes options * * @var array + * + * [ + * verb => [ + * routeKey(or from) => [ + * key => value, + * ] + * ], + * ] */ protected $routesOptions = []; @@ -1239,12 +1247,15 @@ protected function create(string $verb, string $from, $to, ?array $options = nul $name = $options['as'] ?? $from; + helper('array'); + // Don't overwrite any existing 'froms' so that auto-discovered routes // do not overwrite any app/Config/Routes settings. The app // routes should always be the "source of truth". // this works only because discovered routes are added just prior // to attempting to route the request. - if (isset($this->routes[$verb][$name]) && ! $overwrite) { + $fromExists = dot_array_search('*.route.' . $from, $this->routes[$verb] ?? []) !== null; + if ((isset($this->routes[$verb][$name]) || $fromExists) && ! $overwrite) { return; } diff --git a/system/Session/Session.php b/system/Session/Session.php index f864a09b6f19..a0d9f4902584 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -17,7 +17,6 @@ use Config\Cookie as CookieConfig; use Config\Services; use Psr\Log\LoggerAwareTrait; -use Psr\Log\LoggerInterface; use SessionHandlerInterface; /** @@ -156,13 +155,6 @@ class Session implements SessionInterface */ protected $sidRegexp; - /** - * Logger instance to record error messages and warnings. - * - * @var LoggerInterface - */ - protected $logger; - /** * Constructor. * @@ -489,7 +481,7 @@ public function set($data, $value = null) * * @param string|null $key Identifier of the session property to retrieve * - * @return mixed The property value(s) + * @return array|bool|float|int|object|string|null The property value(s) */ public function get(?string $key = null) { diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index 47fb6dc92e58..4afb98883794 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -28,6 +28,7 @@ use Config\Modules; use Config\Services; use Exception; +use Nexus\PHPUnit\Extension\Expeditable; use PHPUnit\Framework\TestCase; /** @@ -35,6 +36,7 @@ */ abstract class CIUnitTestCase extends TestCase { + use Expeditable; use ReflectionHelper; /** @@ -134,6 +136,7 @@ abstract class CIUnitTestCase extends TestCase * If not present, will use the defaultGroup. * * @var string + * @phpstan-var non-empty-string */ protected $DBGroup = 'tests'; @@ -147,7 +150,7 @@ abstract class CIUnitTestCase extends TestCase /** * Migration Runner instance. * - * @var MigrationRunner|mixed + * @var MigrationRunner|null */ protected $migrations; diff --git a/system/Test/DOMParser.php b/system/Test/DOMParser.php index 531adf737364..5ab12e44ab29 100644 --- a/system/Test/DOMParser.php +++ b/system/Test/DOMParser.php @@ -37,10 +37,7 @@ class DOMParser public function __construct() { if (! extension_loaded('DOM')) { - // always there in travis-ci - // @codeCoverageIgnoreStart - throw new BadMethodCallException('DOM extension is required, but not currently loaded.'); - // @codeCoverageIgnoreEnd + throw new BadMethodCallException('DOM extension is required, but not currently loaded.'); // @codeCoverageIgnore } $this->dom = new DOMDocument('1.0', 'utf-8'); @@ -61,8 +58,11 @@ public function getBody(): string */ public function withString(string $content) { - // converts all special characters to utf-8 - $content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'); + // DOMDocument::loadHTML() will treat your string as being in ISO-8859-1 + // (the HTTP/1.1 default character set) unless you tell it otherwise. + // https://stackoverflow.com/a/8218649 + // So encode characters to HTML numeric string references. + $content = mb_encode_numericentity($content, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); // turning off some errors libxml_use_internal_errors(true); @@ -86,7 +86,7 @@ public function withString(string $content) * Loads the contents of a file as a string * so that we can work with it. * - * @return DOMParser + * @return $this */ public function withFile(string $path) { @@ -181,7 +181,7 @@ public function seeCheckboxIsChecked(string $element): bool /** * Search the DOM using an XPath expression. * - * @return DOMNodeList + * @return DOMNodeList|false */ protected function doXPath(?string $search, string $element, array $paths = []) { diff --git a/system/Test/Fabricator.php b/system/Test/Fabricator.php index 1effdb790bb7..d540351e82e0 100644 --- a/system/Test/Fabricator.php +++ b/system/Test/Fabricator.php @@ -374,7 +374,7 @@ public function makeArray() $result = []; foreach ($this->formatters as $field => $formatter) { - $result[$field] = $this->faker->{$formatter}; + $result[$field] = $this->faker->{$formatter}(); } } // If no formatters were defined then look for a model fake() method diff --git a/system/Test/FeatureTestCase.php b/system/Test/FeatureTestCase.php index ebe291ece944..5f20da5cef7c 100644 --- a/system/Test/FeatureTestCase.php +++ b/system/Test/FeatureTestCase.php @@ -159,11 +159,9 @@ public function call(string $method, string $path, ?array $params = null) // Clean up any open output buffers // not relevant to unit testing - // @codeCoverageIgnoreStart if (\ob_get_level() > 0 && (! isset($this->clean) || $this->clean === true)) { - \ob_end_clean(); + \ob_end_clean(); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd // Simulate having a blank session $_SESSION = []; @@ -207,15 +205,13 @@ public function call(string $method, string $path, ?array $params = null) Services::router()->setDirectory(null); // Ensure the output buffer is identical so no tests are risky - // @codeCoverageIgnoreStart while (\ob_get_level() > $buffer) { - \ob_end_clean(); + \ob_end_clean(); // @codeCoverageIgnore } while (\ob_get_level() < $buffer) { - \ob_start(); + \ob_start(); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd return new FeatureResponse($response); } diff --git a/system/Test/FeatureTestTrait.php b/system/Test/FeatureTestTrait.php index 404df1f2df21..17b9008861dc 100644 --- a/system/Test/FeatureTestTrait.php +++ b/system/Test/FeatureTestTrait.php @@ -149,11 +149,9 @@ public function call(string $method, string $path, ?array $params = null) // Clean up any open output buffers // not relevant to unit testing - // @codeCoverageIgnoreStart if (\ob_get_level() > 0 && (! isset($this->clean) || $this->clean === true)) { - \ob_end_clean(); + \ob_end_clean(); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd // Simulate having a blank session $_SESSION = []; @@ -197,15 +195,13 @@ public function call(string $method, string $path, ?array $params = null) Services::router()->setDirectory(null); // Ensure the output buffer is identical so no tests are risky - // @codeCoverageIgnoreStart while (\ob_get_level() > $buffer) { - \ob_end_clean(); + \ob_end_clean(); // @codeCoverageIgnore } while (\ob_get_level() < $buffer) { - \ob_start(); + \ob_start(); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd return new TestResponse($response); } diff --git a/system/Test/TestResponse.php b/system/Test/TestResponse.php index 16a50f74d1e5..d84d01b681ee 100644 --- a/system/Test/TestResponse.php +++ b/system/Test/TestResponse.php @@ -28,6 +28,8 @@ * @no-final * * @internal + * + * @mixin DOMParser */ class TestResponse extends TestCase { @@ -347,7 +349,7 @@ public function assertCookieExpired(string $key, string $prefix = '') /** * Returns the response's body as JSON * - * @return false|mixed + * @return false|string|null */ public function getJSON() { diff --git a/system/ThirdParty/Escaper/Exception/ExceptionInterface.php b/system/ThirdParty/Escaper/Exception/ExceptionInterface.php index 87edfd2aa582..8f5fd89aa1b3 100644 --- a/system/ThirdParty/Escaper/Exception/ExceptionInterface.php +++ b/system/ThirdParty/Escaper/Exception/ExceptionInterface.php @@ -4,6 +4,8 @@ namespace Laminas\Escaper\Exception; -interface ExceptionInterface +use Throwable; + +interface ExceptionInterface extends Throwable { } diff --git a/system/ThirdParty/Kint/resources/compiled/rich.js b/system/ThirdParty/Kint/resources/compiled/rich.js index 2f0ef6a1d0e2..8354914c50a3 100644 --- a/system/ThirdParty/Kint/resources/compiled/rich.js +++ b/system/ThirdParty/Kint/resources/compiled/rich.js @@ -1 +1 @@ -void 0===window.kintRich&&(window.kintRich=function(){"use strict";var l={selectText:function(e){var t=window.getSelection(),a=document.createRange();a.selectNodeContents(e),t.removeAllRanges(),t.addRange(a)},toggle:function(e,t){var a=l.getChildren(e);a&&(e.classList.toggle("kint-show",t),1===a.childNodes.length&&(a=a.childNodes[0].childNodes[0])&&a.classList&&a.classList.contains("kint-parent")&&l.toggle(a,t))},toggleChildren:function(e,t){var a=l.getChildren(e);if(a){var o=a.getElementsByClassName("kint-parent"),n=o.length;for(void 0===t&&(t=e.classList.contains("kint-show"));n--;)l.toggle(o[n],t)}},switchTab:function(e){var t=e.previousSibling,a=0;for(e.parentNode.getElementsByClassName("kint-active-tab")[0].classList.remove("kint-active-tab"),e.classList.add("kint-active-tab");t;)1===t.nodeType&&a++,t=t.previousSibling;for(var o=e.parentNode.nextSibling.childNodes,n=0;n"},openInNewWindow:function(e){var t=window.open();t&&(t.document.open(),t.document.write(l.mktag("html")+l.mktag("head")+l.mktag("title")+"Kint ("+(new Date).toISOString()+")"+l.mktag("/title")+l.mktag('meta charset="utf-8"')+l.mktag('script class="kint-rich-script" nonce="'+l.script.nonce+'"')+l.script.innerHTML+l.mktag("/script")+l.mktag('style class="kint-rich-style" nonce="'+l.style.nonce+'"')+l.style.innerHTML+l.mktag("/style")+l.mktag("/head")+l.mktag("body")+'
'+e.parentNode.outerHTML+"
"+l.mktag("/body")),t.document.close())},sortTable:function(e,a){var t=e.tBodies[0];[].slice.call(e.tBodies[0].rows).sort(function(e,t){if(e=e.cells[a].textContent.trim().toLocaleLowerCase(),t=t.cells[a].textContent.trim().toLocaleLowerCase(),isNaN(e)||isNaN(t)){if(isNaN(e)&&!isNaN(t))return 1;if(isNaN(t)&&!isNaN(e))return-1}else e=parseFloat(e),t=parseFloat(t);return eli:not(.kint-active-tab)").forEach(function(e){l.isFolderOpen()&&!l.folder.contains(e)||0===e.offsetWidth&&0===e.offsetHeight||l.keyboardNav.targets.push(e)}),e&&-1!==l.keyboardNav.targets.indexOf(e)&&(l.keyboardNav.target=l.keyboardNav.targets.indexOf(e))},sync:function(e){var t=document.querySelector(".kint-focused");t&&t.classList.remove("kint-focused"),l.keyboardNav.active&&((t=l.keyboardNav.targets[l.keyboardNav.target]).classList.add("kint-focused"),e||l.keyboardNav.scroll(t))},scroll:function(e){var t,a;e!==l.folder.querySelector("dt > nav")&&(a=(t=function(e){return e.offsetTop+(e.offsetParent?t(e.offsetParent):0)})(e),l.isFolderOpen()?(e=l.folder.querySelector("dd.kint-foldout")).scrollTo(0,a-e.clientHeight/2):window.scrollTo(0,a-window.innerHeight/2))},moveCursor:function(e){for(l.keyboardNav.target+=e;l.keyboardNav.target<0;)l.keyboardNav.target+=l.keyboardNav.targets.length;for(;l.keyboardNav.target>=l.keyboardNav.targets.length;)l.keyboardNav.target-=l.keyboardNav.targets.length;l.keyboardNav.sync()},setCursor:function(e){if(l.isFolderOpen()&&!l.folder.contains(e))return!1;l.keyboardNav.fetchTargets();for(var t=0;t"},openInNewWindow:function(e){var t=window.open();t&&(t.document.open(),t.document.write(l.mktag("html")+l.mktag("head")+l.mktag("title")+"Kint ("+(new Date).toISOString()+")"+l.mktag("/title")+l.mktag('meta charset="utf-8"')+l.mktag('script class="kint-rich-script" nonce="'+l.script.nonce+'"')+l.script.innerHTML+l.mktag("/script")+l.mktag('style class="kint-rich-style" nonce="'+l.style.nonce+'"')+l.style.innerHTML+l.mktag("/style")+l.mktag("/head")+l.mktag("body")+'
'+e.parentNode.outerHTML+"
"+l.mktag("/body")),t.document.close())},sortTable:function(e,a){var t=e.tBodies[0];[].slice.call(e.tBodies[0].rows).sort(function(e,t){if(e=e.cells[a].textContent.trim().toLocaleLowerCase(),t=t.cells[a].textContent.trim().toLocaleLowerCase(),isNaN(e)||isNaN(t)){if(isNaN(e)&&!isNaN(t))return 1;if(isNaN(t)&&!isNaN(e))return-1}else e=parseFloat(e),t=parseFloat(t);return eli:not(.kint-active-tab)").forEach(function(e){l.isFolderOpen()&&!l.folder.contains(e)||0===e.offsetWidth&&0===e.offsetHeight||l.keyboardNav.targets.push(e)}),e&&-1!==l.keyboardNav.targets.indexOf(e)&&(l.keyboardNav.target=l.keyboardNav.targets.indexOf(e))},sync:function(e){var t=document.querySelector(".kint-focused");t&&t.classList.remove("kint-focused"),l.keyboardNav.active&&((t=l.keyboardNav.targets[l.keyboardNav.target]).classList.add("kint-focused"),e||l.keyboardNav.scroll(t))},scroll:function(e){var t,a;e!==l.folder.querySelector("dt > nav")&&(a=(t=function(e){return e.offsetTop+(e.offsetParent?t(e.offsetParent):0)})(e),l.isFolderOpen()?(e=l.folder.querySelector("dd.kint-foldout")).scrollTo(0,a-e.clientHeight/2):window.scrollTo(0,a-window.innerHeight/2))},moveCursor:function(e){for(l.keyboardNav.target+=e;l.keyboardNav.target<0;)l.keyboardNav.target+=l.keyboardNav.targets.length;for(;l.keyboardNav.target>=l.keyboardNav.targets.length;)l.keyboardNav.target-=l.keyboardNav.targets.length;l.keyboardNav.sync()},setCursor:function(e){if(l.isFolderOpen()&&!l.folder.contains(e))return!1;l.keyboardNav.fetchTargets();for(var t=0;t 'valid_cc_number[visa]' * ]; * - * @param mixed $ccNumber + * @param array|bool|float|int|object|string|null $ccNumber */ public function valid_cc_number($ccNumber, string $type): bool { diff --git a/system/Validation/StrictRules/FormatRules.php b/system/Validation/StrictRules/FormatRules.php index 97df6c04aecb..facb28664b2a 100644 --- a/system/Validation/StrictRules/FormatRules.php +++ b/system/Validation/StrictRules/FormatRules.php @@ -30,7 +30,7 @@ public function __construct() /** * Alpha * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function alpha($str = null): bool { @@ -44,7 +44,7 @@ public function alpha($str = null): bool /** * Alpha with spaces. * - * @param mixed $value Value. + * @param array|bool|float|int|object|string|null $value Value. * * @return bool True if alpha with spaces, else false. */ @@ -60,7 +60,7 @@ public function alpha_space($value = null): bool /** * Alphanumeric with underscores and dashes * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function alpha_dash($str = null): bool { @@ -82,7 +82,7 @@ public function alpha_dash($str = null): bool * _ underscore, + plus, = equals, | vertical bar, : colon, . period * ~ ! # $ % & * - _ + = | : . * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str * * @return bool */ @@ -102,7 +102,7 @@ public function alpha_numeric_punct($str) /** * Alphanumeric * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function alpha_numeric($str = null): bool { @@ -120,7 +120,7 @@ public function alpha_numeric($str = null): bool /** * Alphanumeric w/ spaces * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function alpha_numeric_space($str = null): bool { @@ -138,7 +138,7 @@ public function alpha_numeric_space($str = null): bool /** * Any type of string * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function string($str = null): bool { @@ -148,7 +148,7 @@ public function string($str = null): bool /** * Decimal number * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function decimal($str = null): bool { @@ -166,7 +166,7 @@ public function decimal($str = null): bool /** * String of hexidecimal characters * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function hex($str = null): bool { @@ -184,7 +184,7 @@ public function hex($str = null): bool /** * Integer * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function integer($str = null): bool { @@ -202,7 +202,7 @@ public function integer($str = null): bool /** * Is a Natural number (0,1,2,3, etc.) * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function is_natural($str = null): bool { @@ -220,7 +220,7 @@ public function is_natural($str = null): bool /** * Is a Natural number, but not a zero (1,2,3, etc.) * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function is_natural_no_zero($str = null): bool { @@ -238,7 +238,7 @@ public function is_natural_no_zero($str = null): bool /** * Numeric * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function numeric($str = null): bool { @@ -256,7 +256,7 @@ public function numeric($str = null): bool /** * Compares value against a regular expression pattern. * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function regex_match($str, string $pattern): bool { @@ -273,7 +273,7 @@ public function regex_match($str, string $pattern): bool * * @see http://php.net/manual/en/datetimezone.listidentifiers.php * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function timezone($str = null): bool { @@ -290,7 +290,7 @@ public function timezone($str = null): bool * Tests a string for characters outside of the Base64 alphabet * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045 * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function valid_base64($str = null): bool { @@ -304,7 +304,7 @@ public function valid_base64($str = null): bool /** * Valid JSON * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function valid_json($str = null): bool { @@ -318,7 +318,7 @@ public function valid_json($str = null): bool /** * Checks for a correctly formatted email address * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function valid_email($str = null): bool { @@ -335,7 +335,7 @@ public function valid_email($str = null): bool * Example: * valid_emails[one@example.com,two@example.com] * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function valid_emails($str = null): bool { @@ -349,8 +349,8 @@ public function valid_emails($str = null): bool /** * Validate an IP address (human readable format or binary string - inet_pton) * - * @param mixed $ip - * @param string|null $which IP protocol: 'ipv4' or 'ipv6' + * @param array|bool|float|int|object|string|null $ip + * @param string|null $which IP protocol: 'ipv4' or 'ipv6' */ public function valid_ip($ip = null, ?string $which = null): bool { @@ -367,7 +367,7 @@ public function valid_ip($ip = null, ?string $which = null): bool * Warning: this rule will pass basic strings like * "banana"; use valid_url_strict for a stricter rule. * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function valid_url($str = null): bool { @@ -381,8 +381,8 @@ public function valid_url($str = null): bool /** * Checks a URL to ensure it's formed correctly. * - * @param mixed $str - * @param string|null $validSchemes comma separated list of allowed schemes + * @param array|bool|float|int|object|string|null $str + * @param string|null $validSchemes comma separated list of allowed schemes */ public function valid_url_strict($str = null, ?string $validSchemes = null): bool { @@ -396,7 +396,7 @@ public function valid_url_strict($str = null, ?string $validSchemes = null): boo /** * Checks for a valid date and matches a given date format * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function valid_date($str = null, ?string $format = null): bool { diff --git a/system/Validation/StrictRules/Rules.php b/system/Validation/StrictRules/Rules.php index 76f32a9c1fe4..47dbee9b5ae7 100644 --- a/system/Validation/StrictRules/Rules.php +++ b/system/Validation/StrictRules/Rules.php @@ -31,8 +31,8 @@ public function __construct() /** * The value does not match another field in $data. * - * @param mixed $str - * @param array $data Other field/value pairs + * @param array|bool|float|int|object|string|null $str + * @param array $data Other field/value pairs */ public function differs($str, string $field, array $data): bool { @@ -46,7 +46,7 @@ public function differs($str, string $field, array $data): bool /** * Equals the static value provided. * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function equals($str, string $val): bool { @@ -57,7 +57,7 @@ public function equals($str, string $val): bool * Returns true if $str is $val characters long. * $val = "5" (one) | "5,8,12" (multiple values) * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function exact_length($str, string $val): bool { @@ -71,7 +71,7 @@ public function exact_length($str, string $val): bool /** * Greater than * - * @param mixed $str expects int|string + * @param array|bool|float|int|object|string|null $str expects int|string */ public function greater_than($str, string $min): bool { @@ -89,7 +89,7 @@ public function greater_than($str, string $min): bool /** * Equal to or Greater than * - * @param mixed $str expects int|string + * @param array|bool|float|int|object|string|null $str expects int|string */ public function greater_than_equal_to($str, string $min): bool { @@ -113,7 +113,7 @@ public function greater_than_equal_to($str, string $min): bool * is_not_unique[table.field,where_field,where_value] * is_not_unique[menu.id,active,1] * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function is_not_unique($str, string $field, array $data): bool { @@ -123,7 +123,7 @@ public function is_not_unique($str, string $field, array $data): bool /** * Value should be within an array of values * - * @param mixed $value + * @param array|bool|float|int|object|string|null $value */ public function in_list($value, string $list): bool { @@ -147,7 +147,7 @@ public function in_list($value, string $list): bool * is_unique[table.field,ignore_field,ignore_value] * is_unique[users.email,id,5] * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function is_unique($str, string $field, array $data): bool { @@ -157,7 +157,7 @@ public function is_unique($str, string $field, array $data): bool /** * Less than * - * @param mixed $str expects int|string + * @param array|bool|float|int|object|string|null $str expects int|string */ public function less_than($str, string $max): bool { @@ -175,7 +175,7 @@ public function less_than($str, string $max): bool /** * Equal to or Less than * - * @param mixed $str expects int|string + * @param array|bool|float|int|object|string|null $str expects int|string */ public function less_than_equal_to($str, string $max): bool { @@ -193,8 +193,8 @@ public function less_than_equal_to($str, string $max): bool /** * Matches the value of another field in $data. * - * @param mixed $str - * @param array $data Other field/value pairs + * @param array|bool|float|int|object|string|null $str + * @param array $data Other field/value pairs */ public function matches($str, string $field, array $data): bool { @@ -204,7 +204,7 @@ public function matches($str, string $field, array $data): bool /** * Returns true if $str is $val or fewer characters in length. * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function max_length($str, string $val): bool { @@ -222,7 +222,7 @@ public function max_length($str, string $val): bool /** * Returns true if $str is at least $val length. * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function min_length($str, string $val): bool { @@ -240,7 +240,7 @@ public function min_length($str, string $val): bool /** * Does not equal the static value provided. * - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function not_equals($str, string $val): bool { @@ -250,7 +250,7 @@ public function not_equals($str, string $val): bool /** * Value should not be within an array of values. * - * @param mixed $value + * @param array|bool|float|int|object|string|null $value */ public function not_in_list($value, string $list): bool { @@ -270,7 +270,7 @@ public function not_in_list($value, string $list): bool } /** - * @param mixed $str + * @param array|bool|float|int|object|string|null $str */ public function required($str = null): bool { @@ -285,9 +285,9 @@ public function required($str = null): bool * * required_with[password] * - * @param mixed $str - * @param string|null $fields List of fields that we should check if present - * @param array $data Complete list of fields from the form + * @param array|bool|float|int|object|string|null $str + * @param string|null $fields List of fields that we should check if present + * @param array $data Complete list of fields from the form */ public function required_with($str = null, ?string $fields = null, array $data = []): bool { @@ -302,9 +302,9 @@ public function required_with($str = null, ?string $fields = null, array $data = * * required_without[id,email] * - * @param string|null $str - * @param string|null $otherFields The param fields of required_without[]. - * @param string|null $field This rule param fields aren't present, this field is required. + * @param array|bool|float|int|object|string|null $str + * @param string|null $otherFields The param fields of required_without[]. + * @param string|null $field This rule param fields aren't present, this field is required. */ public function required_without($str = null, ?string $otherFields = null, array $data = [], ?string $error = null, ?string $field = null): bool { diff --git a/system/View/Filters.php b/system/View/Filters.php index 79324322c0b0..e2c827927796 100644 --- a/system/View/Filters.php +++ b/system/View/Filters.php @@ -75,6 +75,7 @@ public static function default($value, string $default): string * Escapes the given value with our `esc()` helper function. * * @param string $value + * @phpstan-param 'html'|'js'|'css'|'url'|'attr'|'raw' $context */ public static function esc($value, string $context = 'html'): string { diff --git a/system/View/Parser.php b/system/View/Parser.php index 9b425602c904..ea8c41fd1cfa 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -277,7 +277,7 @@ protected function parsePair(string $variable, array $data, string $template): a // have something to loop over. preg_match_all( '#' . $this->leftDelimiter . '\s*' . preg_quote($variable, '#') . '\s*' . $this->rightDelimiter . '(.+?)' . - $this->leftDelimiter . '\s*' . '/' . preg_quote($variable, '#') . '\s*' . $this->rightDelimiter . '#s', + $this->leftDelimiter . '\s*/' . preg_quote($variable, '#') . '\s*' . $this->rightDelimiter . '#s', $template, $matches, PREG_SET_ORDER diff --git a/system/View/View.php b/system/View/View.php index b0206d67caba..9a39104728df 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -305,8 +305,9 @@ public function excerpt(string $string, int $length = 20): string /** * Sets several pieces of view data at once. * - * @param string $context The context to escape it for: html, css, js, url - * If null, no escaping will happen + * @param string|null $context The context to escape it for: html, css, js, url + * If null, no escaping will happen + * @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context */ public function setData(array $data = [], ?string $context = null): RendererInterface { @@ -326,6 +327,7 @@ public function setData(array $data = [], ?string $context = null): RendererInte * @param mixed $value * @param string|null $context The context to escape it for: html, css, js, url * If null, no escaping will happen + * @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context */ public function setVar(string $name, $value = null, ?string $context = null): RendererInterface { diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index d9a0ec8f3bed..fb43d37a9ff4 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -167,7 +167,7 @@ public function run() } if ($this->db->DBDriver === 'OCI8') { - $this->db->query('alter session set NLS_DATE_FORMAT=?', ['YYYY/MM/DD HH24:MI:SS']); + $this->db->query('alter session set NLS_DATE_FORMAT=?', ['YYYY-MM-DD HH24:MI:SS']); $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; $data['type_test'][0]['type_date'] = '2020-01-11 22:11:00'; $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; diff --git a/tests/_support/Log/Handlers/TestHandler.php b/tests/_support/Log/Handlers/TestHandler.php index e20a32c2b0b6..82c893e271e9 100644 --- a/tests/_support/Log/Handlers/TestHandler.php +++ b/tests/_support/Log/Handlers/TestHandler.php @@ -47,8 +47,8 @@ public function __construct(array $config) * will stop. Any handlers that have not run, yet, will not * be run. * - * @param $level - * @param $message + * @param string $level + * @param string $message */ public function handle($level, $message): bool { diff --git a/tests/_support/Models/FabricatorModel.php b/tests/_support/Models/FabricatorModel.php index 70efeeb5c942..68987a7f096d 100644 --- a/tests/_support/Models/FabricatorModel.php +++ b/tests/_support/Models/FabricatorModel.php @@ -30,7 +30,7 @@ class FabricatorModel extends Model public function fake(Generator &$faker) { return (object) [ - 'name' => $faker->ipv4, + 'name' => $faker->ipv4(), 'description' => $faker->words(10), ]; } diff --git a/tests/_support/View/SampleClassWithInitController.php b/tests/_support/View/SampleClassWithInitController.php index 531b208dc6d7..32eb4f70a7a3 100644 --- a/tests/_support/View/SampleClassWithInitController.php +++ b/tests/_support/View/SampleClassWithInitController.php @@ -23,6 +23,8 @@ */ class SampleClassWithInitController { + private ResponseInterface $response; + public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { $this->response = $response; diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index 4535096ccc61..fee7759bb264 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -66,6 +66,16 @@ public function testRunEmptyDefaultRoute() $this->assertStringContainsString('Welcome to CodeIgniter', $output); } + public function testRunEmptyDefaultRouteReturnResponse() + { + $_SERVER['argv'] = ['index.php']; + $_SERVER['argc'] = 1; + + $response = $this->codeigniter->useSafeOutput(true)->run(null, true); + + $this->assertStringContainsString('Welcome to CodeIgniter', $response->getBody()); + } + public function testRunClosureRoute() { $_SERVER['argv'] = ['index.php', 'pages/about']; @@ -78,7 +88,7 @@ public function testRunClosureRoute() $routes->add('pages/(:segment)', static function ($segment) { echo 'You want to see "' . esc($segment) . '" page.'; }); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -97,7 +107,7 @@ public function testRun404Override() $routes = Services::routes(); $routes->setAutoRoute(false); $routes->set404Override('Tests\Support\Controllers\Hello::index'); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -116,7 +126,7 @@ public function testRun404OverrideControllerReturnsResponse() $routes = Services::routes(); $routes->setAutoRoute(false); $routes->set404Override('Tests\Support\Controllers\Popcorn::pop'); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -126,6 +136,23 @@ public function testRun404OverrideControllerReturnsResponse() $this->assertStringContainsString('Oops', $output); } + public function testRun404OverrideReturnResponse() + { + $_SERVER['argv'] = ['index.php', '/']; + $_SERVER['argc'] = 2; + + // Inject mock router. + $routes = Services::routes(); + $routes->setAutoRoute(false); + $routes->set404Override('Tests\Support\Controllers\Popcorn::pop'); + $router = Services::router($routes, Services::incomingrequest()); + Services::injectMock('router', $router); + + $response = $this->codeigniter->useSafeOutput(true)->run($routes, true); + + $this->assertStringContainsString('Oops', $response->getBody()); + } + public function testRun404OverrideByClosure() { $_SERVER['argv'] = ['index.php', '/']; @@ -137,7 +164,7 @@ public function testRun404OverrideByClosure() $routes->set404Override(static function () { echo '404 Override by Closure.'; }); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -157,7 +184,7 @@ public function testControllersCanReturnString() // Inject mock router. $routes = Services::routes(); $routes->add('pages/(:segment)', static fn ($segment) => 'You want to see "' . esc($segment) . '" page.'); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -182,7 +209,7 @@ public function testControllersCanReturnResponseObject() return $response->setBody($string); }); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -209,7 +236,7 @@ public function testControllersCanReturnDownloadResponseObject() return $response->download('some.txt', 'some text', true); }); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -228,9 +255,9 @@ public function testControllersRunFilterByClassName() // Inject mock router. $routes = Services::routes(); - $routes->add('pages/about', static fn () => Services::request()->getBody(), ['filter' => Customfilter::class]); + $routes->add('pages/about', static fn () => Services::incomingrequest()->getBody(), ['filter' => Customfilter::class]); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -258,7 +285,7 @@ public function testRoutesIsEmpty() $_SERVER['argc'] = 2; // Inject mock router. - $router = Services::router(null, Services::request(), false); + $router = Services::router(null, Services::incomingrequest(), false); Services::injectMock('router', $router); ob_start(); @@ -335,7 +362,7 @@ public function testRunRedirectionWithNamed() }, ['as' => 'name']); $routes->addRedirect('example', 'name'); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -358,7 +385,7 @@ public function testRunRedirectionWithURI() }); $routes->addRedirect('example', 'pages/uri'); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -382,7 +409,7 @@ public function testRunRedirectionWithURINotSet() $routes = Services::routes(); $routes->addRedirect('example', 'pages/notset'); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -405,7 +432,7 @@ public function testRunRedirectionWithHTTPCode303() $routes = Services::routes(); $routes->addRedirect('example', 'pages/notset', 301); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -422,7 +449,7 @@ public function testStoresPreviousURL() $_SERVER['argc'] = 2; // Inject mock router. - $router = Services::router(null, Services::request(), false); + $router = Services::router(null, Services::incomingrequest(), false); Services::injectMock('router', $router); ob_start(); @@ -446,7 +473,7 @@ public function testNotStoresPreviousURL() $routes = Services::routes(); $routes->addRedirect('example', 'pages/notset', 301); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -470,7 +497,7 @@ public function testNotStoresPreviousURLByCheckingContentType() return $response->setContentType('image/jpeg', ''); }); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); ob_start(); @@ -537,7 +564,7 @@ public function testSpoofRequestMethodCanUsePUT() $this->codeigniter->useSafeOutput(true)->run(); ob_get_clean(); - $this->assertSame('put', Services::request()->getMethod()); + $this->assertSame('put', Services::incomingrequest()->getMethod()); } public function testSpoofRequestMethodCannotUseGET() @@ -561,7 +588,7 @@ public function testSpoofRequestMethodCannotUseGET() $this->codeigniter->useSafeOutput(true)->run(); ob_get_clean(); - $this->assertSame('post', Services::request()->getMethod()); + $this->assertSame('post', Services::incomingrequest()->getMethod()); } /** @@ -588,7 +615,7 @@ public function testPageCacheSendSecureHeaders() return $response->setBody($string); }); - $router = Services::router($routes, Services::request()); + $router = Services::router($routes, Services::incomingrequest()); Services::injectMock('router', $router); /** @var Filters $filterConfig */ @@ -619,7 +646,7 @@ public function testPageCacheSendSecureHeaders() // Clear Page cache command('cache:clear'); - // Remove stream fliters + // Remove stream filters stream_filter_remove($outputStreamFilter); stream_filter_remove($errorStreamFilter); } @@ -654,7 +681,7 @@ public function testPageCacheWithCacheQueryString($cacheQueryStringValue, int $e $_SERVER['REQUEST_URI'] = '/' . $testingUrl; $routes = Services::routes(true); $routes->add($testingUrl, static function () { - CodeIgniter::cache(0); // Dont cache the page in the run() function because CodeIgniter class will create default $cacheConfig and overwrite settings from the dataProvider + CodeIgniter::cache(0); // Don't cache the page in the run() function because CodeIgniter class will create default $cacheConfig and overwrite settings from the dataProvider $response = Services::response(); $string = 'This is a test page, to check cache configuration'; @@ -662,7 +689,7 @@ public function testPageCacheWithCacheQueryString($cacheQueryStringValue, int $e }); // Inject router - $router = Services::router($routes, Services::request(null, false)); + $router = Services::router($routes, Services::incomingrequest(null, false)); Services::injectMock('router', $router); // Cache the page output using default caching function and $cacheConfig with value from the data provider diff --git a/tests/system/Commands/CreateDatabaseTest.php b/tests/system/Commands/CreateDatabaseTest.php index 1d0270f367b9..4a55df843777 100644 --- a/tests/system/Commands/CreateDatabaseTest.php +++ b/tests/system/Commands/CreateDatabaseTest.php @@ -20,6 +20,8 @@ use Config\Database; /** + * @group DatabaseLive + * * @internal */ final class CreateDatabaseTest extends CIUnitTestCase diff --git a/tests/system/Commands/Database/MigrateStatusTest.php b/tests/system/Commands/Database/MigrateStatusTest.php index 8f6997d01127..20e7485ca17f 100644 --- a/tests/system/Commands/Database/MigrateStatusTest.php +++ b/tests/system/Commands/Database/MigrateStatusTest.php @@ -15,6 +15,8 @@ use CodeIgniter\Test\Filters\CITestStreamFilter; /** + * @group DatabaseLive + * * @internal */ final class MigrateStatusTest extends CIUnitTestCase diff --git a/tests/system/Commands/Database/ShowTableInfoTest.php b/tests/system/Commands/Database/ShowTableInfoTest.php index 2824588695c7..1cde7280c164 100644 --- a/tests/system/Commands/Database/ShowTableInfoTest.php +++ b/tests/system/Commands/Database/ShowTableInfoTest.php @@ -18,6 +18,8 @@ use Tests\Support\Database\Seeds\CITestSeeder; /** + * @group DatabaseLive + * * @internal */ final class ShowTableInfoTest extends CIUnitTestCase diff --git a/tests/system/Commands/DatabaseCommandsTest.php b/tests/system/Commands/DatabaseCommandsTest.php index b80fcd5f0bb8..3938ea012aa7 100644 --- a/tests/system/Commands/DatabaseCommandsTest.php +++ b/tests/system/Commands/DatabaseCommandsTest.php @@ -15,6 +15,8 @@ use CodeIgniter\Test\Filters\CITestStreamFilter; /** + * @group DatabaseLive + * * @internal */ final class DatabaseCommandsTest extends CIUnitTestCase diff --git a/tests/system/Commands/GenerateKeyTest.php b/tests/system/Commands/GenerateKeyTest.php index 4ba9e8e0ffba..fbe95abc5564 100644 --- a/tests/system/Commands/GenerateKeyTest.php +++ b/tests/system/Commands/GenerateKeyTest.php @@ -86,8 +86,7 @@ public function testGenerateKeyShowsEncodedKey() /** * @runInSeparateProcess - * - * @preserveGlobalState disabled + * @preserveGlobalState disabled */ public function testGenerateKeyCreatesNewKey() { diff --git a/tests/system/Commands/MigrationIntegrationTest.php b/tests/system/Commands/MigrationIntegrationTest.php index c0970b5c317d..68de997ea7a5 100644 --- a/tests/system/Commands/MigrationIntegrationTest.php +++ b/tests/system/Commands/MigrationIntegrationTest.php @@ -15,6 +15,8 @@ use CodeIgniter\Test\Filters\CITestStreamFilter; /** + * @group DatabaseLive + * * @internal */ final class MigrationIntegrationTest extends CIUnitTestCase diff --git a/tests/system/Commands/SessionsCommandsTest.php b/tests/system/Commands/SessionsCommandsTest.php deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/system/CommonFunctionsSendTest.php b/tests/system/CommonFunctionsSendTest.php index 03e675cfbfa5..365049315778 100644 --- a/tests/system/CommonFunctionsSendTest.php +++ b/tests/system/CommonFunctionsSendTest.php @@ -32,8 +32,7 @@ protected function setUp(): void * See https://github.com/codeigniter4/CodeIgniter4/issues/1393 * * @runInSeparateProcess - * - * @preserveGlobalState disabled + * @preserveGlobalState disabled */ public function testRedirectResponseCookiesSent() { diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php index a50069d3745b..1b3f9aaf7e06 100644 --- a/tests/system/CommonFunctionsTest.php +++ b/tests/system/CommonFunctionsTest.php @@ -177,10 +177,15 @@ public function testEscapeBadContext() esc(['width' => '800', 'height' => '600'], 'bogus'); } + public function testEscapeBadContextZero() + { + $this->expectException('InvalidArgumentException'); + esc('