diff --git a/.gitattributes b/.gitattributes index 29621a714026..1a7b3deb77e8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,19 +5,27 @@ # git files .gitattributes export-ignore -# .gitignore +.gitignore export-ignore -# helper config files -.travis.yml export-ignore -phpdoc.dist.xml export-ignore - -# Misc other files -readme.rst +# Don't give admin files +.github/ export-ignore +admin/ export-ignore +contributing/ export-ignore +.editorconfig export-ignore +.nojekyll export-ignore export-ignore +CODE_OF_CONDUCT.md export-ignore +DCO.txt export-ignore +PULL_REQUEST_TEMPLATE.md export-ignore +stale.yml export-ignore +Vagrantfile.dist export-ignore -# They don't want all of our tests... -tests/bin/ export-ignore -tests/codeigniter/ export-ignore -tests/travis/ export-ignore +# They don't want our test files +tests/system/ export-ignore +utils/ export-ignore +rector.php export-ignore +phpunit.xml.dist export-ignore +phpstan.neon.dist export-ignore -# User Guide Source Files -user_guide_src +# The source user guide, either +user_guide_src/ export-ignore +phpdoc.dist.xml export-ignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..6532121074ad --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "daily" diff --git a/.github/workflows/apidocs-action.yml b/.github/workflows/deploy-apidocs.yml similarity index 75% rename from .github/workflows/apidocs-action.yml rename to .github/workflows/deploy-apidocs.yml index ecad14f2399c..393adaf7c277 100644 --- a/.github/workflows/apidocs-action.yml +++ b/.github/workflows/deploy-apidocs.yml @@ -1,4 +1,7 @@ -name: API Documentation +# When changes are pushed to the develop branch, +# build the current version of the API documentation +# with phpDocumentor and deploy it to the api branch. +name: Deploy API Documentation on: push: @@ -10,10 +13,9 @@ on: jobs: build: - name: Generate API Docs + name: Deploy to api if: (github.repository == 'codeigniter4/CodeIgniter4') runs-on: ubuntu-latest - steps: - name: Setup credentials run: | @@ -25,7 +27,7 @@ jobs: with: path: source - - name: Checkout api + - name: Checkout target uses: actions/checkout@v2 with: repository: codeigniter4/api @@ -35,13 +37,12 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.2' - extensions: intl, json, mbstring, mysqlnd, xdebug, xml, sqlite3 - coverage: xdebug + php-version: '7.4' + extensions: intl - - name: Download GraphViz for phpDocumentor + - name: Install GraphViz for phpDocumentor run: | - sudo apt-get install -yq graphviz + sudo apt install -yq graphviz - name: Download phpDocumentor run: | @@ -50,21 +51,21 @@ jobs: -L https://github.com/phpDocumentor/phpDocumentor/releases/download/v2.9.1/phpDocumentor.phar \ -o admin/phpDocumentor.phar - - name: Prepare API + - name: Prepare run: | cd ./api rm -rf ./api/docs* mkdir -p ./api/docs git reset --hard master - - name: Make the new API docs + - name: Build run: | cd ./source chmod +x admin/phpDocumentor.phar admin/phpDocumentor.phar cp -R ${GITHUB_WORKSPACE}/source/api/build/* ${GITHUB_WORKSPACE}/api/docs - - name: Deploy API + - name: Deploy run: | cd ./api git add . diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy-framework.yml similarity index 87% rename from .github/workflows/deploy.yml rename to .github/workflows/deploy-framework.yml index b7bc1892ea37..ce925ed66639 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy-framework.yml @@ -1,4 +1,6 @@ -name: Deploy +# When a new Release is created, deploy relevant +# files to each of the generated repos. +name: Deploy Framework on: release: @@ -6,8 +8,9 @@ on: jobs: framework: + name: Deploy to framework + if: (github.repository == 'codeigniter4/CodeIgniter4') runs-on: ubuntu-latest - steps: - name: Identify run: | @@ -33,7 +36,7 @@ jobs: run: ./source/.github/scripts/deploy-framework ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/framework ${GITHUB_REF##*/} - name: Release - uses: actions/github-script@0.8.0 + uses: actions/github-script@v3 with: github-token: ${{secrets.ACCESS_TOKEN}} script: | @@ -50,8 +53,9 @@ jobs: }) appstarter: + name: Deploy to appstarter + if: (github.repository == 'codeigniter4/CodeIgniter4') runs-on: ubuntu-latest - steps: - name: Identify run: | @@ -77,7 +81,7 @@ jobs: run: ./source/.github/scripts/deploy-appstarter ${GITHUB_WORKSPACE}/source ${GITHUB_WORKSPACE}/appstarter ${GITHUB_REF##*/} - name: Release - uses: actions/github-script@0.8.0 + uses: actions/github-script@v3 with: github-token: ${{secrets.ACCESS_TOKEN}} script: | diff --git a/.github/workflows/deploy-userguide-latest.yml b/.github/workflows/deploy-userguide-latest.yml new file mode 100644 index 000000000000..22e6eed4db4f --- /dev/null +++ b/.github/workflows/deploy-userguide-latest.yml @@ -0,0 +1,54 @@ +# When changes are pushed to the develop branch, +# build the current version of the User Guide +# with Sphinx and deploy it to the gh-pages branch. +# +# @todo Consolidate checkouts +name: Deploy User Guide (latest) + +on: + push: + branches: + - 'develop' + paths: + - 'user_guide_src/**' + +jobs: + build: + name: Deploy to gh-pages + if: (github.repository == 'codeigniter4/CodeIgniter4') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + # Build the latest User Guide + - name: Build with Sphinx + uses: ammaraskar/sphinx-action@0.4 + with: + docs-folder: user_guide_src/ + + # Create an artifact of the html output + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: HTML Documentation + path: user_guide_src/build/html/ + + # Commit changes to the gh-pages branch + - name: Commit changes + run: | + git clone https://github.com/codeigniter4/CodeIgniter4.git --branch gh-pages --single-branch gh-pages + cp -r user_guide_src/build/html/* gh-pages/ + cd gh-pages + git config --local user.email "action@github.com" + git config --local user.name "${GITHUB_ACTOR}" + git add . + # Ignore failures due to lack of changes + git commit -m "Update User Guide" -a || true + + - name: Push changes + uses: ad-m/github-push-action@v0.6.0 + with: + branch: gh-pages + directory: gh-pages + github_token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/docs_nightly.yml b/.github/workflows/docs_nightly.yml deleted file mode 100644 index 0093e9fbdd1c..000000000000 --- a/.github/workflows/docs_nightly.yml +++ /dev/null @@ -1,52 +0,0 @@ -# When changes are pushed to the develop branch, -# build the current version of the docs and -# deploy to gh-pages so that our development -# docs are always up to date. -name: "Update development docs" - -on: - push: - branches: - - 'develop' - paths: - - 'user_guide_src/**' - -jobs: - build: - if: (github.repository == 'codeigniter4/CodeIgniter4') - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - # Build the current docs - - uses: ammaraskar/sphinx-action@master - with: - docs-folder: "user_guide_src/" - - # Create an artifact of the html output - - uses: actions/upload-artifact@v2 - with: - name: HTML Documentation - path: user_guide_src/build/html/ - - # Commit changes to the gh-pages branch - - name: Commit documentation changes - run: | - git clone https://github.com/codeigniter4/CodeIgniter4.git --branch gh-pages --single-branch gh-pages - cp -r user_guide_src/build/html/* gh-pages/ - cd gh-pages - git config --local user.email "action@github.com" - git config --local user.name "${GITHUB_ACTOR}" - git add . - git commit -m "Update documentation" -a || true - # The above command will fail if no changes were present, so we ignore - # that. - - - name: Push changes - uses: ad-m/github-push-action@master - with: - branch: gh-pages - directory: gh-pages - github_token: ${{ secrets.ACCESS_TOKEN }} diff --git a/.github/workflows/static-analysis-check.yml b/.github/workflows/static-analysis-check.yml deleted file mode 100644 index dbd2166128d6..000000000000 --- a/.github/workflows/static-analysis-check.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: "Static Analysis Check" - -on: - pull_request: - branches: - - 'develop' - - '4.1' - paths: - - 'app/**' - - 'system/**' - push: - branches: - - 'develop' - - '4.1' - paths: - - 'app/**' - - 'system/**' - -jobs: - build: - name: Run Check - runs-on: ubuntu-latest - steps: - - name: Setup PHP Action - uses: shivammathur/setup-php@v2 - with: - extensions: intl - php-version: "7.4" - - - name: Checkout - uses: actions/checkout@v2 - - - name: "Install dependencies" - run: "composer install" - - - name: "Static analysis Check" - run: "vendor/bin/phpstan analyze --level=5 app system" diff --git a/.github/workflows/test-phpstan.yml b/.github/workflows/test-phpstan.yml new file mode 100644 index 000000000000..899b24a69727 --- /dev/null +++ b/.github/workflows/test-phpstan.yml @@ -0,0 +1,69 @@ +# When a PR is opened or a push is made, perform +# a static analysis check on the code using PHPStan. +name: PHPStan + +on: + pull_request: + branches: + - 'develop' + - '4.*' + paths: + - 'app/**' + - 'system/**' + push: + branches: + - 'develop' + - '4.*' + paths: + - 'app/**' + - 'system/**' + +jobs: + build: + name: Analyze code (PHPStan) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + extensions: intl + + - name: Use latest Composer + run: composer self-update + + - name: Validate composer.json + 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 }} + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create PHPStan result cache directory + run: mkdir -p build/phpstan + + - name: Cache PHPStan result cache directory + uses: actions/cache@v2 + with: + path: build/phpstan + key: ${{ runner.os }}-phpstan-${{ github.sha }} + restore-keys: ${{ runner.os }}-phpstan- + + - name: Install dependencies + run: composer install --ansi --no-progress --no-suggest --no-interaction + + - name: Run static analysis + run: vendor/bin/phpstan analyse diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml new file mode 100644 index 000000000000..b1443b82bd64 --- /dev/null +++ b/.github/workflows/test-phpunit.yml @@ -0,0 +1,131 @@ +name: PHPUnit + +on: + push: + branches: + - develop + - '4.*' + paths: + - 'app/**' + - 'public/**' + - 'system/**' + - 'tests/**' + - composer.json + - spark + - '**.php' + - .github/workflows/test-phpunit.yml + pull_request: + branches: + - develop + - '4.*' + paths: + - 'app/**' + - 'public/**' + - 'system/**' + - 'tests/**' + - composer.json + - spark + - '**.php' + - .github/workflows/test-phpunit.yml + +jobs: + + tests: + runs-on: ubuntu-latest + + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} + + strategy: + fail-fast: false + matrix: + php-versions: ['7.2', '7.3', '7.4'] + db-platforms: ['MySQLi', 'Postgre', 'SQLite3'] + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + ports: + - 5432:5432 + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + redis: + image: redis + ports: + - 6379:6379 + options: --health-cmd "redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer:v2 + extensions: imagick + coverage: xdebug + env: + update: true + + - name: Install Memcached + uses: niden/actions-memcached@v7 + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader + composer remove --dev rector/rector phpstan/phpstan codeigniter4/codeigniter4-standard squizlabs/php_codesniffer + composer update + php -r 'file_put_contents("vendor/laminas/laminas-zendframework-bridge/src/autoload.php", "");' + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + + - name: Test with PHPUnit + run: script -e -c "vendor/bin/phpunit -v" + env: + DB: ${{ matrix.db-platforms }} + TERM: xterm-256color + + - if: matrix.php-versions == '7.4' + name: Run Coveralls + run: | + composer global require php-coveralls/php-coveralls:^2.4 + php-coveralls --coverage_clover=build/logs/clover.xml -v + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} + + coveralls-finish: + needs: [tests] + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true \ No newline at end of file diff --git a/.github/workflows/test-rector.yml b/.github/workflows/test-rector.yml new file mode 100644 index 000000000000..4c2126264945 --- /dev/null +++ b/.github/workflows/test-rector.yml @@ -0,0 +1,63 @@ +# When a PR is opened or a push is made, perform +# a static analysis check on the code using Rector. +name: Rector + +on: + pull_request: + branches: + - 'develop' + - '4.*' + paths: + - 'app/**' + - 'system/**' + - '.github/workflows/test-rector.yml' + - 'rector.php' + push: + branches: + - 'develop' + - '4.*' + paths: + - 'app/**' + - 'system/**' + - '.github/workflows/test-rector.yml' + - 'rector.php' + +jobs: + build: + name: Analyze code (Rector) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + extensions: intl + + - name: Use latest Composer + run: composer self-update + + - name: Validate composer.json + 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 }} + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --ansi --no-progress --no-suggest --no-interaction + + - name: Run static analysis + run: vendor/bin/rector process --dry-run diff --git a/.github/workflows/test-userguide.yml b/.github/workflows/test-userguide.yml new file mode 100644 index 000000000000..75e6f4dc2524 --- /dev/null +++ b/.github/workflows/test-userguide.yml @@ -0,0 +1,23 @@ +# When a Pull Request is opened that modifies +# the User Guide source, build the User Guide +# with Sphinx and let the contributor know of +# any errors. +name: Test User Guide + +on: + pull_request: + branches: + - 'develop' + - '4.*' + paths: + - 'user_guide_src/**' + +jobs: + syntax_check: + name: Check User Guide syntax + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ammaraskar/sphinx-action@0.4 + with: + docs-folder: user_guide_src diff --git a/.github/workflows/userguide_ci.yml b/.github/workflows/userguide_ci.yml deleted file mode 100644 index c00d4c56e921..000000000000 --- a/.github/workflows/userguide_ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Builds the user guide and lets the contributor know of -# any user guide build errors. -name: UserGuide CI - -on: - pull_request: - paths: - - 'user_guide_src/**' - -jobs: - syntax_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: ammaraskar/sphinx-action@master - with: - docs-folder: user_guide_src diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 310972ae4121..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,54 +0,0 @@ -language: php - -php: - - 7.2 - - 7.3 - - 7.4 - - nightly - -matrix: - fast_finish: true - allow_failures: - - php: nightly - -global: - - CI=true - - CI_ENVIRONMENT=testing - -# Recommended by Travis support -sudo: required -dist: xenial -group: edge - -env: - - DB=mysqli - - DB=postgres - - DB=sqlite - -services: - - memcached - - mysql - - postgresql - - redis-server - -cache: - directories: - - vendor - -script: - - php vendor/bin/phpunit -v - -before_install: - - mysql -e "CREATE DATABASE IF NOT EXISTS test;" -uroot; - - mysql -e "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'test';" -uroot - - psql -c 'CREATE DATABASE test;' -U postgres - - sudo apt-get install ghostscript - - yes | pecl install imagick - -before_script: - - echo 'extension = memcached.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - echo 'extension = redis.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - composer install --prefer-source - -after_success: - - travis_retry php tests/bin/php-coveralls.phar -v diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af9afd83fc2..32a476fe4221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [Unreleased](https://github.com/codeigniter4/CodeIgniter4/tree/HEAD) + +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.4...HEAD) + +**Deprecations:** + +- `CodeIgniter\Database\ModelFactory` is now deprecated in favor of `CodeIgniter\Config\Factories::models()` +- `CodeIgniter\Config\Config` is now deprecated in favor of `CodeIgniter\Config\Factories::config()` + ## [v4.0.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.4) (2020-07-15) [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.3...v4.0.4) diff --git a/README.md b/README.md index a4319ced0345..5d67a8277f34 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CodeIgniter 4 Development -[![Build Status](https://travis-ci.org/codeigniter4/CodeIgniter4.svg?branch=develop)](https://travis-ci.org/codeigniter4/CodeIgniter4) +[![Build Status](https://github.com/codeigniter4/CodeIgniter4/workflows/PHPUnit/badge.svg)](https://github.com/codeigniter4/CodeIgniter4/actions?query=workflow%3A%22PHPUnit%22) [![Coverage Status](https://coveralls.io/repos/github/codeigniter4/CodeIgniter4/badge.svg?branch=develop)](https://coveralls.io/github/codeigniter4/CodeIgniter4?branch=develop) [![Downloads](https://poser.pugx.org/codeigniter4/framework/downloads)](https://packagist.org/packages/codeigniter4/framework) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/codeigniter4/CodeIgniter4)](https://packagist.org/packages/codeigniter4/framework) @@ -39,7 +39,6 @@ not to the project root. A better practice would be to configure a virtual host framework are exposed. **Please** read the user guide for a better explanation of how CI4 works! -The user guide updating and deployment is a bit awkward at the moment, but we are working on it! ## Repository Management diff --git a/admin/framework/README.md b/admin/framework/README.md index 275f01536660..62e401fa4a1b 100644 --- a/admin/framework/README.md +++ b/admin/framework/README.md @@ -2,17 +2,17 @@ ## What is CodeIgniter? -CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure. +CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure. More information can be found at the [official site](http://codeigniter.com). This repository holds the distributable version of the framework, -including the user guide. It has been built from the +including the user guide. It has been built from the [development repository](https://github.com/codeigniter4/CodeIgniter4). More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. The user guide corresponding to this version of the framework can be found -[here](https://codeigniter4.github.io/userguide/). +[here](https://codeigniter4.github.io/userguide/). ## Important Change with index.php @@ -25,7 +25,6 @@ not to the project root. A better practice would be to configure a virtual host framework are exposed. **Please** read the user guide for a better explanation of how CI4 works! -The user guide updating and deployment is a bit awkward at the moment, but we are working on it! ## Repository Management @@ -33,7 +32,7 @@ We use Github issues, in our main repository, to track **BUGS** and to track app We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss FEATURE REQUESTS. -This repository is a "distribution" one, built by our release preparation script. +This repository is a "distribution" one, built by our release preparation script. Problems with it can be raised on our forum, or as issues in the main repository. ## Contributing @@ -44,7 +43,7 @@ Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/ ## Server Requirements -PHP version 7.2 or higher is required, with the following extensions installed: +PHP version 7.2 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) - [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library diff --git a/admin/framework/phpunit.xml.dist b/admin/framework/phpunit.xml.dist index 88aca1fb5c29..a0f8204f7160 100644 --- a/admin/framework/phpunit.xml.dist +++ b/admin/framework/phpunit.xml.dist @@ -36,7 +36,7 @@ - + diff --git a/admin/module/phpunit.xml.dist b/admin/module/phpunit.xml.dist index 4d872910916e..8ecb72e70ed8 100644 --- a/admin/module/phpunit.xml.dist +++ b/admin/module/phpunit.xml.dist @@ -36,7 +36,7 @@ - + diff --git a/admin/pre-commit b/admin/pre-commit index c655cd04fe25..2d8b61166d92 100644 --- a/admin/pre-commit +++ b/admin/pre-commit @@ -1,7 +1,7 @@ #!/bin/sh PROJECT=`php -r "echo dirname(dirname(dirname(realpath('$0'))));"` -STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\\\.php` +STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\\\.php$` # Determine if a file list is passed if [ "$#" -eq 1 ] @@ -14,17 +14,35 @@ then fi SFILES=${SFILES:-$STAGED_FILES_CMD} -echo "Checking PHP Lint..." -for FILE in $SFILES -do - php -l -d display_errors=0 "$PROJECT/$FILE" +echo "Starting CodeIgniter precommit..." + +if [ "$SFILES" != "" ] +then + echo "Linting PHP code..." + for FILE in $SFILES + do + php -l -d display_errors=0 "$PROJECT/$FILE" + if [ $? != 0 ] + then + echo "Fix the error(s) before commit." + exit 1 + fi + FILES="$FILES $FILE" + done +fi + +if [ "$FILES" != "" ] +then + echo "Running PHPStan..." + # Run on whole codebase + ./vendor/bin/phpstan analyse + if [ $? != 0 ] then - echo "Fix the error before commit." + echo "Fix the phpstan error(s) before commit." exit 1 fi - FILES="$FILES $FILE" -done +fi if [ "$FILES" != "" ] then diff --git a/admin/starter/README.md b/admin/starter/README.md index 7c20eb17b64a..a51bbb529a95 100644 --- a/admin/starter/README.md +++ b/admin/starter/README.md @@ -2,17 +2,17 @@ ## What is CodeIgniter? -CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure. +CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure. More information can be found at the [official site](http://codeigniter.com). This repository holds a composer-installable app starter. -It has been built from the +It has been built from the [development repository](https://github.com/codeigniter4/CodeIgniter4). More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. The user guide corresponding to this version of the framework can be found -[here](https://codeigniter4.github.io/userguide/). +[here](https://codeigniter4.github.io/userguide/). ## Installation & updates @@ -38,7 +38,6 @@ not to the project root. A better practice would be to configure a virtual host framework are exposed. **Please** read the user guide for a better explanation of how CI4 works! -The user guide updating and deployment is a bit awkward at the moment, but we are working on it! ## Repository Management @@ -46,12 +45,12 @@ We use Github issues, in our main repository, to track **BUGS** and to track app We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss FEATURE REQUESTS. -This repository is a "distribution" one, built by our release preparation script. +This repository is a "distribution" one, built by our release preparation script. Problems with it can be raised on our forum, or as issues in the main repository. ## Server Requirements -PHP version 7.2 or higher is required, with the following extensions installed: +PHP version 7.2 or higher is required, with the following extensions installed: - [intl](http://php.net/manual/en/intl.requirements.php) - [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library diff --git a/admin/starter/phpunit.xml.dist b/admin/starter/phpunit.xml.dist index 96947df5a615..64628e210e3d 100644 --- a/admin/starter/phpunit.xml.dist +++ b/admin/starter/phpunit.xml.dist @@ -36,7 +36,7 @@ - + diff --git a/app/Config/App.php b/app/Config/App.php index 4c0e9a9d02ae..3bba4a902197 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -1,278 +1,434 @@ - APPPATH * ]; * - * @var array + * @var array */ public $psr4 = [ APP_NAMESPACE => APPPATH, // For custom app namespace @@ -60,7 +60,7 @@ class Autoload extends AutoloadConfig * 'MyClass' => '/path/to/class/file.php' * ]; * - * @var array + * @var array */ public $classmap = []; } diff --git a/app/Config/Boot/development.php b/app/Config/Boot/development.php index 63fdd88be55d..05a861258fc9 100644 --- a/app/Config/Boot/development.php +++ b/app/Config/Boot/development.php @@ -1,32 +1,32 @@ + */ public $memcached = [ 'host' => '127.0.0.1', 'port' => 11211, @@ -84,14 +98,15 @@ class Cache extends BaseConfig 'raw' => false, ]; - /* - | ------------------------------------------------------------------------- - | Redis settings - | ------------------------------------------------------------------------- - | Your Redis server can be specified below, if you are using - | the Redis or Predis drivers. - | - */ + /** + * ------------------------------------------------------------------------- + * Redis settings + * ------------------------------------------------------------------------- + * Your Redis server can be specified below, if you are using + * the Redis or Predis drivers. + * + * @var array + */ public $redis = [ 'host' => '127.0.0.1', 'password' => null, @@ -100,21 +115,22 @@ class Cache extends BaseConfig 'database' => 0, ]; - /* - |-------------------------------------------------------------------------- - | Available Cache Handlers - |-------------------------------------------------------------------------- - | - | This is an array of cache engine alias' and class names. Only engines - | that are listed here are allowed to be used. - | - */ + /** + * -------------------------------------------------------------------------- + * Available Cache Handlers + * -------------------------------------------------------------------------- + * + * This is an array of cache engine alias' and class names. Only engines + * that are listed here are allowed to be used. + * + * @var array + */ public $validHandlers = [ - 'dummy' => \CodeIgniter\Cache\Handlers\DummyHandler::class, - 'file' => \CodeIgniter\Cache\Handlers\FileHandler::class, - 'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class, - 'predis' => \CodeIgniter\Cache\Handlers\PredisHandler::class, - 'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class, - 'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class, + 'dummy' => DummyHandler::class, + 'file' => FileHandler::class, + 'memcached' => MemcachedHandler::class, + 'predis' => PredisHandler::class, + 'redis' => RedisHandler::class, + 'wincache' => WincacheHandler::class, ]; } diff --git a/app/Config/Constants.php b/app/Config/Constants.php index b25f71cdcc26..8f8498a58a0a 100644 --- a/app/Config/Constants.php +++ b/app/Config/Constants.php @@ -1,36 +1,38 @@ '', 'pConnect' => false, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'cacheOn' => false, - 'cacheDir' => '', 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -67,8 +66,6 @@ class Database extends \CodeIgniter\Database\Config 'DBPrefix' => 'db_', // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS 'pConnect' => false, 'DBDebug' => (ENVIRONMENT !== 'production'), - 'cacheOn' => false, - 'cacheDir' => '', 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -91,21 +88,6 @@ public function __construct() if (ENVIRONMENT === 'testing') { $this->defaultGroup = 'tests'; - - // Under Travis-CI, we can set an ENV var named 'DB_GROUP' - // so that we can test against multiple databases. - if ($group = getenv('DB')) - { - if (is_file(TESTPATH . 'travis/Database.php')) - { - require TESTPATH . 'travis/Database.php'; - - if (! empty($dbconfig) && array_key_exists($group, $dbconfig)) - { - $this->tests = $dbconfig[$group]; - } - } - } } } diff --git a/app/Config/DocTypes.php b/app/Config/DocTypes.php index 67d5dd208e3e..dc85c0440290 100755 --- a/app/Config/DocTypes.php +++ b/app/Config/DocTypes.php @@ -1,15 +1,15 @@ - + */ + public $list = [ 'xhtml11' => '', 'xhtml1-strict' => '', 'xhtml1-trans' => '', diff --git a/app/Config/Email.php b/app/Config/Email.php index d9ca1420f6ad..1d0c181e3a41 100644 --- a/app/Config/Email.php +++ b/app/Config/Email.php @@ -1,11 +1,11 @@ \CodeIgniter\Filters\CSRF::class, - 'toolbar' => \CodeIgniter\Filters\DebugToolbar::class, - 'honeypot' => \CodeIgniter\Filters\Honeypot::class, + 'csrf' => CSRF::class, + 'toolbar' => DebugToolbar::class, + 'honeypot' => Honeypot::class, ]; - // Always applied before every request + /** + * List of filter aliases that are always + * applied before and after every request. + * + * @var array + */ public $globals = [ 'before' => [ - //'honeypot' + // 'honeypot', // 'csrf', ], 'after' => [ 'toolbar', - //'honeypot' + // 'honeypot', ], ]; - // Works on all of a particular HTTP method - // (GET, POST, etc) as BEFORE filters only - // like: 'post' => ['CSRF', 'throttle'], + /** + * List of filter aliases that works on a + * particular HTTP method (GET, POST, etc.). + * + * Example: + * 'post' => ['csrf', 'throttle'] + * + * @var array + */ public $methods = []; - // List filter aliases and any before/after uri patterns - // that they should run on, like: - // 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']], + /** + * List of filter aliases that should run on any + * before or after URI patterns. + * + * Example: + * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']] + * + * @var array + */ public $filters = []; } diff --git a/app/Config/ForeignCharacters.php b/app/Config/ForeignCharacters.php index 8ee6f113d78e..174ddb16a872 100644 --- a/app/Config/ForeignCharacters.php +++ b/app/Config/ForeignCharacters.php @@ -1,6 +1,9 @@ - + */ public $formatters = [ - 'application/json' => \CodeIgniter\Format\JSONFormatter::class, - 'application/xml' => \CodeIgniter\Format\XMLFormatter::class, - 'text/xml' => \CodeIgniter\Format\XMLFormatter::class, + 'application/json' => 'CodeIgniter\Format\JSONFormatter', + 'application/xml' => 'CodeIgniter\Format\XMLFormatter', + 'text/xml' => 'CodeIgniter\Format\XMLFormatter', ]; - /* - |-------------------------------------------------------------------------- - | Formatters Options - |-------------------------------------------------------------------------- - | - | Additional Options to adjust default formatters behaviour. - | For each mime type, list the additional options that should be used. - | - */ + /** + * -------------------------------------------------------------------------- + * Formatters Options + * -------------------------------------------------------------------------- + * + * Additional Options to adjust default formatters behaviour. + * For each mime type, list the additional options that should be used. + * + * @var array + */ public $formatterOptions = [ 'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, 'application/xml' => 0, @@ -62,25 +68,12 @@ class Format extends BaseConfig * * @param string $mime * - * @return \CodeIgniter\Format\FormatterInterface + * @return FormatterInterface + * + * @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead. */ public function getFormatter(string $mime) { - if (! array_key_exists($mime, $this->formatters)) - { - throw new \InvalidArgumentException('No Formatter defined for mime type: ' . $mime); - } - - $class = $this->formatters[$mime]; - - if (! class_exists($class)) - { - throw new \BadMethodCallException($class . ' is not a valid Formatter.'); - } - - return new $class(); + return Services::format()->getFormatter($mime); } - - //-------------------------------------------------------------------- - } diff --git a/app/Config/Generators.php b/app/Config/Generators.php new file mode 100644 index 000000000000..5ea633e5a954 --- /dev/null +++ b/app/Config/Generators.php @@ -0,0 +1,38 @@ + + */ + public $views = [ + 'make:command' => 'CodeIgniter\\Commands\\Generators\\Views\\command.tpl.php', + 'make:controller' => 'CodeIgniter\\Commands\\Generators\\Views\\controller.tpl.php', + 'make:entity' => 'CodeIgniter\\Commands\\Generators\\Views\\entity.tpl.php', + 'make:filter' => 'CodeIgniter\\Commands\\Generators\\Views\\filter.tpl.php', + 'make:migration' => 'CodeIgniter\\Commands\\Generators\\Views\\migration.tpl.php', + 'make:model' => 'CodeIgniter\\Commands\\Generators\\Views\\model.tpl.php', + 'make:seeder' => 'CodeIgniter\\Commands\\Generators\\Views\\seed.tpl.php', + 'session:migration' => 'CodeIgniter\\Commands\\Generators\\Views\\session_migration.tpl.php', + ]; +} diff --git a/app/Config/Honeypot.php b/app/Config/Honeypot.php index 3d9e37266934..54bdcd4d0f73 100644 --- a/app/Config/Honeypot.php +++ b/app/Config/Honeypot.php @@ -1,10 +1,11 @@ - + * @var array */ public $handlers = [ - 'gd' => \CodeIgniter\Images\Handlers\GDHandler::class, - 'imagick' => \CodeIgniter\Images\Handlers\ImageMagickHandler::class, + 'gd' => GDHandler::class, + 'imagick' => ImageMagickHandler::class, ]; } diff --git a/app/Config/Kint.php b/app/Config/Kint.php index 09db83dd5971..665de0bb1def 100644 --- a/app/Config/Kint.php +++ b/app/Config/Kint.php @@ -1,23 +1,22 @@ - [ /* diff --git a/app/Config/Migrations.php b/app/Config/Migrations.php index b83fe907ff95..1fa3100af07c 100644 --- a/app/Config/Migrations.php +++ b/app/Config/Migrations.php @@ -1,50 +1,55 @@ - php spark migrate:create - | - | Typical formats: - | YmdHis_ - | Y-m-d-His_ - | Y_m_d_His_ - | - */ + /** + * -------------------------------------------------------------------------- + * Timestamp Format + * -------------------------------------------------------------------------- + * + * This is the format that will be used when creating new migrations + * using the CLI command: + * > php spark migrate:create + * + * Typical formats: + * - YmdHis_ + * - Y-m-d-His_ + * - Y_m_d_His_ + * + * @var string + */ public $timestampFormat = 'Y-m-d-His_'; - } diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index fd5ba3690260..ce014db338ac 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -1,18 +1,19 @@ - $types) @@ -525,7 +522,4 @@ public static function guessExtensionFromType(string $type, ?string $proposed_ex return null; } - - //-------------------------------------------------------------------- - } diff --git a/app/Config/Modules.php b/app/Config/Modules.php index 40cb987564ca..8c1d049a78b9 100644 --- a/app/Config/Modules.php +++ b/app/Config/Modules.php @@ -2,42 +2,50 @@ namespace Config; -use CodeIgniter\Modules\Modules as CoreModules; +use CodeIgniter\Modules\Modules as BaseModules; -class Modules extends CoreModules +class Modules extends BaseModules { - /* - |-------------------------------------------------------------------------- - | Auto-Discovery Enabled? - |-------------------------------------------------------------------------- - | - | If true, then auto-discovery will happen across all elements listed in - | $activeExplorers below. If false, no auto-discovery will happen at all, - | giving a slight performance boost. + /** + * -------------------------------------------------------------------------- + * Enable Auto-Discovery? + * -------------------------------------------------------------------------- + * + * If true, then auto-discovery will happen across all elements listed in + * $activeExplorers below. If false, no auto-discovery will happen at all, + * giving a slight performance boost. + * + * @var boolean */ public $enabled = true; - /* - |-------------------------------------------------------------------------- - | Auto-Discovery Within Composer Packages Enabled? - |-------------------------------------------------------------------------- - | - | If true, then auto-discovery will happen across all namespaces loaded - | by Composer, as well as the namespaces configured locally. + /** + * -------------------------------------------------------------------------- + * Enable Auto-Discovery Within Composer Packages? + * -------------------------------------------------------------------------- + * + * If true, then auto-discovery will happen across all namespaces loaded + * by Composer, as well as the namespaces configured locally. + * + * @var boolean */ public $discoverInComposer = true; - /* - |-------------------------------------------------------------------------- - | Auto-discover Rules - |-------------------------------------------------------------------------- - | - | Aliases list of all discovery classes that will be active and used during - | the current application request. - | If it is not listed, only the base application elements will be used. - */ + /** + * -------------------------------------------------------------------------- + * Auto-Discovery Rules + * -------------------------------------------------------------------------- + * + * Aliases list of all discovery classes that will be active and used during + * the current application request. + * + * If it is not listed, only the base application elements will be used. + * + * @var string[] + */ public $aliases = [ 'events', + 'filters', 'registrars', 'routes', 'services', diff --git a/app/Config/Pager.php b/app/Config/Pager.php index 9b6ed83cb948..44993132c860 100644 --- a/app/Config/Pager.php +++ b/app/Config/Pager.php @@ -1,35 +1,39 @@ - + */ public $templates = [ 'default_full' => 'CodeIgniter\Pager\Views\default_full', 'default_simple' => 'CodeIgniter\Pager\Views\default_simple', 'default_head' => 'CodeIgniter\Pager\Views\default_head', ]; - /* - |-------------------------------------------------------------------------- - | Items Per Page - |-------------------------------------------------------------------------- - | - | The default number of results shown in a single page. - | - */ + /** + * -------------------------------------------------------------------------- + * Items Per Page + * -------------------------------------------------------------------------- + * + * The default number of results shown in a single page. + * + * @var integer + */ public $perPage = 20; } diff --git a/app/Config/Paths.php b/app/Config/Paths.php index 6ca2d37349a4..bd77d27051b3 100644 --- a/app/Config/Paths.php +++ b/app/Config/Paths.php @@ -1,9 +1,14 @@ -set404Override(); $routes->setAutoRoute(true); -/** +/* * -------------------------------------------------------------------- * Route Definitions * -------------------------------------------------------------------- @@ -32,7 +34,7 @@ // route since we don't have to scan directories. $routes->get('/', 'Home::index'); -/** +/* * -------------------------------------------------------------------- * Additional Routing * -------------------------------------------------------------------- diff --git a/app/Config/Services.php b/app/Config/Services.php index c58da709043a..4b6082dd0568 100644 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -1,4 +1,6 @@ - + */ public $platforms = [ 'windows nt 10.0' => 'Windows 10', 'windows nt 6.3' => 'Windows 8.1', @@ -58,8 +68,16 @@ class UserAgents extends BaseConfig 'symbian' => 'Symbian OS', ]; - // The order of this array should NOT be changed. Many browsers return - // multiple browser types so we want to identify the sub-type first. + /** + * ------------------------------------------------------------------- + * Browsers + * ------------------------------------------------------------------- + * + * The order of this array should NOT be changed. Many browsers return + * multiple browser types so we want to identify the subtype first. + * + * @var array + */ public $browsers = [ 'OPR' => 'Opera', 'Flock' => 'Flock', @@ -94,6 +112,13 @@ class UserAgents extends BaseConfig 'Vivaldi' => 'Vivaldi', ]; + /** + * ------------------------------------------------------------------- + * Mobiles + * ------------------------------------------------------------------- + * + * @var array + */ public $mobiles = [ // legacy array, old values commented out 'mobileexplorer' => 'Mobile Explorer', @@ -194,7 +219,15 @@ class UserAgents extends BaseConfig 'cellphone' => 'Generic Mobile', ]; - // There are hundreds of bots but these are the most common. + /** + * ------------------------------------------------------------------- + * Robots + * ------------------------------------------------------------------- + * + * There are hundred of bots but these are the most common. + * + * @var array + */ public $robots = [ 'googlebot' => 'Googlebot', 'msnbot' => 'MSNBot', diff --git a/app/Config/Validation.php b/app/Config/Validation.php index 97f08c752671..9cb802e5561a 100644 --- a/app/Config/Validation.php +++ b/app/Config/Validation.php @@ -1,4 +1,11 @@ - */ public $templates = [ 'list' => 'CodeIgniter\Validation\Views\list', diff --git a/app/Config/View.php b/app/Config/View.php index f66b2532f0f7..c945e2e068c8 100644 --- a/app/Config/View.php +++ b/app/Config/View.php @@ -1,6 +1,10 @@ - - + diff --git a/public/index.php b/public/index.php index 7b3cc121695e..127780da57e0 100644 --- a/public/index.php +++ b/public/index.php @@ -11,11 +11,6 @@ // Path to the front controller (this file) define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR); -// Location of the Paths config file. -// This is the line that might need to be changed, depending on your folder structure. -$pathsPath = realpath(FCPATH . '../app/Config/Paths.php') ?: FCPATH . '../app/Config/Paths.php'; -// ^^^ Change this if you move your application folder - /* *--------------------------------------------------------------- * BOOTSTRAP THE APPLICATION @@ -29,7 +24,10 @@ chdir(__DIR__); // Load our paths config file -require $pathsPath; +// This is the line that might need to be changed, depending on your folder structure. +require realpath(FCPATH . '../app/Config/Paths.php') ?: FCPATH . '../app/Config/Paths.php'; +// ^^^ Change this if you move your application folder + $paths = new Config\Paths(); // Location of the framework bootstrap file. diff --git a/qa-bootstrap.php b/qa-bootstrap.php deleted file mode 100644 index f44c5019f411..000000000000 --- a/qa-bootstrap.php +++ /dev/null @@ -1,5 +0,0 @@ -parameters(); + + // paths to refactor; solid alternative to CLI arguments + $parameters->set(Option::PATHS, [__DIR__ . '/app', __DIR__ . '/system']); + + // is there a file you need to skip? + $parameters->set(Option::EXCLUDE_PATHS, [ + __DIR__ . '/app/Views', + __DIR__ . '/system/Debug/Toolbar/Views/toolbar.tpl.php', + __DIR__ . '/system/ThirdParty', + ]); + + // Rector relies on autoload setup of your project; Composer autoload is included by default; to add more: + $parameters->set(Option::AUTOLOAD_PATHS, [ + // autoload specific file + __DIR__ . '/system/Test/bootstrap.php', + ]); + + // auto import fully qualified class names + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $parameters->set(Option::SKIP, [ + // skipped for UnderscoreToCamelCaseVariableNameRector rule + // as the underscored variable removed in 4.1 branch + UnderscoreToCamelCaseVariableNameRector::class => [__DIR__ . '/system/Autoloader/Autoloader.php'], + ]); + + $parameters->set(Option::ENABLE_CACHE, true); + + $services = $containerConfigurator->services(); + $services->set(UnderscoreToCamelCaseVariableNameRector::class); + $services->set(SimplifyUselessVariableRector::class); + $services->set(RemoveAlwaysElseRector::class); +}; diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index f42e5346a504..9778adaa77d6 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -39,8 +39,10 @@ namespace CodeIgniter\API; +use CodeIgniter\Format\FormatterInterface; +use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\Response; -use Config\Format; +use Config\Services; /** * Response trait. @@ -49,8 +51,8 @@ * consistent HTTP responses under a variety of common * situations when working as an API. * - * @property \CodeIgniter\HTTP\IncomingRequest $request - * @property \CodeIgniter\HTTP\Response $response + * @property IncomingRequest $request + * @property Response $response * * @package CodeIgniter\API */ @@ -102,6 +104,13 @@ trait ResponseTrait */ protected $format = 'json'; + /** + * Current Formatter instance. This is usually set by ResponseTrait::format + * + * @var FormatterInterface + */ + protected $formatter; + //-------------------------------------------------------------------- /** @@ -123,7 +132,8 @@ public function respond($data = null, int $status = null, string $message = '') // Create the output var here in case of $this->response([]); $output = null; - } // If data is null but status provided, keep the output empty. + } + // If data is null but status provided, keep the output empty. elseif ($data === null && is_numeric($status)) { $output = null; @@ -134,8 +144,7 @@ public function respond($data = null, int $status = null, string $message = '') $output = $this->format($data); } - return $this->response->setBody($output) - ->setStatusCode($status, $message); + return $this->response->setBody($output)->setStatusCode($status, $message); } //-------------------------------------------------------------------- @@ -159,7 +168,7 @@ public function fail($messages, int $status = 400, string $code = null, string $ $response = [ 'status' => $status, - 'error' => $code === null ? $status : $code, + 'error' => $code ?? $status, 'messages' => $messages, ]; @@ -386,25 +395,25 @@ protected function format($data = null) return $data; } - $config = new Format(); - $format = "application/$this->format"; + $format = Services::format(); + $mime = "application/{$this->format}"; // Determine correct response type through content negotiation if not explicitly declared - if (empty($this->format) || ! in_array($this->format, ['json', 'xml'])) + if (empty($this->format) || ! in_array($this->format, ['json', 'xml'], true)) { - $format = $this->request->negotiate('media', $config->supportedResponseFormats, false); + $mime = $this->request->negotiate('media', $format->getConfig()->supportedResponseFormats, false); } - $this->response->setContentType($format); + $this->response->setContentType($mime); // if we don't have a formatter, make one if (! isset($this->formatter)) { // if no formatter, use the default - $this->formatter = $config->getFormatter($format); // @phpstan-ignore-line + $this->formatter = $format->getFormatter($mime); } - if ($format !== 'application/json') + if ($mime !== 'application/json') { // Recursively convert objects into associative arrays // Conversion not required for JSONFormatter diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 411e6e7dd3e5..04679e51d984 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -39,6 +39,10 @@ namespace CodeIgniter\Autoloader; +use Config\Autoload; +use Config\Modules; +use InvalidArgumentException; + /** * CodeIgniter Autoloader * @@ -99,18 +103,18 @@ class Autoloader * Reads in the configuration array (described above) and stores * the valid parts that we'll need. * - * @param \Config\Autoload $config - * @param \Config\Modules $moduleConfig + * @param Autoload $config + * @param Modules $modules * * @return $this */ - public function initialize(\Config\Autoload $config, \Config\Modules $moduleConfig) + public function initialize(Autoload $config, Modules $modules) { // We have to have one or the other, though we don't enforce the need // to have both present in order to work. if (empty($config->psr4) && empty($config->classmap)) { - throw new \InvalidArgumentException('Config array must contain either the \'psr4\' key or the \'classmap\' key.'); + throw new InvalidArgumentException('Config array must contain either the \'psr4\' key or the \'classmap\' key.'); } if (isset($config->psr4)) @@ -124,7 +128,7 @@ public function initialize(\Config\Autoload $config, \Config\Modules $moduleConf } // Should we load through Composer's namespaces, also? - if ($moduleConfig->discoverInComposer) + if ($modules->discoverInComposer) { $this->discoverComposerNamespaces(); } @@ -173,7 +177,7 @@ public function register() * @param array|string $namespace * @param string $path * - * @return Autoloader + * @return $this */ public function addNamespace($namespace, string $path = null) { @@ -187,18 +191,18 @@ public function addNamespace($namespace, string $path = null) { foreach ($path as $dir) { - $this->prefixes[$prefix][] = rtrim($dir, '/') . '/'; + $this->prefixes[$prefix][] = rtrim($dir, '\\/') . DIRECTORY_SEPARATOR; } continue; } - $this->prefixes[$prefix][] = rtrim($path, '/') . '/'; + $this->prefixes[$prefix][] = rtrim($path, '\\/') . DIRECTORY_SEPARATOR; } } else { - $this->prefixes[trim($namespace, '\\')][] = rtrim($path, '/') . '/'; + $this->prefixes[trim($namespace, '\\')][] = rtrim($path, '\\/') . DIRECTORY_SEPARATOR; } return $this; @@ -232,11 +236,14 @@ public function getNamespace(string $prefix = null) * * @param string $namespace * - * @return Autoloader + * @return $this */ public function removeNamespace(string $namespace) { - unset($this->prefixes[trim($namespace, '\\')]); + if (isset($this->prefixes[trim($namespace, '\\')])) + { + unset($this->prefixes[trim($namespace, '\\')]); + } return $this; } @@ -283,7 +290,7 @@ protected function loadInNamespace(string $class) { $class = 'Config\\' . $class; $filePath = APPPATH . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; - $filename = $this->requireFile($filePath); + $filename = $this->includeFile($filePath); if ($filename) { @@ -302,7 +309,7 @@ protected function loadInNamespace(string $class) if (strpos($class, $namespace) === 0) { $filePath = $directory . str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen($namespace))) . '.php'; - $filename = $this->requireFile($filePath); + $filename = $this->includeFile($filePath); if ($filename) { @@ -346,7 +353,7 @@ protected function loadLegacy(string $class) foreach ($paths as $path) { - if ($file = $this->requireFile($path . $class)) + if ($file = $this->includeFile($path . $class)) { return $file; } @@ -358,20 +365,19 @@ protected function loadLegacy(string $class) //-------------------------------------------------------------------- /** - * A central way to require a file is loaded. Split out primarily - * for testing purposes. + * A central way to include a file. Split out primarily for testing purposes. * * @param string $file * * @return string|false The filename on success, false if the file is not loaded */ - protected function requireFile(string $file) + protected function includeFile(string $file) { $file = $this->sanitizeFilename($file); if (is_file($file)) { - require_once $file; + include_once $file; return $file; } diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 9e516468c734..0d2922611ee1 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -169,11 +169,11 @@ public function locateFile(string $file, string $folder = null, string $ext = 'p */ public function getClassname(string $file) : string { - $php = file_get_contents($file); - $tokens = token_get_all($php); - $dlm = false; - $namespace = ''; - $class_name = ''; + $php = file_get_contents($file); + $tokens = token_get_all($php); + $dlm = false; + $namespace = ''; + $className = ''; foreach ($tokens as $i => $token) { @@ -202,17 +202,17 @@ public function getClassname(string $file) : string && $tokens[$i - 1][0] === T_WHITESPACE && $token[0] === T_STRING) { - $class_name = $token[1]; + $className = $token[1]; break; } } - if (empty( $class_name )) + if (empty( $className )) { return ''; } - return $namespace . '\\' . $class_name; + return $namespace . '\\' . $className; } //-------------------------------------------------------------------- @@ -354,16 +354,16 @@ protected function getNamespaces() */ public function findQualifiedNameFromPath(string $path) { - $path = realpath($path); + $path = realpath($path) ?: $path; - if (! $path) + if (! is_file($path)) { return false; } foreach ($this->getNamespaces() as $namespace) { - $namespace['path'] = realpath($namespace['path']); + $namespace['path'] = realpath($namespace['path']) ?: $namespace['path']; if (empty($namespace['path'])) { @@ -412,7 +412,8 @@ public function listFiles(string $path): array foreach ($this->getNamespaces() as $namespace) { - $fullPath = realpath($namespace['path'] . $path); + $fullPath = $namespace['path'] . $path; + $fullPath = realpath($fullPath) ?: $fullPath; if (! is_dir($fullPath)) { @@ -454,7 +455,8 @@ public function listNamespaceFiles(string $prefix, string $path): array // autoloader->getNamespace($prefix) returns an array of paths for that namespace foreach ($this->autoloader->getNamespace($prefix) as $namespacePath) { - $fullPath = realpath(rtrim($namespacePath, '/') . '/' . $path); + $fullPath = rtrim($namespacePath, '/') . '/' . $path; + $fullPath = realpath($fullPath) ?: $fullPath; if (! is_dir($fullPath)) { @@ -485,7 +487,8 @@ public function listNamespaceFiles(string $prefix, string $path): array */ protected function legacyLocate(string $file, string $folder = null) { - $path = realpath(APPPATH . (empty($folder) ? $file : $folder . '/' . $file)); + $path = APPPATH . (empty($folder) ? $file : $folder . '/' . $file); + $path = realpath($path) ?: $path; if (is_file($path)) { diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php index 8d47afeadd88..2281ab8586d8 100644 --- a/system/CLI/BaseCommand.php +++ b/system/CLI/BaseCommand.php @@ -40,6 +40,7 @@ namespace CodeIgniter\CLI; use Psr\Log\LoggerInterface; +use ReflectionException; use Throwable; /** @@ -96,7 +97,7 @@ abstract class BaseCommand /** * The Logger to use for a command * - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ protected $logger; @@ -104,7 +105,7 @@ abstract class BaseCommand * Instance of Commands so * commands can call other commands. * - * @var \CodeIgniter\CLI\Commands + * @var Commands */ protected $commands; @@ -113,8 +114,8 @@ abstract class BaseCommand /** * BaseCommand constructor. * - * @param \Psr\Log\LoggerInterface $logger - * @param \CodeIgniter\CLI\Commands $commands + * @param LoggerInterface $logger + * @param Commands $commands */ public function __construct(LoggerInterface $logger, Commands $commands) { @@ -141,7 +142,7 @@ abstract public function run(array $params); * @param array $params * * @return mixed - * @throws \ReflectionException + * @throws ReflectionException */ protected function call(string $command, array $params = []) { @@ -171,6 +172,7 @@ protected function showError(Throwable $e) public function showHelp() { CLI::write(lang('CLI.helpUsage'), 'yellow'); + if (! empty($this->usage)) { $usage = $this->usage; @@ -184,6 +186,7 @@ public function showHelp() $usage .= ' [arguments]'; } } + CLI::write($this->setPad($usage, 0, 0, 2)); if (! empty($this->description)) @@ -198,6 +201,7 @@ public function showHelp() CLI::newLine(); CLI::write(lang('CLI.helpArguments'), 'yellow'); $length = max(array_map('strlen', array_keys($this->arguments))); + foreach ($this->arguments as $argument => $description) { CLI::write(CLI::color($this->setPad($argument, $length, 2, 2), 'green') . $description); @@ -209,6 +213,7 @@ public function showHelp() CLI::newLine(); CLI::write(lang('CLI.helpOptions'), 'yellow'); $length = max(array_map('strlen', array_keys($this->options))); + foreach ($this->options as $option => $description) { CLI::write(CLI::color($this->setPad($option, $length, 2, 2), 'green') . $description); @@ -223,12 +228,12 @@ public function showHelp() * * @param string $item * @param integer $max - * @param integer $extra // How many extra spaces to add at the end + * @param integer $extra How many extra spaces to add at the end * @param integer $indent * * @return string */ - protected function setPad(string $item, int $max, int $extra = 2, int $indent = 0): string + public function setPad(string $item, int $max, int $extra = 2, int $indent = 0): string { $max += $extra + $indent; @@ -244,6 +249,10 @@ protected function setPad(string $item, int $max, int $extra = 2, int $indent = * @param integer $pad * * @return integer + * + * @deprecated Use setPad() instead. + * + * @codeCoverageIgnore */ public function getPad(array $array, int $pad): int { diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 8da7814aee0b..6d95f95505b6 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -40,6 +40,8 @@ namespace CodeIgniter\CLI; use CodeIgniter\CLI\Exceptions\CLIException; +use Config\Services; +use Throwable; /** * Set of static methods useful for CLI request handling. @@ -257,37 +259,37 @@ public static function input(string $prefix = null): string */ public static function prompt(string $field, $options = null, string $validation = null): string { - $extra_output = ''; - $default = ''; + $extraOutput = ''; + $default = ''; if (is_string($options)) { - $extra_output = ' [' . static::color($options, 'white') . ']'; - $default = $options; + $extraOutput = ' [' . static::color($options, 'white') . ']'; + $default = $options; } if (is_array($options) && $options) { - $opts = $options; - $extra_output_default = static::color($opts[0], 'white'); + $opts = $options; + $extraOutputDefault = static::color($opts[0], 'white'); unset($opts[0]); if (empty($opts)) { - $extra_output = $extra_output_default; + $extraOutput = $extraOutputDefault; } else { - $extra_output = ' [' . $extra_output_default . ', ' . implode(', ', $opts) . ']'; - $validation .= '|in_list[' . implode(',', $options) . ']'; - $validation = trim($validation, '|'); + $extraOutput = ' [' . $extraOutputDefault . ', ' . implode(', ', $opts) . ']'; + $validation .= '|in_list[' . implode(',', $options) . ']'; + $validation = trim($validation, '|'); } $default = $options[0]; } - static::fwrite(STDOUT, $field . $extra_output . ': '); + static::fwrite(STDOUT, $field . $extraOutput . ': '); // Read the input from keyboard. $input = trim(static::input()) ?: $default; @@ -320,7 +322,7 @@ protected static function validate(string $field, string $value, string $rules): { $label = $field; $field = 'temp'; - $validation = \Config\Services::validation(null, false); + $validation = Services::validation(null, false); $validation->setRule($field, $label, $rules); $validation->run([$field => $value]); @@ -726,50 +728,63 @@ public static function getHeight(int $default = 32): int */ public static function generateDimensions() { - if (static::isWindows()) + try { - // Shells such as `Cygwin` and `Git bash` returns incorrect values - // when executing `mode CON`, so we use `tput` instead - // @codeCoverageIgnoreStart - if (($shell = getenv('SHELL')) && preg_match('/(?:bash|zsh)(?:\.exe)?$/', $shell) || getenv('TERM')) - { - static::$height = (int) exec('tput lines'); - static::$width = (int) exec('tput cols'); - } - else + if (static::isWindows()) { - $return = -1; - $output = []; - exec('mode CON', $output, $return); - - if ($return === 0 && $output) + // Shells such as `Cygwin` and `Git bash` returns incorrect values + // when executing `mode CON`, so we use `tput` instead + // @codeCoverageIgnoreStart + if (($shell = getenv('SHELL')) && preg_match('/(?:bash|zsh)(?:\.exe)?$/', $shell) || getenv('TERM')) + { + static::$height = (int) exec('tput lines'); + static::$width = (int) exec('tput cols'); + } + else { - // Look for the next lines ending in ": " - // Searching for "Columns:" or "Lines:" will fail on non-English locales - if (preg_match('/:\s*(\d+)\n[^:]+:\s*(\d+)\n/', implode("\n", $output), $matches)) + $return = -1; + $output = []; + exec('mode CON', $output, $return); + + if ($return === 0 && $output) { - static::$height = (int) $matches[1]; - static::$width = (int) $matches[2]; + // Look for the next lines ending in ": " + // Searching for "Columns:" or "Lines:" will fail on non-English locales + if (preg_match('/:\s*(\d+)\n[^:]+:\s*(\d+)\n/', implode("\n", $output), $matches)) + { + static::$height = (int) $matches[1]; + static::$width = (int) $matches[2]; + } } } - } - // @codeCoverageIgnoreEnd - } - else - { - if (($size = exec('stty size')) && preg_match('/(\d+)\s+(\d+)/', $size, $matches)) - { - static::$height = (int) $matches[1]; - static::$width = (int) $matches[2]; + // @codeCoverageIgnoreEnd } else { - // @codeCoverageIgnoreStart - static::$height = (int) exec('tput lines'); - static::$width = (int) exec('tput cols'); - // @codeCoverageIgnoreEnd + if (($size = exec('stty size')) && preg_match('/(\d+)\s+(\d+)/', $size, $matches)) + { + static::$height = (int) $matches[1]; + static::$width = (int) $matches[2]; + } + else + { + // @codeCoverageIgnoreStart + static::$height = (int) exec('tput lines'); + static::$width = (int) exec('tput cols'); + // @codeCoverageIgnoreEnd + } } } + // @codeCoverageIgnoreStart + catch (Throwable $e) + { + // Reset the dimensions so that the default values will be returned later. + // Then let the developer know of the error. + static::$height = null; + static::$width = null; + log_message('error', $e->getMessage()); + } + // @codeCoverageIgnoreEnd } //-------------------------------------------------------------------- @@ -825,11 +840,11 @@ public static function showProgress($thisStep = 1, int $totalSteps = 10) * * @param string $string * @param integer $max - * @param integer $pad_left + * @param integer $padLeft * * @return string */ - public static function wrap(string $string = null, int $max = 0, int $pad_left = 0): string + public static function wrap(string $string = null, int $max = 0, int $padLeft = 0): string { if (empty($string)) { @@ -846,20 +861,20 @@ public static function wrap(string $string = null, int $max = 0, int $pad_left = $max = CLI::getWidth(); } - $max = $max - $pad_left; + $max = $max - $padLeft; $lines = wordwrap($string, $max, PHP_EOL); - if ($pad_left > 0) + if ($padLeft > 0) { $lines = explode(PHP_EOL, $lines); $first = true; - array_walk($lines, function (&$line, $index) use ($pad_left, &$first) { + array_walk($lines, function (&$line, $index) use ($padLeft, &$first) { if (! $first) { - $line = str_repeat(' ', $pad_left) . $line; + $line = str_repeat(' ', $padLeft) . $line; } else { @@ -947,6 +962,8 @@ public static function getURI(): string * // segment(3) is 'three', not '-f' or 'anOption' * > php spark one two -f anOption three * + * **IMPORTANT:** The index here is one-based instead of zero-based. + * * @param integer $index * * @return mixed|null @@ -1015,9 +1032,12 @@ public static function getOptions(): array * Returns the options as a string, suitable for passing along on * the CLI to other commands. * + * @param boolean $useLongOpts Use '--' for long options? + * @param boolean $trim Trim final string output? + * * @return string */ - public static function getOptionString(): string + public static function getOptionString(bool $useLongOpts = false, bool $trim = false): string { if (empty(static::$options)) { @@ -1028,17 +1048,28 @@ public static function getOptionString(): string foreach (static::$options as $name => $value) { + if ($useLongOpts && mb_strlen($name) > 1) + { + $out .= "--{$name} "; + } + else + { + $out .= "-{$name} "; + } + // If there's a space, we need to group // so it will pass correctly. if (mb_strpos($value, ' ') !== false) { - $value = '"' . $value . '"'; + $out .= '"' . $value . '" '; + } + elseif ($value !== null) + { + $out .= "{$value} "; } - - $out .= "-{$name} $value "; } - return $out; + return $trim ? trim($out) : $out; } //-------------------------------------------------------------------- @@ -1054,45 +1085,45 @@ public static function getOptionString(): string public static function table(array $tbody, array $thead = []) { // All the rows in the table will be here until the end - $table_rows = []; + $tableRows = []; // We need only indexes and not keys if (! empty($thead)) { - $table_rows[] = array_values($thead); + $tableRows[] = array_values($thead); } foreach ($tbody as $tr) { - $table_rows[] = array_values($tr); + $tableRows[] = array_values($tr); } // Yes, it really is necessary to know this count - $total_rows = count($table_rows); + $totalRows = count($tableRows); // Store all columns lengths // $all_cols_lengths[row][column] = length - $all_cols_lengths = []; + $allColsLengths = []; // Store maximum lengths by column // $max_cols_lengths[column] = length - $max_cols_lengths = []; + $maxColsLengths = []; // Read row by row and define the longest columns - for ($row = 0; $row < $total_rows; $row ++) + for ($row = 0; $row < $totalRows; $row ++) { $column = 0; // Current column index - foreach ($table_rows[$row] as $col) + foreach ($tableRows[$row] as $col) { // Sets the size of this column in the current row - $all_cols_lengths[$row][$column] = static::strlen($col); + $allColsLengths[$row][$column] = static::strlen($col); // If the current column does not have a value among the larger ones // or the value of this is greater than the existing one // then, now, this assumes the maximum length - if (! isset($max_cols_lengths[$column]) || $all_cols_lengths[$row][$column] > $max_cols_lengths[$column]) + if (! isset($maxColsLengths[$column]) || $allColsLengths[$row][$column] > $maxColsLengths[$column]) { - $max_cols_lengths[$column] = $all_cols_lengths[$row][$column]; + $maxColsLengths[$column] = $allColsLengths[$row][$column]; } // We can go check the size of the next column... @@ -1102,15 +1133,15 @@ public static function table(array $tbody, array $thead = []) // Read row by row and add spaces at the end of the columns // to match the exact column length - for ($row = 0; $row < $total_rows; $row ++) + for ($row = 0; $row < $totalRows; $row ++) { $column = 0; - foreach ($table_rows[$row] as $col) + foreach ($tableRows[$row] as $col) { - $diff = $max_cols_lengths[$column] - static::strlen($col); + $diff = $maxColsLengths[$column] - static::strlen($col); if ($diff) { - $table_rows[$row][$column] = $table_rows[$row][$column] . str_repeat(' ', $diff); + $tableRows[$row][$column] = $tableRows[$row][$column] . str_repeat(' ', $diff); } $column ++; } @@ -1119,13 +1150,13 @@ public static function table(array $tbody, array $thead = []) $table = ''; // Joins columns and append the well formatted rows to the table - for ($row = 0; $row < $total_rows; $row ++) + for ($row = 0; $row < $totalRows; $row ++) { // Set the table border-top if ($row === 0) { $cols = '+'; - foreach ($table_rows[$row] as $col) + foreach ($tableRows[$row] as $col) { $cols .= str_repeat('-', static::strlen($col) + 2) . '+'; } @@ -1133,10 +1164,10 @@ public static function table(array $tbody, array $thead = []) } // Set the columns borders - $table .= '| ' . implode(' | ', $table_rows[$row]) . ' |' . PHP_EOL; + $table .= '| ' . implode(' | ', $tableRows[$row]) . ' |' . PHP_EOL; // Set the thead and table borders-bottom - if (isset($cols) && ($row === 0 && ! empty($thead) || $row + 1 === $total_rows)) + if (isset($cols) && ($row === 0 && ! empty($thead) || $row + 1 === $totalRows)) { $table .= $cols . PHP_EOL; } diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php index 507db346a9d3..9187bace3f3a 100644 --- a/system/CLI/CommandRunner.php +++ b/system/CLI/CommandRunner.php @@ -41,6 +41,7 @@ use CodeIgniter\Controller; use Config\Services; +use ReflectionException; /** * Command runner @@ -73,7 +74,7 @@ public function __construct() * @param array ...$params * * @return mixed - * @throws \ReflectionException + * @throws ReflectionException */ public function _remap($method, ...$params) { @@ -94,7 +95,7 @@ public function _remap($method, ...$params) * @param array $params * * @return mixed - * @throws \ReflectionException + * @throws ReflectionException */ public function index(array $params) { diff --git a/system/CLI/Commands.php b/system/CLI/Commands.php index 04c390ef7740..772ef8554a20 100644 --- a/system/CLI/Commands.php +++ b/system/CLI/Commands.php @@ -70,7 +70,7 @@ class Commands /** * Constructor * - * @param \CodeIgniter\Log\Logger|null $logger + * @param Logger|null $logger */ public function __construct($logger = null) { diff --git a/system/CLI/Console.php b/system/CLI/Console.php index 987fea1d7766..332925edf2d9 100644 --- a/system/CLI/Console.php +++ b/system/CLI/Console.php @@ -40,6 +40,10 @@ namespace CodeIgniter\CLI; use CodeIgniter\CodeIgniter; +use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\Response; +use CodeIgniter\HTTP\ResponseInterface; +use Exception; /** * Console @@ -59,7 +63,7 @@ class Console /** * Console constructor. * - * @param \CodeIgniter\CodeIgniter $app + * @param CodeIgniter $app */ public function __construct(CodeIgniter $app) { @@ -73,8 +77,8 @@ public function __construct(CodeIgniter $app) * * @param boolean $useSafeOutput * - * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\Response|\CodeIgniter\HTTP\ResponseInterface|mixed - * @throws \Exception + * @return RequestInterface|Response|ResponseInterface|mixed + * @throws Exception */ public function run(bool $useSafeOutput = false) { diff --git a/system/CLI/Exceptions/CLIException.php b/system/CLI/Exceptions/CLIException.php index 474064ef888a..048d1eae6735 100644 --- a/system/CLI/Exceptions/CLIException.php +++ b/system/CLI/Exceptions/CLIException.php @@ -39,10 +39,12 @@ namespace CodeIgniter\CLI\Exceptions; +use RuntimeException; + /** * CLIException */ -class CLIException extends \RuntimeException +class CLIException extends RuntimeException { /** * Thrown when `$color` specified for `$type` is not within the @@ -51,7 +53,7 @@ class CLIException extends \RuntimeException * @param string $type * @param string $color * - * @return \CodeIgniter\CLI\Exceptions\CLIException + * @return CLIException */ public static function forInvalidColor(string $type, string $color) { diff --git a/system/CLI/GeneratorCommand.php b/system/CLI/GeneratorCommand.php index 0bf96cac8bd0..78124e591837 100644 --- a/system/CLI/GeneratorCommand.php +++ b/system/CLI/GeneratorCommand.php @@ -39,8 +39,11 @@ namespace CodeIgniter\CLI; +use Config\Generators; use Config\Services; use Psr\Log\LoggerInterface; +use RuntimeException; +use Throwable; /** * GeneratorCommand can be used as base class @@ -73,10 +76,19 @@ abstract class GeneratorCommand extends BaseCommand * @var array */ private $defaultOptions = [ - '-n' => 'Set root namespace. Defaults to APP_NAMESPACE.', - '-force' => 'Force overwrite existing files.', + '-n' => 'Set root namespace. Defaults to APP_NAMESPACE.', + '--force' => 'Force overwrite existing files.', ]; + /** + * Whether to sort class imports. + * + * @internal + * + * @var boolean + */ + private $sortImports = true; + /** * The params array for easy access by other methods. * @@ -84,16 +96,24 @@ abstract class GeneratorCommand extends BaseCommand */ protected $params = []; + /** + * Instance of Config\Generators + * + * @var Generators + */ + protected $config; + /** * Constructor. * - * @param \Psr\Log\LoggerInterface $logger - * @param \CodeIgniter\CLI\Commands $commands + * @param LoggerInterface $logger + * @param Commands $commands */ public function __construct(LoggerInterface $logger, Commands $commands) { $this->arguments = array_merge($this->defaultArguments, $this->arguments); $this->options = array_merge($this->options, $this->defaultOptions); + $this->config = config('Config\Generators'); parent::__construct($logger, $commands); } @@ -120,11 +140,13 @@ public function run(array $params) { CLI::error(lang('CLI.generateFileExists', [clean_path($path)]), 'light_gray', 'red'); CLI::newLine(); + return; } // Next, check if the directory to save the file is existing. $dir = dirname($path); + if (! is_dir($dir)) { mkdir($dir, 0755, true); @@ -137,6 +159,7 @@ public function run(array $params) { CLI::error(lang('CLI.generateFileError') . clean_path($path), 'light_gray', 'red'); CLI::newLine(); + return; } @@ -144,6 +167,21 @@ public function run(array $params) CLI::newLine(); } + /** + * Allows child generators to modify + * the internal `$sortImports` flag. + * + * @param boolean $sort + * + * @return $this + */ + protected function setSortImports(bool $sort) + { + $this->sortImports = $sort; + + return $this; + } + /** * Gets the class name from input. This can be overridden * if name is really required by providing a prompt. @@ -152,7 +190,8 @@ public function run(array $params) */ protected function getClassName(): string { - $name = $this->params[0] ?? CLI::getSegment(0); + $name = $this->params[0] ?? CLI::getSegment(2); + return $name ?? ''; } @@ -168,9 +207,8 @@ protected function sanitizeClassName(string $class): string { $class = trim($class); $class = str_replace('/', '\\', $class); - $class = implode('\\', array_map('pascalize', explode('\\', $class))); - return $class; + return implode('\\', array_map('pascalize', explode('\\', $class))); } /** @@ -203,6 +241,7 @@ protected function qualifyClassName(string $class): string protected function getRootNamespace(): string { $rootNamespace = $this->params['n'] ?? CLI::getOption('n') ?? APP_NAMESPACE; + return trim(str_replace('/', '\\', $rootNamespace), '\\'); } @@ -230,14 +269,16 @@ protected function buildPath(string $class): string // Check if the namespace is actually defined and we are not just typing gibberish. $base = Services::autoloader()->getNamespace($root); + if (! $base = reset($base)) { - throw new \RuntimeException(lang('CLI.namespaceNotDefined', [$root])); + throw new RuntimeException(lang('CLI.namespaceNotDefined', [$root])); } - $base = realpath($base) ?: $base; + $base = realpath($base) ?: $base; $path = $base . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.php'; $filename = $this->modifyBasename(basename($path)); + return implode(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $path), 0, -1)) . DIRECTORY_SEPARATOR . $filename; } @@ -310,6 +351,7 @@ protected function setReplacements(string $template, string $class): string $template = str_replace($namespaces, $this->getNamespace($class), $template); $class = str_replace($this->getNamespace($class) . '\\', '', $class); + return str_replace($classes, $class, $template); } @@ -322,15 +364,37 @@ protected function setReplacements(string $template, string $class): string */ protected function sortImports(string $template): string { - if (preg_match('/(?P(?:use [^;]+;$\n?)+)/m', $template, $match)) + if ($this->sortImports && preg_match('/(?P(?:^use [^;]+;$\n?)+)/m', $template, $match)) { $imports = explode("\n", trim($match['imports'])); sort($imports); + return str_replace(trim($match['imports']), implode("\n", $imports), $template); } - // @codeCoverageIgnoreStart return $template; - // @codeCoverageIgnoreEnd + } + + /** + * Gets a generator view as defined in the `Config\Generators::$views`, + * with fallback to `$default` when the defined view does not exist. + * + * @param string $default Path to the fallback view. + * @param array $data Data to be passed to the view. + * + * @return string + */ + protected function getGeneratorViewFile(string $default, array $data = []): string + { + try + { + return view($this->config->views[$this->name], $data, ['debug' => false]); + } + catch (Throwable $e) + { + log_message('error', $e->getMessage()); + + return view($default, $data, ['debug' => false]); + } } } diff --git a/system/Cache/CacheFactory.php b/system/Cache/CacheFactory.php index c3501ec55ca7..cea2c36cbced 100644 --- a/system/Cache/CacheFactory.php +++ b/system/Cache/CacheFactory.php @@ -41,6 +41,7 @@ use CodeIgniter\Cache\Exceptions\CacheException; use CodeIgniter\Exceptions\CriticalError; +use Config\Cache; /** * Class Cache @@ -54,13 +55,13 @@ class CacheFactory /** * Attempts to create the desired cache handler, based upon the * - * @param \Config\Cache $config - * @param string|null $handler - * @param string|null $backup + * @param Cache $config + * @param string|null $handler + * @param string|null $backup * - * @return \CodeIgniter\Cache\CacheInterface + * @return CacheInterface */ - public static function getHandler($config, string $handler = null, string $backup = null) + public static function getHandler(Cache $config, string $handler = null, string $backup = null) { if (! isset($config->validHandlers) || ! is_array($config->validHandlers)) { diff --git a/system/Cache/Exceptions/CacheException.php b/system/Cache/Exceptions/CacheException.php index a99e3d440344..f4f5b852297b 100644 --- a/system/Cache/Exceptions/CacheException.php +++ b/system/Cache/Exceptions/CacheException.php @@ -38,17 +38,19 @@ namespace CodeIgniter\Cache\Exceptions; +use RuntimeException; + /** * CacheException */ -class CacheException extends \RuntimeException implements ExceptionInterface +class CacheException extends RuntimeException implements ExceptionInterface { /** * Thrown when handler has no permission to write cache. * * @param string $path * - * @return \CodeIgniter\Cache\Exceptions\CacheException + * @return CacheException */ public static function forUnableToWrite(string $path) { @@ -58,7 +60,7 @@ public static function forUnableToWrite(string $path) /** * Thrown when an unrecognized handler is used. * - * @return \CodeIgniter\Cache\Exceptions\CacheException + * @return CacheException */ public static function forInvalidHandlers() { @@ -68,7 +70,7 @@ public static function forInvalidHandlers() /** * Thrown when no backup handler is setup in config. * - * @return \CodeIgniter\Cache\Exceptions\CacheException + * @return CacheException */ public static function forNoBackup() { @@ -78,7 +80,7 @@ public static function forNoBackup() /** * Thrown when specified handler was not found. * - * @return \CodeIgniter\Cache\Exceptions\CacheException + * @return CacheException */ public static function forHandlerNotFound() { diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index f80d21567536..0301cf45b400 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -41,6 +41,7 @@ use CodeIgniter\Cache\CacheInterface; use CodeIgniter\Cache\Exceptions\CacheException; +use Config\Cache; /** * File system cache handler @@ -67,10 +68,10 @@ class FileHandler implements CacheInterface /** * Constructor. * - * @param \Config\Cache $config + * @param Cache $config * @throws CacheException */ - public function __construct($config) + public function __construct(Cache $config) { $path = ! empty($config->storePath) ? $config->storePath : WRITEPATH . 'cache'; if (! is_really_writable($path)) @@ -185,9 +186,9 @@ public function increment(string $key, int $offset = 1) return false; } - $new_value = $data['data'] + $offset; + $newValue = $data['data'] + $offset; - return $this->save($key, $new_value, $data['ttl']) ? $new_value : false; + return $this->save($key, $newValue, $data['ttl']) ? $newValue : false; } //-------------------------------------------------------------------- @@ -218,9 +219,9 @@ public function decrement(string $key, int $offset = 1) return false; } - $new_value = $data['data'] - $offset; + $newValue = $data['data'] - $offset; - return $this->save($key, $new_value, $data['ttl']) ? $new_value : false; + return $this->save($key, $newValue, $data['ttl']) ? $newValue : false; } //-------------------------------------------------------------------- @@ -382,30 +383,30 @@ protected function writeFile($path, $data, $mode = 'wb') * If the second parameter is set to TRUE, any directories contained * within the supplied base directory will be nuked as well. * - * @param string $path File path - * @param boolean $del_dir Whether to delete any directories found in the path - * @param boolean $htdocs Whether to skip deleting .htaccess and index page files - * @param integer $_level Current directory depth level (default: 0; internal use only) + * @param string $path File path + * @param boolean $delDir Whether to delete any directories found in the path + * @param boolean $htdocs Whether to skip deleting .htaccess and index page files + * @param integer $_level Current directory depth level (default: 0; internal use only) * * @return boolean */ - protected function deleteFiles(string $path, bool $del_dir = false, bool $htdocs = false, int $_level = 0): bool + protected function deleteFiles(string $path, bool $delDir = false, bool $htdocs = false, int $_level = 0): bool { // Trim the trailing slash $path = rtrim($path, '/\\'); - if (! $current_dir = @opendir($path)) + if (! $currentDir = @opendir($path)) { return false; } - while (false !== ($filename = @readdir($current_dir))) + while (false !== ($filename = @readdir($currentDir))) { if ($filename !== '.' && $filename !== '..') { if (is_dir($path . DIRECTORY_SEPARATOR . $filename) && $filename[0] !== '.') { - $this->deleteFiles($path . DIRECTORY_SEPARATOR . $filename, $del_dir, $htdocs, $_level + 1); + $this->deleteFiles($path . DIRECTORY_SEPARATOR . $filename, $delDir, $htdocs, $_level + 1); } elseif ($htdocs !== true || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) { @@ -414,9 +415,9 @@ protected function deleteFiles(string $path, bool $del_dir = false, bool $htdocs } } - closedir($current_dir); + closedir($currentDir); - return ($del_dir === true && $_level > 0) ? @rmdir($path) : true; + return ($delDir === true && $_level > 0) ? @rmdir($path) : true; } //-------------------------------------------------------------------- @@ -429,37 +430,37 @@ protected function deleteFiles(string $path, bool $del_dir = false, bool $htdocs * * Any sub-folders contained within the specified path are read as well. * - * @param string $source_dir Path to source - * @param boolean $top_level_only Look only at the top level directory specified? - * @param boolean $_recursion Internal variable to determine recursion status - do not use in calls + * @param string $sourceDir Path to source + * @param boolean $topLevelOnly Look only at the top level directory specified? + * @param boolean $_recursion Internal variable to determine recursion status - do not use in calls * * @return array|false */ - protected function getDirFileInfo(string $source_dir, bool $top_level_only = true, bool $_recursion = false) + protected function getDirFileInfo(string $sourceDir, bool $topLevelOnly = true, bool $_recursion = false) { static $_filedata = []; - $relative_path = $source_dir; + $relativePath = $sourceDir; - if ($fp = @opendir($source_dir)) + if ($fp = @opendir($sourceDir)) { // reset the array and make sure $source_dir has a trailing slash on the initial call if ($_recursion === false) { - $_filedata = []; - $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $_filedata = []; + $sourceDir = rtrim(realpath($sourceDir) ?: $sourceDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } // Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast while (false !== ($file = readdir($fp))) { - if (is_dir($source_dir . $file) && $file[0] !== '.' && $top_level_only === false) + if (is_dir($sourceDir . $file) && $file[0] !== '.' && $topLevelOnly === false) { - $this->getDirFileInfo($source_dir . $file . DIRECTORY_SEPARATOR, $top_level_only, true); + $this->getDirFileInfo($sourceDir . $file . DIRECTORY_SEPARATOR, $topLevelOnly, true); } elseif ($file[0] !== '.') { - $_filedata[$file] = $this->getFileInfo($source_dir . $file); - $_filedata[$file]['relative_path'] = $relative_path; + $_filedata[$file] = $this->getFileInfo($sourceDir . $file); + $_filedata[$file]['relative_path'] = $relativePath; } } @@ -481,24 +482,24 @@ protected function getDirFileInfo(string $source_dir, bool $top_level_only = tru * Options are: name, server_path, size, date, readable, writable, executable, fileperms * Returns FALSE if the file cannot be found. * - * @param string $file Path to file - * @param mixed $returned_values Array or comma separated string of information returned + * @param string $file Path to file + * @param mixed $returnedValues Array or comma separated string of information returned * * @return array|false */ - protected function getFileInfo(string $file, $returned_values = ['name', 'server_path', 'size', 'date']) + protected function getFileInfo(string $file, $returnedValues = ['name', 'server_path', 'size', 'date']) { if (! is_file($file)) { return false; } - if (is_string($returned_values)) + if (is_string($returnedValues)) { - $returned_values = explode(',', $returned_values); + $returnedValues = explode(',', $returnedValues); } - foreach ($returned_values as $key) + foreach ($returnedValues as $key) { switch ($key) { diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index d1676eefbc55..dcba186369bb 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -40,6 +40,10 @@ use CodeIgniter\Cache\CacheInterface; use CodeIgniter\Exceptions\CriticalError; +use Config\Cache; +use Exception; +use Memcache; +use Memcached; /** * Mamcached cache handler @@ -57,7 +61,7 @@ class MemcachedHandler implements CacheInterface /** * The memcached object * - * @var \Memcached|\Memcache + * @var Memcached|Memcache */ protected $memcached; @@ -78,9 +82,9 @@ class MemcachedHandler implements CacheInterface /** * Constructor. * - * @param \Config\Cache $config + * @param Cache $config */ - public function __construct($config) + public function __construct(Cache $config) { $this->prefix = $config->prefix ?: ''; @@ -97,11 +101,11 @@ public function __construct($config) */ public function __destruct() { - if ($this->memcached instanceof \Memcached) + if ($this->memcached instanceof Memcached) { $this->memcached->quit(); } - elseif ($this->memcached instanceof \Memcache) + elseif ($this->memcached instanceof Memcache) { $this->memcached->close(); } @@ -118,13 +122,13 @@ public function initialize() // so that the CacheFactory can attempt to initiate the next cache handler. try { - if (class_exists('\Memcached')) + if (class_exists(Memcached::class)) { - // Create new instance of \Memcached - $this->memcached = new \Memcached(); + // Create new instance of Memcached + $this->memcached = new Memcached(); if ($this->config['raw']) { - $this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); } // Add server @@ -142,18 +146,18 @@ public function initialize() throw new CriticalError('Cache: Memcached connection failed.'); } } - elseif (class_exists('\Memcache')) + elseif (class_exists(Memcache::class)) { - // Create new instance of \Memcache - $this->memcached = new \Memcache(); + // Create new instance of Memcache + $this->memcached = new Memcache(); // Check if we can connect to the server - $can_connect = $this->memcached->connect( + $canConnect = $this->memcached->connect( $this->config['host'], $this->config['port'] ); // If we can't connect, throw a CriticalError exception - if ($can_connect === false) + if ($canConnect === false) { throw new CriticalError('Cache: Memcache connection failed.'); } @@ -173,7 +177,7 @@ public function initialize() // If a CriticalError exception occurs, throw it up. throw $e; } - catch (\Exception $e) + catch (Exception $e) { // If an \Exception occurs, convert it into a CriticalError exception and throw it. throw new CriticalError('Cache: Memcache(d) connection refused (' . $e->getMessage() . ').'); @@ -193,17 +197,17 @@ public function get(string $key) { $key = $this->prefix . $key; - if ($this->memcached instanceof \Memcached) + if ($this->memcached instanceof Memcached) { $data = $this->memcached->get($key); // check for unmatched key - if ($this->memcached->getResultCode() === \Memcached::RES_NOTFOUND) + if ($this->memcached->getResultCode() === Memcached::RES_NOTFOUND) { return null; } } - elseif ($this->memcached instanceof \Memcache) + elseif ($this->memcached instanceof Memcache) { $flags = false; $data = $this->memcached->get($key, $flags); // @phpstan-ignore-line @@ -242,12 +246,12 @@ public function save(string $key, $value, int $ttl = 60) ]; } - if ($this->memcached instanceof \Memcached) + if ($this->memcached instanceof Memcached) { return $this->memcached->set($key, $value, $ttl); } - if ($this->memcached instanceof \Memcache) + if ($this->memcached instanceof Memcache) { return $this->memcached->set($key, $value, 0, $ttl); } diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php index bdfcea07fa96..b6fd0ecf1de4 100644 --- a/system/Cache/Handlers/PredisHandler.php +++ b/system/Cache/Handlers/PredisHandler.php @@ -40,6 +40,9 @@ use CodeIgniter\Cache\CacheInterface; use CodeIgniter\Exceptions\CriticalError; +use Config\Cache; +use Exception; +use Predis\Client; /** * Predis cache handler @@ -70,7 +73,7 @@ class PredisHandler implements CacheInterface /** * Predis connection * - * @var \Predis\Client + * @var Client */ protected $redis; @@ -79,9 +82,9 @@ class PredisHandler implements CacheInterface /** * Constructor. * - * @param \Config\Cache $config + * @param Cache $config */ - public function __construct($config) + public function __construct(Cache $config) { $this->prefix = $config->prefix ?: ''; @@ -103,12 +106,12 @@ public function initialize() try { // Create a new instance of Predis\Client - $this->redis = new \Predis\Client($this->config, ['prefix' => $this->prefix]); + $this->redis = new Client($this->config, ['prefix' => $this->prefix]); // Check if the connection is valid by trying to get the time. $this->redis->time(); } - catch (\Exception $e) + catch (Exception $e) { // thrown if can't connect to redis server. throw new CriticalError('Cache: Predis connection refused (' . $e->getMessage() . ').'); @@ -167,7 +170,7 @@ public function get(string $key) */ public function save(string $key, $value, int $ttl = 60) { - switch ($data_type = gettype($value)) + switch ($dataType = gettype($value)) { case 'array': case 'object': @@ -184,7 +187,7 @@ public function save(string $key, $value, int $ttl = 60) return false; } - if (! $this->redis->hmset($key, ['__ci_type' => $data_type, '__ci_value' => $value])) + if (! $this->redis->hmset($key, ['__ci_type' => $dataType, '__ci_value' => $value])) { return false; } diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index c997979667b4..19def194d6c3 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -41,6 +41,9 @@ use CodeIgniter\Cache\CacheInterface; use CodeIgniter\Exceptions\CriticalError; +use Config\Cache; +use Redis; +use RedisException; /** * Redis cache handler @@ -71,7 +74,7 @@ class RedisHandler implements CacheInterface /** * Redis connection * - * @var \Redis + * @var Redis */ protected $redis; @@ -80,9 +83,9 @@ class RedisHandler implements CacheInterface /** * Constructor. * - * @param \Config\Cache $config + * @param Cache $config */ - public function __construct($config) + public function __construct(Cache $config) { $this->prefix = $config->prefix ?: ''; @@ -114,7 +117,7 @@ public function initialize() { $config = $this->config; - $this->redis = new \Redis(); + $this->redis = new Redis(); // Try to connect to Redis, if an issue occurs throw a CriticalError exception, // so that the CacheFactory can attempt to initiate the next cache handler. @@ -142,7 +145,7 @@ public function initialize() throw new CriticalError('Cache: Redis select database failed.'); } } - catch (\RedisException $e) + catch (RedisException $e) { // $this->redis->connect() can sometimes throw a RedisException. // We need to convert the exception into a CriticalError exception and throw it. @@ -202,7 +205,7 @@ public function save(string $key, $value, int $ttl = 60) { $key = $this->prefix . $key; - switch ($data_type = gettype($value)) + switch ($dataType = gettype($value)) { case 'array': case 'object': @@ -219,11 +222,12 @@ public function save(string $key, $value, int $ttl = 60) return false; } - if (! $this->redis->hMSet($key, ['__ci_type' => $data_type, '__ci_value' => $value])) + if (! $this->redis->hMSet($key, ['__ci_type' => $dataType, '__ci_value' => $value])) { return false; } - elseif ($ttl) + + if ($ttl) { $this->redis->expireAt($key, time() + $ttl); } diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php index 9e66b2854e8b..42b0e99bf57e 100644 --- a/system/Cache/Handlers/WincacheHandler.php +++ b/system/Cache/Handlers/WincacheHandler.php @@ -40,6 +40,7 @@ namespace CodeIgniter\Cache\Handlers; use CodeIgniter\Cache\CacheInterface; +use Config\Cache; /** * Cache handler for WinCache from Microsoft & IIS. @@ -61,9 +62,9 @@ class WincacheHandler implements CacheInterface /** * Constructor. * - * @param \Config\Cache $config + * @param Cache $config */ - public function __construct($config) + public function __construct(Cache $config) { $this->prefix = $config->prefix ?: ''; } diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 71ccf17c160c..049f3eecf904 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -47,15 +47,20 @@ use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\DownloadResponse; use CodeIgniter\HTTP\RedirectResponse; +use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\Request; use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\URI; use CodeIgniter\Router\Exceptions\RedirectException; use CodeIgniter\Router\RouteCollectionInterface; +use Config\App; use Config\Cache; use Config\Services; use Exception; +use Kint; +use Kint\Renderer\CliRenderer; +use Kint\Renderer\RichRenderer; /** * This class is the core of the framework, and will analyse the @@ -101,14 +106,14 @@ class CodeIgniter /** * Current request. * - * @var HTTP\Request|HTTP\IncomingRequest|CLIRequest + * @var HTTP\Request|HTTP\IncomingRequest|CLIRequest|RequestInterface */ protected $request; /** * Current response. * - * @var HTTP\ResponseInterface + * @var ResponseInterface */ protected $response; @@ -167,9 +172,9 @@ class CodeIgniter /** * Constructor. * - * @param \Config\App $config + * @param App $config */ - public function __construct(\Config\App $config) + public function __construct(App $config) { $this->startTime = microtime(true); $this->config = $config; @@ -208,7 +213,7 @@ public function initialize() if (! CI_DEBUG) { // @codeCoverageIgnoreStart - \Kint::$enabled_mode = false; + Kint::$enabled_mode = false; // @codeCoverageIgnoreEnd } } @@ -280,31 +285,31 @@ protected function initializeKint() */ $config = config('Config\Kint'); - \Kint::$max_depth = $config->maxDepth; - \Kint::$display_called_from = $config->displayCalledFrom; - \Kint::$expanded = $config->expanded; + Kint::$max_depth = $config->maxDepth; + Kint::$display_called_from = $config->displayCalledFrom; + Kint::$expanded = $config->expanded; if (! empty($config->plugins) && is_array($config->plugins)) { - \Kint::$plugins = $config->plugins; + Kint::$plugins = $config->plugins; } - \Kint\Renderer\RichRenderer::$theme = $config->richTheme; - \Kint\Renderer\RichRenderer::$folder = $config->richFolder; - \Kint\Renderer\RichRenderer::$sort = $config->richSort; + RichRenderer::$theme = $config->richTheme; + RichRenderer::$folder = $config->richFolder; + RichRenderer::$sort = $config->richSort; if (! empty($config->richObjectPlugins) && is_array($config->richObjectPlugins)) { - \Kint\Renderer\RichRenderer::$object_plugins = $config->richObjectPlugins; + RichRenderer::$object_plugins = $config->richObjectPlugins; } if (! empty($config->richTabPlugins) && is_array($config->richTabPlugins)) { - \Kint\Renderer\RichRenderer::$tab_plugins = $config->richTabPlugins; + RichRenderer::$tab_plugins = $config->richTabPlugins; } - \Kint\Renderer\CliRenderer::$cli_colors = $config->cliColors; - \Kint\Renderer\CliRenderer::$force_utf8 = $config->cliForceUTF8; - \Kint\Renderer\CliRenderer::$detect_width = $config->cliDetectWidth; - \Kint\Renderer\CliRenderer::$min_terminal_width = $config->cliMinWidth; + CliRenderer::$cli_colors = $config->cliColors; + CliRenderer::$force_utf8 = $config->cliForceUTF8; + CliRenderer::$detect_width = $config->cliDetectWidth; + CliRenderer::$min_terminal_width = $config->cliMinWidth; } //-------------------------------------------------------------------- @@ -317,12 +322,12 @@ protected function initializeKint() * tries to route the response, loads the controller and generally * makes all of the pieces work together. * - * @param \CodeIgniter\Router\RouteCollectionInterface|null $routes - * @param boolean $returnResponse + * @param RouteCollectionInterface|null $routes + * @param boolean $returnResponse * - * @return boolean|\CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\Response|\CodeIgniter\HTTP\ResponseInterface|mixed - * @throws \CodeIgniter\Router\Exceptions\RedirectException - * @throws \Exception + * @return boolean|RequestInterface|Response|ResponseInterface|mixed + * @throws RedirectException + * @throws Exception */ public function run(RouteCollectionInterface $routes = null, bool $returnResponse = false) { @@ -397,12 +402,12 @@ public function useSafeOutput(bool $safe = true) /** * Handles the main request logic and fires the controller. * - * @param \CodeIgniter\Router\RouteCollectionInterface|null $routes - * @param Cache $cacheConfig - * @param boolean $returnResponse + * @param RouteCollectionInterface|null $routes + * @param Cache $cacheConfig + * @param boolean $returnResponse * - * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\Response|\CodeIgniter\HTTP\ResponseInterface|mixed - * @throws \CodeIgniter\Router\Exceptions\RedirectException + * @return RequestInterface|\CodeIgniter\HTTP\Response|ResponseInterface|mixed + * @throws RedirectException */ protected function handleRequest(RouteCollectionInterface $routes = null, Cache $cacheConfig, bool $returnResponse = false) { @@ -425,14 +430,16 @@ protected function handleRequest(RouteCollectionInterface $routes = null, Cache if (! defined('SPARKED')) { $possibleRedirect = $filters->run($uri, 'before'); - if ($possibleRedirect instanceof RedirectResponse) + + // If a Response instance is returned, the Response will be sent back to the client and script execution will stop + if ($possibleRedirect instanceof ResponseInterface || $possibleRedirect instanceof RedirectResponse) { return $possibleRedirect->send(); } - // If a Response instance is returned, the Response will be sent back to the client and script execution will stop - if ($possibleRedirect instanceof ResponseInterface) + // If a Request instance is returned, set the current request to the one that was returned + if ($possibleRedirect instanceof RequestInterface) { - return $possibleRedirect->send(); + $this->request = $possibleRedirect; } } @@ -550,7 +557,7 @@ protected function bootstrapEnvironment() // @codeCoverageIgnoreStart header('HTTP/1.1 503 Service Unavailable.', true, 503); echo 'The application environment is not set correctly.'; - exit(1); // EXIT_ERROR + exit(EXIT_ERROR); // EXIT_ERROR // @codeCoverageIgnoreEnd } } @@ -578,9 +585,9 @@ protected function startBenchmark() * Sets a Request object to be used for this request. * Used when running certain tests. * - * @param \CodeIgniter\HTTP\Request $request + * @param Request $request * - * @return \CodeIgniter\CodeIgniter + * @return $this */ public function setRequest(Request $request) { @@ -664,13 +671,13 @@ protected function forceSecureAccess($duration = 31536000) /** * Determines if a response has been cached for the given URI. * - * @param \Config\Cache $config + * @param Cache $config * - * @throws \Exception + * @throws Exception * - * @return boolean|\CodeIgniter\HTTP\ResponseInterface + * @return boolean|ResponseInterface */ - public function displayCache($config) + public function displayCache(Cache $config) { if ($cachedResponse = cache()->get($this->generateCacheName($config))) { @@ -724,7 +731,7 @@ public static function cache(int $time) * Caches the full response from the current request. Used for * full-page caching for very high performance. * - * @param \Config\Cache $config + * @param Cache $config * * @return mixed */ @@ -817,7 +824,7 @@ public function displayPerformanceMetrics(string $output): string * of the config file. * * @return string|null - * @throws \CodeIgniter\Router\Exceptions\RedirectException + * @throws RedirectException */ protected function tryToRouteIt(RouteCollectionInterface $routes = null) { @@ -917,9 +924,8 @@ protected function startController() { throw PageNotFoundException::forControllerNotFound($this->controller, $this->method); } - else if (! method_exists($this->controller, '_remap') && - ! is_callable([$this->controller, $this->method], false) - ) + if (! method_exists($this->controller, '_remap') && + ! is_callable([$this->controller, $this->method], false)) { throw PageNotFoundException::forMethodNotFound($this->method); } @@ -1094,7 +1100,7 @@ protected function gatherOutput(Cache $cacheConfig = null, $returned = null) * * This helps provider safer, more reliable previous_url() detection. * - * @param \CodeIgniter\HTTP\URI|string $uri + * @param URI|string $uri */ public function storePreviousURL($uri) { @@ -1115,7 +1121,7 @@ public function storePreviousURL($uri) $uri = new URI($uri); } - if (isset($_SESSION)) // @phpstan-ignore-line + if (isset($_SESSION)) { $_SESSION['_ci_previous_url'] = (string) $uri; } diff --git a/system/Commands/Cache/ClearCache.php b/system/Commands/Cache/ClearCache.php index f97af0ff5a84..8fa1edd3221d 100644 --- a/system/Commands/Cache/ClearCache.php +++ b/system/Commands/Cache/ClearCache.php @@ -1,9 +1,51 @@ -handler; + if (! array_key_exists($handler, $config->validHandlers)) { CLI::error($handler . ' is not a valid cache handler.'); + return; } @@ -64,10 +107,13 @@ public function run(array $params) if (! $cache->clean()) { + // @codeCoverageIgnoreStart CLI::error('Error while clearing the cache.'); + return; + // @codeCoverageIgnoreEnd } - CLI::write(CLI::color('Done', 'green')); + CLI::write(CLI::color('Cache cleared.', 'green')); } } diff --git a/system/Commands/Cache/InfoCache.php b/system/Commands/Cache/InfoCache.php new file mode 100644 index 000000000000..1bde168cf6ce --- /dev/null +++ b/system/Commands/Cache/InfoCache.php @@ -0,0 +1,120 @@ +handler !== 'file') + { + CLI::error('This command only supports the file cache handler.'); + + return; + } + + $cache = CacheFactory::getHandler($config); + $caches = $cache->getCacheInfo(); + $tbody = []; + + foreach ($caches as $key => $field) + { + $tbody[] = [ + $key, + clean_path($field['server_path']), + number_to_size($field['size']), + Time::createFromTimestamp($field['date']), + ]; + } + + $thead = [ + CLI::color('Name', 'green'), + CLI::color('Server Path', 'green'), + CLI::color('Size', 'green'), + CLI::color('Date', 'green'), + ]; + + CLI::table($tbody, $thead); + } +} diff --git a/system/Commands/Database/CreateDatabase.php b/system/Commands/Database/CreateDatabase.php new file mode 100644 index 000000000000..08f6e814bcdc --- /dev/null +++ b/system/Commands/Database/CreateDatabase.php @@ -0,0 +1,193 @@ + [options]'; + + /** + * The Command's arguments + * + * @var array + */ + protected $arguments = [ + 'db_name' => 'The database name to use', + ]; + + /** + * The Command's options + * + * @var array + */ + protected $options = [ + '--ext' => 'File extension of the database file for SQLite3. Can be `db` or `sqlite`. Defaults to `db`.', + ]; + + /** + * Creates a new database. + * + * @param array $params + * + * @return void + */ + public function run(array $params) + { + $name = array_shift($params); + + if (empty($name)) + { + $name = CLI::prompt('Database name', null, 'required'); // @codeCoverageIgnore + } + + $db = Database::connect(); + + try + { + // Special SQLite3 handling + if ($db instanceof Connection) + { + $config = config('Database'); + $group = ENVIRONMENT === 'testing' ? 'tests' : $config->defaultGroup; + $ext = $params['ext'] ?? CLI::getOption('ext') ?? 'db'; + + if (! in_array($ext, ['db', 'sqlite'], true)) + { + $ext = CLI::prompt('Please choose a valid file extension', ['db', 'sqlite']); // @codeCoverageIgnore + } + + if (strpos($name, ':memory:') === false) + { + $name = str_replace(['.db', '.sqlite'], '', $name) . ".{$ext}"; + } + + $config->{$group}['DBDriver'] = 'SQLite3'; + $config->{$group}['database'] = $name; + + if (strpos($name, ':memory:') === false) + { + $dbName = strpos($name, DIRECTORY_SEPARATOR) === false ? WRITEPATH . $name : $name; + + if (is_file($dbName)) + { + CLI::error("Database \"{$dbName}\" already exists.", 'light_gray', 'red'); + CLI::newLine(); + + return; + } + + unset($dbName); + } + + // Connect to new SQLite3 to create new database, + // then reset the altered Config\Database instance + $db = Database::connect(null, false); + $db->connect(); + Factories::reset('config'); + + if (! is_file($db->getDatabase()) && strpos($name, ':memory:') === false) + { + // @codeCoverageIgnoreStart + CLI::error('Database creation failed.', 'light_gray', 'red'); + CLI::newLine(); + + return; + // @codeCoverageIgnoreEnd + } + } + else + { + if (! Database::forge()->createDatabase($name)) + { + // @codeCoverageIgnoreStart + CLI::error('Database creation failed.', 'light_gray', 'red'); + CLI::newLine(); + + return; + // @codeCoverageIgnoreEnd + } + } + + CLI::write("Database \"{$name}\" successfully created.", 'green'); + CLI::newLine(); + } + catch (Throwable $e) + { + $this->showError($e); + } + } +} diff --git a/system/Commands/Database/Migrate.php b/system/Commands/Database/Migrate.php index 8370286b0415..a7fffbe4908b 100644 --- a/system/Commands/Database/Migrate.php +++ b/system/Commands/Database/Migrate.php @@ -42,11 +42,10 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use Config\Services; +use Throwable; /** * Runs all new migrations. - * - * @package CodeIgniter\Commands */ class Migrate extends BaseCommand { @@ -79,28 +78,23 @@ class Migrate extends BaseCommand */ protected $usage = 'migrate [options]'; - /** - * the Command's Arguments - * - * @var array - */ - protected $arguments = []; - /** * the Command's Options * * @var array */ protected $options = [ - '-n' => 'Set migration namespace', - '-g' => 'Set database group', - '-all' => 'Set for all namespaces, will ignore (-n) option', + '-n' => 'Set migration namespace', + '-g' => 'Set database group', + '--all' => 'Set for all namespaces, will ignore (-n) option', ]; /** * Ensures that all migrations have been run. * * @param array $params + * + * @return void */ public function run(array $params) { @@ -127,10 +121,11 @@ public function run(array $params) if (! $runner->latest($group)) { - CLI::write(lang('Migrations.generalFault'), 'red'); + CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // @codeCoverageIgnore } $messages = $runner->getCliMessages(); + foreach ($messages as $message) { CLI::write($message); @@ -138,9 +133,11 @@ public function run(array $params) CLI::write('Done migrations.', 'green'); } - catch (\Throwable $e) + // @codeCoverageIgnoreStart + catch (Throwable $e) { $this->showError($e); } + // @codeCoverageIgnoreEnd } } diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php index 32a912ed03e9..03ed862b907d 100644 --- a/system/Commands/Database/MigrateRefresh.php +++ b/system/Commands/Database/MigrateRefresh.php @@ -45,12 +45,9 @@ /** * Does a rollback followed by a latest to refresh the current state * of the database. - * - * @package CodeIgniter\Commands */ class MigrateRefresh extends BaseCommand { - /** * The group the command is lumped under * when listing commands. @@ -78,14 +75,7 @@ class MigrateRefresh extends BaseCommand * * @var string */ - protected $usage = 'migrate:refresh [Options]'; - - /** - * the Command's Arguments - * - * @var array - */ - protected $arguments = []; + protected $usage = 'migrate:refresh [options]'; /** * the Command's Options @@ -93,10 +83,10 @@ class MigrateRefresh extends BaseCommand * @var array */ protected $options = [ - '-n' => 'Set migration namespace', - '-g' => 'Set database group', - '-all' => 'Set latest for all namespace, will ignore (-n) option', - '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', + '-n' => 'Set migration namespace', + '-g' => 'Set database group', + '--all' => 'Set latest for all namespace, will ignore (-n) option', + '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', ]; /** @@ -104,24 +94,28 @@ class MigrateRefresh extends BaseCommand * of the database. * * @param array $params + * + * @return void */ public function run(array $params) { - $params = ['b' => 0]; + $params['b'] = 0; if (ENVIRONMENT === 'production') { - $force = CLI::getOption('f'); - if (is_null($force) && CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'n') + // @codeCoverageIgnoreStart + $force = array_key_exists('f', $params) || CLI::getOption('f'); + + if (! $force && CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'n') { return; } $params['f'] = null; + // @codeCoverageIgnoreEnd } $this->call('migrate:rollback', $params); - $this->call('migrate'); + $this->call('migrate', $params); } - } diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php index dab8870e4544..de9f1f400e92 100644 --- a/system/Commands/Database/MigrateRollback.php +++ b/system/Commands/Database/MigrateRollback.php @@ -42,16 +42,14 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use Config\Services; +use Throwable; /** * Runs all of the migrations in reverse order, until they have * all been unapplied. - * - * @package CodeIgniter\Commands */ class MigrateRollback extends BaseCommand { - /** * The group the command is lumped under * when listing commands. @@ -94,27 +92,30 @@ class MigrateRollback extends BaseCommand /** * Runs all of the migrations in reverse order, until they have - * all been un-applied. + * all been unapplied. * * @param array $params + * + * @return void */ public function run(array $params) { if (ENVIRONMENT === 'production') { + // @codeCoverageIgnoreStart $force = array_key_exists('f', $params) || CLI::getOption('f'); - // @phpstan-ignore-next-line - if (is_null($force) && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') + + if (! $force && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') { return; } + // @codeCoverageIgnoreEnd } $runner = Services::migrations(); + $group = $params['g'] ?? CLI::getOption('g'); - $group = $params['g'] ?? CLI::getOption('g'); - - if (! is_null($group)) + if (is_string($group)) { $runner->setGroup($group); } @@ -126,20 +127,23 @@ public function run(array $params) if (! $runner->regress($batch)) { - CLI::write(lang('Migrations.generalFault'), 'red'); + CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // @codeCoverageIgnore } $messages = $runner->getCliMessages(); + foreach ($messages as $message) { CLI::write($message); } - CLI::write('Done'); + CLI::write('Done rolling back migrations.', 'green'); } - catch (\Throwable $e) + // @codeCoverageIgnoreStart + catch (Throwable $e) { $this->showError($e); } + // @codeCoverageIgnoreEnd } } diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index e6bcef43068c..286d315a2365 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -45,12 +45,9 @@ /** * Displays a list of all migrations and whether they've been run or not. - * - * @package CodeIgniter\Commands */ class MigrateStatus extends BaseCommand { - /** * The group the command is lumped under * when listing commands. @@ -80,17 +77,10 @@ class MigrateStatus extends BaseCommand */ protected $usage = 'migrate:status [options]'; - /** - * the Command's Arguments - * - * @var array - */ - protected $arguments = []; - /** * the Command's Options * - * @var array + * @var array */ protected $options = [ '-g' => 'Set database group', @@ -99,12 +89,11 @@ class MigrateStatus extends BaseCommand /** * Namespaces to ignore when looking for migrations. * - * @var array + * @var string[] */ protected $ignoredNamespaces = [ 'CodeIgniter', 'Config', - 'Tests\Support', 'Kint', 'Laminas\ZendFrameworkBridge', 'Laminas\Escaper', @@ -114,15 +103,16 @@ class MigrateStatus extends BaseCommand /** * Displays a list of all migrations and whether they've been run or not. * - * @param array $params + * @param array $params + * + * @return void */ public function run(array $params) { $runner = Services::migrations(); + $group = $params['g'] ?? CLI::getOption('g'); - $group = $params['g'] ?? CLI::getOption('g'); - - if (! is_null($group)) + if (is_string($group)) { $runner->setGroup($group); } @@ -130,63 +120,89 @@ public function run(array $params) // Get all namespaces $namespaces = Services::autoloader()->getNamespace(); - // Determines whether any migrations were found - $found = false; + // Collection of migration status + $status = []; - // Loop for all $namespaces foreach ($namespaces as $namespace => $path) { - if (in_array($namespace, $this->ignoredNamespaces)) + if (ENVIRONMENT !== 'testing') + { + // Make Tests\\Support discoverable for testing + $this->ignoredNamespaces[] = 'Tests\Support'; // @codeCoverageIgnore + } + + if (in_array($namespace, $this->ignoredNamespaces, true)) { continue; } - $runner->setNamespace($namespace); - $migrations = $runner->findMigrations(); + if (APP_NAMESPACE !== 'App' && $namespace === 'App') + { + continue; // @codeCoverageIgnore + } + + $migrations = $runner->findNamespaceMigrations($namespace); if (empty($migrations)) { continue; } - $found = true; $history = $runner->getHistory(); - - CLI::write($namespace); - ksort($migrations); - $max = 0; - foreach ($migrations as $version => $migration) + foreach ($migrations as $uid => $migration) { - $file = substr($migration->name, strpos($migration->name, $version . '_')); - $migrations[$version]->name = $file; + $migrations[$uid]->name = mb_substr($migration->name, mb_strpos($migration->name, $uid . '_')); - $max = max($max, strlen($file)); - } - - CLI::write(' ' . str_pad(lang('Migrations.filename'), $max + 4) . lang('Migrations.on'), 'yellow'); + $date = '---'; + $group = '---'; + $batch = '---'; - foreach ($migrations as $uid => $migration) - { - $date = ''; foreach ($history as $row) { - if ($runner->getObjectUid($row) !== $uid) + // @codeCoverageIgnoreStart + if ($runner->getObjectUid($row) !== $migration->uid) { continue; } - $date = date('Y-m-d H:i:s', $row->time); + $date = date('Y-m-d H:i:s', $row->time); + $group = $row->group; + $batch = $row->batch; + // @codeCoverageIgnoreEnd } - CLI::write(str_pad(' ' . $migration->name, $max + 6) . ($date ? $date : '---')); + + $status[] = [ + $namespace, + $migration->version, + $migration->name, + $group, + $date, + $batch, + ]; } } - if (! $found) + if (! $status) { - CLI::error(lang('Migrations.noneFound')); + // @codeCoverageIgnoreStart + CLI::error(lang('Migrations.noneFound'), 'light_gray', 'red'); + CLI::newLine(); + + return; + // @codeCoverageIgnoreEnd } - } + $headers = [ + CLI::color(lang('Migrations.namespace'), 'yellow'), + CLI::color(lang('Migrations.version'), 'yellow'), + CLI::color(lang('Migrations.filename'), 'yellow'), + CLI::color(lang('Migrations.group'), 'yellow'), + CLI::color(str_replace(': ', '', lang('Migrations.on')), 'yellow'), + CLI::color(lang('Migrations.batch'), 'yellow'), + ]; + + CLI::table($status, $headers); + } } diff --git a/system/Commands/Database/Seed.php b/system/Commands/Database/Seed.php index fe23a9bf86b5..3455ced6c914 100644 --- a/system/Commands/Database/Seed.php +++ b/system/Commands/Database/Seed.php @@ -42,16 +42,15 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use CodeIgniter\Database\Seeder; +use Config\Database; +use Throwable; /** * Runs the specified Seeder file to populate the database * with some data. - * - * @package CodeIgniter\Commands */ class Seed extends BaseCommand { - /** * The group the command is lumped under * when listing commands. @@ -79,45 +78,39 @@ class Seed extends BaseCommand * * @var string */ - protected $usage = 'db:seed [seeder_name]'; + protected $usage = 'db:seed '; /** * the Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'seeder_name' => 'The seeder name to run', ]; - /** - * the Command's Options - * - * @var array - */ - protected $options = []; - /** * Passes to Seeder to populate the database. * * @param array $params + * + * @return void */ public function run(array $params) { - $seeder = new Seeder(new \Config\Database()); - + $seeder = new Seeder(new Database()); $seedName = array_shift($params); if (empty($seedName)) { - $seedName = CLI::prompt(lang('Migrations.migSeeder'), null, 'required'); + $seedName = CLI::prompt(lang('Migrations.migSeeder'), null, 'required'); // @codeCoverageIgnore } try { $seeder->call($seedName); } - catch (\Throwable $e) + catch (Throwable $e) { $this->showError($e); } diff --git a/system/Commands/Encryption/GenerateKey.php b/system/Commands/Encryption/GenerateKey.php index a938d4ce04fa..cb48945f6934 100644 --- a/system/Commands/Encryption/GenerateKey.php +++ b/system/Commands/Encryption/GenerateKey.php @@ -83,10 +83,10 @@ class GenerateKey extends BaseCommand * @var array */ protected $options = [ - '-force' => 'Force overwrite existing key in `.env` file.', - '-length' => 'The length of the random string that should be returned in bytes. Defaults to 32.', - '-prefix' => 'Prefix to prepend to encoded key (either hex2bin or base64). Defaults to hex2bin.', - '-show' => 'Shows the generated key in the terminal instead of storing in the `.env` file.', + '--force' => 'Force overwrite existing key in `.env` file.', + '--length' => 'The length of the random string that should be returned in bytes. Defaults to 32.', + '--prefix' => 'Prefix to prepend to encoded key (either hex2bin or base64). Defaults to hex2bin.', + '--show' => 'Shows the generated key in the terminal instead of storing in the `.env` file.', ]; /** diff --git a/system/Commands/Generators/CreateCommand.php b/system/Commands/Generators/CreateCommand.php index dd4583830284..c998f9ded6c3 100644 --- a/system/Commands/Generators/CreateCommand.php +++ b/system/Commands/Generators/CreateCommand.php @@ -80,11 +80,14 @@ class CreateCommand extends GeneratorCommand * @var array */ protected $options = [ - '-command' => 'The command name. Defaults to "command:name"', - '-group' => 'The group of command. Defaults to "CodeIgniter" for basic commands, and "Generators" for generator commands.', - '-type' => 'Type of command. Whether a basic command or a generator command. Defaults to "basic".', + '--command' => 'The command name. Defaults to "command:name"', + '--group' => 'The group of command. Defaults to "CodeIgniter" for basic commands, and "Generators" for generator commands.', + '--type' => 'Type of command. Whether a basic command or a generator command. Defaults to "basic".', ]; + /** + * {@inheritDoc} + */ protected function getClassName(): string { $className = parent::getClassName(); @@ -97,18 +100,27 @@ protected function getClassName(): string return $className; } + /** + * {@inheritDoc} + */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Commands\\' . $class; } + /** + * {@inheritDoc} + */ protected function getTemplate(): string { - $template = view('CodeIgniter\\Commands\\Generators\\Views\\command.tpl.php', [], ['debug' => false]); + $template = $this->getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\command.tpl.php'); return str_replace('<@php', ' 'Extends from CodeIgniter\\Controller instead of BaseController', - '-restful' => 'Extends from a RESTful resource. Options are \'controller\' or \'presenter\'.', + '--bare' => 'Extends from CodeIgniter\\Controller instead of BaseController', + '--restful' => 'Extends from a RESTful resource. Options are \'controller\' or \'presenter\'.', ]; + /** + * {@inheritDoc} + */ protected function getClassName(): string { $className = parent::getClassName(); @@ -99,19 +102,27 @@ protected function getClassName(): string return $className; } + /** + * {@inheritDoc} + */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Controllers\\' . $class; } + /** + * {@inheritDoc} + */ protected function getTemplate(): string { - $template = view('CodeIgniter\\Commands\\Generators\\Views\\controller.tpl.php', [], ['debug' => false]); - $template = str_replace('<@php', 'getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\controller.tpl.php'); - return $template; + return str_replace('<@php', 'params) || CLI::getOption('bare'); @@ -126,7 +137,8 @@ protected function setReplacements(string $template, string $class): string ] = $this->getParentClass($bare, $rest); $template = parent::setReplacements($template, $class); - $template = str_replace([ + + return str_replace([ '{useStatement}', '{extends}', '{restfulMethods}', @@ -137,8 +149,6 @@ protected function setReplacements(string $template, string $class): string ], $template ); - - return $template; } /** @@ -193,6 +203,12 @@ protected function getParentClass(?bool $bare, $rest): array ]; } + /** + * If the controller extends any RESTful controller, this will provide + * the additional REST API methods. + * + * @return string + */ protected function getAdditionalRestfulMethods(): string { return <<<'EOF' diff --git a/system/Commands/Generators/CreateEntity.php b/system/Commands/Generators/CreateEntity.php index d1a526108c71..8aaf6a9a8824 100644 --- a/system/Commands/Generators/CreateEntity.php +++ b/system/Commands/Generators/CreateEntity.php @@ -42,6 +42,9 @@ use CodeIgniter\CLI\CLI; use CodeIgniter\CLI\GeneratorCommand; +/** + * Creates a skeleton Entity file. + */ class CreateEntity extends GeneratorCommand { /** @@ -74,6 +77,9 @@ class CreateEntity extends GeneratorCommand 'name' => 'The model class name', ]; + /** + * {@inheritDoc} + */ protected function getClassName(): string { $className = parent::getClassName(); @@ -86,14 +92,20 @@ protected function getClassName(): string return $className; } + /** + * {@inheritDoc} + */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Entities\\' . $class; } + /** + * {@inheritDoc} + */ protected function getTemplate(): string { - $template = view('CodeIgniter\\Commands\\Generators\\Views\\entity.tpl.php', [], ['debug' => false]); + $template = $this->getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\entity.tpl.php'); return str_replace('<@php', ' 'The filter class name', ]; + /** + * {@inheritDoc} + */ protected function getClassName(): string { $className = parent::getClassName(); @@ -49,14 +92,20 @@ protected function getClassName(): string return $className; } + /** + * {@inheritDoc} + */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Filters\\' . $class; } + /** + * {@inheritDoc} + */ protected function getTemplate(): string { - $template = view('CodeIgniter\\Commands\\Generators\\Views\\filter.tpl.php', [], ['debug' => false]); + $template = $this->getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\filter.tpl.php'); return str_replace('<@php', ' [options]'; + protected $usage = 'make:migration [options]'; /** * The Command's Arguments @@ -80,70 +78,43 @@ class CreateMigration extends GeneratorCommand ]; /** - * Gets the class name from input. - * - * @return string + * {@inheritDoc} */ protected function getClassName(): string { $class = parent::getClassName(); + if (empty($class)) { - // @codeCoverageIgnoreStart - $class = CLI::prompt(lang('Migrations.nameMigration'), null, 'required'); - // @codeCoverageIgnoreEnd + $class = CLI::prompt(lang('Migrations.nameMigration'), null, 'required'); // @codeCoverageIgnore } return $class; } /** - * Gets the qualified class name. - * - * @param string $rootNamespace - * @param string $class - * - * @return string + * {@inheritDoc} */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Database\\Migrations\\' . $class; } + /** + * {@inheritDoc} + */ protected function modifyBasename(string $filename): string { return gmdate(config('Migrations')->timestampFormat) . $filename; } /** - * Gets the template for this class. - * - * @return string + * {@inheritDoc} */ protected function getTemplate(): string { - return <<getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\migration.tpl.php'); -EOD; + return str_replace('<@php', ' 'Database group to use. Defaults to "default".', - '-entity' => 'Use an Entity as return type.', - '-table' => 'Supply a different table name. Defaults to the pluralized name.', + '--dbgroup' => 'Database group to use. Defaults to "default".', + '--entity' => 'Use an Entity as return type.', + '--table' => 'Supply a different table name. Defaults to the pluralized name.', ]; + /** + * {@inheritDoc} + */ protected function getClassName(): string { $className = parent::getClassName(); @@ -97,30 +103,39 @@ protected function getClassName(): string return $className; } + /** + * {@inheritDoc} + */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Models\\' . $class; } + /** + * {@inheritDoc} + */ protected function getTemplate(): string { $dbgroup = $this->params['dbgroup'] ?? CLI::getOption('dbgroup'); + if (! is_string($dbgroup)) { $dbgroup = 'default'; } - $template = view('CodeIgniter\\Commands\\Generators\\Views\\model.tpl.php', [], ['debug' => false]); - $template = str_replace(['<@php', '{dbgroup}'], ['getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\model.tpl.php'); - return $template; + return str_replace(['<@php', '{dbgroup}'], ['params) || CLI::getOption('entity'); - $entity = array_key_exists('entity', $this->params) || CLI::getOption('entity'); if (! $entity) { $entity = 'array'; // default to array return @@ -128,15 +143,17 @@ protected function setReplacements(string $template, string $class): string else { $entity = str_replace('\\Models', '\\Entities', $class); + if ($pos = strripos($entity, 'Model')) { // Strip 'Model' from name $entity = substr($entity, 0, $pos); } } + $template = str_replace('{return}', $entity, $template); + $table = $this->params['table'] ?? CLI::getOption('table'); - $table = $this->params['table'] ?? CLI::getOption('table'); if (! is_string($table)) { $table = str_replace($this->getNamespace($class) . '\\', '', $class); diff --git a/system/Commands/Generators/CreateScaffold.php b/system/Commands/Generators/CreateScaffold.php index 93f5ba06387b..98f66040bf06 100644 --- a/system/Commands/Generators/CreateScaffold.php +++ b/system/Commands/Generators/CreateScaffold.php @@ -82,15 +82,23 @@ class CreateScaffold extends BaseCommand 'name' => 'The class name', ]; + /** + * The Command's options. + * + * @var array + */ protected $options = [ - '-bare' => 'Add the \'-bare\' option to controller scaffold.', - '-restful' => 'Add the \'-restful\' option to controller scaffold.', - '-dbgroup' => 'Add the \'-dbgroup\' option to model scaffold.', - '-table' => 'Add the \'-table\' option to the model scaffold.', - '-n' => 'Set root namespace. Defaults to APP_NAMESPACE.', - '-force' => 'Force overwrite existing files.', + '--bare' => 'Add the \'-bare\' option to controller scaffold.', + '--restful' => 'Add the \'-restful\' option to controller scaffold.', + '--dbgroup' => 'Add the \'-dbgroup\' option to model scaffold.', + '--table' => 'Add the \'-table\' option to the model scaffold.', + '-n' => 'Set root namespace. Defaults to APP_NAMESPACE.', + '--force' => 'Force overwrite existing files.', ]; + /** + * {@inheritDoc} + */ public function run(array $params) { // Resolve options @@ -107,12 +115,14 @@ public function run(array $params) // Sets additional options $genOptions = ['n' => $namespace]; + if ($force) { $genOptions['force'] = null; } $controllerOpts = []; + if ($bare) { $controllerOpts['bare'] = null; @@ -129,11 +139,11 @@ public function run(array $params) ]; // Call those commands! - $class = $params[0]; + $class = $params[0] ?? CLI::getSegment(2); $this->call('make:controller', array_merge([$class], $controllerOpts, $genOptions)); $this->call('make:model', array_merge([$class], $modelOpts, $genOptions)); $this->call('make:entity', array_merge([$class], $genOptions)); - $this->call('migrate:create', array_merge([$class], $genOptions)); + $this->call('make:migration', array_merge([$class], $genOptions)); $this->call('make:seeder', array_merge([$class], $genOptions)); } } diff --git a/system/Commands/Generators/CreateSeeder.php b/system/Commands/Generators/CreateSeeder.php index a636d212a9dd..fe734b2920cf 100644 --- a/system/Commands/Generators/CreateSeeder.php +++ b/system/Commands/Generators/CreateSeeder.php @@ -87,11 +87,10 @@ class CreateSeeder extends GeneratorCommand protected function getClassName(): string { $class = parent::getClassName(); + if (empty($class)) { - // @codeCoverageIgnoreStart - $class = CLI::prompt(lang('Migrations.nameSeeder'), null, 'required'); - // @codeCoverageIgnoreEnd + $class = CLI::prompt(lang('Migrations.nameSeeder'), null, 'required'); // @codeCoverageIgnore } return $class; @@ -117,21 +116,8 @@ protected function getNamespacedClass(string $rootNamespace, string $class): str */ protected function getTemplate(): string { - return <<getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\seed.tpl.php'); -EOD; + return str_replace('<@php', ' 'Set table name', ]; + /** + * {@inheritDoc} + */ protected function getClassName(): string { $tableName = $this->params['t'] ?? CLI::getOption('t') ?? 'ci_sessions'; + return "Migration_create_{$tableName}_table"; } + /** + * {@inheritDoc} + */ protected function getNamespacedClass(string $rootNamespace, string $class): string { return $rootNamespace . '\\Database\\Migrations\\' . $class; } + /** + * {@inheritDoc} + */ protected function modifyBasename(string $filename): string { return str_replace('Migration', gmdate(config('Migrations')->timestampFormat), $filename); } + /** + * {@inheritDoc} + */ protected function getTemplate(): string { $data = [ @@ -104,7 +117,8 @@ protected function getTemplate(): string 'matchIP' => config('App')->sessionMatchIP ?? false, ]; - $template = view('\\CodeIgniter\\Commands\\Generators\\Views\\migration.tpl.php', $data, ['debug' => false]); - return str_replace('@php', '?php', $template); + $template = $this->getGeneratorViewFile('CodeIgniter\\Commands\\Generators\\Views\\session_migration.tpl.php', $data); + + return str_replace('<@php', ' [options]'; + + /** + * The Command's arguments. + * + * @var array + */ + protected $arguments = [ + 'name' => 'The migration file name.', + ]; + + /** + * The Command's options. + * + * @var array + */ + protected $options = [ + '-n' => 'Set root namespace. Defaults to APP_NAMESPACE', + '--force' => 'Force overwrite existing files.', + ]; + + /** + * Actually execute a command. + * + * @param array $params + */ + public function run(array $params) + { + // Resolve arguments before passing to make:migration + $params[0] = $params[0] ?? CLI::getSegment(2); + $params['n'] = $params['n'] ?? CLI::getOption('n') ?? APP_NAMESPACE; + + if (array_key_exists('force', $params) || CLI::getOption('force')) + { + $params['force'] = null; + } + + $this->call('make:migration', $params); + } +} diff --git a/system/Commands/Generators/Views/migration.tpl.php b/system/Commands/Generators/Views/migration.tpl.php index 9bcef3dbc552..db0741a56b75 100644 --- a/system/Commands/Generators/Views/migration.tpl.php +++ b/system/Commands/Generators/Views/migration.tpl.php @@ -6,49 +6,13 @@ class {class} extends Migration { - - protected $DBGroup = ''; - - public function up() { - $this->forge->addField([ - 'id' => [ - 'type' => 'VARCHAR', - 'constraint' => 128, - 'null' => false, - ], - 'ip_address' => [ - 'type' => 'VARCHAR', - 'constraint' => 45, - 'null' => false, - ], - 'timestamp' => [ - 'type' => 'INT', - 'constraint' => 10, - 'unsigned' => true, - 'null' => false, - 'default' => 0, - ], - 'data' => [ - 'type' => 'TEXT', - 'null' => false, - 'default' => '', - ], - ]); - - $this->forge->addKey(['id', 'ip_address'], true); - - $this->forge->addKey('id', true); - - $this->forge->addKey('timestamp'); - $this->forge->createTable('', true); + // } - //-------------------------------------------------------------------- - public function down() { - $this->forge->dropTable('', true); + // } } diff --git a/system/Commands/Generators/Views/seed.tpl.php b/system/Commands/Generators/Views/seed.tpl.php new file mode 100644 index 000000000000..bf80c7a7ad9d --- /dev/null +++ b/system/Commands/Generators/Views/seed.tpl.php @@ -0,0 +1,13 @@ +<@php + +namespace {namespace}; + +use CodeIgniter\Database\Seeder; + +class {class} extends Seeder +{ + public function run() + { + // + } +} diff --git a/system/Commands/Generators/Views/session_migration.tpl.php b/system/Commands/Generators/Views/session_migration.tpl.php new file mode 100644 index 000000000000..9bcef3dbc552 --- /dev/null +++ b/system/Commands/Generators/Views/session_migration.tpl.php @@ -0,0 +1,54 @@ +<@php + +namespace {namespace}; + +use CodeIgniter\Database\Migration; + +class {class} extends Migration +{ + + protected $DBGroup = ''; + + + public function up() + { + $this->forge->addField([ + 'id' => [ + 'type' => 'VARCHAR', + 'constraint' => 128, + 'null' => false, + ], + 'ip_address' => [ + 'type' => 'VARCHAR', + 'constraint' => 45, + 'null' => false, + ], + 'timestamp' => [ + 'type' => 'INT', + 'constraint' => 10, + 'unsigned' => true, + 'null' => false, + 'default' => 0, + ], + 'data' => [ + 'type' => 'TEXT', + 'null' => false, + 'default' => '', + ], + ]); + + $this->forge->addKey(['id', 'ip_address'], true); + + $this->forge->addKey('id', true); + + $this->forge->addKey('timestamp'); + $this->forge->createTable('', true); + } + + //-------------------------------------------------------------------- + + public function down() + { + $this->forge->dropTable('', true); + } +} diff --git a/system/Commands/Housekeeping/ClearLogs.php b/system/Commands/Housekeeping/ClearLogs.php index 4e0c3009ec01..7fc526c86a98 100644 --- a/system/Commands/Housekeeping/ClearLogs.php +++ b/system/Commands/Housekeeping/ClearLogs.php @@ -82,7 +82,7 @@ class ClearLogs extends BaseCommand * @var array */ protected $options = [ - '-force' => 'Force delete of all logs files without prompting.', + '--force' => 'Force delete of all logs files without prompting.', ]; /** diff --git a/system/Commands/Server/Serve.php b/system/Commands/Server/Serve.php index f08362afaabb..120905497349 100644 --- a/system/Commands/Server/Serve.php +++ b/system/Commands/Server/Serve.php @@ -115,9 +115,9 @@ class Serve extends BaseCommand * @var array */ protected $options = [ - '-php' => 'The PHP Binary [default: "PHP_BINARY"]', - '-host' => 'The HTTP Host [default: "localhost"]', - '-port' => 'The HTTP Host Port [default: "8080"]', + '--php' => 'The PHP Binary [default: "PHP_BINARY"]', + '--host' => 'The HTTP Host [default: "localhost"]', + '--port' => 'The HTTP Host Port [default: "8080"]', ]; /** @@ -148,7 +148,7 @@ public function run(array $params) CLI::write('Press Control-C to stop.'); // Set the Front Controller path as Document Root. - $docroot = escapeshellarg(FCPATH); // @phpstan-ignore-line + $docroot = escapeshellarg(FCPATH); // Mimic Apache's mod_rewrite functionality with user settings. $rewrite = escapeshellarg(__DIR__ . '/rewrite.php'); diff --git a/system/Common.php b/system/Common.php index f15453751627..b145db336e58 100644 --- a/system/Common.php +++ b/system/Common.php @@ -38,7 +38,10 @@ */ use CodeIgniter\Config\Config; +use CodeIgniter\Config\Factories; +use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; +use CodeIgniter\Database\ModelFactory; use CodeIgniter\Files\Exceptions\FileNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; @@ -95,7 +98,7 @@ function app_timezone(): string * * @param string|null $key * - * @return \CodeIgniter\Cache\CacheInterface|mixed + * @return CacheInterface|mixed */ function cache(string $key = null) { @@ -134,8 +137,7 @@ function clean_path(string $path): string return 'APPPATH' . DIRECTORY_SEPARATOR . substr($path, strlen(APPPATH)); case strpos($path, SYSTEMPATH) === 0: return 'SYSTEMPATH' . DIRECTORY_SEPARATOR . substr($path, strlen(SYSTEMPATH)); - case strpos($path, FCPATH) === 0: // @phpstan-ignore-line - // @phpstan-ignore-next-line + case strpos($path, FCPATH) === 0: return 'FCPATH' . DIRECTORY_SEPARATOR . substr($path, strlen(FCPATH)); case defined('VENDORPATH') && strpos($path, VENDORPATH) === 0: return 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($path, strlen(VENDORPATH)); @@ -237,16 +239,15 @@ function command(string $command) ob_start(); $runner->run($command, $params); - $output = ob_get_clean(); - return $output; + return ob_get_clean(); } } if (! function_exists('config')) { /** - * More simple way of getting config instances + * More simple way of getting config instances from Factories * * @param string $name * @param boolean $getShared @@ -255,7 +256,7 @@ function command(string $command) */ function config(string $name, bool $getShared = true) { - return Config::get($name, $getShared); + return Factories::config($name, $getShared); } } @@ -356,10 +357,10 @@ function csrf_meta(string $id = null): string * If $getShared === false then a new connection instance will be provided, * otherwise it will all calls will return the same instance. * - * @param \CodeIgniter\Database\ConnectionInterface|array|string $db - * @param boolean $getShared + * @param ConnectionInterface|array|string|null $db + * @param boolean $getShared * - * @return \CodeIgniter\Database\BaseConnection + * @return BaseConnection */ function db_connect($db = null, bool $getShared = true) { @@ -443,7 +444,7 @@ function env(string $key, $default = null) * @param string $encoding * * @return string|array - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ function esc($data, string $context = 'html', string $encoding = null) { @@ -467,7 +468,7 @@ function esc($data, string $context = 'html', string $encoding = null) return $data; } - if (! in_array($context, ['html', 'js', 'css', 'url', 'attr'])) + if (! in_array($context, ['html', 'js', 'css', 'url', 'attr'], true)) { throw new InvalidArgumentException('Invalid escape context provided.'); } @@ -514,7 +515,7 @@ function esc($data, string $context = 'html', string $encoding = null) * @param RequestInterface $request * @param ResponseInterface $response * - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException + * @throws HTTPException */ function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) { @@ -595,24 +596,24 @@ function force_https(int $duration = 31536000, RequestInterface $request = null, * be just temporary, but would probably be kept for a few years. * * @link http://www.hardened-php.net/suhosin/ - * @param string $function_name Function to check for + * @param string $functionName Function to check for * @return boolean TRUE if the function exists and is safe to call, * FALSE otherwise. * * @codeCoverageIgnore This is too exotic */ - function function_usable(string $function_name): bool + function function_usable(string $functionName): bool { static $_suhosin_func_blacklist; - if (function_exists($function_name)) + if (function_exists($functionName)) { if (! isset($_suhosin_func_blacklist)) { $_suhosin_func_blacklist = extension_loaded('suhosin') ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) : []; } - return ! in_array($function_name, $_suhosin_func_blacklist, true); + return ! in_array($functionName, $_suhosin_func_blacklist, true); } return false; @@ -631,10 +632,12 @@ function function_usable(string $function_name): bool * 3. system/Helpers * * @param string|array $filenames - * @throws \CodeIgniter\Files\Exceptions\FileNotFoundException + * @throws FileNotFoundException */ function helper($filenames) { + static $loaded = []; + $loader = Services::locator(true); if (! is_array($filenames)) @@ -658,6 +661,12 @@ function helper($filenames) $filename .= '_helper'; } + // Check if this helper has already been loaded + if (in_array($filename, $loaded)) + { + continue; + } + // If the file is namespaced, we'll just grab that // file and not search for any others if (strpos($filename, '\\') !== false) @@ -670,6 +679,7 @@ function helper($filenames) } $includes[] = $path; + $loaded[] = $filename; } // No namespaces, so search in all available locations @@ -694,6 +704,7 @@ function helper($filenames) else { $localIncludes[] = $path; + $loaded[] = $filename; } } } @@ -703,6 +714,7 @@ function helper($filenames) { // @codeCoverageIgnoreStart $includes[] = $appHelper; + $loaded[] = $filename; // @codeCoverageIgnoreEnd } @@ -713,6 +725,7 @@ function helper($filenames) if (! empty($systemHelper)) { $includes[] = $systemHelper; + $loaded[] = $filename; } } } @@ -758,7 +771,7 @@ function is_cli(): bool * * @return boolean * - * @throws \Exception + * @throws Exception * @codeCoverageIgnore Not practical to test, as travis runs on linux */ function is_really_writable(string $file): bool @@ -786,7 +799,8 @@ function is_really_writable(string $file): bool return true; } - elseif (! is_file($file) || ( $fp = @fopen($file, 'ab')) === false) + + if (! is_file($file) || ( $fp = @fopen($file, 'ab')) === false) { return false; } @@ -860,7 +874,7 @@ function log_message(string $level, string $message, array $context = []) if (! function_exists('model')) { /** - * More simple way of getting model instances + * More simple way of getting model instances from Factories * * @param string $name * @param boolean $getShared @@ -870,7 +884,7 @@ function log_message(string $level, string $message, array $context = []) */ function model(string $name, bool $getShared = true, ConnectionInterface &$conn = null) { - return \CodeIgniter\Database\ModelFactory::get($name, $getShared, $conn); + return Factories::models($name, $getShared, $conn); } } @@ -933,7 +947,7 @@ function old(string $key, $default = null, $escape = 'html') * * @param string $uri * - * @return \CodeIgniter\HTTP\RedirectResponse + * @return RedirectResponse */ function redirect(string $uri = null): RedirectResponse { @@ -996,7 +1010,7 @@ function remove_invisible_characters(string $str, bool $urlEncoded = true): stri * have a route defined in the routes Config file. * * @param string $method - * @param array ...$params + * @param mixed ...$params * * @return false|string */ @@ -1018,7 +1032,7 @@ function route_to(string $method, ...$params) * * @param string $val * - * @return \CodeIgniter\Session\Session|mixed|null + * @return Session|mixed|null */ function session(string $val = null) { @@ -1150,7 +1164,7 @@ function stringify_attributes($attributes, bool $js = false): string * * @param string|null $name * - * @return \CodeIgniter\Debug\Timer|mixed + * @return Timer|mixed */ function timer(string $name = null) { @@ -1231,7 +1245,7 @@ function view(string $name, array $data = [], array $options = []): string * @param string|null $cacheName * * @return string - * @throws \ReflectionException + * @throws ReflectionException */ function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null): string { diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php index a7f711eabfd7..fc46b5bd74f3 100644 --- a/system/ComposerScripts.php +++ b/system/ComposerScripts.php @@ -40,6 +40,7 @@ namespace CodeIgniter; use ReflectionClass; +use ReflectionException; /** * ComposerScripts @@ -66,7 +67,7 @@ class ComposerScripts * the bare-minimum required files for our dependencies * to appropriate locations. * - * @throws \ReflectionException + * @throws ReflectionException */ public static function postUpdate() { @@ -111,7 +112,7 @@ protected static function moveFile(string $source, string $destination): bool * @param string $class * * @return string - * @throws \ReflectionException + * @throws ReflectionException */ protected static function getClassFilePath(string $class) { @@ -178,7 +179,7 @@ protected static function copyDir($source, $dest) * Moves the Laminas Escaper files into our base repo so that it's * available for packaged releases where the users don't user Composer. * - * @throws \ReflectionException + * @throws ReflectionException */ public static function moveEscaper() { diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index 4c0786f5c7a9..611739992bb8 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -40,13 +40,43 @@ namespace CodeIgniter\Config; /** - * AUTO-LOADER + * AUTOLOADER * * This file defines the namespaces and class maps so the Autoloader * can find the files as needed. */ class AutoloadConfig { + /** + * ------------------------------------------------------------------- + * Namespaces + * ------------------------------------------------------------------- + * This maps the locations of any namespaces in your application to + * their location on the file system. These are used by the autoloader + * to locate files the first time they have been instantiated. + * + * The '/app' and '/system' directories are already mapped for you. + * you may change the name of the 'App' namespace if you wish, + * but this should be done prior to creating any namespaced classes, + * else you will need to modify all of those classes for this to work. + * + * @var array + */ + public $psr4 = []; + + /** + * ------------------------------------------------------------------- + * Class Map + * ------------------------------------------------------------------- + * The class map provides a map of class names and their exact + * location on the drive. Classes loaded in this manner will have + * slightly faster performance because they will not have to be + * searched for within one or more directories as they would if they + * were being autoloaded through a namespace. + * + * @var array + */ + public $classmap = []; /** * ------------------------------------------------------------------- @@ -102,7 +132,7 @@ public function __construct() { if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing') { - $this->psr4['Tests\Support'] = SUPPORTPATH; // @phpstan-ignore-line + $this->psr4['Tests\Support'] = SUPPORTPATH; $this->classmap['CodeIgniter\Log\TestLogger'] = SYSTEMPATH . 'Test/TestLogger.php'; $this->classmap['CIDatabaseTestCase'] = SYSTEMPATH . 'Test/CIDatabaseTestCase.php'; } diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php index df26d1a36033..a4c781859336 100644 --- a/system/Config/BaseConfig.php +++ b/system/Config/BaseConfig.php @@ -39,6 +39,12 @@ namespace CodeIgniter\Config; +use Config\Modules; +use Config\Services; +use ReflectionClass; +use ReflectionException; +use RuntimeException; + /** * Class BaseConfig * @@ -69,7 +75,7 @@ class BaseConfig /** * The modules configuration. * - * @var \Config\Modules + * @var Modules */ protected static $moduleConfig; @@ -107,13 +113,7 @@ public function __construct() } } - if (defined('ENVIRONMENT') && ENVIRONMENT !== 'testing') - { - // well, this won't happen during unit testing - // @codeCoverageIgnoreStart - $this->registerProperties(); - // @codeCoverageIgnoreEnd - } + $this->registerProperties(); } //-------------------------------------------------------------------- @@ -195,7 +195,7 @@ protected function getEnvValue(string $property, string $prefix, string $shortPr * Provides external libraries a simple way to register one or more * options into a config file. * - * @throws \ReflectionException + * @throws ReflectionException */ protected function registerProperties() { @@ -206,7 +206,7 @@ protected function registerProperties() if (! static::$didDiscovery) { - $locator = \Config\Services::locator(); + $locator = Services::locator(); $registrarsFiles = $locator->search('Config/Registrar.php'); foreach ($registrarsFiles as $file) @@ -218,7 +218,7 @@ protected function registerProperties() static::$didDiscovery = true; } - $shortName = (new \ReflectionClass($this))->getShortName(); + $shortName = (new ReflectionClass($this))->getShortName(); // Check the registrar class for a method named after this class' shortName foreach (static::$registrars as $callable) @@ -235,7 +235,7 @@ protected function registerProperties() if (! is_array($properties)) { - throw new \RuntimeException('Registrars must return an array of properties and their values.'); + throw new RuntimeException('Registrars must return an array of properties and their values.'); } foreach ($properties as $property => $value) diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index 3ea86e32a8e7..19b22c7efb0e 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -134,7 +134,7 @@ protected static function getSharedInstance(string $key, ...$params) * * @param boolean $getShared * - * @return \CodeIgniter\Autoloader\Autoloader + * @return Autoloader */ public static function autoloader(bool $getShared = true) { @@ -160,7 +160,7 @@ public static function autoloader(bool $getShared = true) * * @param boolean $getShared * - * @return \CodeIgniter\Autoloader\FileLocator + * @return FileLocator */ public static function locator(bool $getShared = true) { @@ -171,7 +171,7 @@ public static function locator(bool $getShared = true) static::$instances['locator'] = new FileLocator(static::autoloader()); } - return static::$instances['locator']; + return static::$mocks['locator'] ?? static::$instances['locator']; } return new FileLocator(static::autoloader()); @@ -205,15 +205,15 @@ public static function __callStatic(string $name, array $arguments) /** * Reset shared instances and mocks for testing. * - * @param boolean $init_autoloader Initializes autoloader instance + * @param boolean $initAutoloader Initializes autoloader instance */ - public static function reset(bool $init_autoloader = false) + public static function reset(bool $initAutoloader = false) { static::$mocks = []; static::$instances = []; - if ($init_autoloader) + if ($initAutoloader) { static::autoloader()->initialize(new Autoload(), new Modules()); } @@ -267,7 +267,7 @@ protected static function discoverServices(string $name, array $arguments) { $classname = $locator->getClassname($file); - if (! in_array($classname, ['CodeIgniter\\Config\\Services'])) + if (! in_array($classname, ['CodeIgniter\\Config\\Services'], true)) { static::$services[] = new $classname(); } @@ -286,7 +286,7 @@ protected static function discoverServices(string $name, array $arguments) // Try to find the desired service method foreach (static::$services as $class) { - if (method_exists(get_class($class), $name)) + if (method_exists($class, $name)) { return $class::$name(...$arguments); } diff --git a/system/Config/Config.php b/system/Config/Config.php index d97862acb6ad..7416dcdb4db9 100644 --- a/system/Config/Config.php +++ b/system/Config/Config.php @@ -42,20 +42,10 @@ /** * Class Config * - * @package CodeIgniter\Config + * @deprecated Use CodeIgniter\Config\Factories::config() */ class Config { - /** - * Cache for instance of any configurations that - * have been requested as "shared" instance. - * - * @var array - */ - static private $instances = []; - - //-------------------------------------------------------------------- - /** * Create new configuration instances or return * a shared instance @@ -67,95 +57,25 @@ class Config */ public static function get(string $name, bool $getShared = true) { - $class = $name; - if (($pos = strrpos($name, '\\')) !== false) - { - $class = substr($name, $pos + 1); - } - - if (! $getShared) - { - return self::createClass($name); - } - - if (! isset( self::$instances[$class] )) - { - self::$instances[$class] = self::createClass($name); - } - return self::$instances[$class]; + return Factories::config($name, $getShared); } - //-------------------------------------------------------------------- - /** * Helper method for injecting mock instances while testing. * - * @param string $class + * @param string $name * @param object $instance */ - public static function injectMock(string $class, $instance) + public static function injectMock(string $name, $instance) { - self::$instances[$class] = $instance; + Factories::injectMock('config', $name, $instance); } - //-------------------------------------------------------------------- - /** - * Resets the instances array + * Resets the static arrays */ public static function reset() { - static::$instances = []; - } - - //-------------------------------------------------------------------- - - /** - * Find configuration class and create instance - * - * @param string $name Classname - * - * @return mixed|null - */ - private static function createClass(string $name) - { - if (class_exists($name)) - { - return new $name(); - } - - $locator = Services::locator(); - $file = $locator->locateFile($name, 'Config'); - - if (empty($file)) - { - // No file found - check if the class was namespaced - if (strpos($name, '\\') !== false) - { - // Class was namespaced and locateFile couldn't find it - return null; - } - - // Check all namespaces - $files = $locator->search('Config/' . $name); - if (empty($files)) - { - return null; - } - - // Get the first match (prioritizes user and framework) - $file = reset($files); - } - - $name = $locator->getClassname($file); - - if (empty($name)) - { - return null; - } - - return new $name(); + Factories::reset('config'); } - - //-------------------------------------------------------------------- } diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index ff7f464b4afa..433e691ad168 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Config; +use InvalidArgumentException; + /** * Environment-specific configuration */ @@ -99,7 +101,7 @@ public function parse(): ?array // Ensure the file is readable if (! is_readable($this->path)) { - throw new \InvalidArgumentException("The .env file is not readable: {$this->path}"); + throw new InvalidArgumentException("The .env file is not readable: {$this->path}"); } $vars = []; @@ -201,7 +203,7 @@ public function normaliseVariable(string $name, string $value = ''): array * @param string $value * * @return string - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function sanitizeValue(string $value): string { @@ -242,7 +244,7 @@ protected function sanitizeValue(string $value): string // Unquoted values cannot contain whitespace if (preg_match('/\s+/', $value) > 0) { - throw new \InvalidArgumentException('.env values containing spaces must be surrounded by quotes.'); + throw new InvalidArgumentException('.env values containing spaces must be surrounded by quotes.'); } } diff --git a/system/Config/Factories.php b/system/Config/Factories.php new file mode 100644 index 000000000000..6371b1252b12 --- /dev/null +++ b/system/Config/Factories.php @@ -0,0 +1,385 @@ + + */ + protected static $configs = []; + + /** + * Explicit configuration for the Config + * component to prevent logic loops. + * + * @var array + */ + private static $configValues = [ + 'component' => 'config', + 'path' => 'Config', + 'instanceOf' => null, + 'prefersApp' => true, + ]; + + /** + * Mapping of class basenames (no namespace) to + * their instances. + * + * @var array + */ + protected static $basenames = []; + + /** + * Store for instances of any component that + * has been requested as a "shared". + * A multi-dimensional array with components as + * keys to the array of name-indexed instances. + * + * @var array + */ + protected static $instances = []; + + //-------------------------------------------------------------------- + + /** + * Loads instances based on the method component name. Either + * creates a new instance or returns an existing shared instance. + * + * @param string $component + * @param array $arguments + * + * @return mixed + */ + public static function __callStatic(string $component, array $arguments) + { + // Load the component-specific configuration + $config = self::getConfig(strtolower($component)); + + // First argument is the name, second is whether to use a shared instance + $name = trim(array_shift($arguments), '\\ '); + $getShared = array_shift($arguments) ?? true; + + if (! $getShared) + { + if ($class = self::locateClass($config, $name)) + { + return new $class(...$arguments); + } + + return null; + } + + $basename = self::getBasename($name); + + // Check for an existing instance + if (isset(self::$basenames[$config['component']][$basename])) + { + $class = self::$basenames[$config['component']][$basename]; + } + else + { + // Try to locate the class + if (! $class = self::locateClass($config, $name)) + { + return null; + } + + self::$instances[$config['component']][$class] = new $class(...$arguments); + self::$basenames[$config['component']][$basename] = $class; + } + + return self::$instances[$config['component']][$class]; + } + + /** + * Finds a component class + * + * @param array $config The array of component-specific directives + * @param string $name Class name, namespace optional + * + * @return string|null + */ + protected static function locateClass(array $config, string $name): ?string + { + // Check for low-hanging fruit + if (class_exists($name, false) && self::verifyPrefersApp($config, $name) && self::verifyInstanceOf($config, $name)) + { + return $name; + } + + // Determine the relative class names we need + $basename = self::getBasename($name); + $appname = $config['component'] === 'config' + ? 'Config\\' . $basename + : rtrim(APP_NAMESPACE, '\\') . '\\' . $config['path'] . '\\' . $basename; + + // If an App version was requested see if it verifies + if ($config['prefersApp'] && class_exists($appname) && self::verifyInstanceOf($config, $name)) + { + return $appname; + } + + // If we have ruled out an App version and the class exists then try it + if (class_exists($name) && self::verifyInstanceOf($config, $name)) + { + return $name; + } + + // Have to do this the hard way... + $locator = Services::locator(); + + // Check if the class was namespaced + if (strpos($name, '\\') !== false) + { + if (! $file = $locator->locateFile($name, $config['path'])) + { + return null; + } + + $files = [$file]; + } + // No namespace? Search for it + else + { + // Check all namespaces, prioritizing App and modules + if (! $files = $locator->search($config['path'] . DIRECTORY_SEPARATOR . $name)) + { + return null; + } + } + + // Check all files for a valid class + foreach ($files as $file) + { + $class = $locator->getClassname($file); + + if ($class && self::verifyInstanceOf($config, $class)) + { + return $class; + } + } + + return null; + } + + //-------------------------------------------------------------------- + + /** + * Verifies that a class & config satisfy the "prefersApp" option + * + * @param array $config The array of component-specific directives + * @param string $name Class name, namespace optional + * + * @return boolean + */ + protected static function verifyPrefersApp(array $config, string $name): bool + { + // Anything without that restriction passes + if (! $config['prefersApp']) + { + return true; + } + + // Special case for Config since its App namespace is actually \Config + if ($config['component'] === 'config') + { + return strpos($name, 'Config') === 0; + } + + return strpos($name, APP_NAMESPACE) === 0; + } + + /** + * Verifies that a class & config satisfy the "instanceOf" option + * + * @param array $config The array of component-specific directives + * @param string $name Class name, namespace optional + * + * @return boolean + */ + protected static function verifyInstanceOf(array $config, string $name): bool + { + // Anything without that restriction passes + if (! $config['instanceOf']) + { + return true; + } + + return is_a($name, $config['instanceOf'], true); + } + + //-------------------------------------------------------------------- + + /** + * Returns the component-specific configuration + * + * @param string $component Lowercase, plural component name + * + * @return array + */ + public static function getConfig(string $component): array + { + $component = strtolower($component); + + // Check for a stored version + if (isset(self::$configs[$component])) + { + return self::$configs[$component]; + } + + // Handle Config as a special case to prevent logic loops + if ($component === 'config') + { + $values = self::$configValues; + } + // Load values from the best Factory configuration (will include Registrars) + else + { + $values = config('Factory')->$component ?? []; + } + + return self::setConfig($component, $values); + } + + /** + * Normalizes, stores, and returns the configuration for a specific component + * + * @param string $component Lowercase, plural component name + * @param array $values + * + * @return array The result after applying defaults and normalization + */ + public static function setConfig(string $component, array $values): array + { + // Allow the config to replace the component name, to support "aliases" + $values['component'] = strtolower($values['component'] ?? $component); + + // Reset this component so instances can be rediscovered with the updated config + self::reset($values['component']); + + // If no path was available then use the component + $values['path'] = trim($values['path'] ?? ucfirst($values['component']), '\\ '); + + // Add defaults for any missing values + $values = array_merge(Factory::$default, $values); + + // Store the result to the supplied name and potential alias + self::$configs[$component] = $values; + self::$configs[$values['component']] = $values; + + return $values; + } + + /** + * Resets the static arrays, optionally just for one component + * + * @param string $component Lowercase, plural component name + */ + public static function reset(string $component = null) + { + if ($component) + { + unset(static::$configs[$component]); + unset(static::$basenames[$component]); + unset(static::$instances[$component]); + + return; + } + + static::$configs = []; + static::$basenames = []; + static::$instances = []; + } + + /** + * Helper method for injecting mock instances + * + * @param string $component Lowercase, plural component name + * @param string $name The name of the instance + * @param object $instance + */ + public static function injectMock(string $component, string $name, object $instance) + { + // Force a configuration to exist for this component + $component = strtolower($component); + self::getConfig($component); + + $class = get_class($instance); + $basename = self::getBasename($name); + + self::$instances[$component][$class] = $instance; + self::$basenames[$component][$basename] = $class; + } + + /** + * Gets a basename from a class name, namespaced or not. + * + * @param string $name + * + * @return string + */ + public static function getBasename(string $name): string + { + // Determine the basename + if ($basename = strrchr($name, '\\')) + { + return substr($basename, 1); + } + + return $name; + } +} diff --git a/system/Config/Factory.php b/system/Config/Factory.php new file mode 100644 index 000000000000..28bc12b136df --- /dev/null +++ b/system/Config/Factory.php @@ -0,0 +1,75 @@ + null, + 'path' => null, + 'instanceOf' => null, + 'prefersApp' => true, + ]; + + /** + * Specifies that Models should always favor child + * classes to allow easy extension of module Models. + * + * @var array + */ + public $models = [ + 'preferApp' => true, + ]; +} diff --git a/system/Config/Services.php b/system/Config/Services.php index 615739d68bc9..eed802ce638c 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -40,6 +40,7 @@ namespace CodeIgniter\Config; use CodeIgniter\Cache\CacheFactory; +use CodeIgniter\Cache\CacheInterface; use CodeIgniter\CLI\Commands; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\MigrationRunner; @@ -51,6 +52,7 @@ use CodeIgniter\Encryption\EncrypterInterface; use CodeIgniter\Encryption\Encryption; use CodeIgniter\Filters\Filters; +use CodeIgniter\Format\Format; use CodeIgniter\Honeypot\Honeypot; use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\CURLRequest; @@ -63,6 +65,7 @@ use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\URI; use CodeIgniter\HTTP\UserAgent; +use CodeIgniter\Images\Handlers\BaseHandler; use CodeIgniter\Language\Language; use CodeIgniter\Log\Logger; use CodeIgniter\Pager\Pager; @@ -84,6 +87,7 @@ use Config\Encryption as EncryptionConfig; use Config\Exceptions as ExceptionsConfig; use Config\Filters as FiltersConfig; +use Config\Format as FormatConfig; use Config\Honeypot as HoneypotConfig; use Config\Images; use Config\Migrations; @@ -115,10 +119,10 @@ class Services extends BaseService * The cache class provides a simple way to store and retrieve * complex data for later. * - * @param \Config\Cache|null $config - * @param boolean $getShared + * @param Cache|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Cache\CacheInterface + * @return CacheInterface */ public static function cache(Cache $config = null, bool $getShared = true) { @@ -138,10 +142,10 @@ public static function cache(Cache $config = null, bool $getShared = true) * The CLI Request class provides for ways to interact with * a command line request. * - * @param \Config\App|null $config - * @param boolean $getShared + * @param App|null $config + * @param boolean $getShared * - * @return \CodeIgniter\HTTP\CLIRequest + * @return CLIRequest */ public static function clirequest(App $config = null, bool $getShared = true) { @@ -162,7 +166,7 @@ public static function clirequest(App $config = null, bool $getShared = true) * * @param boolean $getShared * - * @return \CodeIgniter\CLI\Commands + * @return Commands */ public static function commands(bool $getShared = true) { @@ -178,12 +182,12 @@ public static function commands(bool $getShared = true) * The CURL Request class acts as a simple HTTP client for interacting * with other servers, typically through APIs. * - * @param array $options - * @param \CodeIgniter\HTTP\ResponseInterface|null $response - * @param \Config\App|null $config - * @param boolean $getShared + * @param array $options + * @param ResponseInterface|null $response + * @param App|null $config + * @param boolean $getShared * - * @return \CodeIgniter\HTTP\CURLRequest + * @return CURLRequest */ public static function curlrequest(array $options = [], ResponseInterface $response = null, App $config = null, bool $getShared = true) { @@ -208,10 +212,10 @@ public static function curlrequest(array $options = [], ResponseInterface $respo /** * The Email class allows you to send email via mail, sendmail, SMTP. * - * @param \Config\Email|array|null $config - * @param boolean $getShared + * @param EmailConfig|array|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Email\Email|mixed + * @return Email|mixed */ public static function email($config = null, bool $getShared = true) { @@ -231,8 +235,8 @@ public static function email($config = null, bool $getShared = true) /** * The Encryption class provides two-way encryption. * - * @param \Config\Encryption|null $config - * @param boolean $getShared + * @param EncryptionConfig|null $config + * @param boolean $getShared * * @return EncrypterInterface Encryption handler */ @@ -258,12 +262,12 @@ public static function encrypter(EncryptionConfig $config = null, $getShared = f * - set_error_handler * - register_shutdown_function * - * @param \Config\Exceptions|null $config - * @param \CodeIgniter\HTTP\IncomingRequest|null $request - * @param \CodeIgniter\HTTP\Response|null $response - * @param boolean $getShared + * @param ExceptionsConfig|null $config + * @param IncomingRequest|null $request + * @param Response|null $response + * @param boolean $getShared * - * @return \CodeIgniter\Debug\Exceptions + * @return Exceptions */ public static function exceptions( ExceptionsConfig $config = null, @@ -292,10 +296,10 @@ public static function exceptions( * and actions taken based on the request, while after filters can * act on or modify the response itself before it is sent to the client. * - * @param \Config\Filters|null $config - * @param boolean $getShared + * @param FiltersConfig|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Filters\Filters + * @return Filters */ public static function filters(FiltersConfig $config = null, bool $getShared = true) { @@ -311,14 +315,36 @@ public static function filters(FiltersConfig $config = null, bool $getShared = t //-------------------------------------------------------------------- + /** + * The Format class is a convenient place to create Formatters. + * + * @param FormatConfig|null $config + * @param boolean $getShared + * + * @return Format + */ + public static function format(FormatConfig $config = null, bool $getShared = true) + { + if ($getShared) + { + return static::getSharedInstance('format', $config); + } + + $config = $config ?? config('Format'); + + return new Format($config); + } + + //-------------------------------------------------------------------- + /** * The Honeypot provides a secret input on forms that bots should NOT * fill in, providing an additional safeguard when accepting user input. * - * @param \Config\Honeypot|null $config - * @param boolean $getShared + * @param HoneypotConfig|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Honeypot\Honeypot + * @return Honeypot */ public static function honeypot(HoneypotConfig $config = null, bool $getShared = true) { @@ -338,11 +364,11 @@ public static function honeypot(HoneypotConfig $config = null, bool $getShared = * Acts as a factory for ImageHandler classes and returns an instance * of the handler. Used like Services::image()->withFile($path)->rotate(90)->save(); * - * @param string|null $handler - * @param \Config\Images|null $config - * @param boolean $getShared + * @param string|null $handler + * @param Images|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Images\Handlers\BaseHandler + * @return BaseHandler */ public static function image(string $handler = null, Images $config = null, bool $getShared = true) { @@ -367,7 +393,7 @@ public static function image(string $handler = null, Images $config = null, bool * * @param boolean $getShared * - * @return \CodeIgniter\Debug\Iterator + * @return Iterator */ public static function iterator(bool $getShared = true) { @@ -387,7 +413,7 @@ public static function iterator(bool $getShared = true) * @param string|null $locale * @param boolean $getShared * - * @return \CodeIgniter\Language\Language + * @return Language */ public static function language(string $locale = null, bool $getShared = true) { @@ -410,7 +436,7 @@ public static function language(string $locale = null, bool $getShared = true) * * @param boolean $getShared * - * @return \CodeIgniter\Log\Logger + * @return Logger */ public static function logger(bool $getShared = true) { @@ -427,11 +453,11 @@ public static function logger(bool $getShared = true) /** * Return the appropriate Migration runner. * - * @param \Config\Migrations|null $config - * @param \CodeIgniter\Database\ConnectionInterface|null $db - * @param boolean $getShared + * @param Migrations|null $config + * @param ConnectionInterface|null $db + * @param boolean $getShared * - * @return \CodeIgniter\Database\MigrationRunner + * @return MigrationRunner */ public static function migrations(Migrations $config = null, ConnectionInterface $db = null, bool $getShared = true) { @@ -452,10 +478,10 @@ public static function migrations(Migrations $config = null, ConnectionInterface * working the request to determine correct language, encoding, charset, * and more. * - * @param \CodeIgniter\HTTP\RequestInterface|null $request - * @param boolean $getShared + * @param RequestInterface|null $request + * @param boolean $getShared * - * @return \CodeIgniter\HTTP\Negotiate + * @return Negotiate */ public static function negotiator(RequestInterface $request = null, bool $getShared = true) { @@ -474,11 +500,11 @@ public static function negotiator(RequestInterface $request = null, bool $getSha /** * Return the appropriate pagination handler. * - * @param \Config\Pager|null $config - * @param \CodeIgniter\View\RendererInterface|null $view - * @param boolean $getShared + * @param PagerConfig|null $config + * @param RendererInterface|null $view + * @param boolean $getShared * - * @return \CodeIgniter\Pager\Pager + * @return Pager */ public static function pager(PagerConfig $config = null, RendererInterface $view = null, bool $getShared = true) { @@ -498,11 +524,11 @@ public static function pager(PagerConfig $config = null, RendererInterface $view /** * The Parser is a simple template parser. * - * @param string|null $viewPath - * @param \Config\View|null $config - * @param boolean $getShared + * @param string|null $viewPath + * @param ViewConfig|null $config + * @param boolean $getShared * - * @return \CodeIgniter\View\Parser + * @return Parser */ public static function parser(string $viewPath = null, ViewConfig $config = null, bool $getShared = true) { @@ -524,11 +550,11 @@ public static function parser(string $viewPath = null, ViewConfig $config = null * The default View class within CodeIgniter is intentionally simple, but this * service could easily be replaced by a template engine if the user needed to. * - * @param string|null $viewPath - * @param \Config\View|null $config - * @param boolean $getShared + * @param string|null $viewPath + * @param ViewConfig|null $config + * @param boolean $getShared * - * @return \CodeIgniter\View\View + * @return View */ public static function renderer(string $viewPath = null, ViewConfig $config = null, bool $getShared = true) { @@ -548,10 +574,10 @@ public static function renderer(string $viewPath = null, ViewConfig $config = nu /** * The Request class models an HTTP request. * - * @param \Config\App|null $config - * @param boolean $getShared + * @param App|null $config + * @param boolean $getShared * - * @return \CodeIgniter\HTTP\IncomingRequest + * @return IncomingRequest */ public static function request(App $config = null, bool $getShared = true) { @@ -575,10 +601,10 @@ public static function request(App $config = null, bool $getShared = true) /** * The Response class models an HTTP response. * - * @param \Config\App|null $config - * @param boolean $getShared + * @param App|null $config + * @param boolean $getShared * - * @return \CodeIgniter\HTTP\Response + * @return Response */ public static function response(App $config = null, bool $getShared = true) { @@ -597,8 +623,8 @@ public static function response(App $config = null, bool $getShared = true) /** * The Redirect class provides nice way of working with redirects. * - * @param \Config\App|null $config - * @param boolean $getShared + * @param App|null $config + * @param boolean $getShared * * @return RedirectResponse */ @@ -624,7 +650,7 @@ public static function redirectResponse(App $config = null, bool $getShared = tr * * @param boolean $getShared * - * @return \CodeIgniter\Router\RouteCollection + * @return RouteCollection */ public static function routes(bool $getShared = true) { @@ -642,11 +668,11 @@ public static function routes(bool $getShared = true) * The Router class uses a RouteCollection's array of routes, and determines * the correct Controller and Method to execute. * - * @param \CodeIgniter\Router\RouteCollectionInterface|null $routes - * @param \CodeIgniter\HTTP\Request|null $request - * @param boolean $getShared + * @param RouteCollectionInterface|null $routes + * @param Request|null $request + * @param boolean $getShared * - * @return \CodeIgniter\Router\Router + * @return Router */ public static function router(RouteCollectionInterface $routes = null, Request $request = null, bool $getShared = true) { @@ -667,10 +693,10 @@ public static function router(RouteCollectionInterface $routes = null, Request $ * The Security class provides a few handy tools for keeping the site * secure, most notably the CSRF protection tools. * - * @param \Config\App|null $config - * @param boolean $getShared + * @param App|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Security\Security + * @return Security */ public static function security(App $config = null, bool $getShared = true) { @@ -689,10 +715,10 @@ public static function security(App $config = null, bool $getShared = true) /** * Return the session manager. * - * @param \Config\App|null $config - * @param boolean $getShared + * @param App|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Session\Session + * @return Session */ public static function session(App $config = null, bool $getShared = true) { @@ -727,7 +753,7 @@ public static function session(App $config = null, bool $getShared = true) * * @param boolean $getShared * - * @return \CodeIgniter\Throttle\Throttler + * @return Throttler */ public static function throttler(bool $getShared = true) { @@ -747,7 +773,7 @@ public static function throttler(bool $getShared = true) * * @param boolean $getShared * - * @return \CodeIgniter\Debug\Timer + * @return Timer */ public static function timer(bool $getShared = true) { @@ -764,10 +790,10 @@ public static function timer(bool $getShared = true) /** * Return the debug toolbar. * - * @param \Config\Toolbar|null $config - * @param boolean $getShared + * @param ToolbarConfig|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Debug\Toolbar + * @return Toolbar */ public static function toolbar(ToolbarConfig $config = null, bool $getShared = true) { @@ -789,7 +815,7 @@ public static function toolbar(ToolbarConfig $config = null, bool $getShared = t * @param string $uri * @param boolean $getShared * - * @return \CodeIgniter\HTTP\URI + * @return URI */ public static function uri(string $uri = null, bool $getShared = true) { @@ -806,10 +832,10 @@ public static function uri(string $uri = null, bool $getShared = true) /** * The Validation class provides tools for validating input data. * - * @param \Config\Validation|null $config - * @param boolean $getShared + * @param ValidationConfig|null $config + * @param boolean $getShared * - * @return \CodeIgniter\Validation\Validation + * @return Validation */ public static function validation(ValidationConfig $config = null, bool $getShared = true) { @@ -831,7 +857,7 @@ public static function validation(ValidationConfig $config = null, bool $getShar * * @param boolean $getShared * - * @return \CodeIgniter\View\Cell + * @return Cell */ public static function viewcell(bool $getShared = true) { @@ -850,7 +876,7 @@ public static function viewcell(bool $getShared = true) * * @param boolean $getShared * - * @return \CodeIgniter\Typography\Typography + * @return Typography */ public static function typography(bool $getShared = true) { diff --git a/system/Config/View.php b/system/Config/View.php index 0de8cd40d534..5a35b50f761c 100644 --- a/system/Config/View.php +++ b/system/Config/View.php @@ -44,6 +44,22 @@ */ class View extends BaseConfig { + /** + * Parser Filters map a filter name with any PHP callable. When the + * Parser prepares a variable for display, it will chain it + * through the filters in the order defined, inserting any parameters. + * + * To prevent potential abuse, all filters MUST be defined here + * in order for them to be available for use within the Parser. + */ + public $filters = []; + + /** + * Parser Plugins provide a way to extend the functionality provided + * by the core Parser by creating aliases that will be replaced with + * any callable. Can be single or tag pair. + */ + public $plugins = []; /** * Built-in View filters. diff --git a/system/Controller.php b/system/Controller.php index 779d498a13c4..958618c51d7a 100644 --- a/system/Controller.php +++ b/system/Controller.php @@ -31,7 +31,7 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright 2019-2020 CodeIgniter Foundation - * @license https://opensource.org/licenses/MIT MIT License + * @license https://opensource.org/licenses/MIT - MIT License * @link https://codeigniter.com * @since Version 4.0.0 * @filesource @@ -39,6 +39,7 @@ namespace CodeIgniter; +use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Validation\Exceptions\ValidationException; @@ -53,17 +54,13 @@ */ class Controller { - /** - * An array of helpers to be automatically loaded - * upon class instantiation. + * Helpers that will be automatically loaded on class instantiation. * * @var array */ protected $helpers = []; - //-------------------------------------------------------------------- - /** * Instance of the main Request object. * @@ -86,31 +83,28 @@ class Controller protected $logger; /** - * Whether HTTPS access should be enforced - * for all methods in this controller. + * Should enforce HTTPS access for all methods in this controller. * - * @var integer Number of seconds to set HSTS header + * @var integer Number of seconds to set HSTS header */ protected $forceHTTPS = 0; /** - * Once validation has been run, - * will hold the Validation instance. + * Once validation has been run, will hold the Validation instance. * * @var Validation */ protected $validator; //-------------------------------------------------------------------- - /** * Constructor. * - * @param RequestInterface $request - * @param ResponseInterface $response - * @param \Psr\Log\LoggerInterface $logger + * @param RequestInterface $request + * @param ResponseInterface $response + * @param LoggerInterface $logger * - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException + * @throws HTTPException */ public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { @@ -123,7 +117,8 @@ public function initController(RequestInterface $request, ResponseInterface $res $this->forceHTTPS($this->forceHTTPS); } - $this->loadHelpers(); + // Autoload helper files. + helper($this->helpers); } //-------------------------------------------------------------------- @@ -138,7 +133,7 @@ public function initController(RequestInterface $request, ResponseInterface $res * considered secure for. Only with HSTS header. * Default value is 1 year. * - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException + * @throws HTTPException */ protected function forceHTTPS(int $duration = 31536000) { @@ -148,8 +143,8 @@ protected function forceHTTPS(int $duration = 31536000) //-------------------------------------------------------------------- /** - * Provides a simple way to tie into the main CodeIgniter class - * and tell it how long to cache the current page for. + * Provides a simple way to tie into the main CodeIgniter class and + * tell it how long to cache the current page for. * * @param integer $time */ @@ -162,6 +157,8 @@ protected function cachePage(int $time) /** * Handles "auto-loading" helper files. + * + * @deprecated Use `helper` function instead of using this method. */ protected function loadHelpers() { @@ -170,10 +167,7 @@ protected function loadHelpers() return; } - foreach ($this->helpers as $helper) - { - helper($helper); - } + helper($this->helpers); } //-------------------------------------------------------------------- @@ -213,11 +207,6 @@ protected function validate($rules, array $messages = []): bool $rules = $validation->$rules; } - return $this->validator - ->withRequest($this->request) - ->setRules($rules, $messages) - ->run(); + return $this->validator->withRequest($this->request)->setRules($rules, $messages)->run(); } - - //-------------------------------------------------------------------- } diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 5e79b0b9b0c3..4a9191454a87 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -42,6 +42,7 @@ use Closure; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Exceptions\DataException; +use InvalidArgumentException; /** * Class BaseBuilder @@ -271,9 +272,9 @@ class BaseBuilder /** * Constructor * - * @param string|array $tableName - * @param \CodeIgniter\Database\ConnectionInterface $db - * @param array $options + * @param string|array $tableName + * @param ConnectionInterface $db + * @param array $options * @throws DatabaseException */ public function __construct($tableName, ConnectionInterface &$db, array $options = null) @@ -301,6 +302,18 @@ public function __construct($tableName, ConnectionInterface &$db, array $options //-------------------------------------------------------------------- + /** + * Returns the current database connection + * + * @return ConnectionInterface + */ + public function db(): ConnectionInterface + { + return $this->db; + } + + //-------------------------------------------------------------------- + /** * Sets a test mode status. * @@ -496,8 +509,8 @@ public function selectCount(string $select = '', string $alias = '') * @param string $type * * @return BaseBuilder - * @throws \CodeIgniter\Database\Exceptions\DataException - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DataException + * @throws DatabaseException */ protected function maxMinAvgSum(string $select = '', string $alias = '', string $type = 'MAX') { @@ -513,7 +526,7 @@ protected function maxMinAvgSum(string $select = '', string $alias = '', string $type = strtoupper($type); - if (! in_array($type, ['MAX', 'MIN', 'AVG', 'SUM', 'COUNT'])) + if (! in_array($type, ['MAX', 'MIN', 'AVG', 'SUM', 'COUNT'], true)) { throw new DatabaseException('Invalid function type: ' . $type); } @@ -754,7 +767,7 @@ public function orWhere($key, $value = null, bool $escape = null) * @used-by having() * @used-by orHaving() * - * @param string $qb_key 'QBWhere' or 'QBHaving' + * @param string $qbKey 'QBWhere' or 'QBHaving' * @param mixed $key * @param mixed $value * @param string $type @@ -762,7 +775,7 @@ public function orWhere($key, $value = null, bool $escape = null) * * @return BaseBuilder */ - protected function whereHaving(string $qb_key, $key, $value = null, string $type = 'AND ', bool $escape = null) + protected function whereHaving(string $qbKey, $key, $value = null, string $type = 'AND ', bool $escape = null) { if (! is_array($key)) { @@ -774,7 +787,7 @@ protected function whereHaving(string $qb_key, $key, $value = null, string $type foreach ($key as $k => $v) { - $prefix = empty($this->$qb_key) ? $this->groupGetType('') : $this->groupGetType($type); + $prefix = empty($this->$qbKey) ? $this->groupGetType('') : $this->groupGetType($type); if ($v !== null) { @@ -815,7 +828,7 @@ protected function whereHaving(string $qb_key, $key, $value = null, string $type $v = " :$bind:"; } } - elseif (! $this->hasOperator($k) && $qb_key !== 'QBHaving') + elseif (! $this->hasOperator($k) && $qbKey !== 'QBHaving') { // value appears not to have been set, assign the test to IS NULL $k .= ' IS NULL'; @@ -825,7 +838,7 @@ protected function whereHaving(string $qb_key, $key, $value = null, string $type $k = substr($k, 0, $match[0][1]) . ($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL'); } - $this->{$qb_key}[] = [ + $this->{$qbKey}[] = [ 'condition' => $prefix . $k . $v, 'escape' => $escape, ]; @@ -1002,7 +1015,7 @@ public function orHavingNotIn(string $key = null, $values = null, bool $escape = * @param string $type * @param boolean $escape * @param string $clause (Internal use only) - * @throws \InvalidArgumentException + * @throws InvalidArgumentException * * @return BaseBuilder */ @@ -1012,7 +1025,7 @@ protected function _whereIn(string $key = null, $values = null, bool $not = fals { if (CI_DEBUG) { - throw new \InvalidArgumentException(sprintf('%s() expects $key to be a non-empty string', debug_backtrace(0, 2)[1]['function'])); + throw new InvalidArgumentException(sprintf('%s() expects $key to be a non-empty string', debug_backtrace(0, 2)[1]['function'])); } // @codeCoverageIgnoreStart return $this; @@ -1023,7 +1036,7 @@ protected function _whereIn(string $key = null, $values = null, bool $not = fals { if (CI_DEBUG) { - throw new \InvalidArgumentException(sprintf('%s() expects $values to be of type array or closure', debug_backtrace(0, 2)[1]['function'])); + throw new InvalidArgumentException(sprintf('%s() expects $values to be of type array or closure', debug_backtrace(0, 2)[1]['function'])); } // @codeCoverageIgnoreStart return $this; @@ -1291,16 +1304,16 @@ protected function _like($field, string $match = '', string $type = 'AND ', stri $bind = $this->setBind($k, "%$v%", $escape); } - $like_statement = $this->_like_statement($prefix, $k, $not, $bind, $insensitiveSearch); + $likeStatement = $this->_like_statement($prefix, $k, $not, $bind, $insensitiveSearch); // some platforms require an escape sequence definition for LIKE wildcards if ($escape === true && $this->db->likeEscapeStr !== '') { - $like_statement .= sprintf($this->db->likeEscapeStr, $this->db->likeEscapeChar); + $likeStatement .= sprintf($this->db->likeEscapeStr, $this->db->likeEscapeChar); } $this->{$clause}[] = [ - 'condition' => $like_statement, + 'condition' => $likeStatement, 'escape' => $escape, ]; } @@ -1323,14 +1336,14 @@ protected function _like($field, string $match = '', string $type = 'AND ', stri */ protected function _like_statement(string $prefix = null, string $column, string $not = null, string $bind, bool $insensitiveSearch = false): string { - $like_statement = "{$prefix} {$column} {$not} LIKE :{$bind}:"; + $likeStatement = "{$prefix} {$column} {$not} LIKE :{$bind}:"; if ($insensitiveSearch === true) { - $like_statement = "{$prefix} LOWER({$column}) {$not} LIKE :{$bind}:"; + $likeStatement = "{$prefix} LOWER({$column}) {$not} LIKE :{$bind}:"; } - return $like_statement; + return $likeStatement; } //-------------------------------------------------------------------- @@ -1636,7 +1649,7 @@ public function orderBy(string $orderBy, string $direction = '', bool $escape = if ($escape === false) { - $qb_orderBy[] = [ + $qbOrderBy[] = [ 'field' => $orderBy, 'direction' => $direction, 'escape' => false, @@ -1644,10 +1657,10 @@ public function orderBy(string $orderBy, string $direction = '', bool $escape = } else { - $qb_orderBy = []; + $qbOrderBy = []; foreach (explode(',', $orderBy) as $field) { - $qb_orderBy[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) + $qbOrderBy[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) ? [ 'field' => ltrim(substr($field, 0, $match[0][1])), @@ -1663,7 +1676,7 @@ public function orderBy(string $orderBy, string $direction = '', bool $escape = } } - $this->QBOrderBy = array_merge($this->QBOrderBy, $qb_orderBy); + $this->QBOrderBy = array_merge($this->QBOrderBy, $qbOrderBy); return $this; } @@ -1938,11 +1951,19 @@ public function countAllResults(bool $reset = true) $limit = $this->QBLimit; $this->QBLimit = false; - $sql = ($this->QBDistinct === true || ! empty($this->QBGroupBy)) - ? - $this->countString . $this->db->protectIdentifiers('numrows') . "\nFROM (\n" . $this->compileSelect() . "\n) CI_count_all_results" - : - $this->compileSelect($this->countString . $this->db->protectIdentifiers('numrows')); + if ($this->QBDistinct === true || ! empty($this->QBGroupBy)) + { + // We need to backup the original SELECT in case DBPrefix is used + $select = $this->QBSelect; + $sql = $this->countString . $this->db->protectIdentifiers('numrows') . "\nFROM (\n" . $this->compileSelect() . "\n) CI_count_all_results"; + // Restore SELECT part + $this->QBSelect = $select; + unset($select); + } + else + { + $sql = $this->compileSelect($this->countString . $this->db->protectIdentifiers('numrows')); + } if ($this->testMode) { @@ -1977,6 +1998,7 @@ public function countAllResults(bool $reset = true) } //-------------------------------------------------------------------- + /** * Get compiled 'where' condition string * @@ -2077,19 +2099,19 @@ public function insertBatch(array $set = null, bool $escape = null, int $batchSi $table = $this->QBFrom[0]; // Batch this baby - $affected_rows = 0; + $affectedRows = 0; for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batchSize) { $sql = $this->_insertBatch($this->db->protectIdentifiers($table, true, $escape, false), $this->QBKeys, array_slice($this->QBSet, $i, $batchSize)); if ($this->testMode) { - ++ $affected_rows; + ++ $affectedRows; } else { $this->db->query($sql, $this->binds, false); - $affected_rows += $this->db->affectedRows(); + $affectedRows += $this->db->affectedRows(); } } @@ -2098,7 +2120,7 @@ public function insertBatch(array $set = null, bool $escape = null, int $batchSi $this->resetWrite(); } - return $affected_rows; + return $affectedRows; } //-------------------------------------------------------------------- @@ -2506,7 +2528,7 @@ protected function _update(string $table, array $values): string * chosen to be update. * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ protected function validateUpdate(): bool { @@ -2536,7 +2558,7 @@ protected function validateUpdate(): bool * @param integer $batchSize The size of the batch to run * * @return mixed Number of rows affected, SQL string, or FALSE on failure - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function updateBatch(array $set = null, string $index = null, int $batchSize = 100) { @@ -2583,9 +2605,9 @@ public function updateBatch(array $set = null, string $index = null, int $batchS $table = $this->QBFrom[0]; // Batch this baby - $affected_rows = 0; - $savedSQL = []; - $savedQBWhere = $this->QBWhere; + $affectedRows = 0; + $savedSQL = []; + $savedQBWhere = $this->QBWhere; for ($i = 0, $total = count($this->QBSet); $i < $total; $i += $batchSize) { $sql = $this->_updateBatch($table, array_slice($this->QBSet, $i, $batchSize), $this->db->protectIdentifiers($index) @@ -2598,7 +2620,7 @@ public function updateBatch(array $set = null, string $index = null, int $batchS else { $this->db->query($sql, $this->binds, false); - $affected_rows += $this->db->affectedRows(); + $affectedRows += $this->db->affectedRows(); } $this->QBWhere = $savedQBWhere; @@ -2606,7 +2628,7 @@ public function updateBatch(array $set = null, string $index = null, int $batchS $this->resetWrite(); - return $this->testMode ? $savedSQL : $affected_rows; + return $this->testMode ? $savedSQL : $affectedRows; } //-------------------------------------------------------------------- @@ -2663,7 +2685,7 @@ protected function _updateBatch(string $table, array $values, string $index): st * @param boolean $escape * * @return BaseBuilder|null - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function setUpdateBatch($key, string $index = '', bool $escape = null) { @@ -2678,13 +2700,13 @@ public function setUpdateBatch($key, string $index = '', bool $escape = null) foreach ($key as $v) { - $index_set = false; - $clean = []; + $indexSet = false; + $clean = []; foreach ($v as $k2 => $v2) { if ($k2 === $index) { - $index_set = true; + $indexSet = true; } $bind = $this->setBind($k2, $v2, $escape); @@ -2692,7 +2714,7 @@ public function setUpdateBatch($key, string $index = '', bool $escape = null) $clean[$this->db->protectIdentifiers($k2, false, $escape)] = ":$bind:"; } - if ($index_set === false) + if ($indexSet === false) { throw new DatabaseException('One or more rows submitted for batch updating is missing the specified index.'); } @@ -2801,14 +2823,14 @@ public function getCompiledDelete(bool $reset = true): string * * Compiles a delete string and runs the query * - * @param mixed $where The where clause - * @param integer $limit The limit clause - * @param boolean $reset_data + * @param mixed $where The where clause + * @param integer $limit The limit clause + * @param boolean $resetData * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ - public function delete($where = '', int $limit = null, bool $reset_data = true) + public function delete($where = '', int $limit = null, bool $resetData = true) { $table = $this->db->protectIdentifiers($this->QBFrom[0], true, null, false); @@ -2845,7 +2867,7 @@ public function delete($where = '', int $limit = null, bool $reset_data = true) $sql = $this->_limit($sql, true); } - if ($reset_data) + if ($resetData) { $this->resetWrite(); } @@ -2958,16 +2980,16 @@ protected function trackAliases($table) * Generates a query string based on which functions were used. * Should not be called directly. * - * @param mixed $select_override + * @param mixed $selectOverride * * @return string */ - protected function compileSelect($select_override = false): string + protected function compileSelect($selectOverride = false): string { // Write the "select" portion of the query - if ($select_override !== false) + if ($selectOverride !== false) { - $sql = $select_override; + $sql = $selectOverride; } else { @@ -2984,8 +3006,8 @@ protected function compileSelect($select_override = false): string // is because until the user calls the from() function we don't know if there are aliases foreach ($this->QBSelect as $key => $val) { - $no_escape = $this->QBNoEscape[$key] ?? null; - $this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $no_escape); + $noEscape = $this->QBNoEscape[$key] ?? null; + $this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $noEscape); } $sql .= implode(', ', $this->QBSelect); @@ -3054,22 +3076,23 @@ protected function compileIgnore(string $statement) * where(), orWhere(), having(), orHaving are called prior to from(), * join() and prefixTable is added only if needed. * - * @param string $qb_key 'QBWhere' or 'QBHaving' + * @param string $qbKey 'QBWhere' or 'QBHaving' * * @return string SQL statement */ - protected function compileWhereHaving(string $qb_key): string + protected function compileWhereHaving(string $qbKey): string { - if (! empty($this->$qb_key)) + if (! empty($this->$qbKey)) { - foreach ($this->$qb_key as &$qbkey) + foreach ($this->$qbKey as &$qbkey) { // Is this condition already compiled? if (is_string($qbkey)) { continue; } - elseif ($qbkey['escape'] === false) + + if ($qbkey['escape'] === false) { $qbkey = $qbkey['condition']; continue; @@ -3120,8 +3143,8 @@ protected function compileWhereHaving(string $qb_key): string $qbkey = implode('', $conditions); } - return ($qb_key === 'QBHaving' ? "\nHAVING " : "\nWHERE ") - . implode("\n", $this->$qb_key); + return ($qbKey === 'QBHaving' ? "\nHAVING " : "\nWHERE ") + . implode("\n", $this->$qbKey); } return ''; @@ -3191,7 +3214,8 @@ protected function compileOrderBy(): string return $this->QBOrderBy = "\nORDER BY " . implode(', ', $this->QBOrderBy); } - elseif (is_string($this->QBOrderBy)) + + if (is_string($this->QBOrderBy)) { return $this->QBOrderBy; } @@ -3322,15 +3346,15 @@ public function resetQuery() /** * Resets the query builder values. Called by the get() function * - * @param array $qb_reset_items An array of fields to reset + * @param array $qbResetItems An array of fields to reset * * @return void */ - protected function resetRun(array $qb_reset_items) + protected function resetRun(array $qbResetItems) { - foreach ($qb_reset_items as $item => $default_value) + foreach ($qbResetItems as $item => $defaultValue) { - $this->$item = $default_value; + $this->$item = $defaultValue; } } diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index c30aafa1bfa2..173f652f9b3d 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -39,8 +39,10 @@ namespace CodeIgniter\Database; +use Closure; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Events\Events; +use Throwable; /** * Class BaseConnection @@ -128,20 +130,6 @@ abstract class BaseConnection implements ConnectionInterface */ protected $DBDebug = false; - /** - * Should we cache results? - * - * @var boolean - */ - protected $cacheOn = false; - - /** - * Path to store cache files. - * - * @var string - */ - protected $cacheDir; - /** * Character set * @@ -351,7 +339,7 @@ public function __construct(array $params) * Initializes the database connection/settings. * * @return mixed|void - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function initialize() { @@ -375,7 +363,7 @@ public function initialize() // Connect to the database and set the connection ID $this->connID = $this->connect($this->pConnect); } - catch (\Throwable $e) + catch (Throwable $e) { log_message('error', 'Error connecting to the database: ' . $e->getMessage()); } @@ -403,7 +391,7 @@ public function initialize() // Try to connect $this->connID = $this->connect($this->pConnect); } - catch (\Throwable $e) + catch (Throwable $e) { log_message('error', 'Error connecting to the database: ' . $e->getMessage()); } @@ -602,7 +590,7 @@ public function setAliasedTables(array $aliases) */ public function addTableAlias(string $table) { - if (! in_array($table, $this->aliasedTables)) + if (! in_array($table, $this->aliasedTables, true)) { $this->aliasedTables[] = $table; } @@ -780,17 +768,17 @@ public function transStrict(bool $mode = true) /** * Start Transaction * - * @param boolean $test_mode = FALSE + * @param boolean $testMode = FALSE * @return boolean */ - public function transStart(bool $test_mode = false): bool + public function transStart(bool $testMode = false): bool { if (! $this->transEnabled) { return false; } - return $this->transBegin($test_mode); + return $this->transBegin($testMode); } //-------------------------------------------------------------------- @@ -844,17 +832,18 @@ public function transStatus(): bool /** * Begin Transaction * - * @param boolean $test_mode + * @param boolean $testMode * @return boolean */ - public function transBegin(bool $test_mode = false): bool + public function transBegin(bool $testMode = false): bool { if (! $this->transEnabled) { return false; } + // When transactions are nested we only begin/commit/rollback the outermost ones - elseif ($this->transDepth > 0) + if ($this->transDepth > 0) { $this->transDepth ++; return true; @@ -868,7 +857,7 @@ public function transBegin(bool $test_mode = false): bool // Reset the transaction failure flag. // If the $test_mode flag is set to TRUE transactions will be rolled back // even if the queries produce a successful result. - $this->transFailure = ($test_mode === true); + $this->transFailure = ($testMode === true); if ($this->_transBegin()) { @@ -892,8 +881,9 @@ public function transCommit(): bool { return false; } + // When transactions are nested we only begin/commit/rollback the outermost ones - elseif ($this->transDepth > 1 || $this->_transCommit()) + if ($this->transDepth > 1 || $this->_transCommit()) { $this->transDepth --; return true; @@ -915,8 +905,9 @@ public function transRollback(): bool { return false; } + // When transactions are nested we only begin/commit/rollback the outermost ones - elseif ($this->transDepth > 1 || $this->_transRollback()) + if ($this->transDepth > 1 || $this->_transRollback()) { $this->transDepth --; return true; @@ -990,12 +981,12 @@ public function table($tableName) * ->get(); * }) * - * @param \Closure $func - * @param array $options Passed to the prepare() method + * @param Closure $func + * @param array $options Passed to the prepare() method * * @return BasePreparedQuery|null */ - public function prepare(\Closure $func, array $options = []) + public function prepare(Closure $func, array $options = []) { if (empty($this->connID)) { @@ -1117,13 +1108,13 @@ public function protectIdentifiers($item, bool $prefixSingle = false, bool $prot if (is_array($item)) { - $escaped_array = []; + $escapedArray = []; foreach ($item as $k => $v) { - $escaped_array[$this->protectIdentifiers($k)] = $this->protectIdentifiers($v, $prefixSingle, $protectIdentifiers, $fieldExists); + $escapedArray[$this->protectIdentifiers($k)] = $this->protectIdentifiers($v, $prefixSingle, $protectIdentifiers, $fieldExists); } - return $escaped_array; + return $escapedArray; } // This is basically a bug fix for queries that use MAX, MIN, etc. @@ -1171,13 +1162,13 @@ public function protectIdentifiers($item, bool $prefixSingle = false, bool $prot // // NOTE: The ! empty() condition prevents this method // from breaking when QB isn't enabled. - if (! empty($this->aliasedTables) && in_array($parts[0], $this->aliasedTables)) + if (! empty($this->aliasedTables) && in_array($parts[0], $this->aliasedTables, true)) { if ($protectIdentifiers === true) { foreach ($parts as $key => $val) { - if (! in_array($val, $this->reservedIdentifiers)) + if (! in_array($val, $this->reservedIdentifiers, true)) { $parts[$key] = $this->escapeIdentifiers($val); } @@ -1262,7 +1253,7 @@ public function protectIdentifiers($item, bool $prefixSingle = false, bool $prot } } - if ($protectIdentifiers === true && ! in_array($item, $this->reservedIdentifiers)) + if ($protectIdentifiers === true && ! in_array($item, $this->reservedIdentifiers, true)) { $item = $this->escapeIdentifiers($item); } @@ -1283,11 +1274,12 @@ public function protectIdentifiers($item, bool $prefixSingle = false, bool $prot */ public function escapeIdentifiers($item) { - if ($this->escapeChar === '' || empty($item) || in_array($item, $this->reservedIdentifiers)) + if ($this->escapeChar === '' || empty($item) || in_array($item, $this->reservedIdentifiers, true)) { return $item; } - elseif (is_array($item)) + + if (is_array($item)) { foreach ($item as $key => $value) { @@ -1296,21 +1288,23 @@ public function escapeIdentifiers($item) return $item; } + // Avoid breaking functions and literal values inside queries - elseif (ctype_digit($item) || $item[0] === "'" || ( $this->escapeChar !== '"' && $item[0] === '"') || - strpos($item, '(') !== false - ) + if (ctype_digit($item) + || $item[0] === "'" + || ( $this->escapeChar !== '"' && $item[0] === '"') + || strpos($item, '(') !== false) { return $item; } - static $preg_ec = []; + static $pregEc = []; - if (empty($preg_ec)) + if (empty($pregEc)) { if (is_array($this->escapeChar)) { - $preg_ec = [ + $pregEc = [ preg_quote($this->escapeChar[0], '/'), preg_quote($this->escapeChar[1], '/'), $this->escapeChar[0], @@ -1319,8 +1313,8 @@ public function escapeIdentifiers($item) } else { - $preg_ec[0] = $preg_ec[1] = preg_quote($this->escapeChar, '/'); - $preg_ec[2] = $preg_ec[3] = $this->escapeChar; + $pregEc[0] = $pregEc[1] = preg_quote($this->escapeChar, '/'); + $pregEc[2] = $pregEc[3] = $this->escapeChar; } } @@ -1328,11 +1322,11 @@ public function escapeIdentifiers($item) { if (strpos($item, '.' . $id) !== false) { - return preg_replace('/' . $preg_ec[0] . '?([^' . $preg_ec[1] . '\.]+)' . $preg_ec[1] . '?\./i', $preg_ec[2] . '$1' . $preg_ec[3] . '.', $item); + return preg_replace('/' . $pregEc[0] . '?([^' . $pregEc[1] . '\.]+)' . $pregEc[1] . '?\./i', $pregEc[2] . '$1' . $pregEc[3] . '.', $item); } } - return preg_replace('/' . $preg_ec[0] . '?([^' . $preg_ec[1] . '\.]+)' . $preg_ec[1] . '?(\.)?/i', $preg_ec[2] . '$1' . $preg_ec[3] . '$2', $item); + return preg_replace('/' . $pregEc[0] . '?([^' . $pregEc[1] . '\.]+)' . $pregEc[1] . '?(\.)?/i', $pregEc[2] . '$1' . $pregEc[3] . '$2', $item); } //-------------------------------------------------------------------- @@ -1345,7 +1339,7 @@ public function escapeIdentifiers($item) * @param string $table the table * * @return string - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function prefixTable(string $table = ''): string { @@ -1384,19 +1378,23 @@ public function escape($str) { return array_map([&$this, 'escape'], $str); } - else if (is_string($str) || ( is_object($str) && method_exists($str, '__toString'))) + + if (is_string($str) || ( is_object($str) && method_exists($str, '__toString'))) { return "'" . $this->escapeString($str) . "'"; } - else if (is_bool($str)) + + if (is_bool($str)) { return ($str === false) ? 0 : 1; } - else if (is_numeric($str) && $str < 0) + + if (is_numeric($str) && $str < 0) { return "'{$str}'"; } - else if ($str === null) + + if ($str === null) { return 'NULL'; } @@ -1522,7 +1520,7 @@ public function callFunction(string $functionName, ...$params): bool * * @param boolean $constrainByPrefix = FALSE * @return boolean|array - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function listTables(bool $constrainByPrefix = false) { @@ -1587,7 +1585,7 @@ public function listTables(bool $constrainByPrefix = false) */ public function tableExists(string $tableName): bool { - return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables()); + return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables(), true); } //-------------------------------------------------------------------- @@ -1662,7 +1660,7 @@ public function getFieldNames(string $table) */ public function fieldExists(string $fieldName, string $tableName): bool { - return in_array($fieldName, $this->getFieldNames($tableName)); + return in_array($fieldName, $this->getFieldNames($tableName), true); } //-------------------------------------------------------------------- diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php index 247d68ce1615..f1349cce2576 100644 --- a/system/Database/BasePreparedQuery.php +++ b/system/Database/BasePreparedQuery.php @@ -38,8 +38,10 @@ namespace CodeIgniter\Database; +use BadMethodCallException; use CodeIgniter\Database\MySQLi\Connection; use CodeIgniter\Events\Events; +use mysqli_stmt; /** * Base prepared query @@ -50,7 +52,7 @@ abstract class BasePreparedQuery implements PreparedQueryInterface /** * The prepared statement itself. * - * @var resource|\mysqli_stmt + * @var resource|mysqli_stmt */ protected $statement; @@ -79,7 +81,7 @@ abstract class BasePreparedQuery implements PreparedQueryInterface /** * A reference to the db connection to use. * - * @var BaseConnection|MySQLi\Connection + * @var BaseConnection|Connection */ protected $db; @@ -88,7 +90,7 @@ abstract class BasePreparedQuery implements PreparedQueryInterface /** * Constructor. * - * @param \CodeIgniter\Database\ConnectionInterface $db + * @param ConnectionInterface $db */ public function __construct(ConnectionInterface $db) { @@ -228,7 +230,7 @@ public function getQueryString(): string { if (! $this->query instanceof QueryInterface) { - throw new \BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.'); + throw new BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.'); } return $this->query->getQuery(); diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php index 40ee09e85139..10d2c7f444cd 100644 --- a/system/Database/BaseResult.php +++ b/system/Database/BaseResult.php @@ -134,7 +134,8 @@ public function getResult(string $type = 'object'): array { return $this->getResultArray(); } - elseif ($type === 'object') + + if ($type === 'object') { return $this->getResultObject(); } @@ -331,7 +332,8 @@ public function getRow($n = 0, string $type = 'object') { return $this->getRowObject($n); } - elseif ($type === 'array') + + if ($type === 'array') { return $this->getRowArray($n); } @@ -548,7 +550,8 @@ public function getUnbufferedRow(string $type = 'object') { return $this->fetchAssoc(); } - elseif ($type === 'object') + + if ($type === 'object') { return $this->fetchObject(); } diff --git a/system/Database/BaseUtils.php b/system/Database/BaseUtils.php index 8261fabcda76..61fb0609abef 100644 --- a/system/Database/BaseUtils.php +++ b/system/Database/BaseUtils.php @@ -95,7 +95,7 @@ public function __construct(ConnectionInterface &$db) * List databases * * @return array|boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function listDatabases() { @@ -104,7 +104,8 @@ public function listDatabases() { return $this->db->dataCache['db_names']; } - elseif ($this->listDatabases === false) + + if ($this->listDatabases === false) { if ($this->db->DBDebug) { @@ -134,12 +135,12 @@ public function listDatabases() /** * Determine if a particular database exists * - * @param string $database_name + * @param string $databaseName * @return boolean */ - public function databaseExists(string $database_name): bool + public function databaseExists(string $databaseName): bool { - return in_array($database_name, $this->listDatabases()); + return in_array($databaseName, $this->listDatabases(), true); } //-------------------------------------------------------------------- @@ -147,11 +148,11 @@ public function databaseExists(string $database_name): bool /** * Optimize Table * - * @param string $table_name + * @param string $tableName * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ - public function optimizeTable(string $table_name) + public function optimizeTable(string $tableName) { if ($this->optimizeTable === false) { @@ -162,7 +163,7 @@ public function optimizeTable(string $table_name) return false; } - $query = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($table_name))); + $query = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($tableName))); if ($query !== false) { $query = $query->getResultArray(); @@ -178,7 +179,7 @@ public function optimizeTable(string $table_name) * Optimize Database * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function optimizeDatabase() { @@ -192,9 +193,9 @@ public function optimizeDatabase() } $result = []; - foreach ($this->db->listTables() as $table_name) + foreach ($this->db->listTables() as $tableName) { - $res = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($table_name))); + $res = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($tableName))); if (is_bool($res)) { return $res; @@ -207,7 +208,7 @@ public function optimizeDatabase() // Postgre & SQLite3 returns empty array if (empty($res)) { - $key = $table_name; + $key = $tableName; } else { @@ -228,11 +229,11 @@ public function optimizeDatabase() /** * Repair Table * - * @param string $table_name + * @param string $tableName * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ - public function repairTable(string $table_name) + public function repairTable(string $tableName) { if ($this->repairTable === false) { @@ -243,7 +244,7 @@ public function repairTable(string $table_name) return false; } - $query = $this->db->query(sprintf($this->repairTable, $this->db->escapeIdentifiers($table_name))); + $query = $this->db->query(sprintf($this->repairTable, $this->db->escapeIdentifiers($tableName))); if (is_bool($query)) { return $query; @@ -342,7 +343,7 @@ public function getXMLFromResult(ResultInterface $query, array $params = []): st * * @param array|string $params * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function backup($params = []) { diff --git a/system/Database/Config.php b/system/Database/Config.php index cab2d470517d..b919fc857fa9 100644 --- a/system/Database/Config.php +++ b/system/Database/Config.php @@ -40,6 +40,7 @@ namespace CodeIgniter\Database; use CodeIgniter\Config\BaseConfig; +use InvalidArgumentException; /** * Class Config @@ -59,7 +60,7 @@ class Config extends BaseConfig * The main instance used to manage all of * our open database connections. * - * @var \CodeIgniter\Database\Database|null + * @var Database|null */ static protected $factory; @@ -97,7 +98,7 @@ public static function connect($group = null, bool $getShared = true) if (is_string($group) && ! isset($config->$group) && strpos($group, 'custom-') !== 0) { - throw new \InvalidArgumentException($group . ' is not a valid database connection group.'); + throw new InvalidArgumentException($group . ' is not a valid database connection group.'); } if ($getShared && isset(static::$instances[$group])) @@ -114,7 +115,7 @@ public static function connect($group = null, bool $getShared = true) $connection = static::$factory->load($config, $group); - static::$instances[$group] = & $connection; + static::$instances[$group] = &$connection; return $connection; } diff --git a/system/Database/Database.php b/system/Database/Database.php index 85db427b182b..b15268651f65 100644 --- a/system/Database/Database.php +++ b/system/Database/Database.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Database; +use InvalidArgumentException; + /** * Database Connection Factory * @@ -81,7 +83,7 @@ public function load(array $params = [], string $alias) // No DB specified? Beat them senseless... if (empty($params['DBDriver'])) { - throw new \InvalidArgumentException('You have not selected a database type to connect to.'); + throw new InvalidArgumentException('You have not selected a database type to connect to.'); } $className = strpos($params['DBDriver'], '\\') === false @@ -148,13 +150,13 @@ public function loadUtils(ConnectionInterface $db) * @param array $params * * @return array - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function parseDSN(array $params): array { if (($dsn = parse_url($params['DSN'])) === false) { - throw new \InvalidArgumentException('Your DSN connection string is invalid.'); + throw new InvalidArgumentException('Your DSN connection string is invalid.'); } $dsnParams = [ diff --git a/system/Database/Exceptions/DataException.php b/system/Database/Exceptions/DataException.php index f47cd233c8be..d9512ed87a80 100644 --- a/system/Database/Exceptions/DataException.php +++ b/system/Database/Exceptions/DataException.php @@ -2,14 +2,16 @@ namespace CodeIgniter\Database\Exceptions; -class DataException extends \RuntimeException implements ExceptionInterface +use RuntimeException; + +class DataException extends RuntimeException implements ExceptionInterface { /** * Used by the Model's trigger() method when the callback cannot be found. * * @param string $method * - * @return \CodeIgniter\Database\Exceptions\DataException + * @return DataException */ public static function forInvalidMethodTriggered(string $method) { @@ -22,7 +24,7 @@ public static function forInvalidMethodTriggered(string $method) * * @param string $mode * - * @return \CodeIgniter\Database\Exceptions\DataException + * @return DataException */ public static function forEmptyDataset(string $mode) { @@ -36,7 +38,7 @@ public static function forEmptyDataset(string $mode) * * @param string $mode * - * @return \CodeIgniter\Database\Exceptions\DataException + * @return DataException */ public static function forEmptyPrimaryKey(string $mode) { @@ -50,7 +52,7 @@ public static function forEmptyPrimaryKey(string $mode) * * @param string $argument * - * @return \CodeIgniter\Database\Exceptions\DataException + * @return DataException */ public static function forInvalidArgument(string $argument) { diff --git a/system/Database/Exceptions/DatabaseException.php b/system/Database/Exceptions/DatabaseException.php index cb411cf9c4d9..2879c1353e8f 100644 --- a/system/Database/Exceptions/DatabaseException.php +++ b/system/Database/Exceptions/DatabaseException.php @@ -38,9 +38,9 @@ namespace CodeIgniter\Database\Exceptions; -use CodeIgniter\Exceptions\ExceptionInterface; +use Error; -class DatabaseException extends \Error implements ExceptionInterface +class DatabaseException extends Error implements ExceptionInterface { /** * Exit status code diff --git a/system/Database/Forge.php b/system/Database/Forge.php index a87103eaf595..b8d30ce438ad 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -40,6 +40,9 @@ namespace CodeIgniter\Database; use CodeIgniter\Database\Exceptions\DatabaseException; +use InvalidArgumentException; +use RuntimeException; +use Throwable; /** * Class Forge @@ -190,7 +193,7 @@ class Forge /** * Constructor. * - * @param \CodeIgniter\Database\ConnectionInterface $db + * @param ConnectionInterface $db */ public function __construct(ConnectionInterface $db) { @@ -218,7 +221,7 @@ public function getConnection() * @param boolean $ifNotExists Whether to add IF NOT EXISTS condition * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function createDatabase(string $dbName, bool $ifNotExists = false): bool { @@ -228,6 +231,7 @@ public function createDatabase(string $dbName, bool $ifNotExists = false): bool { return true; } + $ifNotExists = false; } @@ -238,25 +242,39 @@ public function createDatabase(string $dbName, bool $ifNotExists = false): bool throw new DatabaseException('This feature is not available for the database you are using.'); } - return false; + return false; // @codeCoverageIgnore } - elseif (! $this->db->query(sprintf($ifNotExists ? $this->createDatabaseIfStr : $this->createDatabaseStr, $dbName, $this->db->charset, $this->db->DBCollat)) - ) + + try { - if ($this->db->DBDebug) + if (! $this->db->query(sprintf($ifNotExists ? $this->createDatabaseIfStr : $this->createDatabaseStr, $dbName, $this->db->charset, $this->db->DBCollat))) { - throw new DatabaseException('Unable to create the specified database.'); + // @codeCoverageIgnoreStart + if ($this->db->DBDebug) + { + throw new DatabaseException('Unable to create the specified database.'); + } + + return false; + // @codeCoverageIgnoreEnd } - return false; - } + if (! empty($this->db->dataCache['db_names'])) + { + $this->db->dataCache['db_names'][] = $dbName; + } - if (! empty($this->db->dataCache['db_names'])) - { - $this->db->dataCache['db_names'][] = $dbName; + return true; } + catch (Throwable $e) + { + if ($this->db->DBDebug) + { + throw new DatabaseException('Unable to create the specified database.', 0, $e); + } - return true; + return false; // @codeCoverageIgnore + } } //-------------------------------------------------------------------- @@ -267,7 +285,7 @@ public function createDatabase(string $dbName, bool $ifNotExists = false): bool * @param string $dbName * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ private function databaseExists(string $dbName): bool { @@ -292,7 +310,7 @@ private function databaseExists(string $dbName): bool * @param string $dbName * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function dropDatabase(string $dbName): bool { @@ -305,7 +323,8 @@ public function dropDatabase(string $dbName): bool return false; } - elseif (! $this->db->query(sprintf($this->dropDatabaseStr, $dbName))) + + if (! $this->db->query(sprintf($this->dropDatabaseStr, $dbName))) { if ($this->db->DBDebug) { @@ -415,7 +434,7 @@ public function addField($field) { if (strpos($field, ' ') === false) { - throw new \InvalidArgumentException('Field information is required for that operation.'); + throw new InvalidArgumentException('Field information is required for that operation.'); } $this->fields[] = $field; @@ -441,8 +460,8 @@ public function addField($field) * @param string $onUpdate * @param string $onDelete * - * @return \CodeIgniter\Database\Forge - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @return Forge + * @throws DatabaseException */ public function addForeignKey(string $fieldName = '', string $tableName = '', string $tableField = '', string $onUpdate = '', string $onDelete = '') { @@ -469,8 +488,8 @@ public function addForeignKey(string $fieldName = '', string $tableName = '', st * @param string $table Table name * @param string $foreignName Foreign name * - * @return boolean|\CodeIgniter\Database\BaseResult|\CodeIgniter\Database\Query|false|mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @return boolean|BaseResult|Query|false|mixed + * @throws DatabaseException */ public function dropForeignKey(string $table, string $foreignName) { @@ -495,28 +514,28 @@ public function dropForeignKey(string $table, string $foreignName) /** * Create Table * - * @param string $table Table name - * @param boolean $if_not_exists Whether to add IF NOT EXISTS condition - * @param array $attributes Associative array of table attributes + * @param string $table Table name + * @param boolean $ifNotExists Whether to add IF NOT EXISTS condition + * @param array $attributes Associative array of table attributes * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ - public function createTable(string $table, bool $if_not_exists = false, array $attributes = []) + public function createTable(string $table, bool $ifNotExists = false, array $attributes = []) { if ($table === '') { - throw new \InvalidArgumentException('A table name is required for that operation.'); + throw new InvalidArgumentException('A table name is required for that operation.'); } $table = $this->db->DBPrefix . $table; if (count($this->fields) === 0) { - throw new \RuntimeException('Field information is required.'); + throw new RuntimeException('Field information is required.'); } - $sql = $this->_createTable($table, $if_not_exists, $attributes); + $sql = $this->_createTable($table, $ifNotExists, $attributes); if (is_bool($sql)) { @@ -559,26 +578,26 @@ public function createTable(string $table, bool $if_not_exists = false, array $a /** * Create Table * - * @param string $table Table name - * @param boolean $if_not_exists Whether to add 'IF NOT EXISTS' condition - * @param array $attributes Associative array of table attributes + * @param string $table Table name + * @param boolean $ifNotExists Whether to add 'IF NOT EXISTS' condition + * @param array $attributes Associative array of table attributes * * @return mixed */ - protected function _createTable(string $table, bool $if_not_exists, array $attributes) + protected function _createTable(string $table, bool $ifNotExists, array $attributes) { // For any platforms that don't support Create If Not Exists... - if ($if_not_exists === true && $this->createTableIfStr === false) + if ($ifNotExists === true && $this->createTableIfStr === false) { if ($this->db->tableExists($table)) { return true; } - $if_not_exists = false; + $ifNotExists = false; } - $sql = ($if_not_exists) ? sprintf($this->createTableIfStr, $this->db->escapeIdentifiers($table)) + $sql = ($ifNotExists) ? sprintf($this->createTableIfStr, $this->db->escapeIdentifiers($table)) : 'CREATE TABLE'; $columns = $this->_processFields(true); @@ -644,7 +663,7 @@ protected function _createTableAttributes(array $attributes): string * @param boolean $cascade Whether to add an CASCADE condition * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function dropTable(string $tableName, bool $ifExists = false, bool $cascade = false) { @@ -696,17 +715,17 @@ public function dropTable(string $tableName, bool $ifExists = false, bool $casca * * Generates a platform-specific DROP TABLE string * - * @param string $table Table name - * @param boolean $if_exists Whether to add an IF EXISTS condition - * @param boolean $cascade Whether to add an CASCADE condition + * @param string $table Table name + * @param boolean $ifExists Whether to add an IF EXISTS condition + * @param boolean $cascade Whether to add an CASCADE condition * * @return string|boolean */ - protected function _dropTable(string $table, bool $if_exists, bool $cascade) + protected function _dropTable(string $table, bool $ifExists, bool $cascade) { $sql = 'DROP TABLE'; - if ($if_exists) + if ($ifExists) { if ($this->dropTableIfStr === false) { @@ -729,19 +748,20 @@ protected function _dropTable(string $table, bool $if_exists, bool $cascade) /** * Rename Table * - * @param string $table_name Old table name - * @param string $new_table_name New table name + * @param string $tableName Old table name + * @param string $newTableName New table name * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ - public function renameTable(string $table_name, string $new_table_name) + public function renameTable(string $tableName, string $newTableName) { - if ($table_name === '' || $new_table_name === '') + if ($tableName === '' || $newTableName === '') { - throw new \InvalidArgumentException('A table name is required for that operation.'); + throw new InvalidArgumentException('A table name is required for that operation.'); } - elseif ($this->renameTableStr === false) + + if ($this->renameTableStr === false) { if ($this->db->DBDebug) { @@ -752,17 +772,17 @@ public function renameTable(string $table_name, string $new_table_name) } $result = $this->db->query(sprintf($this->renameTableStr, - $this->db->escapeIdentifiers($this->db->DBPrefix . $table_name), - $this->db->escapeIdentifiers($this->db->DBPrefix . $new_table_name)) + $this->db->escapeIdentifiers($this->db->DBPrefix . $tableName), + $this->db->escapeIdentifiers($this->db->DBPrefix . $newTableName)) ); if ($result && ! empty($this->db->dataCache['table_names'])) { - $key = array_search(strtolower($this->db->DBPrefix . $table_name), + $key = array_search(strtolower($this->db->DBPrefix . $tableName), array_map('strtolower', $this->db->dataCache['table_names']), true); if ($key !== false) { - $this->db->dataCache['table_names'][$key] = $this->db->DBPrefix . $new_table_name; + $this->db->dataCache['table_names'][$key] = $this->db->DBPrefix . $newTableName; } } @@ -778,7 +798,7 @@ public function renameTable(string $table_name, string $new_table_name) * @param string|array $field Column definition * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function addColumn(string $table, $field): bool { @@ -818,15 +838,15 @@ public function addColumn(string $table, $field): bool /** * Column Drop * - * @param string $table Table name - * @param string|array $column_name Column name Array or comma separated + * @param string $table Table name + * @param string|array $columnName Column name Array or comma separated * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ - public function dropColumn(string $table, $column_name) + public function dropColumn(string $table, $columnName) { - $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $column_name); + $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $columnName); if ($sql === false) { if ($this->db->DBDebug) @@ -849,7 +869,7 @@ public function dropColumn(string $table, $column_name) * @param string|array $field Column definition * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function modifyColumn(string $table, $field): bool { @@ -863,7 +883,7 @@ public function modifyColumn(string $table, $field): bool if (count($this->fields) === 0) { - throw new \RuntimeException('Field information is required'); + throw new RuntimeException('Field information is required'); } $sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields()); @@ -897,18 +917,18 @@ public function modifyColumn(string $table, $field): bool /** * ALTER TABLE * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $fields Column definition + * @param string $alterType ALTER type + * @param string $table Table name + * @param mixed $fields Column definition * * @return string|string[]|false */ - protected function _alterTable(string $alter_type, string $table, $fields) + protected function _alterTable(string $alterType, string $table, $fields) { $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' '; // DROP has everything it needs now. - if ($alter_type === 'DROP') + if ($alterType === 'DROP') { if (is_string($fields)) { @@ -922,7 +942,7 @@ protected function _alterTable(string $alter_type, string $table, $fields) return $sql . implode(', ', $fields); } - $sql .= ($alter_type === 'ADD') ? 'ADD ' : $alter_type . ' COLUMN '; + $sql .= ($alterType === 'ADD') ? 'ADD ' : $alterType . ' COLUMN '; $sqls = []; foreach ($fields as $data) @@ -939,11 +959,11 @@ protected function _alterTable(string $alter_type, string $table, $fields) /** * Process fields * - * @param boolean $create_table + * @param boolean $createTable * * @return array */ - protected function _processFields(bool $create_table = false): array + protected function _processFields(bool $createTable = false): array { $fields = []; @@ -957,7 +977,7 @@ protected function _processFields(bool $create_table = false): array $attributes = array_change_key_case($attributes, CASE_UPPER); - if ($create_table === true && empty($attributes['TYPE'])) + if ($createTable === true && empty($attributes['TYPE'])) { continue; } @@ -981,7 +1001,7 @@ protected function _processFields(bool $create_table = false): array // @phpstan-ignore-next-line isset($attributes['TYPE']) && $this->_attributeUnsigned($attributes, $field); - if ($create_table === false) + if ($createTable === false) { if (isset($attributes['AFTER'])) { @@ -1006,7 +1026,7 @@ protected function _processFields(bool $create_table = false): array $field['null'] = ' NOT NULL'; } } - elseif ($create_table === true) + elseif ($createTable === true) { $field['null'] = ' NOT NULL'; } @@ -1111,7 +1131,8 @@ protected function _attributeUnsigned(array &$attributes, array &$field) return; } - elseif (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0) + + if (is_string($key) && strcasecmp($attributes['TYPE'], $key) === 0) { $field['type'] = $key; @@ -1256,7 +1277,7 @@ protected function _processIndexes(string $table) continue; } - if (in_array($i, $this->uniqueKeys)) + if (in_array($i, $this->uniqueKeys, true)) { $sqls[] = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' ADD CONSTRAINT ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i])) @@ -1297,17 +1318,17 @@ protected function _processForeignKeys(string $table): string { foreach ($this->foreignKeys as $field => $fkey) { - $name_index = $table . '_' . $field . '_foreign'; + $nameIndex = $table . '_' . $field . '_foreign'; - $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers($name_index) + $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers($nameIndex) . ' FOREIGN KEY(' . $this->db->escapeIdentifiers($field) . ') REFERENCES ' . $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['table']) . ' (' . $this->db->escapeIdentifiers($fkey['field']) . ')'; - if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions)) + if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) { $sql .= ' ON DELETE ' . $fkey['onDelete']; } - if ($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions)) + if ($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions, true)) { $sql .= ' ON UPDATE ' . $fkey['onUpdate']; } diff --git a/system/Database/Migration.php b/system/Database/Migration.php index 1f259ca7dc66..bece2a05decf 100644 --- a/system/Database/Migration.php +++ b/system/Database/Migration.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Database; +use Config\Database; + /** * Class Migration */ @@ -71,11 +73,11 @@ abstract class Migration /** * Constructor. * - * @param \CodeIgniter\Database\Forge $forge + * @param Forge $forge */ public function __construct(Forge $forge = null) { - $this->forge = ! is_null($forge) ? $forge : \Config\Database::forge($this->DBGroup ?? config('Database')->defaultGroup); + $this->forge = ! is_null($forge) ? $forge : Database::forge($this->DBGroup ?? config('Database')->defaultGroup); $this->db = $this->forge->getConnection(); } diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index baecdbf597f6..0f39291641b7 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -39,9 +39,13 @@ namespace CodeIgniter\Database; use CodeIgniter\CLI\CLI; +use CodeIgniter\Events\Events; use CodeIgniter\Exceptions\ConfigException; +use Config\Database; use Config\Migrations as MigrationsConfig; use Config\Services; +use RuntimeException; +use stdClass; /** * Class MigrationRunner @@ -95,7 +99,7 @@ class MigrationRunner * The main database connection. Used to store * migration information in. * - * @var ConnectionInterface + * @var BaseConnection */ protected $db; @@ -153,8 +157,8 @@ class MigrationRunner * - existing connection instance * - array of database configuration values * - * @param MigrationsConfig $config - * @param \CodeIgniter\Database\ConnectionInterface|array|string $db + * @param MigrationsConfig $config + * @param ConnectionInterface|array|string|null $db * * @throws ConfigException */ @@ -177,11 +181,15 @@ public function __construct(MigrationsConfig $config, $db = null) } //-------------------------------------------------------------------- - /** * Locate and run all new migrations * * @param string|null $group + * + * @throws ConfigException + * @throws RuntimeException + * + * @return boolean */ public function latest(string $group = null) { @@ -242,15 +250,19 @@ public function latest(string $group = null) $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + + throw new RuntimeException($message); } } + $data = get_object_vars($this); + $data['method'] = 'latest'; + Events::trigger('migrate', $data); + return true; } //-------------------------------------------------------------------- - /** * Migrate down to a previous batch * @@ -259,8 +271,10 @@ public function latest(string $group = null) * @param integer $targetBatch Target batch number, or negative for a relative batch, 0 for all * @param string|null $group * - * @return mixed Current batch number on success, FALSE on failure or no migrations are found * @throws ConfigException + * @throws RuntimeException + * + * @return mixed Current batch number on success, FALSE on failure or no migrations are found */ public function regress(int $targetBatch = 0, string $group = null) { @@ -293,7 +307,7 @@ public function regress(int $targetBatch = 0, string $group = null) } // Make sure $targetBatch is found - if ($targetBatch !== 0 && ! in_array($targetBatch, $batches)) + if ($targetBatch !== 0 && ! in_array($targetBatch, $batches, true)) { $message = lang('Migrations.batchNotFound') . $targetBatch; @@ -302,7 +316,8 @@ public function regress(int $targetBatch = 0, string $group = null) $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + + throw new RuntimeException($message); } // Save the namespace to restore it after loading migrations @@ -314,6 +329,7 @@ public function regress(int $targetBatch = 0, string $group = null) // Gather migrations down through each batch until reaching the target $migrations = []; + while ($batch = array_pop($batches)) { // Check if reached target @@ -338,7 +354,8 @@ public function regress(int $targetBatch = 0, string $group = null) $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + + throw new RuntimeException($message); } // Add the history and put it on the list @@ -365,10 +382,15 @@ public function regress(int $targetBatch = 0, string $group = null) $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + + throw new RuntimeException($message); } } + $data = get_object_vars($this); + $data['method'] = 'regress'; + Events::trigger('migrate', $data); + // Restore the namespace $this->namespace = $tmpNamespace; @@ -413,7 +435,7 @@ public function force(string $path, string $namespace, string $group = null) $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + throw new RuntimeException($message); } // Check the history for a match @@ -459,7 +481,7 @@ public function force(string $path, string $namespace, string $group = null) $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + throw new RuntimeException($message); } //-------------------------------------------------------------------- @@ -563,7 +585,7 @@ protected function migrationFromFile(string $path, string $namespace) $locator = Services::locator(true); // Create migration object using stdClass - $migration = new \stdClass(); + $migration = new stdClass(); // Get migration version number $migration->version = $this->getMigrationNumber($name); @@ -617,7 +639,7 @@ public function setGroup(string $group) * * @param string $name * - * @return \CodeIgniter\Database\MigrationRunner + * @return MigrationRunner */ public function setName(string $name) { @@ -728,8 +750,7 @@ public function clearHistory() { if ($this->db->tableExists($this->table)) { - $this->db->table($this->table) - ->truncate(); + $this->db->table($this->table)->truncate(); } } @@ -850,7 +871,7 @@ public function getBatches(): array ->get() ->getResultArray(); - return array_column($batches, 'batch'); + return array_map('intval', array_column($batches, 'batch')); } //-------------------------------------------------------------------- @@ -947,7 +968,7 @@ public function ensureTable() return; } - $forge = \Config\Database::forge($this->db); + $forge = Database::forge($this->db); $forge->addField([ 'id' => [ @@ -1019,7 +1040,7 @@ protected function migrate($direction, $migration): bool $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + throw new RuntimeException($message); } // Initialize migration @@ -1054,7 +1075,7 @@ protected function migrate($direction, $migration): bool $this->cliMessages[] = "\t" . CLI::color($message, 'red'); return false; } - throw new \RuntimeException($message); + throw new RuntimeException($message); } $instance->{$direction}(); diff --git a/system/Database/ModelFactory.php b/system/Database/ModelFactory.php index 6efc3448f6fd..8d033af815af 100644 --- a/system/Database/ModelFactory.php +++ b/system/Database/ModelFactory.php @@ -1,22 +1,19 @@ locateFile($name, 'Models'); - - if (empty($file)) - { - // No file found - check if the class was namespaced - if (strpos($name, '\\') !== false) - { - // Class was namespaced and locateFile couldn't find it - return null; - } - - // Check all namespaces - $files = $locator->search('Models/' . $name); - if (empty($files)) - { - return null; - } - - // Get the first match (prioritizes user and framework) - $file = reset($files); - } - - $name = $locator->getClassname($file); - - if (empty($name)) - { - return null; - } - - return new $name($connection); + Factories::reset('models'); } } diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 1c6de4232853..13d13d3dc786 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -42,6 +42,10 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; +use LogicException; +use mysqli_sql_exception; +use stdClass; +use Throwable; /** * Connection for MySQLi @@ -88,7 +92,7 @@ class Connection extends BaseConnection implements ConnectionInterface * @param boolean $persistent * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function connect(bool $persistent = false) { @@ -106,7 +110,7 @@ public function connect(bool $persistent = false) $socket = ''; } - $client_flags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0; + $clientFlags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0; $this->mysqli = mysqli_init(); mysqli_report(MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX); @@ -161,11 +165,11 @@ public function connect(bool $persistent = false) // https://bugs.php.net/bug.php?id=68344 elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT') && version_compare($this->mysqli->client_info, '5.6', '>=')) { - $client_flags += MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; + $clientFlags += MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } } - $client_flags += MYSQLI_CLIENT_SSL; + $clientFlags += MYSQLI_CLIENT_SSL; $this->mysqli->ssl_set( $ssl['key'] ?? null, $ssl['cert'] ?? null, $ssl['ca'] ?? null, $ssl['capath'] ?? null, $ssl['cipher'] ?? null @@ -176,11 +180,11 @@ public function connect(bool $persistent = false) try { if ($this->mysqli->real_connect($hostname, $this->username, $this->password, - $this->database, $port, $socket, $client_flags) + $this->database, $port, $socket, $clientFlags) ) { // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails - if (($client_flags & MYSQLI_CLIENT_SSL) && version_compare($this->mysqli->client_info, '5.7.3', '<=') + if (($clientFlags & MYSQLI_CLIENT_SSL) && version_compare($this->mysqli->client_info, '5.7.3', '<=') && empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'") ->fetch_object()->Value) ) @@ -214,7 +218,7 @@ public function connect(bool $persistent = false) return $this->mysqli; } } - catch (\Throwable $e) + catch (Throwable $e) { // Clean sensitive information from errors. $msg = $e->getMessage(); @@ -330,7 +334,7 @@ public function execute(string $sql) { return $this->connID->query($this->prepQuery($sql)); } - catch (\mysqli_sql_exception $e) + catch (mysqli_sql_exception $e) { log_message('error', $e); if ($this->DBDebug) @@ -474,7 +478,7 @@ protected function _listColumns(string $table = ''): string * Returns an array of objects with field data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _fieldData(string $table): array @@ -490,7 +494,7 @@ public function _fieldData(string $table): array $retVal = []; for ($i = 0, $c = count($query); $i < $c; $i++) { - $retVal[$i] = new \stdClass(); + $retVal[$i] = new stdClass(); $retVal[$i]->name = $query[$i]->Field; sscanf($query[$i]->Type, '%[a-z](%d)', $retVal[$i]->type, $retVal[$i]->max_length); @@ -509,9 +513,9 @@ public function _fieldData(string $table): array * Returns an array of objects with index data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException - * @throws \LogicException + * @throws LogicException */ public function _indexData(string $table): array { @@ -533,7 +537,7 @@ public function _indexData(string $table): array { if (empty($keys[$index['Key_name']])) { - $keys[$index['Key_name']] = new \stdClass(); + $keys[$index['Key_name']] = new stdClass(); $keys[$index['Key_name']]->name = $index['Key_name']; if ($index['Key_name'] === 'PRIMARY') @@ -575,7 +579,7 @@ public function _indexData(string $table): array * Returns an array of objects with Foreign key data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _foreignKeyData(string $table): array @@ -606,7 +610,7 @@ public function _foreignKeyData(string $table): array $retVal = []; foreach ($query as $row) { - $obj = new \stdClass(); + $obj = new stdClass(); $obj->constraint_name = $row->CONSTRAINT_NAME; $obj->table_name = $row->TABLE_NAME; $obj->column_name = $row->COLUMN_NAME; diff --git a/system/Database/MySQLi/Forge.php b/system/Database/MySQLi/Forge.php index 27a82e12757d..72bcf0484ce9 100644 --- a/system/Database/MySQLi/Forge.php +++ b/system/Database/MySQLi/Forge.php @@ -136,7 +136,7 @@ protected function _createTableAttributes(array $attributes): string { $sql .= ' ' . strtoupper($key) . ' = '; - if (in_array(strtoupper($key), $this->_quoted_table_options)) + if (in_array(strtoupper($key), $this->_quoted_table_options, true)) { $sql .= $this->db->escape($attributes[$key]); } @@ -165,16 +165,16 @@ protected function _createTableAttributes(array $attributes): string /** * ALTER TABLE * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $field Column definition + * @param string $alterType ALTER type + * @param string $table Table name + * @param mixed $field Column definition * @return string|string[] */ - protected function _alterTable(string $alter_type, string $table, $field) + protected function _alterTable(string $alterType, string $table, $field) { - if ($alter_type === 'DROP') + if ($alterType === 'DROP') { - return parent::_alterTable($alter_type, $table, $field); + return parent::_alterTable($alterType, $table, $field); } $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); @@ -182,11 +182,11 @@ protected function _alterTable(string $alter_type, string $table, $field) { if ($data['_literal'] !== false) { - $field[$i] = ($alter_type === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal']; + $field[$i] = ($alterType === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal']; } else { - if ($alter_type === 'ADD') + if ($alterType === 'ADD') { $field[$i]['_literal'] = "\n\tADD "; } @@ -212,11 +212,11 @@ protected function _alterTable(string $alter_type, string $table, $field) */ protected function _processColumn(array $field): string { - $extra_clause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; + $extraClause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; - if (empty($extra_clause) && isset($field['first']) && $field['first'] === true) + if (empty($extraClause) && isset($field['first']) && $field['first'] === true) { - $extra_clause = ' FIRST'; + $extraClause = ' FIRST'; } return $this->db->escapeIdentifiers($field['name']) @@ -228,7 +228,7 @@ protected function _processColumn(array $field): string . $field['auto_increment'] . $field['unique'] . (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment']) - . $extra_clause; + . $extraClause; } //-------------------------------------------------------------------- @@ -265,7 +265,7 @@ protected function _processIndexes(string $table): string // @phpstan-ignore-next-line is_array($this->keys[$i]) || $this->keys[$i] = [$this->keys[$i]]; - $unique = in_array($i, $this->uniqueKeys) ? 'UNIQUE ' : ''; + $unique = in_array($i, $this->uniqueKeys, true) ? 'UNIQUE ' : ''; $sql .= ",\n\t{$unique}KEY " . $this->db->escapeIdentifiers(implode('_', $this->keys[$i])) . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ')'; diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 3ef41047f0da..bea6cdec2d83 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -39,6 +39,7 @@ namespace CodeIgniter\Database\MySQLi; +use BadMethodCallException; use CodeIgniter\Database\BasePreparedQuery; use CodeIgniter\Database\PreparedQueryInterface; @@ -90,7 +91,7 @@ public function _execute(array $data): bool { if (is_null($this->statement)) { - throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); } // First off -bind the parameters diff --git a/system/Database/MySQLi/Result.php b/system/Database/MySQLi/Result.php index eb929b538b8d..23f81639142a 100644 --- a/system/Database/MySQLi/Result.php +++ b/system/Database/MySQLi/Result.php @@ -42,6 +42,7 @@ use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity; +use stdClass; /** * Result for MySQLi @@ -87,7 +88,7 @@ public function getFieldNames(): array */ public function getFieldData(): array { - static $data_types = [ + static $dataTypes = [ MYSQLI_TYPE_DECIMAL => 'decimal', MYSQLI_TYPE_NEWDECIMAL => 'newdecimal', MYSQLI_TYPE_FLOAT => 'float', @@ -124,11 +125,11 @@ public function getFieldData(): array foreach ($fieldData as $i => $data) { - $retVal[$i] = new \stdClass(); + $retVal[$i] = new stdClass(); $retVal[$i]->name = $data->name; $retVal[$i]->type = $data->type; $retVal[$i]->type_name = in_array($data->type, [1, 247], true) - ? 'char' : (isset($data_types[$data->type]) ? $data_types[$data->type] : null); + ? 'char' : (isset($dataTypes[$data->type]) ? $dataTypes[$data->type] : null); $retVal[$i]->max_length = $data->max_length; $retVal[$i]->primary_key = (int) ($data->flags & 2); $retVal[$i]->length = $data->length; diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index b8b38a07dc31..e57f0abf1c5d 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -241,7 +241,7 @@ public function replace(array $set = null) * * @param mixed $where * @param integer $limit - * @param boolean $reset_data + * @param boolean $resetData * * @return mixed * @throws DatabaseException @@ -249,14 +249,14 @@ public function replace(array $set = null) * @internal param the $mixed limit clause * @internal param $bool */ - public function delete($where = '', int $limit = null, bool $reset_data = true) + public function delete($where = '', int $limit = null, bool $resetData = true) { if (! empty($limit) || ! empty($this->QBLimit)) { throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.'); } - return parent::delete($where, $limit, $reset_data); + return parent::delete($where, $limit, $resetData); } //-------------------------------------------------------------------- diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index d96d52296eca..afa023c92887 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -41,6 +41,8 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; +use ErrorException; +use stdClass; /** * Connection for Postgre @@ -196,7 +198,7 @@ public function execute(string $sql) { return pg_query($this->connID, $sql); } - catch (\ErrorException $e) + catch (ErrorException $e) { log_message('error', $e); if ($this->DBDebug) @@ -240,7 +242,8 @@ public function escape($str) { return pg_escape_literal($this->connID, $str); } - elseif (is_bool($str)) + + if (is_bool($str)) { return $str ? 'TRUE' : 'FALSE'; } @@ -312,7 +315,7 @@ protected function _listColumns(string $table = ''): string * Returns an array of objects with field data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _fieldData(string $table): array @@ -331,7 +334,7 @@ public function _fieldData(string $table): array $retVal = []; for ($i = 0, $c = count($query); $i < $c; $i ++) { - $retVal[$i] = new \stdClass(); + $retVal[$i] = new stdClass(); $retVal[$i]->name = $query[$i]->column_name; $retVal[$i]->type = $query[$i]->data_type; $retVal[$i]->default = $query[$i]->column_default; @@ -347,7 +350,7 @@ public function _fieldData(string $table): array * Returns an array of objects with index data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _indexData(string $table): array @@ -366,7 +369,7 @@ public function _indexData(string $table): array $retVal = []; foreach ($query as $row) { - $obj = new \stdClass(); + $obj = new stdClass(); $obj->name = $row->indexname; $_fields = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); $obj->fields = array_map(function ($v) { @@ -394,7 +397,7 @@ public function _indexData(string $table): array * Returns an array of objects with Foreign key data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _foreignKeyData(string $table): array @@ -420,7 +423,7 @@ public function _foreignKeyData(string $table): array $retVal = []; foreach ($query as $row) { - $obj = new \stdClass(); + $obj = new stdClass(); $obj->constraint_name = $row->constraint_name; $obj->table_name = $row->table_name; $obj->column_name = $row->column_name; diff --git a/system/Database/Postgre/Forge.php b/system/Database/Postgre/Forge.php index c4c45e3a42da..4ab2620555e1 100644 --- a/system/Database/Postgre/Forge.php +++ b/system/Database/Postgre/Forge.php @@ -101,17 +101,17 @@ protected function _createTableAttributes(array $attributes): string /** * ALTER TABLE * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $field Column definition + * @param string $alterType ALTER type + * @param string $table Table name + * @param mixed $field Column definition * * @return string|array|boolean */ - protected function _alterTable(string $alter_type, string $table, $field) + protected function _alterTable(string $alterType, string $table, $field) { - if (in_array($alter_type, ['DROP', 'ADD'], true)) + if (in_array($alterType, ['DROP', 'ADD'], true)) { - return parent::_alterTable($alter_type, $table, $field); + return parent::_alterTable($alterType, $table, $field); } $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); @@ -238,15 +238,15 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) * * Generates a platform-specific DROP TABLE string * - * @param string $table Table name - * @param boolean $if_exists Whether to add an IF EXISTS condition + * @param string $table Table name + * @param boolean $ifExists Whether to add an IF EXISTS condition * @param boolean $cascade * * @return string */ - protected function _dropTable(string $table, bool $if_exists, bool $cascade): string + protected function _dropTable(string $table, bool $ifExists, bool $cascade): string { - $sql = parent::_dropTable($table, $if_exists, $cascade); + $sql = parent::_dropTable($table, $ifExists, $cascade); if ($cascade === true) { diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php index ca8df0d3ee4e..b9138045efaa 100644 --- a/system/Database/Postgre/PreparedQuery.php +++ b/system/Database/Postgre/PreparedQuery.php @@ -39,8 +39,10 @@ namespace CodeIgniter\Database\Postgre; +use BadMethodCallException; use CodeIgniter\Database\BasePreparedQuery; use CodeIgniter\Database\PreparedQueryInterface; +use Exception; /** * Prepared query for Postgre @@ -78,7 +80,7 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface * Unused in the MySQLi driver. * * @return mixed - * @throws \Exception + * @throws Exception */ public function _prepare(string $sql, array $options = []) { @@ -113,7 +115,7 @@ public function _execute(array $data): bool { if (is_null($this->statement)) { - throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); } $this->result = pg_execute($this->db->connID, $this->name, $data); diff --git a/system/Database/Postgre/Result.php b/system/Database/Postgre/Result.php index 30cc9a58c44a..4b5d52c641be 100644 --- a/system/Database/Postgre/Result.php +++ b/system/Database/Postgre/Result.php @@ -42,6 +42,7 @@ use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity; +use stdClass; /** * Result for Postgre @@ -90,7 +91,7 @@ public function getFieldData(): array for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i ++) { - $retVal[$i] = new \stdClass(); + $retVal[$i] = new stdClass(); $retVal[$i]->name = pg_field_name($this->resultID, $i); $retVal[$i]->type = pg_field_type_oid($this->resultID, $i); $retVal[$i]->type_name = pg_field_type($this->resultID, $i); diff --git a/system/Database/Query.php b/system/Database/Query.php index f0b44c514d37..08c593818d10 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -168,12 +168,23 @@ public function setQuery(string $sql, $binds = null, bool $setEscape = true) /** * Will store the variables to bind into the query later. * - * @param array $binds + * @param array $binds + * @param boolean $setEscape * * @return $this */ - public function setBinds(array $binds) + public function setBinds(array $binds, bool $setEscape = true) { + if ($setEscape) + { + array_walk($binds, function (&$item) { + $item = [ + $item, + true, + ]; + }); + } + $this->binds = $binds; return $this; @@ -246,6 +257,7 @@ public function getStartTime(bool $returnRaw = false, int $decimals = 6) } //-------------------------------------------------------------------- + /** * Returns the duration of this query during execution, or null if * the query has not been executed yet. diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php index 3a01578e3f3d..1501c5f5f809 100644 --- a/system/Database/SQLite3/Builder.php +++ b/system/Database/SQLite3/Builder.php @@ -63,6 +63,15 @@ class Builder extends BaseBuilder */ protected $canLimitWhereUpdates = false; + /** + * ORDER BY random keyword + * + * @var array + */ + protected $randomKeyword = [ + 'RANDOM()', + ]; + /** * @var array */ diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index 5427a5ba4332..29bb837cd582 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -42,6 +42,10 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; +use ErrorException; +use Exception; +use SQLite3; +use stdClass; /** * Connection for SQLite3 @@ -65,24 +69,13 @@ class Connection extends BaseConnection implements ConnectionInterface //-------------------------------------------------------------------- - /** - * ORDER BY random keyword - * - * @var array - */ - protected $_random_keyword = [ - 'RANDOM()', - ]; - - //-------------------------------------------------------------------- - /** * Connect to the database. * * @param boolean $persistent * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function connect(bool $persistent = false) { @@ -98,10 +91,10 @@ public function connect(bool $persistent = false) } return (! $this->password) - ? new \SQLite3($this->database) - : new \SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password); + ? new SQLite3($this->database) + : new SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password); } - catch (\Exception $e) + catch (Exception $e) { throw new DatabaseException('SQLite3 error: ' . $e->getMessage()); } @@ -161,7 +154,7 @@ public function getVersion(): string return $this->dataCache['version']; } - $version = \SQLite3::version(); + $version = SQLite3::version(); return $this->dataCache['version'] = $version['versionString']; } @@ -183,7 +176,7 @@ public function execute(string $sql) ? $this->connID->exec($sql) : $this->connID->query($sql); } - catch (\ErrorException $e) + catch (ErrorException $e) { log_message('error', $e); if ($this->DBDebug) @@ -315,7 +308,7 @@ public function getFieldNames(string $table) * Returns an array of objects with field data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _fieldData(string $table): array @@ -334,7 +327,7 @@ public function _fieldData(string $table): array $retVal = []; for ($i = 0, $c = count($query); $i < $c; $i++) { - $retVal[$i] = new \stdClass(); + $retVal[$i] = new stdClass(); $retVal[$i]->name = $query[$i]->name; $retVal[$i]->type = $query[$i]->type; $retVal[$i]->max_length = null; @@ -352,7 +345,7 @@ public function _fieldData(string $table): array * Returns an array of objects with index data * * @param string $table - * @return \stdClass[] + * @return stdClass[] * @throws DatabaseException */ public function _indexData(string $table): array @@ -369,7 +362,7 @@ public function _indexData(string $table): array $retVal = []; foreach ($query as $row) { - $obj = new \stdClass(); + $obj = new stdClass(); $obj->name = $row->name; // Get fields for index @@ -397,7 +390,7 @@ public function _indexData(string $table): array * Returns an array of objects with Foreign key data * * @param string $table - * @return \stdClass[] + * @return stdClass[] */ public function _foreignKeyData(string $table): array { @@ -421,7 +414,7 @@ public function _foreignKeyData(string $table): array foreach ($query as $row) { - $obj = new \stdClass(); + $obj = new stdClass(); $obj->constraint_name = $row->from . ' to ' . $row->table . '.' . $row->to; $obj->table_name = $table; $obj->foreign_table_name = $row->table; diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php index 4c175a2dc91b..19a8ce9911f8 100644 --- a/system/Database/SQLite3/Forge.php +++ b/system/Database/SQLite3/Forge.php @@ -105,7 +105,7 @@ public function createDatabase(string $dbName, bool $ifNotExists = false): bool * @param string $dbName * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function dropDatabase(string $dbName): bool { @@ -149,15 +149,15 @@ public function dropDatabase(string $dbName): bool /** * ALTER TABLE * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $field Column definition + * @param string $alterType ALTER type + * @param string $table Table name + * @param mixed $field Column definition * * @return string|array|null */ - protected function _alterTable(string $alter_type, string $table, $field) + protected function _alterTable(string $alterType, string $table, $field) { - switch ($alter_type) + switch ($alterType) { case 'DROP': $sqlTable = new Table($this->db, $this); @@ -176,7 +176,7 @@ protected function _alterTable(string $alter_type, string $table, $field) return null; default: - return parent::_alterTable($alter_type, $table, $field); + return parent::_alterTable($alterType, $table, $field); } } @@ -234,7 +234,7 @@ protected function _processIndexes(string $table): array continue; } - if (in_array($i, $this->uniqueKeys)) + if (in_array($i, $this->uniqueKeys, true)) { $sqls[] = 'CREATE UNIQUE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i])) . ' ON ' . $this->db->escapeIdentifiers($table) @@ -251,6 +251,7 @@ protected function _processIndexes(string $table): array } //-------------------------------------------------------------------- + /** * Field attribute TYPE * @@ -307,7 +308,7 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) * @param string $foreignName Foreign name * * @return boolean - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function dropForeignKey(string $table, string $foreignName): bool { diff --git a/system/Database/SQLite3/PreparedQuery.php b/system/Database/SQLite3/PreparedQuery.php index f5f5c54361d4..cc79f4a9be48 100644 --- a/system/Database/SQLite3/PreparedQuery.php +++ b/system/Database/SQLite3/PreparedQuery.php @@ -39,6 +39,7 @@ namespace CodeIgniter\Database\SQLite3; +use BadMethodCallException; use CodeIgniter\Database\BasePreparedQuery; use CodeIgniter\Database\PreparedQueryInterface; @@ -99,7 +100,7 @@ public function _execute(array $data): bool { if (is_null($this->statement)) { - throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); } foreach ($data as $key => $item) diff --git a/system/Database/SQLite3/Result.php b/system/Database/SQLite3/Result.php index 2fe310ed90d2..29a52e3cf868 100644 --- a/system/Database/SQLite3/Result.php +++ b/system/Database/SQLite3/Result.php @@ -38,10 +38,12 @@ namespace CodeIgniter\Database\SQLite3; +use Closure; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity; +use stdClass; /** * Result for SQLite3 @@ -86,7 +88,7 @@ public function getFieldNames(): array */ public function getFieldData(): array { - static $data_types = [ + static $dataTypes = [ SQLITE3_INTEGER => 'integer', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', @@ -99,11 +101,11 @@ public function getFieldData(): array for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i ++) { - $retVal[$i] = new \stdClass(); + $retVal[$i] = new stdClass(); $retVal[$i]->name = $this->resultID->columnName($i); // @phpstan-ignore-line $type = $this->resultID->columnType($i); // @phpstan-ignore-line $retVal[$i]->type = $type; - $retVal[$i]->type_name = isset($data_types[$type]) ? $data_types[$type] : null; + $retVal[$i]->type_name = isset($dataTypes[$type]) ? $dataTypes[$type] : null; $retVal[$i]->max_length = null; $retVal[$i]->length = null; } @@ -138,7 +140,7 @@ public function freeResult() * @param integer $n * * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException */ public function dataSeek(int $n = 0) { @@ -182,7 +184,8 @@ protected function fetchObject(string $className = 'stdClass') { return false; } - elseif ($className === 'stdClass') + + if ($className === 'stdClass') { return (object) $row; } @@ -194,7 +197,7 @@ protected function fetchObject(string $className = 'stdClass') return $classObj->setAttributes($row); } - $classSet = \Closure::bind(function ($key, $value) { + $classSet = Closure::bind(function ($key, $value) { $this->$key = $value; }, $classObj, $className ); diff --git a/system/Database/SQLite3/Table.php b/system/Database/SQLite3/Table.php index 53d3f7757c42..c4df34a68856 100644 --- a/system/Database/SQLite3/Table.php +++ b/system/Database/SQLite3/Table.php @@ -121,7 +121,7 @@ public function __construct(Connection $db, Forge $forge) * * @param string $table * - * @return \CodeIgniter\Database\SQLite3\Table + * @return Table */ public function fromTable(string $table) { @@ -158,6 +158,7 @@ public function fromTable(string $table) * Called after `fromTable` and any actions, like `dropColumn`, etc, * to finalize the action. It creates a temp table, creates the new * table with modifications, and copies the data over to the new table. + * Resets the connection dataCache to be sure changes are collected. * * @return boolean */ @@ -181,6 +182,8 @@ public function run(): bool $this->db->query('PRAGMA foreign_keys = ON'); + $this->db->resetDataCache(); + return $success; } @@ -189,7 +192,7 @@ public function run(): bool * * @param string|array $columns * - * @return \CodeIgniter\Database\SQLite3\Table + * @return Table */ public function dropColumn($columns) { @@ -218,7 +221,7 @@ public function dropColumn($columns) * * @param array $field * - * @return \CodeIgniter\Database\SQLite3\Table + * @return Table */ public function modifyColumn(array $field) { @@ -238,7 +241,7 @@ public function modifyColumn(array $field) * * @param string $column * - * @return \CodeIgniter\Database\SQLite3\Table + * @return Table */ public function dropForeignKey(string $column) { diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php index 68057096b553..bf34473bc291 100644 --- a/system/Database/Seeder.php +++ b/system/Database/Seeder.php @@ -40,14 +40,16 @@ namespace CodeIgniter\Database; use CodeIgniter\CLI\CLI; -use Config\Database as DatabaseConfig; +use Config\Database; +use Faker\Factory; +use Faker\Generator; +use InvalidArgumentException; /** * Class Seeder */ class Seeder { - /** * The name of the database group to use. * @@ -65,7 +67,7 @@ class Seeder /** * An instance of the main Database configuration * - * @var DatabaseConfig + * @var Database */ protected $config; @@ -90,56 +92,72 @@ class Seeder */ protected $silent = false; - //-------------------------------------------------------------------- + /** + * Faker Generator instance. + * + * @var \Faker\Generator|null + */ + private static $faker; /** * Seeder constructor. * - * @param DatabaseConfig $config - * @param BaseConnection $db + * @param Database $config + * @param BaseConnection|null $db */ - public function __construct(DatabaseConfig $config, BaseConnection $db = null) + public function __construct(Database $config, BaseConnection $db = null) { $this->seedPath = $config->filesPath ?? APPPATH . 'Database/'; if (empty($this->seedPath)) { - throw new \InvalidArgumentException('Invalid filesPath set in the Config\Database.'); + throw new InvalidArgumentException('Invalid filesPath set in the Config\Database.'); } - $this->seedPath = rtrim($this->seedPath, '/') . '/Seeds/'; + $this->seedPath = rtrim($this->seedPath, '\\/') . '/Seeds/'; if (! is_dir($this->seedPath)) { - throw new \InvalidArgumentException('Unable to locate the seeds directory. Please check Config\Database::filesPath'); + throw new InvalidArgumentException('Unable to locate the seeds directory. Please check Config\Database::filesPath'); } - $this->config = & $config; + $this->config = &$config; + + $db = $db ?? Database::connect($this->DBGroup); - if (is_null($db)) + $this->db = &$db; + $this->forge = Database::forge($this->DBGroup); + } + + /** + * Gets the Faker Generator instance. + * + * @return Generator|null + */ + public static function faker(): ?Generator + { + if (self::$faker === null && class_exists(Factory::class)) { - $db = \Config\Database::connect($this->DBGroup); + self::$faker = Factory::create(); } - $this->db = & $db; - - $this->forge = \Config\Database::forge($this->DBGroup); + return self::$faker; } - //-------------------------------------------------------------------- - /** * Loads the specified seeder and runs it. * * @param string $class * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException + * + * @return void */ public function call(string $class) { if (empty($class)) { - throw new \InvalidArgumentException('No Seeder was specified.'); + throw new InvalidArgumentException('No seeder was specified.'); } $path = str_replace('.php', '', $class) . '.php'; @@ -147,6 +165,9 @@ public function call(string $class) // If we have namespaced class, simply try to load it. if (strpos($class, '\\') !== false) { + /** + * @var Seeder + */ $seeder = new $class($this->config); } // Otherwise, try to load the class manually. @@ -156,10 +177,11 @@ public function call(string $class) if (! is_file($path)) { - throw new \InvalidArgumentException('The specified Seeder is not a valid file: ' . $path); + throw new InvalidArgumentException('The specified seeder is not a valid file: ' . $path); } // Assume the class has the correct namespace + // @codeCoverageIgnoreStart $class = APP_NAMESPACE . '\Database\Seeds\\' . $class; if (! class_exists($class, false)) @@ -167,11 +189,14 @@ public function call(string $class) require_once $path; } + /** + * @var Seeder + */ $seeder = new $class($this->config); + // @codeCoverageIgnoreEnd } - $seeder->setSilent($this->silent); - $seeder->run(); + $seeder->setSilent($this->silent)->run(); unset($seeder); @@ -181,30 +206,26 @@ public function call(string $class) } } - //-------------------------------------------------------------------- - /** * Sets the location of the directory that seed files can be located in. * * @param string $path * - * @return Seeder + * @return $this */ public function setPath(string $path) { - $this->seedPath = rtrim($path, '/') . '/'; + $this->seedPath = rtrim($path, '\\/') . '/'; return $this; } - //-------------------------------------------------------------------- - /** * Sets the silent treatment. * * @param boolean $silent * - * @return Seeder + * @return $this */ public function setSilent(bool $silent) { @@ -213,8 +234,6 @@ public function setSilent(bool $silent) return $this; } - //-------------------------------------------------------------------- - /** * Run the database seeds. This is where the magic happens. * @@ -222,10 +241,10 @@ public function setSilent(bool $silent) * of inserting their data here. * * @return mixed + * + * @codeCoverageIgnore */ public function run() { } - - //-------------------------------------------------------------------- } diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index 5e4b824a978e..4a27d7d0b848 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -42,6 +42,7 @@ use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\Response; +use Config\Exceptions as ExceptionsConfig; use Config\Paths; use function error_reporting; use ErrorException; @@ -73,21 +74,21 @@ class Exceptions /** * Config for debug exceptions. * - * @var \Config\Exceptions + * @var ExceptionsConfig */ protected $config; /** * The incoming request. * - * @var \CodeIgniter\HTTP\IncomingRequest + * @var IncomingRequest */ protected $request; /** * The outgoing response. * - * @var \CodeIgniter\HTTP\Response + * @var Response */ protected $response; @@ -96,11 +97,11 @@ class Exceptions /** * Constructor. * - * @param \Config\Exceptions $config - * @param \CodeIgniter\HTTP\IncomingRequest $request - * @param \CodeIgniter\HTTP\Response $response + * @param ExceptionsConfig $config + * @param IncomingRequest $request + * @param Response $response */ - public function __construct(\Config\Exceptions $config, IncomingRequest $request, Response $response) + public function __construct(ExceptionsConfig $config, IncomingRequest $request, Response $response) { $this->ob_level = ob_get_level(); @@ -138,7 +139,7 @@ public function initialize() * (Yay PHP7!). Will log the error, display it if display_errors is on, * and fire an event that allows custom actions to be taken at this point. * - * @param \Throwable $exception + * @param Throwable $exception * * @codeCoverageIgnore */ @@ -150,7 +151,7 @@ public function exceptionHandler(Throwable $exception) ] = $this->determineCodes($exception); // Log it - if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes)) + if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes, true)) { log_message('critical', $exception->getMessage() . "\n{trace}", [ 'trace' => $exception->getTraceAsString(), @@ -190,7 +191,7 @@ public function exceptionHandler(Throwable $exception) * @param string|null $file * @param integer|null $line * - * @throws \ErrorException + * @throws ErrorException */ public function errorHandler(int $severity, string $message, string $file = null, int $line = null) { @@ -219,7 +220,7 @@ public function shutdownHandler() if (! is_null($error)) { // Fatal Error? - if (in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) + if (in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) { $this->exceptionHandler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line'])); } @@ -232,16 +233,16 @@ public function shutdownHandler() * Determines the view to display based on the exception thrown, * whether an HTTP or CLI request, etc. * - * @param \Throwable $exception - * @param string $template_path + * @param Throwable $exception + * @param string $templatePath * * @return string The path and filename of the view file to use */ - protected function determineView(Throwable $exception, string $template_path): string + protected function determineView(Throwable $exception, string $templatePath): string { // Production environments should have a custom exception file. - $view = 'production.php'; - $template_path = rtrim($template_path, '\\/ ') . DIRECTORY_SEPARATOR; + $view = 'production.php'; + $templatePath = rtrim($templatePath, '\\/ ') . DIRECTORY_SEPARATOR; if (str_ireplace(['off', 'none', 'no', 'false', 'null'], '', ini_get('display_errors'))) { @@ -255,7 +256,7 @@ protected function determineView(Throwable $exception, string $template_path): s } // Allow for custom views based upon the status code - if (is_file($template_path . 'error_' . $exception->getCode() . '.php')) + if (is_file($templatePath . 'error_' . $exception->getCode() . '.php')) { return 'error_' . $exception->getCode() . '.php'; } @@ -268,8 +269,8 @@ protected function determineView(Throwable $exception, string $template_path): s /** * Given an exception and status code will display the error to the client. * - * @param \Throwable $exception - * @param integer $statusCode + * @param Throwable $exception + * @param integer $statusCode */ protected function render(Throwable $exception, int $statusCode) { @@ -316,8 +317,8 @@ protected function render(Throwable $exception, int $statusCode) /** * Gathers the variables that will be made available to the view. * - * @param \Throwable $exception - * @param integer $statusCode + * @param Throwable $exception + * @param integer $statusCode * * @return array */ @@ -337,7 +338,7 @@ protected function collectVars(Throwable $exception, int $statusCode): array /** * Determines the HTTP status code and the exit status code for this request. * - * @param \Throwable $exception + * @param Throwable $exception * * @return array */ @@ -389,8 +390,8 @@ public static function cleanPath(string $file): string case strpos($file, SYSTEMPATH) === 0: $file = 'SYSTEMPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(SYSTEMPATH)); break; - case strpos($file, FCPATH) === 0: // @phpstan-ignore-line - $file = 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH)); // @phpstan-ignore-line + case strpos($file, FCPATH) === 0: + $file = 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH)); break; case defined('VENDORPATH') && strpos($file, VENDORPATH) === 0: $file = 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(VENDORPATH)); @@ -416,7 +417,7 @@ public static function describeMemory(int $bytes): string { return $bytes . 'B'; } - else if ($bytes < 1048576) + if ($bytes < 1048576) { return round($bytes / 1024, 2) . 'KB'; } diff --git a/system/Debug/Iterator.php b/system/Debug/Iterator.php index c9101df11a31..e3b6d7f53fff 100644 --- a/system/Debug/Iterator.php +++ b/system/Debug/Iterator.php @@ -38,6 +38,8 @@ namespace CodeIgniter\Debug; +use Closure; + /** * Iterator for debugging. */ @@ -66,12 +68,12 @@ class Iterator * Tests are simply closures that the user can define any sequence of * things to happen during the test. * - * @param string $name - * @param \Closure $closure + * @param string $name + * @param Closure $closure * * @return $this */ - public function add(string $name, \Closure $closure) + public function add(string $name, Closure $closure) { $name = strtolower($name); @@ -99,21 +101,21 @@ public function run(int $iterations = 1000, bool $output = true) // clear memory before start gc_collect_cycles(); - $start = microtime(true); - $start_mem = $max_memory = memory_get_usage(true); + $start = microtime(true); + $startMem = $maxMemory = memory_get_usage(true); for ($i = 0; $i < $iterations; $i ++) { $result = $test(); - $max_memory = max($max_memory, memory_get_usage(true)); + $maxMemory = max($maxMemory, memory_get_usage(true)); unset($result); } $this->results[$name] = [ 'time' => microtime(true) - $start, - 'memory' => $max_memory - $start_mem, + 'memory' => $maxMemory - $startMem, 'n' => $iterations, ]; } diff --git a/system/Debug/Timer.php b/system/Debug/Timer.php index 736f558b3a3f..5e4ae4307463 100644 --- a/system/Debug/Timer.php +++ b/system/Debug/Timer.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Debug; +use RuntimeException; + /** * Class Timer * @@ -98,7 +100,7 @@ public function stop(string $name) if (empty($this->timers[$name])) { - throw new \RuntimeException('Cannot stop timer: invalid name given.'); + throw new RuntimeException('Cannot stop timer: invalid name given.'); } $this->timers[$name]['end'] = microtime(true); diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index c83026ef6e06..6224f2edd999 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -38,6 +38,9 @@ namespace CodeIgniter\Debug; +use CodeIgniter\CodeIgniter; +use CodeIgniter\Debug\Toolbar\Collectors\BaseCollector; +use CodeIgniter\Debug\Toolbar\Collectors\Config; use CodeIgniter\Debug\Toolbar\Collectors\History; use CodeIgniter\Format\JSONFormatter; use CodeIgniter\Format\XMLFormatter; @@ -69,7 +72,7 @@ class Toolbar /** * Collectors to be used and displayed. * - * @var \CodeIgniter\Debug\Toolbar\Collectors\BaseCollector[] + * @var BaseCollector[] */ protected $collectors = []; @@ -102,10 +105,10 @@ public function __construct(ToolbarConfig $config) /** * Returns all the data required by Debug Bar * - * @param float $startTime App start time - * @param float $totalTime - * @param \CodeIgniter\HTTP\RequestInterface $request - * @param \CodeIgniter\HTTP\ResponseInterface $response + * @param float $startTime App start time + * @param float $totalTime + * @param RequestInterface $request + * @param ResponseInterface $response * * @return string JSON encoded data */ @@ -120,7 +123,7 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques $data['totalMemory'] = number_format((memory_get_peak_usage()) / 1024 / 1024, 3); $data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7); $data['segmentCount'] = (int) ceil($data['totalTime'] / $data['segmentDuration']); - $data['CI_VERSION'] = \CodeIgniter\CodeIgniter::CI_VERSION; + $data['CI_VERSION'] = CodeIgniter::CI_VERSION; $data['collectors'] = []; foreach ($this->collectors as $collector) @@ -198,7 +201,7 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques 'contentType' => esc($response->getHeaderLine('content-type')), ]; - $data['config'] = \CodeIgniter\Debug\Toolbar\Collectors\Config::display(); + $data['config'] = Config::display(); if ($response->CSP !== null) { diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php index 00aa7400eaf5..9388444dfeac 100644 --- a/system/Debug/Toolbar/Collectors/Database.php +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -106,7 +106,7 @@ public function __construct() * The static method used during Events to collect * data. * - * @param \CodeIgniter\Database\Query $query + * @param Query $query * * @internal param $ array \CodeIgniter\Database\Query */ diff --git a/system/Debug/Toolbar/Collectors/Events.php b/system/Debug/Toolbar/Collectors/Events.php index 111675ee2311..1e14c79d0481 100644 --- a/system/Debug/Toolbar/Collectors/Events.php +++ b/system/Debug/Toolbar/Collectors/Events.php @@ -145,17 +145,22 @@ public function display(): array { $data['events'][$key] = [ 'event' => $key, - 'duration' => number_format(($row['end'] - $row['start']) * 1000, 2), + 'duration' => ($row['end'] - $row['start']) * 1000, 'count' => 1, ]; continue; } - $data['events'][$key]['duration'] += number_format(($row['end'] - $row['start']) * 1000, 2); + $data['events'][$key]['duration'] += ($row['end'] - $row['start']) * 1000; $data['events'][$key]['count']++; } + foreach ($data['events'] as &$row) + { + $row['duration'] = number_format($row['duration'], 2); + } + return $data; } diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index c7f96ade60de..13bf70646626 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -39,6 +39,9 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; use Config\Services; +use ReflectionException; +use ReflectionFunction; +use ReflectionMethod; /** * Routes collector @@ -76,7 +79,7 @@ class Routes extends BaseCollector * Returns the data of this collector to be formatted in the toolbar * * @return array - * @throws \ReflectionException + * @throws ReflectionException */ public function display(): array { @@ -92,19 +95,19 @@ public function display(): array // Closure routes if (is_callable($router->controllerName())) { - $method = new \ReflectionFunction($router->controllerName()); + $method = new ReflectionFunction($router->controllerName()); } else { try { - $method = new \ReflectionMethod($router->controllerName(), $router->methodName()); + $method = new ReflectionMethod($router->controllerName(), $router->methodName()); } - catch (\ReflectionException $e) + catch (ReflectionException $e) { // If we're here, the method doesn't exist // and is likely calculated in _remap. - $method = new \ReflectionMethod($router->controllerName(), '_remap'); + $method = new ReflectionMethod($router->controllerName(), '_remap'); } } diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js index 6319d1ccf2dc..ca38f9b47bb5 100644 --- a/system/Debug/Toolbar/Views/toolbar.js +++ b/system/Debug/Toolbar/Views/toolbar.js @@ -579,7 +579,7 @@ var ciDebugBar = { var expires = ""; } - document.cookie = name + "=" + value + expires + "; path=/"; + document.cookie = name + "=" + value + expires + "; path=/; samesite=Lax"; }, //-------------------------------------------------------------------- diff --git a/system/Email/Email.php b/system/Email/Email.php index 57cf79e248f2..90863f29830c 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -41,6 +41,7 @@ use CodeIgniter\Events\Events; use Config\Mimes; +use ErrorException; /** * CodeIgniter Email Class @@ -226,6 +227,7 @@ class Email */ public $BCCBatchSize = 200; //-------------------------------------------------------------------- + /** * Subject header * @@ -367,6 +369,7 @@ class Email */ protected static $func_overload; //-------------------------------------------------------------------- + /** * Constructor - Sets Email Preferences * @@ -380,6 +383,7 @@ public function __construct($config = null) isset(static::$func_overload) || static::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); } //-------------------------------------------------------------------- + /** * Initialize preferences * @@ -414,6 +418,7 @@ public function initialize($config) return $this; } //-------------------------------------------------------------------- + /** * Initialize the Email Data * @@ -441,6 +446,7 @@ public function clear($clearAttachments = false) return $this; } //-------------------------------------------------------------------- + /** * Set FROM * @@ -491,6 +497,7 @@ public function setFrom($from, $name = '', $returnPath = null) return $this; } //-------------------------------------------------------------------- + /** * Set Reply-to * @@ -531,6 +538,7 @@ public function setReplyTo($replyto, $name = '') return $this; } //-------------------------------------------------------------------- + /** * Set Recipients * @@ -554,6 +562,7 @@ public function setTo($to) return $this; } //-------------------------------------------------------------------- + /** * Set CC * @@ -577,6 +586,7 @@ public function setCC($cc) return $this; } //-------------------------------------------------------------------- + /** * Set BCC * @@ -609,6 +619,7 @@ public function setBCC($bcc, $limit = '') return $this; } //-------------------------------------------------------------------- + /** * Set Email Subject * @@ -625,6 +636,7 @@ public function setSubject($subject) return $this; } //-------------------------------------------------------------------- + /** * Set Body * @@ -638,6 +650,7 @@ public function setMessage($body) return $this; } //-------------------------------------------------------------------- + /** * Assign file attachments * @@ -686,6 +699,7 @@ public function attach($file, $disposition = '', $newname = null, $mime = '') return $this; } //-------------------------------------------------------------------- + /** * Set and return attachment Content-ID * @@ -709,6 +723,7 @@ public function setAttachmentCID($filename) return false; } //-------------------------------------------------------------------- + /** * Add a Header Item * @@ -723,6 +738,7 @@ public function setHeader($header, $value) return $this; } //-------------------------------------------------------------------- + /** * Convert a String to an Array * @@ -739,6 +755,7 @@ protected function stringToArray($email) return $email; } //-------------------------------------------------------------------- + /** * Set Multipart Value * @@ -752,6 +769,7 @@ public function setAltMessage($str) return $this; } //-------------------------------------------------------------------- + /** * Set Mailtype * @@ -765,6 +783,7 @@ public function setMailType($type = 'text') return $this; } //-------------------------------------------------------------------- + /** * Set Wordwrap * @@ -778,6 +797,7 @@ public function setWordWrap($wordWrap = true) return $this; } //-------------------------------------------------------------------- + /** * Set Protocol * @@ -791,6 +811,7 @@ public function setProtocol($protocol = 'mail') return $this; } //-------------------------------------------------------------------- + /** * Set Priority * @@ -804,6 +825,7 @@ public function setPriority($n = 3) return $this; } //-------------------------------------------------------------------- + /** * Set Newline Character * @@ -813,10 +835,11 @@ public function setPriority($n = 3) */ public function setNewline($newline = "\n") { - $this->newline = in_array($newline, ["\n", "\r\n", "\r"]) ? $newline : "\n"; + $this->newline = in_array($newline, ["\n", "\r\n", "\r"], true) ? $newline : "\n"; return $this; } //-------------------------------------------------------------------- + /** * Set CRLF * @@ -830,6 +853,7 @@ public function setCRLF($CRLF = "\n") return $this; } //-------------------------------------------------------------------- + /** * Get the Message ID * @@ -841,6 +865,7 @@ protected function getMessageID() return '<' . uniqid('', true) . strstr($from, '@') . '>'; } //-------------------------------------------------------------------- + /** * Get Mail Protocol * @@ -853,6 +878,7 @@ protected function getProtocol() return $this->protocol; } //-------------------------------------------------------------------- + /** * Get Mail Encoding * @@ -860,7 +886,7 @@ protected function getProtocol() */ protected function getEncoding() { - in_array($this->encoding, $this->bitDepths) || $this->encoding = '8bit'; // @phpstan-ignore-line + in_array($this->encoding, $this->bitDepths, true) || $this->encoding = '8bit'; // @phpstan-ignore-line foreach ($this->baseCharsets as $charset) { if (strpos($this->charset, $charset) === 0) @@ -872,6 +898,7 @@ protected function getEncoding() return $this->encoding; } //-------------------------------------------------------------------- + /** * Get content type (text/html/attachment) * @@ -883,16 +910,15 @@ protected function getContentType() { return empty($this->attachments) ? 'html' : 'html-attach'; } - elseif ($this->mailType === 'text' && ! empty($this->attachments)) + + if ($this->mailType === 'text' && ! empty($this->attachments)) { return 'plain-attach'; } - else - { - return 'plain'; - } + return 'plain'; } //-------------------------------------------------------------------- + /** * Set RFC 822 Date * @@ -907,6 +933,7 @@ protected function setDate() return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone); } //-------------------------------------------------------------------- + /** * Mime message * @@ -917,6 +944,7 @@ protected function getMimeMessage() return 'This is a multi-part message in MIME format.' . $this->newline . 'Your email application may not support this format.'; } //-------------------------------------------------------------------- + /** * Validate Email Address * @@ -942,6 +970,7 @@ public function validateEmail($email) return true; } //-------------------------------------------------------------------- + /** * Email Validation * @@ -960,6 +989,7 @@ public function isValidEmail($email) return (bool) filter_var($email, FILTER_VALIDATE_EMAIL); } //-------------------------------------------------------------------- + /** * Clean Extended Email Address: Joe Smith * @@ -981,6 +1011,7 @@ public function cleanEmail($email) return $cleanEmail; } //-------------------------------------------------------------------- + /** * Build alternative plain text message * @@ -1008,6 +1039,7 @@ protected function getAltMessage() return ($this->wordWrap) ? $this->wordWrap($body, 76) : $body; } //-------------------------------------------------------------------- + /** * Word Wrap * @@ -1088,6 +1120,7 @@ public function wordWrap($str, $charlim = null) return $output; } //-------------------------------------------------------------------- + /** * Build final headers */ @@ -1101,6 +1134,7 @@ protected function buildHeaders() $this->setHeader('Mime-Version', '1.0'); } //-------------------------------------------------------------------- + /** * Write Headers as a string */ @@ -1130,6 +1164,7 @@ protected function writeHeaders() } } //-------------------------------------------------------------------- + /** * Build Final Body and attachments */ @@ -1207,27 +1242,27 @@ protected function buildMessage() $this->appendAttachments($body, $boundary); break; case 'html-attach': - $alt_boundary = uniqid('B_ALT_', true); - $last_boundary = null; + $altBoundary = uniqid('B_ALT_', true); + $lastBoundary = null; if ($this->attachmentsHaveMultipart('mixed')) { - $atc_boundary = uniqid('B_ATC_', true); - $hdr .= 'Content-Type: multipart/mixed; boundary="' . $atc_boundary . '"'; - $last_boundary = $atc_boundary; + $atcBoundary = uniqid('B_ATC_', true); + $hdr .= 'Content-Type: multipart/mixed; boundary="' . $atcBoundary . '"'; + $lastBoundary = $atcBoundary; } if ($this->attachmentsHaveMultipart('related')) { - $rel_boundary = uniqid('B_REL_', true); - $rel_boundary_header = 'Content-Type: multipart/related; boundary="' . $rel_boundary . '"'; - if (isset($last_boundary)) + $relBoundary = uniqid('B_REL_', true); + $relBoundaryHeader = 'Content-Type: multipart/related; boundary="' . $relBoundary . '"'; + if (isset($lastBoundary)) { - $body .= '--' . $last_boundary . $this->newline . $rel_boundary_header; + $body .= '--' . $lastBoundary . $this->newline . $relBoundaryHeader; } else { - $hdr .= $rel_boundary_header; + $hdr .= $relBoundaryHeader; } - $last_boundary = $rel_boundary; + $lastBoundary = $relBoundary; } if ($this->getProtocol() === 'mail') { @@ -1235,27 +1270,27 @@ protected function buildMessage() } static::strlen($body) && $body .= $this->newline . $this->newline; $body .= $this->getMimeMessage() . $this->newline . $this->newline - . '--' . $last_boundary . $this->newline - . 'Content-Type: multipart/alternative; boundary="' . $alt_boundary . '"' . $this->newline . $this->newline - . '--' . $alt_boundary . $this->newline + . '--' . $lastBoundary . $this->newline + . 'Content-Type: multipart/alternative; boundary="' . $altBoundary . '"' . $this->newline . $this->newline + . '--' . $altBoundary . $this->newline . 'Content-Type: text/plain; charset=' . $this->charset . $this->newline . 'Content-Transfer-Encoding: ' . $this->getEncoding() . $this->newline . $this->newline . $this->getAltMessage() . $this->newline . $this->newline - . '--' . $alt_boundary . $this->newline + . '--' . $altBoundary . $this->newline . 'Content-Type: text/html; charset=' . $this->charset . $this->newline . 'Content-Transfer-Encoding: quoted-printable' . $this->newline . $this->newline . $this->prepQuotedPrintable($this->body) . $this->newline . $this->newline - . '--' . $alt_boundary . '--' . $this->newline . $this->newline; - if (! empty($rel_boundary)) + . '--' . $altBoundary . '--' . $this->newline . $this->newline; + if (! empty($relBoundary)) { $body .= $this->newline . $this->newline; - $this->appendAttachments($body, $rel_boundary, 'related'); + $this->appendAttachments($body, $relBoundary, 'related'); } // multipart/mixed attachments - if (! empty($atc_boundary)) + if (! empty($atcBoundary)) { $body .= $this->newline . $this->newline; - $this->appendAttachments($body, $atc_boundary, 'mixed'); + $this->appendAttachments($body, $atcBoundary, 'mixed'); } break; } @@ -1274,6 +1309,7 @@ protected function attachmentsHaveMultipart($type) return false; } //-------------------------------------------------------------------- + /** * Prepares attachment string * @@ -1305,6 +1341,7 @@ protected function appendAttachments(&$body, $boundary, $multipart = null) empty($name) || $body .= '--' . $boundary . '--'; } //-------------------------------------------------------------------- + /** * Prep Quoted Printable * @@ -1320,7 +1357,7 @@ protected function prepQuotedPrintable($str) // ASCII code numbers for "safe" characters that can always be // used literally, without encoding, as described in RFC 2049. // http://www.ietf.org/rfc/rfc2049.txt - static $ascii_safe_chars = [ + static $asciiSafeChars = [ // ' ( ) + , - . / : = ? 39, 40, @@ -1447,7 +1484,7 @@ protected function prepQuotedPrintable($str) { $char = $escape . strtoupper(sprintf('%02s', dechex($ascii))); // =3D } - elseif (! in_array($ascii, $ascii_safe_chars, true)) + elseif (! in_array($ascii, $asciiSafeChars, true)) { $char = $escape . strtoupper(sprintf('%02s', dechex($ascii))); } @@ -1468,6 +1505,7 @@ protected function prepQuotedPrintable($str) return static::substr($output, 0, static::strlen($this->CRLF) * -1); } //-------------------------------------------------------------------- + /** * Prep Q Encoding * @@ -1536,6 +1574,7 @@ protected function prepQEncoding($str) return $output . '?='; } //-------------------------------------------------------------------- + /** * Send Email * @@ -1590,6 +1629,7 @@ public function send($autoClear = true) return $result; } //-------------------------------------------------------------------- + /** * Batch Bcc Send. Sends groups of BCCs in batches */ @@ -1636,6 +1676,7 @@ public function batchBCCSend() Events::trigger('email', $this->archive); } //-------------------------------------------------------------------- + /** * Unwrap special elements */ @@ -1649,6 +1690,7 @@ protected function unwrapSpecials() ); } //-------------------------------------------------------------------- + /** * Strip line-breaks via callback * @@ -1665,6 +1707,7 @@ protected function removeNLCallback($matches) return $matches[1]; } //-------------------------------------------------------------------- + /** * Spool mail to the mail server * @@ -1679,7 +1722,7 @@ protected function spoolEmail() { $success = $this->$method(); } - catch (\ErrorException $e) + catch (ErrorException $e) { $success = false; log_message('error', 'Email: ' . $method . ' throwed ' . $e->getMessage()); @@ -1693,6 +1736,7 @@ protected function spoolEmail() return true; } //-------------------------------------------------------------------- + /** * Validate email for shell * @@ -1720,6 +1764,7 @@ protected function validateEmailForShell(&$email) return (filter_var($email, FILTER_VALIDATE_EMAIL) === $email && preg_match('#\A[a-z0-9._+-]+@[a-z0-9.-]{1,253}\z#i', $email)); } //-------------------------------------------------------------------- + /** * Send using mail() * @@ -1741,6 +1786,7 @@ protected function sendWithMail() return mail($recipients, $this->subject, $this->finalBody, $this->headerStr, '-f ' . $from); } //-------------------------------------------------------------------- + /** * Send using Sendmail * @@ -1777,6 +1823,7 @@ protected function sendWithSendmail() return true; } //-------------------------------------------------------------------- + /** * Send using SMTP * @@ -1841,6 +1888,7 @@ protected function sendWithSmtp() return true; } //-------------------------------------------------------------------- + /** * SMTP End * @@ -1851,6 +1899,7 @@ protected function SMTPEnd() $this->sendCommand($this->SMTPKeepAlive ? 'reset' : 'quit'); } //-------------------------------------------------------------------- + /** * SMTP Connect * @@ -1898,6 +1947,7 @@ protected function SMTPConnect() return $this->sendCommand('hello'); } //-------------------------------------------------------------------- + /** * Send SMTP command * @@ -1967,6 +2017,7 @@ protected function sendCommand($cmd, $data = '') return true; } //-------------------------------------------------------------------- + /** * SMTP Authenticate * @@ -1989,7 +2040,8 @@ protected function SMTPAuthenticate() { return true; } - elseif (strpos($reply, '334') !== 0) + + if (strpos($reply, '334') !== 0) { $this->setErrorMessage(lang('Email.failedSMTPLogin', [$reply])); return false; @@ -2015,6 +2067,7 @@ protected function SMTPAuthenticate() return true; } //-------------------------------------------------------------------- + /** * Send SMTP data * @@ -2059,6 +2112,7 @@ protected function sendData($data) return true; } //-------------------------------------------------------------------- + /** * Get SMTP data * @@ -2078,6 +2132,7 @@ protected function getSMTPData() return $data; } //-------------------------------------------------------------------- + /** * Get Hostname * @@ -2099,6 +2154,7 @@ protected function getHostname() return isset($_SERVER['SERVER_ADDR']) ? '[' . $_SERVER['SERVER_ADDR'] . ']' : '[127.0.0.1]'; } //-------------------------------------------------------------------- + /** * Get Debug Message * @@ -2111,14 +2167,15 @@ public function printDebugger($include = ['headers', 'subject', 'body']) { $msg = implode('', $this->debugMessage); // Determine which parts of our raw data needs to be printed - $raw_data = ''; - is_array($include) || $include = [$include]; // @phpstan-ignore-line - in_array('headers', $include, true) && $raw_data = htmlspecialchars($this->headerStr) . "\n"; - in_array('subject', $include, true) && $raw_data .= htmlspecialchars($this->subject) . "\n"; - in_array('body', $include, true) && $raw_data .= htmlspecialchars($this->finalBody); - return $msg . ($raw_data === '' ? '' : '
' . $raw_data . '
'); + $rawData = ''; + is_array($include) || $include = [$include]; // @phpstan-ignore-line + in_array('headers', $include, true) && $rawData = htmlspecialchars($this->headerStr) . "\n"; + in_array('subject', $include, true) && $rawData .= htmlspecialchars($this->subject) . "\n"; + in_array('body', $include, true) && $rawData .= htmlspecialchars($this->finalBody); + return $msg . ($rawData === '' ? '' : '
' . $rawData . '
'); } //-------------------------------------------------------------------- + /** * Set Message * @@ -2129,6 +2186,7 @@ protected function setErrorMessage($msg) $this->debugMessage[] = $msg . '
'; } //-------------------------------------------------------------------- + /** * Mime Types * @@ -2142,6 +2200,7 @@ protected function mimeTypes($ext = '') return ! empty($mime) ? $mime : 'application/x-unknown-content-type'; } //-------------------------------------------------------------------- + /** * Destructor */ @@ -2150,6 +2209,7 @@ public function __destruct() is_resource($this->SMTPConnect) && $this->sendCommand('quit'); } //-------------------------------------------------------------------- + /** * Byte-safe strlen() * @@ -2162,6 +2222,7 @@ protected static function strlen($str) return (static::$func_overload) ? mb_strlen($str, '8bit') : strlen($str); } //-------------------------------------------------------------------- + /** * Byte-safe substr() * @@ -2180,6 +2241,7 @@ protected static function substr($str, $start, $length = null) return isset($length) ? substr($str, $start, $length) : substr($str, $start); } //-------------------------------------------------------------------- + /** * Determines the values that should be stored in $archive. * diff --git a/system/Encryption/EncrypterInterface.php b/system/Encryption/EncrypterInterface.php index 8da118a3cb99..3f14c862a12b 100644 --- a/system/Encryption/EncrypterInterface.php +++ b/system/Encryption/EncrypterInterface.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Encryption; +use CodeIgniter\Encryption\Exceptions\EncryptionException; + /** * CodeIgniter Encryption Handler * @@ -46,12 +48,13 @@ */ interface EncrypterInterface { - /** * Encrypt - convert plaintext into ciphertext * - * @param string $data Input data - * @param array $params Over-ridden parameters, specifically the key + * @param string $data Input data + * @param array|string|null $params Overridden parameters, specifically the key + * + * @throws EncryptionException * * @return string */ @@ -60,8 +63,10 @@ public function encrypt($data, $params = null); /** * Decrypt - convert ciphertext into plaintext * - * @param string $data Encrypted data - * @param array $params Over-ridden parameters, specifically the key + * @param string $data Encrypted data + * @param array|string|null $params Overridden parameters, specifically the key + * + * @throws EncryptionException * * @return string */ diff --git a/system/Encryption/Encryption.php b/system/Encryption/Encryption.php index 61301c6e6f80..a4162b4997ec 100644 --- a/system/Encryption/Encryption.php +++ b/system/Encryption/Encryption.php @@ -1,4 +1,5 @@ + */ + protected $handlers = []; /** * Class constructor * - * @param EncryptionConfig $config Configuration parameters - * @return void + * @param EncryptionConfig $config Configuration parameters * - * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException + * @throws EncryptionException + * + * @return void */ public function __construct(EncryptionConfig $config = null) { - $config = $config ?? new \Config\Encryption(); + $config = $config ?? new EncryptionConfig(); $this->key = $config->key; $this->driver = $config->driver; $this->digest = $config->digest ?? 'SHA512'; - // if any aren't there, bomb - if ($this->driver === 'OpenSSL' && ! extension_loaded('openssl')) + // Map what we have installed + $this->handlers = [ + 'OpenSSL' => extension_loaded('openssl'), + // the SodiumHandler uses some API (like sodium_pad) that is available only on v1.0.14+ + 'Sodium' => extension_loaded('sodium') && version_compare(SODIUM_LIBRARY_VERSION, '1.0.14', '>='), + ]; + + // If requested driver is not active, bail + if (! in_array($this->driver, $this->drivers, true) || (array_key_exists($this->driver, $this->handlers) && ! $this->handlers[$this->driver])) { // this should never happen in travis-ci - // @codeCoverageIgnoreStart throw EncryptionException::forNoHandlerAvailable($this->driver); - // @codeCoverageIgnoreEnd } } /** * Initialize or re-initialize an encrypter * - * @param EncryptionConfig $config Configuration parameters - * @return \CodeIgniter\Encryption\EncrypterInterface + * @param EncryptionConfig $config Configuration parameters + * + * @throws EncryptionException * - * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException + * @return EncrypterInterface */ public function initialize(EncryptionConfig $config = null) { @@ -140,7 +161,7 @@ public function initialize(EncryptionConfig $config = null) } // Check for an unknown driver - if (! in_array($this->driver, $this->drivers)) + if (! in_array($this->driver, $this->drivers, true)) { throw EncryptionException::forUnKnownHandler($this->driver); } @@ -155,15 +176,15 @@ public function initialize(EncryptionConfig $config = null) $handlerName = 'CodeIgniter\\Encryption\\Handlers\\' . $this->driver . 'Handler'; $this->encrypter = new $handlerName($config); + return $this->encrypter; } - // -------------------------------------------------------------------- - /** * Create a random key * - * @param integer $length Output length + * @param integer $length Output length + * * @return string */ public static function createKey($length = 32) @@ -171,17 +192,16 @@ public static function createKey($length = 32) return random_bytes($length); } - // -------------------------------------------------------------------- - /** * __get() magic, providing readonly access to some of our protected properties * - * @param string $key Property name + * @param string $key Property name + * * @return mixed */ public function __get($key) { - if (in_array($key, ['key', 'digest', 'driver', 'drivers'], true)) + if ($this->__isset($key)) { return $this->{$key}; } @@ -199,5 +219,4 @@ public function __isset($key): bool { return in_array($key, ['key', 'digest', 'driver', 'drivers'], true); } - } diff --git a/system/Encryption/Exceptions/EncryptionException.php b/system/Encryption/Exceptions/EncryptionException.php index 7a95ccda9b54..74259a896cb6 100644 --- a/system/Encryption/Exceptions/EncryptionException.php +++ b/system/Encryption/Exceptions/EncryptionException.php @@ -1,41 +1,113 @@ $value) // @phpstan-ignore-line + foreach (get_object_vars($config) as $key => $value) { - $this->$pkey = $value; + if (property_exists($this, $key)) + { + $this->{$key} = $value; + } } } /** * Byte-safe substr() * - * @param string $str - * @param integer $start - * @param integer $length + * @param string $str + * @param integer $start + * @param integer $length + * * @return string */ protected static function substr($str, $start, $length = null) @@ -93,7 +96,7 @@ protected static function substr($str, $start, $length = null) */ public function __get($key) { - if (in_array($key, ['cipher', 'key'], true)) + if ($this->__isset($key)) { return $this->{$key}; } @@ -109,6 +112,6 @@ public function __get($key) */ public function __isset($key): bool { - return in_array($key, ['cipher', 'key'], true); + return property_exists($this, $key); } } diff --git a/system/Encryption/Handlers/OpenSSLHandler.php b/system/Encryption/Handlers/OpenSSLHandler.php index 2a40f765a3ad..488c501b9250 100644 --- a/system/Encryption/Handlers/OpenSSLHandler.php +++ b/system/Encryption/Handlers/OpenSSLHandler.php @@ -1,4 +1,5 @@ digest, $this->key); // basic encryption - $iv = ($iv_size = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($iv_size) : null; + $iv = ($ivSize = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($ivSize) : null; $data = \openssl_encrypt($data, $this->cipher, $secret, OPENSSL_RAW_DATA, $iv); @@ -127,16 +110,8 @@ public function encrypt($data, $params = null) return $hmacKey . $result; } - // -------------------------------------------------------------------- - /** - * Decrypt ciphertext, with optional HMAC and base64 encoding - * - * @param string $data Encrypted data - * @param array|string $params Over-ridden parameters, specifically the key - * - * @return string - * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException + * {@inheritDoc} */ public function decrypt($data, $params = null) { @@ -165,15 +140,16 @@ public function decrypt($data, $params = null) $hmacKey = self::substr($data, 0, $hmacLength); $data = self::substr($data, $hmacLength); $hmacCalc = \hash_hmac($this->digest, $data, $secret, true); + if (! hash_equals($hmacKey, $hmacCalc)) { throw EncryptionException::forAuthenticationFailed(); } - if ($iv_size = \openssl_cipher_iv_length($this->cipher)) + if ($ivSize = \openssl_cipher_iv_length($this->cipher)) { - $iv = self::substr($data, 0, $iv_size); - $data = self::substr($data, $iv_size); + $iv = self::substr($data, 0, $ivSize); + $data = self::substr($data, $ivSize); } else { @@ -182,5 +158,4 @@ public function decrypt($data, $params = null) return \openssl_decrypt($data, $this->cipher, $secret, OPENSSL_RAW_DATA, $iv); } - } diff --git a/system/Encryption/Handlers/SodiumHandler.php b/system/Encryption/Handlers/SodiumHandler.php new file mode 100644 index 000000000000..bfbe63860d2c --- /dev/null +++ b/system/Encryption/Handlers/SodiumHandler.php @@ -0,0 +1,177 @@ +parseParams($params); + + if (empty($this->key)) + { + throw EncryptionException::forNeedsStarterKey(); + } + + // create a nonce for this operation + $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes + + // add padding before we encrypt the data + if ($this->blockSize <= 0) + { + throw EncryptionException::forEncryptionFailed(); + } + + $data = sodium_pad($data, $this->blockSize); + + // encrypt message and combine with nonce + $ciphertext = $nonce . sodium_crypto_secretbox($data, $nonce, $this->key); + + // cleanup buffers + sodium_memzero($data); + sodium_memzero($this->key); + + return $ciphertext; + } + + /** + * {@inheritDoc} + */ + public function decrypt($data, $params = null) + { + $this->parseParams($params); + + if (empty($this->key)) + { + throw EncryptionException::forNeedsStarterKey(); + } + + if (mb_strlen($data, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) + { + // message was truncated + throw EncryptionException::forAuthenticationFailed(); + } + + // Extract info from encrypted data + $nonce = self::substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); + $ciphertext = self::substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null); + + // decrypt data + $data = sodium_crypto_secretbox_open($ciphertext, $nonce, $this->key); + + if ($data === false) + { + // message was tampered in transit + throw EncryptionException::forAuthenticationFailed(); // @codeCoverageIgnore + } + + // remove extra padding during encryption + if ($this->blockSize <= 0) + { + throw EncryptionException::forAuthenticationFailed(); + } + + $data = sodium_unpad($data, $this->blockSize); + + // cleanup buffers + sodium_memzero($ciphertext); + sodium_memzero($this->key); + + return $data; + } + + /** + * Parse the $params before doing assignment. + * + * @param array|string|null $params + * + * @throws EncryptionException If key is empty + * + * @return void + */ + protected function parseParams($params) + { + if ($params === null) + { + return; + } + + if (is_array($params)) + { + if (isset($params['key'])) + { + $this->key = $params['key']; + } + + if (isset($params['blockSize'])) + { + $this->blockSize = $params['blockSize']; + } + + return; + } + + $this->key = (string) $params; + } +} diff --git a/system/Entity.php b/system/Entity.php index 6a588e715602..98e106cfdb66 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -41,11 +41,16 @@ use CodeIgniter\Exceptions\CastException; use CodeIgniter\I18n\Time; +use DateTime; +use Exception; +use JsonSerializable; +use ReflectionException; +use stdClass; /** * Entity encapsulation, for use with CodeIgniter\Model */ -class Entity implements \JsonSerializable +class Entity implements JsonSerializable { /** * Maps names used in sets and gets against unique @@ -113,7 +118,7 @@ public function __construct(array $data = null) * * @param array $data * - * @return \CodeIgniter\Entity + * @return $this */ public function fill(array $data = null) { @@ -143,7 +148,7 @@ public function fill(array $data = null) * @param boolean $recursive If true, inner entities will be casted as array as well. * * @return array - * @throws \Exception + * @throws Exception */ public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recursive = false): array { @@ -306,7 +311,7 @@ public function hasChanged(string $key = null): bool * @param string $key * * @return mixed - * @throws \Exception + * @throws Exception */ public function __get(string $key) { @@ -331,7 +336,7 @@ public function __get(string $key) } // Do we need to mutate this into a date? - if (in_array($key, $this->dates)) + if (in_array($key, $this->dates, true)) { $result = $this->mutateDate($result); } @@ -355,18 +360,18 @@ public function __get(string $key) * $this->my_property = $p; * $this->setMyProperty() = $p; * - * @param string $key - * @param null $value + * @param string $key + * @param mixed|null $value * * @return $this - * @throws \Exception + * @throws Exception */ public function __set(string $key, $value = null) { $key = $this->mapProperty($key); // Check if the field should be mutated into a date - if (in_array($key, $this->dates)) + if (in_array($key, $this->dates, true)) { $value = $this->mutateDate($value); } @@ -433,7 +438,7 @@ public function __set(string $key, $value = null) * * @param string $key * - * @throws \ReflectionException + * @throws ReflectionException */ public function __unset(string $key) { @@ -510,8 +515,8 @@ protected function mapProperty(string $key) * * @param mixed $value * - * @return \CodeIgniter\I18n\Time|mixed - * @throws \Exception + * @return Time|mixed + * @throws Exception */ protected function mutateDate($value) { @@ -520,7 +525,7 @@ protected function mutateDate($value) return $value; } - if ($value instanceof \DateTime) + if ($value instanceof DateTime) { return Time::instance($value); } @@ -548,9 +553,8 @@ protected function mutateDate($value) * @param string $type * * @return mixed - * @throws \Exception + * @throws Exception */ - protected function castAs($value, string $type) { if (strpos($type, '?') === 0) @@ -616,14 +620,14 @@ protected function castAs($value, string $type) * @param boolean $asArray * * @return mixed - * @throws \CodeIgniter\Exceptions\CastException + * @throws CastException */ private function castAsJson($value, bool $asArray = false) { - $tmp = ! is_null($value) ? ($asArray ? [] : new \stdClass) : null; + $tmp = ! is_null($value) ? ($asArray ? [] : new stdClass) : null; if (function_exists('json_decode')) { - if ((is_string($value) && strlen($value) > 1 && in_array($value[0], ['[', '{', '"'])) || is_numeric($value)) + if ((is_string($value) && strlen($value) > 1 && in_array($value[0], ['[', '{', '"'], true)) || is_numeric($value)) { $tmp = json_decode($value, $asArray); @@ -640,7 +644,7 @@ private function castAsJson($value, bool $asArray = false) * Support for json_encode() * * @return array|mixed - * @throws \Exception + * @throws Exception */ public function jsonSerialize() { diff --git a/system/Events/Events.php b/system/Events/Events.php index 1c236ee5906b..24927bb70bee 100644 --- a/system/Events/Events.php +++ b/system/Events/Events.php @@ -136,15 +136,15 @@ public static function initialize() * Events::on('create', [$myInstance, 'myMethod']); // Method on an existing instance * Events::on('create', function() {}); // Closure * - * @param string $event_name + * @param string $eventName * @param callable $callback * @param integer $priority */ - public static function on($event_name, $callback, $priority = EVENT_PRIORITY_NORMAL) + public static function on($eventName, $callback, $priority = EVENT_PRIORITY_NORMAL) { - if (! isset(static::$listeners[$event_name])) + if (! isset(static::$listeners[$eventName])) { - static::$listeners[$event_name] = [ + static::$listeners[$eventName] = [ true, // If there's only 1 item, it's sorted. [$priority], [$callback], @@ -152,9 +152,9 @@ public static function on($event_name, $callback, $priority = EVENT_PRIORITY_NOR } else { - static::$listeners[$event_name][0] = false; // Not sorted - static::$listeners[$event_name][1][] = $priority; - static::$listeners[$event_name][2][] = $callback; + static::$listeners[$eventName][0] = false; // Not sorted + static::$listeners[$eventName][1][] = $priority; + static::$listeners[$eventName][2][] = $callback; } } @@ -214,28 +214,28 @@ public static function trigger($eventName, ...$arguments): bool * If the listener could not be found, returns FALSE, or TRUE if * it was removed. * - * @param string $event_name + * @param string $eventName * * @return array */ - public static function listeners($event_name): array + public static function listeners($eventName): array { - if (! isset(static::$listeners[$event_name])) + if (! isset(static::$listeners[$eventName])) { return []; } // The list is not sorted - if (! static::$listeners[$event_name][0]) + if (! static::$listeners[$eventName][0]) { // Sort it! - array_multisort(static::$listeners[$event_name][1], SORT_NUMERIC, static::$listeners[$event_name][2]); + array_multisort(static::$listeners[$eventName][1], SORT_NUMERIC, static::$listeners[$eventName][2]); // Mark it as sorted already! - static::$listeners[$event_name][0] = true; + static::$listeners[$eventName][0] = true; } - return static::$listeners[$event_name][2]; + return static::$listeners[$eventName][2]; } //-------------------------------------------------------------------- @@ -246,24 +246,24 @@ public static function listeners($event_name): array * If the listener couldn't be found, returns FALSE, else TRUE if * it was removed. * - * @param string $event_name + * @param string $eventName * @param callable $listener * * @return boolean */ - public static function removeListener($event_name, callable $listener): bool + public static function removeListener($eventName, callable $listener): bool { - if (! isset(static::$listeners[$event_name])) + if (! isset(static::$listeners[$eventName])) { return false; } - foreach (static::$listeners[$event_name][2] as $index => $check) + foreach (static::$listeners[$eventName][2] as $index => $check) { if ($check === $listener) { - unset(static::$listeners[$event_name][1][$index]); - unset(static::$listeners[$event_name][2][$index]); + unset(static::$listeners[$eventName][1][$index]); + unset(static::$listeners[$eventName][2][$index]); return true; } @@ -280,13 +280,13 @@ public static function removeListener($event_name, callable $listener): bool * If the event_name is specified, only listeners for that event will be * removed, otherwise all listeners for all events are removed. * - * @param string|null $event_name + * @param string|null $eventName */ - public static function removeAllListeners($event_name = null) + public static function removeAllListeners($eventName = null) { - if (! is_null($event_name)) + if (! is_null($eventName)) { - unset(static::$listeners[$event_name]); + unset(static::$listeners[$eventName]); } else { diff --git a/system/Exceptions/AlertError.php b/system/Exceptions/AlertError.php index 8dff43c53ed4..5c6178d358e3 100644 --- a/system/Exceptions/AlertError.php +++ b/system/Exceptions/AlertError.php @@ -1,10 +1,11 @@ getMimeType()); + return Mimes::guessExtensionFromType($this->getMimeType()); } //-------------------------------------------------------------------- @@ -152,7 +153,7 @@ public function getMimeType(): string } $finfo = finfo_open(FILEINFO_MIME_TYPE); - $mimeType = finfo_file($finfo, $this->getRealPath()); + $mimeType = finfo_file($finfo, $this->getRealPath() ?: $this->__toString()); finfo_close($finfo); return $mimeType; } @@ -181,7 +182,7 @@ public function getRandomName(): string * @param string|null $name * @param boolean $overwrite * - * @return \CodeIgniter\Files\File + * @return File */ public function move(string $targetPath, string $name = null, bool $overwrite = false) { @@ -189,7 +190,7 @@ public function move(string $targetPath, string $name = null, bool $overwrite = $name = $name ?? $this->getBaseName(); $destination = $overwrite ? $targetPath . $name : $this->getDestination($targetPath . $name); - $oldName = empty($this->getRealPath()) ? $this->getPath() : $this->getRealPath(); + $oldName = $this->getRealPath() ?: $this->__toString(); if (! @rename($oldName, $destination)) { diff --git a/system/Filters/CSRF.php b/system/Filters/CSRF.php index c79d1913ac85..8a75dfd3168d 100644 --- a/system/Filters/CSRF.php +++ b/system/Filters/CSRF.php @@ -43,6 +43,7 @@ namespace CodeIgniter\Filters; +use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Security\Exceptions\SecurityException; @@ -68,11 +69,11 @@ class CSRF implements FilterInterface * sent back to the client, allowing for error pages, * redirects, etc. * - * @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request - * @param array|null $arguments + * @param RequestInterface|IncomingRequest $request + * @param array|null $arguments * * @return mixed - * @throws \CodeIgniter\Security\Exceptions\SecurityException + * @throws SecurityException */ public function before(RequestInterface $request, $arguments = null) { @@ -103,9 +104,9 @@ public function before(RequestInterface $request, $arguments = null) /** * We don't have anything to do here. * - * @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request - * @param ResponseInterface|\CodeIgniter\HTTP\Response $response - * @param array|null $arguments + * @param RequestInterface|IncomingRequest $request + * @param ResponseInterface|\CodeIgniter\HTTP\Response $response + * @param array|null $arguments * * @return mixed */ diff --git a/system/Filters/DebugToolbar.php b/system/Filters/DebugToolbar.php index 2c3a55c24ef2..f449ed3a725f 100644 --- a/system/Filters/DebugToolbar.php +++ b/system/Filters/DebugToolbar.php @@ -39,7 +39,9 @@ namespace CodeIgniter\Filters; +use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RequestInterface; +use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use Config\Services; @@ -52,8 +54,8 @@ class DebugToolbar implements FilterInterface /** * We don't need to do anything here. * - * @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request - * @param array|null $arguments + * @param RequestInterface|IncomingRequest $request + * @param array|null $arguments * * @return void */ @@ -67,9 +69,9 @@ public function before(RequestInterface $request, $arguments = null) * If the debug flag is set (CI_DEBUG) then collect performance * and debug information and display it in a toolbar. * - * @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request - * @param ResponseInterface|\CodeIgniter\HTTP\Response $response - * @param array|null $arguments + * @param RequestInterface|IncomingRequest $request + * @param ResponseInterface|Response $response + * @param array|null $arguments * * @return void */ diff --git a/system/Filters/FilterInterface.php b/system/Filters/FilterInterface.php index 48b682e41ba5..c0c4b3b222a9 100644 --- a/system/Filters/FilterInterface.php +++ b/system/Filters/FilterInterface.php @@ -58,8 +58,8 @@ interface FilterInterface * sent back to the client, allowing for error pages, * redirects, etc. * - * @param \CodeIgniter\HTTP\RequestInterface $request - * @param null $arguments + * @param RequestInterface $request + * @param null $arguments * * @return mixed */ @@ -73,9 +73,9 @@ public function before(RequestInterface $request, $arguments = null); * to stop execution of other after filters, short of * throwing an Exception or Error. * - * @param \CodeIgniter\HTTP\RequestInterface $request - * @param \CodeIgniter\HTTP\ResponseInterface $response - * @param null $arguments + * @param RequestInterface $request + * @param ResponseInterface $response + * @param null $arguments * * @return mixed */ diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 86c427f73165..c253610288c4 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -42,6 +42,9 @@ use CodeIgniter\Filters\Exceptions\FilterException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use Config\Filters as FiltersConfig; +use Config\Modules; +use Config\Services; /** * Filters @@ -60,10 +63,28 @@ class Filters 'after' => [], ]; + /** + * The collection of filters' class names that will + * be used to execute in each position. + * + * @var array + */ + protected $filtersClass = [ + 'before' => [], + 'after' => [], + ]; + + /** + * Any arguments to be passed to filtersClass. + * + * @var array + */ + protected $argumentsClass = []; + /** * The original config file * - * @var \Config\Filters + * @var FiltersConfig */ protected $config; @@ -96,20 +117,59 @@ class Filters */ protected $arguments = []; - //-------------------------------------------------------------------- + /** + * Handle to the modules config. + * + * @var Modules + */ + protected $modules; /** * Constructor. * - * @param \Config\Filters $config + * @param FiltersConfig $config * @param RequestInterface $request * @param ResponseInterface $response + * @param Modules|null $modules */ - public function __construct($config, RequestInterface $request, ResponseInterface $response) + public function __construct($config, RequestInterface $request, ResponseInterface $response, Modules $modules = null) { $this->config = $config; $this->request = &$request; $this->setResponse($response); + + $this->modules = $modules ?? config('Modules'); + } + + /** + * If discoverFilters is enabled in Config then system will try to + * auto-discover custom filters files in Namespaces and allow access to + * the config object via the variable $customfilters as with the routes file + * + * Sample : + * $filters->aliases['custom-auth'] = \Acme\Blob\Filters\BlobAuth::class; + */ + private function discoverFilters() + { + $locator = Services::locator(); + + // for access by custom filters + $filters = $this->config; + + $files = $locator->search('Config/Filters.php'); + + foreach ($files as $file) + { + $className = $locator->getClassname($file); + + // Don't include our main Filter config again... + if ($className === 'Config\\Filters') + { + continue; + } + + include $file; + } } /** @@ -122,8 +182,6 @@ public function setResponse(ResponseInterface $response) $this->response = &$response; } - //-------------------------------------------------------------------- - /** * Runs through all of the filters for the specified * uri and position. @@ -131,78 +189,57 @@ public function setResponse(ResponseInterface $response) * @param string $uri * @param string $position * - * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\ResponseInterface|mixed - * @throws \CodeIgniter\Filters\Exceptions\FilterException + * @return RequestInterface|ResponseInterface|mixed + * @throws FilterException */ public function run(string $uri, string $position = 'before') { $this->initialize(strtolower($uri)); - foreach ($this->filters[$position] as $alias => $rules) + foreach ($this->filtersClass[$position] as $className) { - if (is_numeric($alias) && is_string($rules)) - { - $alias = $rules; - } + $class = new $className(); - if (! array_key_exists($alias, $this->config->aliases)) + if (! $class instanceof FilterInterface) { - throw FilterException::forNoAlias($alias); + throw FilterException::forIncorrectInterface(get_class($class)); } - if (is_array($this->config->aliases[$alias])) + if ($position === 'before') { - $classNames = $this->config->aliases[$alias]; - } - else - { - $classNames = [$this->config->aliases[$alias]]; - } + $result = $class->before($this->request, $this->argumentsClass[$className] ?? null); - foreach ($classNames as $className) - { - $class = new $className(); - - if (! $class instanceof FilterInterface) + if ($result instanceof RequestInterface) { - throw FilterException::forIncorrectInterface(get_class($class)); + $this->request = $result; + continue; } - if ($position === 'before') + // If the response object was sent back, + // then send it and quit. + if ($result instanceof ResponseInterface) { - $result = $class->before($this->request, $this->arguments[$alias] ?? null); - - if ($result instanceof RequestInterface) - { - $this->request = $result; - continue; - } + // short circuit - bypass any other filters + return $result; + } + // Ignore an empty result + if (empty($result)) + { + continue; + } - // If the response object was sent back, - // then send it and quit. - if ($result instanceof ResponseInterface) - { - // short circuit - bypass any other filters - return $result; - } + return $result; + } - // Ignore an empty result - if (empty($result)) - { - continue; - } + if ($position === 'after') + { + $result = $class->after($this->request, $this->response, $this->argumentsClass[$className] ?? null); - return $result; - } - elseif ($position === 'after') + if ($result instanceof ResponseInterface) { - $result = $class->after($this->request, $this->response, $this->arguments[$alias] ?? null); + $this->response = $result; - if ($result instanceof ResponseInterface) - { - $this->response = $result; - continue; - } + continue; } } } @@ -210,8 +247,6 @@ public function run(string $uri, string $position = 'before') return $position === 'before' ? $this->request : $this->response; } - //-------------------------------------------------------------------- - /** * Runs through our list of filters provided by the configuration * object to get them ready for use, including getting uri masks @@ -236,6 +271,11 @@ public function initialize(string $uri = null) return $this; } + if ($this->modules->shouldDiscover('filters')) + { + $this->discoverFilters(); + } + $this->processGlobals($uri); $this->processMethods(); $this->processFilters($uri); @@ -250,13 +290,14 @@ public function initialize(string $uri = null) $this->filters['after'][] = 'toolbar'; } + $this->processAliasesToClass('before'); + $this->processAliasesToClass('after'); + $this->initialized = true; return $this; } - //-------------------------------------------------------------------- - /** * Returns the processed filters array. * @@ -267,6 +308,16 @@ public function getFilters(): array return $this->filters; } + /** + * Returns the filtersClass array. + * + * @return array + */ + public function getFiltersClass(): array + { + return $this->filtersClass; + } + /** * Adds a new alias to the config file. * MUST be called prior to initialize(); @@ -300,8 +351,6 @@ public function addFilter(string $class, string $alias = null, string $when = 'b return $this; } - //-------------------------------------------------------------------- - /** * Ensures that a specific filter is on and enabled for the current request. * @@ -312,7 +361,7 @@ public function addFilter(string $class, string $alias = null, string $when = 'b * @param string $name * @param string $when * - * @return \CodeIgniter\Filters\Filters + * @return Filters */ public function enableFilter(string $name, string $when = 'before') { @@ -334,16 +383,22 @@ public function enableFilter(string $name, string $when = 'before') throw FilterException::forNoAlias($name); } + $classNames = (array) $this->config->aliases[$name]; + + foreach ($classNames as $className) + { + $this->argumentsClass[$className] = $this->arguments[$name] ?? null; + } + if (! isset($this->filters[$when][$name])) { - $this->filters[$when][] = $name; + $this->filters[$when][] = $name; + $this->filtersClass[$when] = array_merge($this->filtersClass[$when], $classNames); } return $this; } - //-------------------------------------------------------------------- - /** * Returns the arguments for a specified key, or all. * @@ -356,7 +411,6 @@ public function getArguments(string $key = null) return is_null($key) ? $this->arguments : $this->arguments[$key]; } - //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Processors //-------------------------------------------------------------------- @@ -418,8 +472,6 @@ protected function processGlobals(string $uri = null) } } - //-------------------------------------------------------------------- - /** * Add any method-specific flters to the mix. * @@ -442,8 +494,6 @@ protected function processMethods() } } - //-------------------------------------------------------------------- - /** * Add any applicable configured filters to the mix. * @@ -484,6 +534,38 @@ protected function processFilters(string $uri = null) } } + /** + * Maps filter aliases to the equivalent filter classes + * + * @throws FilterException + * + * @return void + */ + protected function processAliasesToClass(string $position) + { + foreach ($this->filters[$position] as $alias => $rules) + { + if (is_numeric($alias) && is_string($rules)) + { + $alias = $rules; + } + + if (! array_key_exists($alias, $this->config->aliases)) + { + throw FilterException::forNoAlias($alias); + } + + if (is_array($this->config->aliases[$alias])) + { + $this->filtersClass[$position] = array_merge($this->filtersClass[$position], $this->config->aliases[$alias]); + } + else + { + $this->filtersClass[$position][] = $this->config->aliases[$alias]; + } + } + } + /** * Check paths for match for URI * @@ -522,5 +604,4 @@ private function pathApplies(string $uri, $paths) return false; } - } diff --git a/system/Filters/Honeypot.php b/system/Filters/Honeypot.php index 5f2d83c12ae7..2f9cd13f9b8e 100644 --- a/system/Filters/Honeypot.php +++ b/system/Filters/Honeypot.php @@ -54,8 +54,8 @@ class Honeypot implements FilterInterface * Checks if Honeypot field is empty; if not * then the requester is a bot * - * @param \CodeIgniter\HTTP\RequestInterface $request - * @param array|null $arguments + * @param RequestInterface $request + * @param array|null $arguments * * @return void */ @@ -71,9 +71,9 @@ public function before(RequestInterface $request, $arguments = null) /** * Attach a honeypot to the current response. * - * @param \CodeIgniter\HTTP\RequestInterface $request - * @param \CodeIgniter\HTTP\ResponseInterface $response - * @param array|null $arguments + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array|null $arguments * * @return void */ diff --git a/system/Format/Exceptions/FormatException.php b/system/Format/Exceptions/FormatException.php index 4529fd0fafa0..abaf3d1456a6 100644 --- a/system/Format/Exceptions/FormatException.php +++ b/system/Format/Exceptions/FormatException.php @@ -1,16 +1,95 @@ -config = $config; + } + + /** + * Returns the current configuration instance. + * + * @return FormatConfig + */ + public function getConfig() + { + return $this->config; + } + + /** + * A Factory method to return the appropriate formatter for the given mime type. + * + * @param string $mime + * + * @throws FormatException + * + * @return FormatterInterface + */ + public function getFormatter(string $mime): FormatterInterface + { + if (! array_key_exists($mime, $this->config->formatters)) + { + throw FormatException::forInvalidMime($mime); + } + + $className = $this->config->formatters[$mime]; + + if (! class_exists($className)) + { + throw FormatException::forInvalidFormatter($className); + } + + $class = new $className(); + + if (! $class instanceof FormatterInterface) + { + throw FormatException::forInvalidFormatter($className); + } + + return $class; + } +} diff --git a/system/Format/JSONFormatter.php b/system/Format/JSONFormatter.php index c26ca5b58b4e..bff2aa1ea249 100644 --- a/system/Format/JSONFormatter.php +++ b/system/Format/JSONFormatter.php @@ -66,7 +66,7 @@ public function format($data) $result = json_encode($data, $options, 512); - if (! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION])) + if (! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION], true)) { throw FormatException::forInvalidJSON(json_last_error_msg()); } diff --git a/system/Format/XMLFormatter.php b/system/Format/XMLFormatter.php index 8dc798d9199a..3c9f2feeab8c 100644 --- a/system/Format/XMLFormatter.php +++ b/system/Format/XMLFormatter.php @@ -41,13 +41,13 @@ use CodeIgniter\Format\Exceptions\FormatException; use Config\Format; +use SimpleXMLElement; /** * XML data formatter */ class XMLFormatter implements FormatterInterface { - /** * Takes the given data and formats it. * @@ -70,15 +70,13 @@ public function format($data) } $options = $config->formatterOptions['application/xml'] ?? 0; - $output = new \SimpleXMLElement('', $options); + $output = new SimpleXMLElement('', $options); - $this->arrayToXML((array)$data, $output); + $this->arrayToXML((array) $data, $output); return $output->asXML(); } - //-------------------------------------------------------------------- - /** * A recursive method to convert an array into a valid XML string. * @@ -95,25 +93,41 @@ protected function arrayToXML(array $data, &$output) { if (is_array($value)) { - if (is_numeric($key)) - { - $key = "item{$key}"; - } - + $key = $this->normalizeXMLTag($key); $subnode = $output->addChild("$key"); $this->arrayToXML($value, $subnode); } else { - if (is_numeric($key)) - { - $key = "item{$key}"; - } - + $key = $this->normalizeXMLTag($key); $output->addChild("$key", htmlspecialchars("$value")); } } } - //-------------------------------------------------------------------- + /** + * Normalizes tags into the allowed by W3C. + * Regex adopted from this StackOverflow answer. + * + * @param string|integer $key + * + * @return string + * + * @see https://stackoverflow.com/questions/60001029/invalid-characters-in-xml-tag-name + */ + protected function normalizeXMLTag($key) + { + $startChar = 'A-Z_a-z' . + '\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}' . + '\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}' . + '\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}' . + '\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}'; + $validName = $startChar . '\\.\\d\\x{B7}\\x{300}-\\x{36F}\\x{203F}-\\x{2040}'; + + $key = trim($key); + $key = preg_replace("/[^{$validName}-]+/u", '', $key); + $key = preg_replace("/^[^{$startChar}]+/u", 'item$0', $key); + + return preg_replace('/^(xml).*/iu', 'item$0', $key); // XML is a reserved starting word + } } diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php index fe7f4eff1b06..5eb0cf7f9f6d 100644 --- a/system/HTTP/CLIRequest.php +++ b/system/HTTP/CLIRequest.php @@ -1,4 +1,5 @@ parseCommand(); } - //-------------------------------------------------------------------- - /** * Returns the "path" of the request script so that it can be used * in routing to the appropriate controller/method. @@ -120,8 +114,6 @@ public function getPath(): string return empty($path) ? '' : $path; } - //-------------------------------------------------------------------- - /** * Returns an associative array of all CLI options found, with * their values. @@ -133,8 +125,6 @@ public function getOptions(): array return $this->options; } - //-------------------------------------------------------------------- - /** * Returns the path segments. * @@ -145,8 +135,6 @@ public function getSegments(): array return $this->segments; } - //-------------------------------------------------------------------- - /** * Returns the value for a single CLI option that was passed in. * @@ -159,8 +147,6 @@ public function getOption(string $key) return $this->options[$key] ?? null; } - //-------------------------------------------------------------------- - /** * Returns the options as a string, suitable for passing along on * the CLI to other commands. @@ -173,9 +159,11 @@ public function getOption(string $key) * * getOptionString() = '-foo bar -baz "queue some stuff"' * + * @param boolean $useLongOpts + * * @return string */ - public function getOptionString(): string + public function getOptionString(bool $useLongOpts = false): string { if (empty($this->options)) { @@ -186,14 +174,25 @@ public function getOptionString(): string foreach ($this->options as $name => $value) { + if ($useLongOpts && mb_strlen($name) > 1) + { + $out .= "--{$name} "; + } + else + { + $out .= "-{$name} "; + } + // If there's a space, we need to group // so it will pass correctly. - if (strpos($value, ' ') !== false) + if (mb_strpos($value, ' ') !== false) { - $value = '"' . $value . '"'; + $out .= '"' . $value . '" '; + } + elseif ($value !== null) + { + $out .= "{$value} "; } - - $out .= "-{$name} $value "; } return trim($out); diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index c9778ea9c421..2cc5d670eb49 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -42,6 +42,7 @@ use CodeIgniter\HTTP\Exceptions\HTTPException; use Config\App; +use InvalidArgumentException; /** * Class OutgoingRequest @@ -145,7 +146,7 @@ public function __construct(App $config, URI $uri, ResponseInterface $response = * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function request($method, string $url, array $options = []): ResponseInterface { @@ -168,7 +169,7 @@ public function request($method, string $url, array $options = []): ResponseInte * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function get(string $url, array $options = []): ResponseInterface { @@ -183,7 +184,7 @@ public function get(string $url, array $options = []): ResponseInterface * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function delete(string $url, array $options = []): ResponseInterface { @@ -213,7 +214,7 @@ public function head(string $url, array $options = []): ResponseInterface * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function options(string $url, array $options = []): ResponseInterface { @@ -228,7 +229,7 @@ public function options(string $url, array $options = []): ResponseInterface * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function patch(string $url, array $options = []): ResponseInterface { @@ -243,7 +244,7 @@ public function patch(string $url, array $options = []): ResponseInterface * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function post(string $url, array $options = []): ResponseInterface { @@ -258,7 +259,7 @@ public function post(string $url, array $options = []): ResponseInterface * @param string $url * @param array $options * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function put(string $url, array $options = []): ResponseInterface { @@ -413,12 +414,12 @@ public function getMethod(bool $upper = false): string * @param string $method * @param string $url * - * @return \CodeIgniter\HTTP\ResponseInterface + * @return ResponseInterface */ public function send(string $method, string $url) { // Reset our curl options so we're on a fresh slate. - $curl_options = []; + $curlOptions = []; if (! empty($this->config['query']) && is_array($this->config['query'])) { @@ -429,16 +430,16 @@ public function send(string $method, string $url) unset($this->config['query']); } - $curl_options[CURLOPT_URL] = $url; - $curl_options[CURLOPT_RETURNTRANSFER] = true; - $curl_options[CURLOPT_HEADER] = true; - $curl_options[CURLOPT_FRESH_CONNECT] = true; + $curlOptions[CURLOPT_URL] = $url; + $curlOptions[CURLOPT_RETURNTRANSFER] = true; + $curlOptions[CURLOPT_HEADER] = true; + $curlOptions[CURLOPT_FRESH_CONNECT] = true; // Disable @file uploads in post data. - $curl_options[CURLOPT_SAFE_UPLOAD] = true; + $curlOptions[CURLOPT_SAFE_UPLOAD] = true; - $curl_options = $this->setCURLOptions($curl_options, $this->config); - $curl_options = $this->applyMethod($method, $curl_options); - $curl_options = $this->applyRequestHeaders($curl_options); + $curlOptions = $this->setCURLOptions($curlOptions, $this->config); + $curlOptions = $this->applyMethod($method, $curlOptions); + $curlOptions = $this->applyRequestHeaders($curlOptions); // Do we need to delay this request? if ($this->delay > 0) @@ -446,7 +447,7 @@ public function send(string $method, string $url) sleep($this->delay); // @phpstan-ignore-line } - $output = $this->sendRequest($curl_options); + $output = $this->sendRequest($curlOptions); // Set the string we want to break our response from $breakString = "\r\n\r\n"; @@ -490,11 +491,11 @@ public function send(string $method, string $url) * Takes all headers current part of this request and adds them * to the cURL request. * - * @param array $curl_options + * @param array $curlOptions * * @return array */ - protected function applyRequestHeaders(array $curl_options = []): array + protected function applyRequestHeaders(array $curlOptions = []): array { if (empty($this->headers)) { @@ -508,7 +509,7 @@ protected function applyRequestHeaders(array $curl_options = []): array if (empty($headers)) { - return $curl_options; + return $curlOptions; } $set = []; @@ -518,9 +519,9 @@ protected function applyRequestHeaders(array $curl_options = []): array $set[] = $name . ': ' . $this->getHeaderLine($name); } - $curl_options[CURLOPT_HTTPHEADER] = $set; + $curlOptions[CURLOPT_HTTPHEADER] = $set; - return $curl_options; + return $curlOptions; } //-------------------------------------------------------------------- @@ -529,23 +530,23 @@ protected function applyRequestHeaders(array $curl_options = []): array * Apply method * * @param string $method - * @param array $curl_options + * @param array $curlOptions * * @return array */ - protected function applyMethod(string $method, array $curl_options): array + protected function applyMethod(string $method, array $curlOptions): array { $method = strtoupper($method); - $this->method = $method; - $curl_options[CURLOPT_CUSTOMREQUEST] = $method; + $this->method = $method; + $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; $size = strlen($this->body); // Have content? if ($size > 0) { - return $this->applyBody($curl_options); + return $this->applyBody($curlOptions); } if ($method === 'PUT' || $method === 'POST') @@ -558,10 +559,10 @@ protected function applyMethod(string $method, array $curl_options): array } else if ($method === 'HEAD') { - $curl_options[CURLOPT_NOBODY] = 1; + $curlOptions[CURLOPT_NOBODY] = 1; } - return $curl_options; + return $curlOptions; } //-------------------------------------------------------------------- @@ -569,18 +570,18 @@ protected function applyMethod(string $method, array $curl_options): array /** * Apply body * - * @param array $curl_options + * @param array $curlOptions * * @return array */ - protected function applyBody(array $curl_options = []): array + protected function applyBody(array $curlOptions = []): array { if (! empty($this->body)) { - $curl_options[CURLOPT_POSTFIELDS] = (string) $this->getBody(); + $curlOptions[CURLOPT_POSTFIELDS] = (string) $this->getBody(); } - return $curl_options; + return $curlOptions; } //-------------------------------------------------------------------- @@ -604,7 +605,7 @@ protected function setResponseHeaders(array $headers = []) } else if (strpos($header, 'HTTP') === 0) { - preg_match('#^HTTP\/([12]\.[01]) ([0-9]+) (.+)#', $header, $matches); + preg_match('#^HTTP\/([12](?:\.[01])?) ([0-9]+) (.+)#', $header, $matches); if (isset($matches[1])) { @@ -624,25 +625,25 @@ protected function setResponseHeaders(array $headers = []) /** * Set CURL options * - * @param array $curl_options + * @param array $curlOptions * @param array $config * @return array - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ - protected function setCURLOptions(array $curl_options = [], array $config = []) + protected function setCURLOptions(array $curlOptions = [], array $config = []) { // Auth Headers if (! empty($config['auth'])) { - $curl_options[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1]; + $curlOptions[CURLOPT_USERPWD] = $config['auth'][0] . ':' . $config['auth'][1]; if (! empty($config['auth'][2]) && strtolower($config['auth'][2]) === 'digest') { - $curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + $curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; } else { - $curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; + $curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; } } @@ -653,8 +654,8 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) if (is_array($cert)) { - $curl_options[CURLOPT_SSLCERTPASSWD] = $cert[1]; - $cert = $cert[0]; + $curlOptions[CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; } if (! is_file($cert)) @@ -662,7 +663,7 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) throw HTTPException::forSSLCertNotFound($cert); } - $curl_options[CURLOPT_SSLCERT] = $cert; + $curlOptions[CURLOPT_SSLCERT] = $cert; } // SSL Verification @@ -670,27 +671,27 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) { if (is_string($config['verify'])) { - $file = realpath($config['ssl_key']); + $file = realpath($config['ssl_key']) ?: $config['ssl_key']; - if (! $file) + if (! is_file($file)) { throw HTTPException::forInvalidSSLKey($config['ssl_key']); } - $curl_options[CURLOPT_CAINFO] = $file; - $curl_options[CURLOPT_SSL_VERIFYPEER] = 1; + $curlOptions[CURLOPT_CAINFO] = $file; + $curlOptions[CURLOPT_SSL_VERIFYPEER] = 1; } else if (is_bool($config['verify'])) { - $curl_options[CURLOPT_SSL_VERIFYPEER] = $config['verify']; + $curlOptions[CURLOPT_SSL_VERIFYPEER] = $config['verify']; } } // Debug if ($config['debug']) { - $curl_options[CURLOPT_VERBOSE] = 1; - $curl_options[CURLOPT_STDERR] = is_string($config['debug']) ? fopen($config['debug'], 'a+') : fopen('php://stderr', 'w'); + $curlOptions[CURLOPT_VERBOSE] = 1; + $curlOptions[CURLOPT_STDERR] = is_string($config['debug']) ? fopen($config['debug'], 'a+') : fopen('php://stderr', 'w'); } // Decode Content @@ -700,12 +701,12 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) if ($accept) { - $curl_options[CURLOPT_ENCODING] = $accept; + $curlOptions[CURLOPT_ENCODING] = $accept; } else { - $curl_options[CURLOPT_ENCODING] = ''; - $curl_options[CURLOPT_HTTPHEADER] = 'Accept-Encoding'; + $curlOptions[CURLOPT_ENCODING] = ''; + $curlOptions[CURLOPT_HTTPHEADER] = 'Accept-Encoding'; } } @@ -721,16 +722,16 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) if ($config['allow_redirects'] === false) { - $curl_options[CURLOPT_FOLLOWLOCATION] = 0; + $curlOptions[CURLOPT_FOLLOWLOCATION] = 0; } else { - $curl_options[CURLOPT_FOLLOWLOCATION] = 1; - $curl_options[CURLOPT_MAXREDIRS] = $settings['max']; + $curlOptions[CURLOPT_FOLLOWLOCATION] = 1; + $curlOptions[CURLOPT_MAXREDIRS] = $settings['max']; if ($settings['strict'] === true) { - $curl_options[CURLOPT_POSTREDIR] = 1 | 2 | 4; + $curlOptions[CURLOPT_POSTREDIR] = 1 | 2 | 4; } $protocols = 0; @@ -739,21 +740,21 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) $protocols += constant('CURLPROTO_' . strtoupper($proto)); } - $curl_options[CURLOPT_REDIR_PROTOCOLS] = $protocols; + $curlOptions[CURLOPT_REDIR_PROTOCOLS] = $protocols; } } // Timeout - $curl_options[CURLOPT_TIMEOUT_MS] = (float) $config['timeout'] * 1000; + $curlOptions[CURLOPT_TIMEOUT_MS] = (float) $config['timeout'] * 1000; // Connection Timeout - $curl_options[CURLOPT_CONNECTTIMEOUT_MS] = (float) $config['connect_timeout'] * 1000; + $curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = (float) $config['connect_timeout'] * 1000; // Post Data - application/x-www-form-urlencoded if (! empty($config['form_params']) && is_array($config['form_params'])) { - $postFields = http_build_query($config['form_params']); - $curl_options[CURLOPT_POSTFIELDS] = $postFields; + $postFields = http_build_query($config['form_params']); + $curlOptions[CURLOPT_POSTFIELDS] = $postFields; // Ensure content-length is set, since CURL doesn't seem to // calculate it when HTTPHEADER is set. @@ -765,11 +766,11 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) if (! empty($config['multipart']) && is_array($config['multipart'])) { // setting the POSTFIELDS option automatically sets multipart - $curl_options[CURLOPT_POSTFIELDS] = $config['multipart']; + $curlOptions[CURLOPT_POSTFIELDS] = $config['multipart']; } // HTTP Errors - $curl_options[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true; + $curlOptions[CURLOPT_FAILONERROR] = array_key_exists('http_errors', $config) ? (bool) $config['http_errors'] : true; // JSON if (isset($config['json'])) @@ -786,22 +787,22 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) { if ($config['version'] === 1.0) { - $curl_options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + $curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; } else if ($config['version'] === 1.1) { - $curl_options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + $curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; } } // Cookie if (isset($config['cookie'])) { - $curl_options[CURLOPT_COOKIEJAR] = $config['cookie']; - $curl_options[CURLOPT_COOKIEFILE] = $config['cookie']; + $curlOptions[CURLOPT_COOKIEJAR] = $config['cookie']; + $curlOptions[CURLOPT_COOKIEFILE] = $config['cookie']; } - return $curl_options; + return $curlOptions; } //-------------------------------------------------------------------- @@ -812,15 +813,15 @@ protected function setCURLOptions(array $curl_options = [], array $config = []) * * @codeCoverageIgnore * - * @param array $curl_options + * @param array $curlOptions * * @return string */ - protected function sendRequest(array $curl_options = []): string + protected function sendRequest(array $curlOptions = []): string { $ch = curl_init(); - curl_setopt_array($ch, $curl_options); + curl_setopt_array($ch, $curlOptions); // Send the request and wait for a response. $output = curl_exec($ch); diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 9a05fdbe6488..ad64d5ccfa52 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -39,6 +39,8 @@ namespace CodeIgniter\HTTP; +use Config\ContentSecurityPolicy as ContentSecurityPolicyConfig; + /** * Class ContentSecurityPolicy * @@ -222,9 +224,9 @@ class ContentSecurityPolicy * * Stores our default values from the Config file. * - * @param \Config\ContentSecurityPolicy $config + * @param ContentSecurityPolicyConfig $config */ - public function __construct(\Config\ContentSecurityPolicy $config) + public function __construct(ContentSecurityPolicyConfig $config) { foreach ($config as $setting => $value) // @phpstan-ignore-line { @@ -658,7 +660,7 @@ protected function addOption($options, string $target, bool $explicitReporting = * placeholders with actual nonces, that we'll then add to our * headers. * - * @param ResponseInterface|\CodeIgniter\HTTP\Response $response + * @param ResponseInterface|Response $response */ protected function generateNonces(ResponseInterface &$response) { @@ -710,7 +712,7 @@ protected function generateNonces(ResponseInterface &$response) * Content-Security-Policy and Content-Security-Policy-Report-Only headers * with their values to the response object. * - * @param ResponseInterface|\CodeIgniter\HTTP\Response $response + * @param ResponseInterface|Response $response */ protected function buildHeaders(ResponseInterface &$response) { @@ -819,7 +821,7 @@ protected function addToHeader(string $name, $values = null) if ($reportOnly === true) { - $reportSources[] = in_array($value, $this->validSources) ? "'{$value}'" : $value; + $reportSources[] = in_array($value, $this->validSources, true) ? "'{$value}'" : $value; } else { @@ -829,7 +831,7 @@ protected function addToHeader(string $name, $values = null) } else { - $sources[] = in_array($value, $this->validSources) ? "'{$value}'" : $value; + $sources[] = in_array($value, $this->validSources, true) ? "'{$value}'" : $value; } } } diff --git a/system/HTTP/DownloadResponse.php b/system/HTTP/DownloadResponse.php index ffa82d707a19..3dbe292fc75a 100644 --- a/system/HTTP/DownloadResponse.php +++ b/system/HTTP/DownloadResponse.php @@ -42,6 +42,8 @@ use CodeIgniter\Exceptions\DownloadException; use CodeIgniter\Files\File; use Config\Mimes; +use DateTime; +use DateTimeZone; /** * HTTP response when a download is requested. @@ -58,7 +60,7 @@ class DownloadResponse extends Message implements ResponseInterface /** * Download for file * - * @var File + * @var File|null */ private $file; @@ -163,7 +165,8 @@ public function getContentLength() : int { return strlen($this->binary); } - elseif ($this->file instanceof File) + + if ($this->file instanceof File) { return $this->file->getSize(); } @@ -181,9 +184,9 @@ private function setContentTypeByMimeType() if ($this->setMime === true) { - if (($last_dot_position = strrpos($this->filename, '.')) !== false) + if (($lastDotPosition = strrpos($this->filename, '.')) !== false) { - $mime = Mimes::guessTypeFromExtension(substr($this->filename, $last_dot_position + 1)); + $mime = Mimes::guessTypeFromExtension(substr($this->filename, $lastDotPosition + 1)); $charset = $this->charset; } } @@ -233,20 +236,20 @@ private function getDownloadFileName(): string */ private function getContentDisposition() : string { - $download_filename = $this->getDownloadFileName(); + $downloadFilename = $this->getDownloadFileName(); - $utf8_filename = $download_filename; + $utf8Filename = $downloadFilename; if (strtoupper($this->charset) !== 'UTF-8') { - $utf8_filename = mb_convert_encoding($download_filename, 'UTF-8', $this->charset); + $utf8Filename = mb_convert_encoding($downloadFilename, 'UTF-8', $this->charset); } - $result = sprintf('attachment; filename="%s"', $download_filename); + $result = sprintf('attachment; filename="%s"', $downloadFilename); - if ($utf8_filename) + if ($utf8Filename) { - $result .= '; filename*=UTF-8\'\'' . rawurlencode($utf8_filename); + $result .= '; filename*=UTF-8\'\'' . rawurlencode($utf8Filename); } return $result; @@ -306,13 +309,13 @@ public function getReason(): string /** * Sets the date header * - * @param \DateTime $date + * @param DateTime $date * * @return ResponseInterface */ - public function setDate(\DateTime $date) + public function setDate(DateTime $date) { - $date->setTimezone(new \DateTimeZone('UTC')); + $date->setTimezone(new DateTimeZone('UTC')); $this->setHeader('Date', $date->format('D, d M Y H:i:s') . ' GMT'); @@ -403,9 +406,9 @@ public function setCache(array $options = []) */ public function setLastModified($date) { - if ($date instanceof \DateTime) + if ($date instanceof DateTime) { - $date->setTimezone(new \DateTimeZone('UTC')); + $date->setTimezone(new DateTimeZone('UTC')); $this->setHeader('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT'); } elseif (is_string($date)) @@ -480,7 +483,7 @@ public function sendHeaders() // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html if (! isset($this->headers['Date'])) { - $this->setDate(\DateTime::createFromFormat('U', (string) time())); + $this->setDate(DateTime::createFromFormat('U', (string) time())); } // HTTP Status @@ -509,7 +512,8 @@ public function sendBody() { return $this->sendBodyByBinary(); } - elseif ($this->file !== null) + + if ($this->file !== null) { return $this->sendBodyByFilePath(); } @@ -524,10 +528,10 @@ public function sendBody() */ private function sendBodyByFilePath() { - $spl_file_object = $this->file->openFile('rb'); + $splFileObject = $this->file->openFile('rb'); // Flush 1MB chunks of data - while (! $spl_file_object->eof() && ($data = $spl_file_object->fread(1048576)) !== false) + while (! $splFileObject->eof() && ($data = $splFileObject->fread(1048576)) !== false) { echo $data; } diff --git a/system/HTTP/Exceptions/HTTPException.php b/system/HTTP/Exceptions/HTTPException.php index 64642a25e8e1..012badf9581d 100644 --- a/system/HTTP/Exceptions/HTTPException.php +++ b/system/HTTP/Exceptions/HTTPException.php @@ -50,7 +50,7 @@ class HTTPException extends FrameworkException implements ExceptionInterface /** * For CurlRequest * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException * * @codeCoverageIgnore */ @@ -64,7 +64,7 @@ public static function forMissingCurl() * * @param string $cert * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forSSLCertNotFound(string $cert) { @@ -76,7 +76,7 @@ public static function forSSLCertNotFound(string $cert) * * @param string $key * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidSSLKey(string $key) { @@ -104,7 +104,7 @@ public static function forCurlError(string $errorNum, string $error) * * @param string $type * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidNegotiationType(string $type) { @@ -116,7 +116,7 @@ public static function forInvalidNegotiationType(string $type) * * @param string $protocols * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidHTTPProtocol(string $protocols) { @@ -126,7 +126,7 @@ public static function forInvalidHTTPProtocol(string $protocols) /** * For Negotiate * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forEmptySupportedNegotiations() { @@ -138,7 +138,7 @@ public static function forEmptySupportedNegotiations() * * @param string $route * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidRedirectRoute(string $route) { @@ -148,7 +148,7 @@ public static function forInvalidRedirectRoute(string $route) /** * For Response * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forMissingResponseStatus() { @@ -160,7 +160,7 @@ public static function forMissingResponseStatus() * * @param integer $code * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidStatusCode(int $code) { @@ -172,7 +172,7 @@ public static function forInvalidStatusCode(int $code) * * @param integer $code * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forUnkownStatusCode(int $code) { @@ -184,7 +184,7 @@ public static function forUnkownStatusCode(int $code) * * @param string $uri * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forUnableToParseURI(string $uri) { @@ -196,7 +196,7 @@ public static function forUnableToParseURI(string $uri) * * @param integer $segment * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forURISegmentOutOfRange(int $segment) { @@ -208,7 +208,7 @@ public static function forURISegmentOutOfRange(int $segment) * * @param integer $port * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidPort(int $port) { @@ -218,7 +218,7 @@ public static function forInvalidPort(int $port) /** * For URI * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forMalformedQueryString() { @@ -228,7 +228,7 @@ public static function forMalformedQueryString() /** * For Uploaded file move * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forAlreadyMoved() { @@ -240,7 +240,7 @@ public static function forAlreadyMoved() * * @param string|null $path * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidFile(string $path = null) { @@ -254,7 +254,7 @@ public static function forInvalidFile(string $path = null) * @param string $target * @param string $error * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forMoveFailed(string $source, string $target, string $error) { @@ -266,7 +266,7 @@ public static function forMoveFailed(string $source, string $target, string $err * * @param string $samesite * - * @return \CodeIgniter\HTTP\Exceptions\HTTPException + * @return HTTPException */ public static function forInvalidSameSiteSetting(string $samesite) { diff --git a/system/HTTP/Files/FileCollection.php b/system/HTTP/Files/FileCollection.php index fb62ccd036d6..f827681fc7e3 100644 --- a/system/HTTP/Files/FileCollection.php +++ b/system/HTTP/Files/FileCollection.php @@ -40,6 +40,9 @@ namespace CodeIgniter\HTTP\Files; +use RecursiveArrayIterator; +use RecursiveIteratorIterator; + /** * Class FileCollection * @@ -277,8 +280,8 @@ protected function fixFilesArray(array $data): array } $stack = [&$pointer]; - $iterator = new \RecursiveIteratorIterator( - new \RecursiveArrayIterator($value), \RecursiveIteratorIterator::SELF_FIRST + $iterator = new RecursiveIteratorIterator( + new RecursiveArrayIterator($value), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $key => $val) @@ -312,14 +315,14 @@ protected function getValueDotNotationSyntax(array $index, array $value) { if (! empty($index)) { - $current_index = array_shift($index); + $currentIndex = array_shift($index); } - if (isset($current_index) && is_array($index) && $index && is_array($value[$current_index]) && $value[$current_index]) + if (isset($currentIndex) && is_array($index) && $index && is_array($value[$currentIndex]) && $value[$currentIndex]) { - return $this->getValueDotNotationSyntax($index, $value[$current_index]); + return $this->getValueDotNotationSyntax($index, $value[$currentIndex]); } - return (isset($current_index) && isset($value[$current_index])) ? $value[$current_index] : null; + return (isset($currentIndex) && isset($value[$currentIndex])) ? $value[$currentIndex] : null; } } diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index e218d1ef27d8..d139c240b638 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -44,6 +44,8 @@ use CodeIgniter\HTTP\Exceptions\HTTPException; use Config\Mimes; use Exception; +use InvalidArgumentException; +use RuntimeException; /** * Value object representing a single file uploaded through an @@ -154,9 +156,9 @@ public function __construct(string $path, string $originalName, string $mimeType * * @return boolean * - * @throws \InvalidArgumentException if the $path specified is invalid. - * @throws \RuntimeException on any error during the move operation. - * @throws \RuntimeException on the second or subsequent call to the method. + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation. + * @throws RuntimeException on the second or subsequent call to the method. */ public function move(string $targetPath, string $name = null, bool $overwrite = false) { diff --git a/system/HTTP/Files/UploadedFileInterface.php b/system/HTTP/Files/UploadedFileInterface.php index ae415911ae96..3ababd2312c4 100644 --- a/system/HTTP/Files/UploadedFileInterface.php +++ b/system/HTTP/Files/UploadedFileInterface.php @@ -39,6 +39,9 @@ namespace CodeIgniter\HTTP\Files; +use InvalidArgumentException; +use RuntimeException; + /** * Value object representing a single file uploaded through an * HTTP request. Used by the IncomingRequest class to @@ -89,9 +92,9 @@ public function __construct(string $path, string $originalName, string $mimeType * @param string $targetPath Path to which to move the uploaded file. * @param string $name the name to rename the file to. * - * @throws \InvalidArgumentException if the $path specified is invalid. - * @throws \RuntimeException on any error during the move operation. - * @throws \RuntimeException on the second or subsequent call to the method. + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation. + * @throws RuntimeException on the second or subsequent call to the method. */ public function move(string $targetPath, string $name = null); diff --git a/system/HTTP/Header.php b/system/HTTP/Header.php index 65982e41b76d..7ba2b50ee26b 100644 --- a/system/HTTP/Header.php +++ b/system/HTTP/Header.php @@ -208,7 +208,7 @@ public function getValueLine(): string { return $this->value; } - else if (! is_array($this->value)) + if (! is_array($this->value)) { return ''; } diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 071f76c2236c..9eb56b7f2401 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -43,7 +43,9 @@ use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\HTTP\Files\FileCollection; use CodeIgniter\HTTP\Files\UploadedFile; +use Config\App; use Config\Services; +use Locale; /** * Class IncomingRequest @@ -93,14 +95,14 @@ class IncomingRequest extends Request /** * File collection * - * @var Files\FileCollection|null + * @var FileCollection|null */ protected $files; /** * Negotiator * - * @var \CodeIgniter\HTTP\Negotiate|null + * @var Negotiate|null */ protected $negotiator; @@ -130,7 +132,7 @@ class IncomingRequest extends Request /** * Configuration settings. * - * @var \Config\App + * @var App */ public $config; @@ -144,7 +146,7 @@ class IncomingRequest extends Request /** * The user agent this request is from. * - * @var \CodeIgniter\HTTP\UserAgent + * @var UserAgent */ protected $userAgent; @@ -153,10 +155,10 @@ class IncomingRequest extends Request /** * Constructor * - * @param object $config - * @param \CodeIgniter\HTTP\URI $uri - * @param string|null $body - * @param \CodeIgniter\HTTP\UserAgent $userAgent + * @param object $config + * @param URI $uri + * @param string|null $body + * @param UserAgent $userAgent */ public function __construct($config, URI $uri = null, $body = 'php://input', UserAgent $userAgent) { @@ -194,7 +196,7 @@ public function __construct($config, URI $uri = null, $body = 'php://input', Use * Handles setting up the locale, perhaps auto-detecting through * content negotiation. * - * @param \Config\App $config + * @param App $config */ public function detectLocale($config) { @@ -246,13 +248,13 @@ public function setLocale(string $locale) { // If it's not a valid locale, set it // to the default locale for the site. - if (! in_array($locale, $this->validLocales)) + if (! in_array($locale, $this->validLocales, true)) { $locale = $this->defaultLocale; } $this->locale = $locale; - \Locale::setDefault($locale); + Locale::setDefault($locale); return $this; } @@ -295,11 +297,13 @@ public function isSecure(): bool { return true; } - elseif ($this->hasHeader('X-Forwarded-Proto') && $this->getHeader('X-Forwarded-Proto')->getValue() === 'https') + + if ($this->hasHeader('X-Forwarded-Proto') && $this->getHeader('X-Forwarded-Proto')->getValue() === 'https') { return true; } - elseif ($this->hasHeader('Front-End-Https') && ! empty($this->getHeader('Front-End-Https')->getValue()) && strtolower($this->getHeader('Front-End-Https')->getValue()) !== 'off') + + if ($this->hasHeader('Front-End-Https') && ! empty($this->getHeader('Front-End-Https')->getValue()) && strtolower($this->getHeader('Front-End-Https')->getValue()) !== 'off') { return true; } @@ -453,7 +457,7 @@ public function getCookie($index = null, $filter = null, $flags = null) /** * Fetch the user agent string * - * @return \CodeIgniter\HTTP\UserAgent + * @return UserAgent */ public function getUserAgent() { @@ -763,7 +767,8 @@ protected function parseQueryString(): string { return ''; } - elseif (strncmp($uri, '/', 1) === 0) + + if (strncmp($uri, '/', 1) === 0) { $uri = explode('?', $uri, 2); $_SERVER['QUERY_STRING'] = $uri[1] ?? ''; diff --git a/system/HTTP/Message.php b/system/HTTP/Message.php index 9cc4594efb59..2e69f3b62d1d 100644 --- a/system/HTTP/Message.php +++ b/system/HTTP/Message.php @@ -46,7 +46,6 @@ */ class Message { - /** * List of all HTTP request headers. * @@ -78,7 +77,7 @@ class Message protected $validProtocolVersions = [ '1.0', '1.1', - '2', + '2.0', ]; /** @@ -88,7 +87,6 @@ class Message */ protected $body; - //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Body //-------------------------------------------------------------------- @@ -103,39 +101,34 @@ public function getBody() return $this->body; } - //-------------------------------------------------------------------- - /** * Sets the body of the current message. * * @param mixed $data * - * @return Message|Response + * @return $this */ - public function setBody($data) + public function setBody($data): self { $this->body = $data; return $this; } - //-------------------------------------------------------------------- - /** * Appends data to the body of the current message. * * @param mixed $data * - * @return Message|Response + * @return $this */ - public function appendBody($data) + public function appendBody($data): self { $this->body .= (string) $data; return $this; } - //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Headers //-------------------------------------------------------------------- @@ -143,7 +136,7 @@ public function appendBody($data) /** * Populates the $headers array with any headers the getServer knows about. */ - public function populateHeaders() + public function populateHeaders(): void { $contentType = $_SERVER['CONTENT_TYPE'] ?? getenv('CONTENT_TYPE'); if (! empty($contentType)) @@ -168,8 +161,6 @@ public function populateHeaders() } } - //-------------------------------------------------------------------- - /** * Returns an array containing all headers. * @@ -188,30 +179,26 @@ public function getHeaders(): array return $this->headers; } - //-------------------------------------------------------------------- - /** * Returns a single header object. If multiple headers with the same * name exist, then will return an array of header objects. * * @param string $name * - * @return array|\CodeIgniter\HTTP\Header|null + * @return array|Header|null */ public function getHeader(string $name) { - $orig_name = $this->getHeaderName($name); + $origName = $this->getHeaderName($name); - if (! isset($this->headers[$orig_name])) + if (! isset($this->headers[$origName])) { return null; } - return $this->headers[$orig_name]; + return $this->headers[$origName]; } - //-------------------------------------------------------------------- - /** * Determines whether a header exists. * @@ -221,13 +208,11 @@ public function getHeader(string $name) */ public function hasHeader(string $name): bool { - $orig_name = $this->getHeaderName($name); + $origName = $this->getHeaderName($name); - return isset($this->headers[$orig_name]); + return isset($this->headers[$origName]); } - //-------------------------------------------------------------------- - /** * Retrieves a comma-separated string of the values for a single header. * @@ -245,27 +230,25 @@ public function hasHeader(string $name): bool */ public function getHeaderLine(string $name): string { - $orig_name = $this->getHeaderName($name); + $origName = $this->getHeaderName($name); - if (! array_key_exists($orig_name, $this->headers)) + if (! array_key_exists($origName, $this->headers)) { return ''; } - return $this->headers[$orig_name]->getValueLine(); + return $this->headers[$origName]->getValueLine(); } - //-------------------------------------------------------------------- - /** * Sets a header and it's value. * * @param string $name * @param array|null|string $value * - * @return Message|Response + * @return $this */ - public function setHeader(string $name, $value) + public function setHeader(string $name, $value): self { $origName = $this->getHeaderName($name); @@ -290,27 +273,23 @@ public function setHeader(string $name, $value) return $this; } - //-------------------------------------------------------------------- - /** * Removes a header from the list of headers we track. * * @param string $name * - * @return Message + * @return $this */ - public function removeHeader(string $name) + public function removeHeader(string $name): self { - $orig_name = $this->getHeaderName($name); + $origName = $this->getHeaderName($name); - unset($this->headers[$orig_name]); + unset($this->headers[$origName]); unset($this->headerMap[strtolower($name)]); return $this; } - //-------------------------------------------------------------------- - /** * Adds an additional header value to any headers that accept * multiple values (i.e. are an array or implement ArrayAccess) @@ -318,21 +297,19 @@ public function removeHeader(string $name) * @param string $name * @param string|null $value * - * @return Message + * @return $this */ - public function appendHeader(string $name, ?string $value) + public function appendHeader(string $name, ?string $value): self { - $orig_name = $this->getHeaderName($name); + $origName = $this->getHeaderName($name); - array_key_exists($orig_name, $this->headers) - ? $this->headers[$orig_name]->appendValue($value) + array_key_exists($origName, $this->headers) + ? $this->headers[$origName]->appendValue($value) : $this->setHeader($name, $value); return $this; } - //-------------------------------------------------------------------- - /** * Adds an additional header value to any headers that accept * multiple values (i.e. are an array or implement ArrayAccess) @@ -340,18 +317,29 @@ public function appendHeader(string $name, ?string $value) * @param string $name * @param string $value * - * @return Message + * @return $this */ - public function prependHeader(string $name, string $value) + public function prependHeader(string $name, string $value): self { - $orig_name = $this->getHeaderName($name); + $origName = $this->getHeaderName($name); - $this->headers[$orig_name]->prependValue($value); + $this->headers[$origName]->prependValue($value); return $this; } - //-------------------------------------------------------------------- + /** + * Takes a header name in any case, and returns the + * normal-case version of the header. + * + * @param string $name + * + * @return string + */ + protected function getHeaderName(string $name): string + { + return $this->headerMap[strtolower($name)] ?? $name; + } /** * Returns the HTTP Protocol Version. @@ -363,23 +351,24 @@ public function getProtocolVersion(): string return $this->protocolVersion ?? '1.1'; } - //-------------------------------------------------------------------- - /** * Sets the HTTP protocol version. * * @param string $version * - * @return Message + * @return $this */ - public function setProtocolVersion(string $version) + public function setProtocolVersion(string $version): self { if (! is_numeric($version)) { $version = substr($version, strpos($version, '/') + 1); } - if (! in_array($version, $this->validProtocolVersions)) + // Make sure that version is in the correct format + $version = number_format((float) $version, 1); + + if (! in_array($version, $this->validProtocolVersions, true)) { throw HTTPException::forInvalidHTTPProtocol(implode(', ', $this->validProtocolVersions)); } @@ -389,22 +378,14 @@ public function setProtocolVersion(string $version) return $this; } - //-------------------------------------------------------------------- - /** - * Takes a header name in any case, and returns the - * normal-case version of the header. - * - * @param string $name + * Determines if this is a json message based on the Content-Type header * - * @return string + * @return boolean */ - protected function getHeaderName(string $name): string + public function isJSON() { - $lower_name = strtolower($name); - - return $this->headerMap[$lower_name] ?? $name; + return $this->hasHeader('Content-Type') + && $this->getHeader('Content-Type')->getValue() === 'application/json'; } - - //-------------------------------------------------------------------- } diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 793e1344945b..9b2204243d7c 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -57,7 +57,7 @@ class Negotiate /** * Request * - * @var \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\IncomingRequest + * @var RequestInterface|IncomingRequest */ protected $request; @@ -66,7 +66,7 @@ class Negotiate /** * Constructor * - * @param \CodeIgniter\HTTP\RequestInterface|null $request + * @param RequestInterface|null $request */ public function __construct(RequestInterface $request = null) { @@ -297,8 +297,8 @@ public function parseHeader(string $header): array usort($results, function ($a, $b) { if ($a['q'] === $b['q']) { - $a_ast = substr_count($a['value'], '*'); - $b_ast = substr_count($b['value'], '*'); + $aAst = substr_count($a['value'], '*'); + $bAst = substr_count($b['value'], '*'); // '*/*' has lower precedence than 'text/*', // and 'text/*' has lower priority than 'text/plain' @@ -306,7 +306,7 @@ public function parseHeader(string $header): array // This seems backwards, but needs to be that way // due to the way PHP7 handles ordering or array // elements created by reference. - if ($a_ast > $b_ast) + if ($aAst > $bAst) { return 1; } @@ -317,7 +317,7 @@ public function parseHeader(string $header): array // This seems backwards, but needs to be that way // due to the way PHP7 handles ordering or array // elements created by reference. - if ($a_ast === $b_ast) + if ($aAst === $bAst) { return count($b['params']) - count($a['params']); } diff --git a/system/HTTP/RedirectResponse.php b/system/HTTP/RedirectResponse.php index 7c1629bd52e9..2377967cb9d3 100644 --- a/system/HTTP/RedirectResponse.php +++ b/system/HTTP/RedirectResponse.php @@ -63,8 +63,7 @@ public function to(string $uri, int $code = null, string $method = 'auto') // for better security. if (strpos($uri, 'http') !== 0) { - $url = current_url(true)->resolveRelativeURI($uri); - $uri = (string)$url; + $uri = (string) current_url(true)->resolveRelativeURI($uri); } return $this->redirect($uri, $method, $code); @@ -79,13 +78,13 @@ public function to(string $uri, int $code = null, string $method = 'auto') * @param integer $code * @param string $method * + * @throws HTTPException + * * @return $this */ public function route(string $route, array $params = [], int $code = 302, string $method = 'auto') { - $routes = Services::routes(true); - - $route = $routes->reverseRoute($route, ...$params); + $route = Services::routes()->reverseRoute($route, ...$params); if (! $route) { @@ -108,36 +107,36 @@ public function route(string $route, array $params = [], int $code = 302, string */ public function back(int $code = null, string $method = 'auto') { - $this->ensureSession(); + Services::session(); return $this->redirect(previous_url(), $method, $code); } /** * Specifies that the current $_GET and $_POST arrays should be - * packaged up with the response. It will then be available via - * the 'old()' helper function. + * packaged up with the response. + * + * It will then be available via the 'old()' helper function. * * @return $this */ public function withInput() { - $session = $this->ensureSession(); + $session = Services::session(); - $input = [ + $session->setFlashdata('_ci_old_input', [ 'get' => $_GET ?? [], 'post' => $_POST ?? [], - ]; + ]); - $session->setFlashdata('_ci_old_input', $input); + // If the validation has any errors, transmit those back + // so they can be displayed when the validation is handled + // within a method different than displaying the form. + $validation = Services::validation(); - // If the validator has any errors, transmit those back - // so they can be displayed when the validation is - // handled within a method different than displaying the form. - $validator = Services::validation(); - if (! empty($validator->getErrors())) + if ($validation->getErrors()) { - $session->setFlashdata('_ci_validation_errors', serialize($validator->getErrors())); + $session->setFlashdata('_ci_validation_errors', serialize($validation->getErrors())); } return $this; @@ -153,9 +152,7 @@ public function withInput() */ public function with(string $key, $message) { - $session = $this->ensureSession(); - - $session->setFlashdata($key, $message); + Services::session()->setFlashdata($key, $message); return $this; } @@ -170,14 +167,7 @@ public function with(string $key, $message) */ public function withCookies() { - $cookies = service('response')->getCookies(); - - if (empty($cookies)) - { - return $this; - } - - foreach ($cookies as $cookie) + foreach (Services::response()->getCookies() as $cookie) { $this->cookies[] = $cookie; } @@ -195,28 +185,11 @@ public function withCookies() */ public function withHeaders() { - $headers = service('response')->getHeaders(); - - if (empty($headers)) - { - return $this; - } - - foreach ($headers as $name => $header) + foreach (Services::response()->getHeaders() as $name => $header) { $this->setHeader($name, $header->getValue()); } return $this; } - - /** - * Ensures the session is loaded and started. - * - * @return \CodeIgniter\Session\Session - */ - protected function ensureSession() - { - return Services::session(); - } } diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index 7c2c3fac0ac3..8e0ff0e0ac53 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -105,15 +105,15 @@ public function getIPAddress(): string return $this->ipAddress; } - $proxy_ips = $this->proxyIPs; + $proxyIps = $this->proxyIPs; if (! empty($this->proxyIPs) && ! is_array($this->proxyIPs)) { - $proxy_ips = explode(',', str_replace(' ', '', $this->proxyIPs)); + $proxyIps = explode(',', str_replace(' ', '', $this->proxyIPs)); } $this->ipAddress = $this->getServer('REMOTE_ADDR'); - if ($proxy_ips) + if ($proxyIps) { foreach (['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'] as $header) { @@ -137,14 +137,14 @@ public function getIPAddress(): string if ($spoof) { - foreach ($proxy_ips as $proxy_ip) + foreach ($proxyIps as $proxyIp) { // Check if we have an IP address or a subnet - if (strpos($proxy_ip, '/') === false) + if (strpos($proxyIp, '/') === false) { // An IP address (and not a subnet) is specified. // We can compare right away. - if ($proxy_ip === $this->ipAddress) + if ($proxyIp === $this->ipAddress) { $this->ipAddress = $spoof; break; @@ -158,7 +158,7 @@ public function getIPAddress(): string isset($separator) || $separator = $this->isValidIP($this->ipAddress, 'ipv6') ? ':' : '.'; // If the proxy entry doesn't match the IP protocol - skip it - if (strpos($proxy_ip, $separator) === false) // @phpstan-ignore-line + if (strpos($proxyIp, $separator) === false) // @phpstan-ignore-line { continue; } @@ -190,7 +190,7 @@ public function getIPAddress(): string } // Split the netmask length off the network address - sscanf($proxy_ip, '%[^/]/%d', $netaddr, $masklen); + sscanf($proxyIp, '%[^/]/%d', $netaddr, $masklen); // Again, an IPv6 address is most likely in a compressed form if ($separator === ':') // @phpstan-ignore-line @@ -236,7 +236,7 @@ public function getIPAddress(): string */ public function isValidIP(string $ip = null, string $which = null): bool { - switch (strtolower($which)) + switch (strtolower((string) $which)) { case 'ipv4': $which = FILTER_FLAG_IPV4; diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index 27ce1a6be510..301a7548244c 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -41,7 +41,11 @@ use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\Pager\PagerInterface; -use Config\Format; +use Config\App; +use Config\Services; +use DateTime; +use DateTimeZone; +use InvalidArgumentException; /** * Representation of an outgoing, getServer-side response. @@ -163,7 +167,7 @@ class Response extends Message implements ResponseInterface /** * Content security policy handler * - * @var \CodeIgniter\HTTP\ContentSecurityPolicy + * @var ContentSecurityPolicy */ public $CSP; @@ -236,7 +240,7 @@ class Response extends Message implements ResponseInterface /** * Constructor * - * @param \Config\App $config + * @param App $config */ public function __construct($config) { @@ -315,7 +319,7 @@ public function getStatusCode(): int * default to the IANA name. * * @return $this - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException For invalid status code arguments. + * @throws HTTPException For invalid status code arguments. */ public function setStatusCode(int $code, string $reason = '') { @@ -376,9 +380,9 @@ public function getReason(): string * * @return Response */ - public function setDate(\DateTime $date) + public function setDate(DateTime $date) { - $date->setTimezone(new \DateTimeZone('UTC')); + $date->setTimezone(new DateTimeZone('UTC')); $this->setHeader('Date', $date->format('D, d M Y H:i:s') . ' GMT'); @@ -390,7 +394,7 @@ public function setDate(\DateTime $date) /** * Set the Link Header * - * @param \CodeIgniter\Pager\PagerInterface $pager + * @param PagerInterface $pager * * @see http://tools.ietf.org/html/rfc5988 * @@ -471,7 +475,7 @@ public function setJSON($body, bool $unencoded = false) * * @return mixed|string * - * @throws \InvalidArgumentException If the body property is not array. + * @throws InvalidArgumentException If the body property is not array. */ public function getJSON() { @@ -479,13 +483,7 @@ public function getJSON() if ($this->bodyFormat !== 'json') { - /** - * @var Format $config - */ - $config = config(Format::class); - $formatter = $config->getFormatter('application/json'); - - $body = $formatter->format($body); + $body = Services::format()->getFormatter('application/json')->format($body); } return $body ?: null; @@ -513,7 +511,7 @@ public function setXML($body) * Retrieves the current body into XML and returns it. * * @return mixed|string - * @throws \InvalidArgumentException If the body property is not array. + * @throws InvalidArgumentException If the body property is not array. */ public function getXML() { @@ -521,13 +519,7 @@ public function getXML() if ($this->bodyFormat !== 'xml') { - /** - * @var Format $config - */ - $config = config(Format::class); - $formatter = $config->getFormatter('application/xml'); - - $body = $formatter->format($body); + $body = Services::format()->getFormatter('application/xml')->format($body); } return $body; @@ -543,7 +535,7 @@ public function getXML() * @param string $format Valid: json, xml * * @return mixed - * @throws \InvalidArgumentException If the body property is not string or array. + * @throws InvalidArgumentException If the body property is not string or array. */ protected function formatBody($body, string $format) { @@ -554,13 +546,7 @@ protected function formatBody($body, string $format) // Nothing much to do for a string... if (! is_string($body) || $format === 'json-unencoded') { - /** - * @var Format $config - */ - $config = config(Format::class); - $formatter = $config->getFormatter($mime); - - $body = $formatter->format($body); + $body = Services::format()->getFormatter($mime)->format($body); } return $body; @@ -662,9 +648,9 @@ public function setCache(array $options = []) */ public function setLastModified($date) { - if ($date instanceof \DateTime) + if ($date instanceof DateTime) { - $date->setTimezone(new \DateTimeZone('UTC')); + $date->setTimezone(new DateTimeZone('UTC')); $this->setHeader('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT'); } elseif (is_string($date)) @@ -724,7 +710,7 @@ public function sendHeaders() // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html if (! isset($this->headers['Date']) && php_sapi_name() !== 'cli-server') { - $this->setDate(\DateTime::createFromFormat('U', (string) time())); + $this->setDate(DateTime::createFromFormat('U', (string) time())); } // HTTP Status @@ -773,7 +759,7 @@ public function getBody() * @param integer $code The type of redirection, defaults to 302 * * @return $this - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException For invalid status code. + * @throws HTTPException For invalid status code. */ public function redirect(string $uri, string $method = 'auto', int $code = null) { @@ -830,7 +816,7 @@ public function redirect(string $uri, string $method = 'auto', int $code = null) * @param string $prefix Cookie name prefix * @param boolean $secure Whether to only transfer cookies via SSL * @param boolean $httponly Whether only make the cookie accessible via HTTP (no javascript) - * @param string|null $samesite + * @param string|null $samesite * * @return $this */ @@ -899,7 +885,7 @@ public function setCookie( } else { - $expire = ($expire > 0) ? time() + $expire : 0; // @phpstan-ignore-line + $expire = ($expire > 0) ? time() + $expire : 0; } $cookie = [ @@ -1109,7 +1095,7 @@ protected function sendCookies() * @param string|null $data The data to be downloaded * @param boolean $setMime Whether to try and send the actual MIME type * - * @return \CodeIgniter\HTTP\DownloadResponse|null + * @return DownloadResponse|null */ public function download(string $filename = '', $data = '', bool $setMime = false) { diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php index b39339d45c5c..deb47db8398a 100644 --- a/system/HTTP/ResponseInterface.php +++ b/system/HTTP/ResponseInterface.php @@ -39,6 +39,9 @@ namespace CodeIgniter\HTTP; +use DateTime; +use InvalidArgumentException; + /** * Representation of an outgoing, getServer-side response. * @@ -158,7 +161,7 @@ public function getStatusCode(): int; * default to the IANA name. * * @return self - * @throws \InvalidArgumentException For invalid status code arguments. + * @throws InvalidArgumentException For invalid status code arguments. */ public function setStatusCode(int $code, string $reason = ''); @@ -186,7 +189,7 @@ public function getReason(): string; * * @return ResponseInterface */ - public function setDate(\DateTime $date); + public function setDate(DateTime $date); //-------------------------------------------------------------------- diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 5158929ae3fe..659fa6f905c8 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -40,6 +40,8 @@ namespace CodeIgniter\HTTP; use CodeIgniter\HTTP\Exceptions\HTTPException; +use Config\App; +use InvalidArgumentException; /** * Abstraction for a uniform resource identifier (URI). @@ -174,7 +176,7 @@ class URI * * @param string $uri * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function __construct(string $uri = null) { @@ -593,9 +595,29 @@ public function getTotalSegments(): int */ public function __toString(): string { + // If hosted in a sub-folder, we will have additional + // segments that show up prior to the URI path we just + // grabbed from the request, so add it on if necessary. + $config = config(App::class); + $baseUri = new self($config->baseURL); + $basePath = trim($baseUri->getPath(), '/') . '/'; + $path = $this->getPath(); + $trimPath = ltrim($path, '/'); + + if ($basePath !== '/' && strpos($trimPath, $basePath) !== 0) + { + $path = $basePath . $trimPath; + } + + // force https if needed + if ($config->forceGlobalSecureRequests) + { + $this->setScheme('https'); + } + return static::createURIString( - $this->getScheme(), $this->getAuthority(), $this->getPath(), // Absolute URIs should use a "/" for an empty path - $this->getQuery(), $this->getFragment() + $this->getScheme(), $this->getAuthority(), $path, // Absolute URIs should use a "/" for an empty path + $this->getQuery(), $this->getFragment() ); } @@ -845,7 +867,7 @@ public function setQuery(string $query) * * @param array $query * - * @return \CodeIgniter\HTTP\URI + * @return URI */ public function setQueryArray(array $query) { @@ -876,7 +898,7 @@ public function addQuery(string $key, $value = null) /** * Removes one or more query vars from the URI. * - * @param array ...$params + * @param string ...$params * * @return $this */ @@ -884,7 +906,7 @@ public function stripQuery(...$params) { foreach ($params as $param) { - unset($this->query[$param]); // @phpstan-ignore-line + unset($this->query[$param]); } return $this; @@ -896,7 +918,7 @@ public function stripQuery(...$params) * Filters the query variables so that only the keys passed in * are kept. The rest are removed from the object. * - * @param array ...$params + * @param string ...$params * * @return $this */ @@ -906,7 +928,7 @@ public function keepQuery(...$params) foreach ($this->query as $key => $value) { - if (! in_array($key, $params)) + if (! in_array($key, $params, true)) { continue; } @@ -1054,7 +1076,7 @@ protected function applyParts(array $parts) * * @param string $uri * - * @return \CodeIgniter\HTTP\URI + * @return URI */ public function resolveRelativeURI(string $uri) { @@ -1062,7 +1084,7 @@ public function resolveRelativeURI(string $uri) * NOTE: We don't use removeDotSegments in this * algorithm since it's already done by this line! */ - $relative = new URI(); + $relative = new self(); $relative->setURI($uri); if ($relative->getScheme() === $this->getScheme()) diff --git a/system/HTTP/UserAgent.php b/system/HTTP/UserAgent.php index 4f7777ac75f6..ac9f6487b6a1 100644 --- a/system/HTTP/UserAgent.php +++ b/system/HTTP/UserAgent.php @@ -77,7 +77,7 @@ class UserAgent /** * Holds the config file contents. * - * @var \Config\UserAgents + * @var UserAgents */ protected $config; @@ -130,7 +130,7 @@ class UserAgent * * Sets the User Agent and runs the compilation routine * - * @param null|\Config\UserAgents $config + * @param null|UserAgents $config */ public function __construct(UserAgents $config = null) { @@ -238,10 +238,10 @@ public function isReferral(): bool } else { - $referer_host = @parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST); - $own_host = parse_url(\base_url(), PHP_URL_HOST); + $refererHost = @parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST); + $ownHost = parse_url(\base_url(), PHP_URL_HOST); - $this->referrer = ($referer_host && $referer_host !== $own_host); + $this->referrer = ($refererHost && $refererHost !== $ownHost); } } diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 9e96aeccaa77..2193316249a7 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -120,3 +120,35 @@ function _array_search_dot(array $indexes, array $array) return $array[$currentIndex]; } } + +if (! function_exists('array_deep_search')) +{ + /** + * Returns the value of an element at a key in an array of uncertain depth. + * + * @param mixed $key + * @param array $array + * + * @return mixed|null + */ + function array_deep_search($key, array $array) + { + if (isset($array[$key])) + { + return $array[$key]; + } + + foreach ($array as $value) + { + if (is_array($value)) + { + if ($result = array_deep_search($key, $value)) + { + return $result; + } + } + } + + return null; + } +} diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index e7125bf53a1b..8450ffdf58c0 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -37,6 +37,9 @@ * @filesource */ +use Config\App; +use Config\Services; + // -------------------------------------------------------------------- /** @@ -62,7 +65,7 @@ * @param boolean $secure True makes the cookie secure * @param boolean $httpOnly True makes the cookie accessible via * http(s) only (no javascript) - * @param string|null $sameSite The cookie SameSite value + * @param string|null $sameSite The cookie SameSite value * * @see (\Config\Services::response())->setCookie() * @see \CodeIgniter\HTTP\Response::setCookie() @@ -81,7 +84,7 @@ function set_cookie( { // The following line shows as a syntax error in NetBeans IDE //(\Config\Services::response())->setcookie - $response = \Config\Services::response(); + $response = Services::response(); $response->setcookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly, $sameSite); } } @@ -103,11 +106,11 @@ function set_cookie( */ function get_cookie($index, bool $xssClean = false) { - $app = config(\Config\App::class); + $app = config(App::class); $appCookiePrefix = $app->cookiePrefix; $prefix = isset($_COOKIE[$index]) ? '' : $appCookiePrefix; - $request = \Config\Services::request(); + $request = Services::request(); $filter = true === $xssClean ? FILTER_SANITIZE_STRING : null; return $request->getCookie($prefix . $index, $filter); @@ -133,6 +136,6 @@ function get_cookie($index, bool $xssClean = false) */ function delete_cookie($name, string $domain = '', string $path = '/', string $prefix = '') { - \Config\Services::response()->deleteCookie($name, $domain, $path, $prefix); + Services::response()->deleteCookie($name, $domain, $path, $prefix); } } diff --git a/system/Helpers/date_helper.php b/system/Helpers/date_helper.php index bf435bc2917b..39e38dfd53f0 100644 --- a/system/Helpers/date_helper.php +++ b/system/Helpers/date_helper.php @@ -54,7 +54,7 @@ * @param string $timezone * * @return integer - * @throws \Exception + * @throws Exception */ function now(string $timezone = null): int { @@ -85,11 +85,11 @@ function now(string $timezone = null): int * @param string $country A two-letter ISO 3166-1 compatible country code (for listIdentifiers) * * @return string - * @throws \Exception + * @throws Exception */ - function timezone_select(string $class = '', string $default = '', int $what = \DateTimeZone::ALL, string $country = null): string + function timezone_select(string $class = '', string $default = '', int $what = DateTimeZone::ALL, string $country = null): string { - $timezones = \DateTimeZone::listIdentifiers($what, $country); + $timezones = DateTimeZone::listIdentifiers($what, $country); $buffer = "