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 .= "\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('