diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index c660b937e04..c4f1605bc52 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -1,122 +1,191 @@ name: PHPUnit -on: [push, pull_request] +on: [ push, pull_request ] permissions: - contents: read + contents: read jobs: - tests: - runs-on: ubuntu-22.04 - if: "!contains(github.event.head_commit.message, '[ci skip]')" - env: - PHP_INI_VALUES: assert.exception=1, zend.assertions=1 + tests: + runs-on: ubuntu-22.04 + if: "!contains(github.event.head_commit.message, '[ci skip]')" + env: + PHP_INI_VALUES: assert.exception=1, zend.assertions=1 - strategy: - fail-fast: false - matrix: - php: [ '8.1', '8.0', '7.4', '7.3', '7.2', '7.1', '7.0', '5.6', '5.5', '5.4'] - DB: [ 'pdo/mysql', 'pdo/pgsql', 'pdo/sqlite', 'mysqli', 'pgsql', 'sqlite' ] - compiler: [ default ] - include: - - php: '8.1' - DB: 'pdo/mysql' - compiler: jit - - php: '8.1' - DB: 'pdo/pgsql' - compiler: jit - - php: '8.1' - DB: 'pdo/sqlite' - compiler: jit - - php: '8.1' - DB: 'mysqli' - compiler: jit - - php: '8.1' - DB: 'pgsql' - compiler: jit - - php: '8.1' - DB: 'sqlite' - compiler: jit - - php: '8.0' - DB: 'pdo/mysql' - compiler: jit - - php: '8.0' - DB: 'pdo/pgsql' - compiler: jit - - php: '8.0' - DB: 'pdo/sqlite' - compiler: jit - - php: '8.0' - DB: 'mysqli' - compiler: jit - - php: '8.0' - DB: 'pgsql' - compiler: jit - - php: '8.0' - DB: 'sqlite' - compiler: jit - - php: '5.6' - DB: 'mysql' - compiler: default - - php: '5.5' - DB: 'mysql' - compiler: default - - php: '5.4' - DB: 'mysql' - compiler: default + strategy: + fail-fast: false + matrix: + php: [ '8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '7.4', '7.3', '7.2', '7.1', '7.0', '5.6', '5.5', '5.4' ] + DB: [ 'pdo/mysql', 'pdo/pgsql', 'pdo/sqlite', 'mysqli', 'pgsql', 'sqlite' ] + compiler: [ default ] + include: + - php: '8.5' + DB: 'pdo/mysql' + compiler: jit + - php: '8.5' + DB: 'pdo/pgsql' + compiler: jit + - php: '8.5' + DB: 'pdo/sqlite' + compiler: jit + - php: '8.5' + DB: 'mysqli' + compiler: jit + - php: '8.5' + DB: 'pgsql' + compiler: jit + - php: '8.5' + DB: 'sqlite' + - php: '8.4' + DB: 'pdo/mysql' + compiler: jit + - php: '8.4' + DB: 'pdo/pgsql' + compiler: jit + - php: '8.4' + DB: 'pdo/sqlite' + compiler: jit + - php: '8.4' + DB: 'mysqli' + compiler: jit + - php: '8.4' + DB: 'pgsql' + compiler: jit + - php: '8.4' + DB: 'sqlite' + - php: '8.3' + DB: 'pdo/mysql' + compiler: jit + - php: '8.3' + DB: 'pdo/pgsql' + compiler: jit + - php: '8.3' + DB: 'pdo/sqlite' + compiler: jit + - php: '8.3' + DB: 'mysqli' + compiler: jit + - php: '8.3' + DB: 'pgsql' + compiler: jit + - php: '8.3' + DB: 'sqlite' + - php: '8.2' + DB: 'pdo/mysql' + compiler: jit + - php: '8.2' + DB: 'pdo/pgsql' + compiler: jit + - php: '8.2' + DB: 'pdo/sqlite' + compiler: jit + - php: '8.2' + DB: 'mysqli' + compiler: jit + - php: '8.2' + DB: 'pgsql' + compiler: jit + - php: '8.2' + DB: 'sqlite' + compiler: jit + - php: '8.1' + DB: 'pdo/mysql' + compiler: jit + - php: '8.1' + DB: 'pdo/pgsql' + compiler: jit + - php: '8.1' + DB: 'pdo/sqlite' + compiler: jit + - php: '8.1' + DB: 'mysqli' + compiler: jit + - php: '8.1' + DB: 'pgsql' + compiler: jit + - php: '8.1' + DB: 'sqlite' + compiler: jit + - php: '8.0' + DB: 'pdo/mysql' + compiler: jit + - php: '8.0' + DB: 'pdo/pgsql' + compiler: jit + - php: '8.0' + DB: 'pdo/sqlite' + compiler: jit + - php: '8.0' + DB: 'mysqli' + compiler: jit + - php: '8.0' + DB: 'pgsql' + compiler: jit + - php: '8.0' + DB: 'sqlite' + compiler: jit + - php: '5.6' + DB: 'mysql' + compiler: default + - php: '5.5' + DB: 'mysql' + compiler: default + - php: '5.4' + DB: 'mysql' + compiler: default - services: - postgres: - image: postgres:12 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: ci_test - ports: - - 5432:5432 - options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + services: + postgres: + image: postgres:12 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: ci_test + ports: + - 5432:5432 + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 - mysql: - image: mysql:5.7 - env: - MYSQL_ALLOW_EMPTY_PASSWORD: true - MYSQL_USER: travis - MYSQL_PASSWORD: travis - MYSQL_DATABASE: ci_test - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_USER: travis + MYSQL_PASSWORD: travis + MYSQL_DATABASE: ci_test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Override PHP ini values for JIT compiler - if: matrix.compiler == 'jit' - run: echo "PHP_INI_VALUES::assert.exception=1, zend.assertions=1, opcache.enable=1, opcache.enable_cli=1, opcache.optimization_level=-1, opcache.jit=1255, opcache.jit_buffer_size=64M" >> $GITHUB_ENV + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Override PHP ini values for JIT compiler + if: matrix.compiler == 'jit' + run: echo "PHP_INI_VALUES::assert.exception=1, zend.assertions=1, opcache.enable=1, opcache.enable_cli=1, opcache.optimization_level=-1, opcache.jit=1255, opcache.jit_buffer_size=64M" >> $GITHUB_ENV - - name: Install PHP${{ matrix.php }} - DB ${{ matrix.DB }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: composer, pecl - extensions: imagick, sqlite3, pgsql, mysqli, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, mbstring - ini-values: ${{ env.PHP_INI_VALUES }} - coverage: xdebug + - name: Install PHP${{ matrix.php }} - DB ${{ matrix.DB }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer, pecl + extensions: imagick, sqlite3, pgsql, mysqli, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, mbstring + ini-values: ${{ env.PHP_INI_VALUES }} + coverage: xdebug - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache composer dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - name: Install composer dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader - - name: PHPUnit Test - run: | - php -d error_reporting=E_ALL -d zend.enable_gc=0 -d date.timezone=UTC -d mbstring.func_overload=7 -d mbstring.internal_encoding=UTF-8 vendor/bin/phpunit --coverage-text --configuration tests/travis/${{ matrix.DB }}.phpunit.xml - env: - XDEBUG_MODE: coverage + - name: PHPUnit Test + run: | + php -d error_reporting=E_ALL -d zend.enable_gc=0 -d date.timezone=UTC -d mbstring.func_overload=7 -d mbstring.internal_encoding=UTF-8 vendor/bin/phpunit --coverage-text --configuration tests/travis/${{ matrix.DB }}.phpunit.xml + env: + XDEBUG_MODE: coverage diff --git a/application/config/config.php b/application/config/config.php index 1e37856fec6..836d9490b3b 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -237,6 +237,20 @@ */ $config['log_filename'] = ''; +/* +|-------------------------------------------------------------------------- +| Log File Extension (DEPRECATED) +|-------------------------------------------------------------------------- +| +| This option is deprecated. Use 'log_filename' instead. +| Kept for backward compatibility with CI 3.1.x configurations. +| +| If 'log_filename' is set, this option is ignored. +| Example: 'txt' or 'log' (without leading dot) +| +*/ +$config['log_file_extension'] = ''; + /* |-------------------------------------------------------------------------- | Log File Permissions diff --git a/application/config/mimes.php b/application/config/mimes.php index b2e989fea9e..d806f69246f 100644 --- a/application/config/mimes.php +++ b/application/config/mimes.php @@ -182,5 +182,6 @@ 'odt' => 'application/vnd.oasis.opendocument.text', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'ott' => 'application/vnd.oasis.opendocument.text-template', - 'oth' => 'application/vnd.oasis.opendocument.text-web' + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'webp' => 'image/webp' ); diff --git a/composer.json b/composer.json index f5b4e8db43d..81c3ea5065e 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,11 @@ { - "description": "The CodeIgniter framework", - "name": "codeigniter/framework", + "description": "Actively maintained CodeIgniter 3 fork with support for PHP 5.4 - PHP 8.5 (and beyond). Fully backward compatible.", + "name": "pocketarc/codeigniter", "type": "project", - "homepage": "https://codeigniter.com", + "homepage": "https://github.com/pocketarc/codeigniter", "license": "MIT", "support": { - "forum": "https://forum.codeigniter.com/", - "wiki": "https://github.com/bcit-ci/CodeIgniter/wiki", - "slack": "https://codeigniterchat.slack.com", - "source": "https://github.com/bcit-ci/CodeIgniter" + "source": "https://github.com/pocketarc/codeigniter" }, "require": { "php": ">=5.4.8" @@ -30,6 +27,6 @@ }, "require-dev": { "mikey179/vfsstream": "1.6.*", - "phpunit/phpunit": "4.* || 5.* || 9.*" + "phpunit/phpunit": "4.* || 5.* || 8.* || 9.*" } } diff --git a/index.php b/index.php index 8b095a77a78..81a588c22f5 100644 --- a/index.php +++ b/index.php @@ -73,7 +73,11 @@ case 'testing': case 'production': ini_set('display_errors', 0); - error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); + if (version_compare(PHP_VERSION, '8.4', '>=')) { + error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_USER_NOTICE & ~E_USER_DEPRECATED); + } else { + error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); + } break; default: diff --git a/readme.rst b/readme.rst index 424848ac3e5..5407e33a557 100644 --- a/readme.rst +++ b/readme.rst @@ -1,82 +1,101 @@ -################### -What is CodeIgniter -################### +######################## +What is this repository? +######################## -CodeIgniter is an Application Development Framework - a toolkit - for people -who build web sites using PHP. Its goal is to enable you to develop projects -much faster than you could if you were writing code from scratch, by providing -a rich set of libraries for commonly needed tasks, as well as a simple -interface and logical structure to access these libraries. CodeIgniter lets -you creatively focus on your project by minimizing the amount of code needed -for a given task. +|tests| |php| |version| |downloads| -************* -CodeIgniter 3 -************* +.. |tests| image:: https://github.com/pocketarc/codeigniter/actions/workflows/test-phpunit.yml/badge.svg?branch=develop + :target: https://github.com/pocketarc/codeigniter/actions/workflows/test-phpunit.yml + :alt: PHPUnit Tests -This repository is for the legacy version, CodeIgniter 3. -`CodeIgniter 4 `_ is the latest -version of the framework. +.. |php| image:: https://img.shields.io/badge/PHP-5.4%20--%208.5-8892BF?logo=php + :alt: PHP 5.4 - 8.5 -CodeIgniter 3 is the legacy version of the framework, intended for use with PHP -5.6+. This version is in maintenance, receiving mostly just security updates. +.. |version| image:: https://img.shields.io/packagist/v/pocketarc/codeigniter + :target: https://packagist.org/packages/pocketarc/codeigniter + :alt: Packagist Version -******************* -Release Information -******************* +.. |downloads| image:: https://img.shields.io/packagist/dt/pocketarc/codeigniter + :alt: Packagist Downloads + +This is a fork of CodeIgniter 3, with the goal of keeping it up to date with modern PHP versions. There is no intention to add new features or change the way CI3 works. This is purely a maintenance fork. + +**PHP Compatibility:** + +- ✅ PHP 5.4 - 8.1 (as per original CI3 support) +- ✅ PHP 8.2 +- ✅ PHP 8.3 +- ✅ PHP 8.4 +- ✅ PHP 8.5 (and beyond as they are released) + +The original CodeIgniter 3.x branch is no longer maintained, and has not been updated to work with PHP 8.2, or any newer version. This fork is intended to fill that gap. + +If the original CodeIgniter 3.x branch is updated to work with PHP 8.2+, and starts to be maintained again, this fork might be retired. + +******************** +Maintenance Policy +******************** + +This fork commits to: -This repo contains in-development code for future releases. To download the -latest stable release please visit the `CodeIgniter Downloads -`_ page. +- Maintaining compatibility with each new PHP version while still supporting PHP 5.4+ +- Applying critical security fixes +- Keeping changes minimal to preserve CI3 behavior +- Reverting breaking changes in CodeIgniter 3.2.0-dev to maintain backward compatibility (e.g. restoring the Cart library, Email helper, and other deprecated-but-removed functionality) +- Running the full CI3 test suite on PHP 8.2+ -************************** -Changelog and New Features -************************** +If you find something that was removed in CI 3.2.0-dev and breaks backward compatibility for your application, please open an issue. We're happy to restore it. -You can find a list of all changes for each release in the `user -guide change log `_. +This fork does NOT: + +- Add new features +- Change existing CI3 behavior +- Provide commercial support +- Make migration to CI4 any harder (or easier) + +**************** +Issues and Pulls +**************** + +Issues and Pull Requests are welcome, but please note that this is a maintenance fork. New features will not be accepted. If you have a new feature you would like to see in CodeIgniter, please submit it to the original CodeIgniter 3.x branch. ******************* Server Requirements ******************* -PHP version 5.6 or newer is recommended. - -It should work on 5.4.8 as well, but we strongly advise you NOT to run -such old versions of PHP, because of potential security and performance -issues, as well as missing features. +PHP version 5.4 or newer, same as the original CI3 requirements. ************ Installation ************ -Please see the `installation section `_ -of the CodeIgniter User Guide. +You can install this fork using Composer: + +.. code-block:: bash + + composer require pocketarc/codeigniter + +After installation, you need to point CodeIgniter to the new system directory. In your `index.php` file, update the `$system_path` variable: + +.. code-block:: php + + $system_path = 'vendor/pocketarc/codeigniter/system'; -******* -License -******* +**Alternative Installation (Manual)** -Please see the `license -agreement `_. +If you prefer the traditional approach of replacing the system directory: -********* -Resources -********* +1. Download this repository +2. Replace your existing `system/` directory with the one from this fork +3. No changes to `index.php` are needed with this method -- `User Guide `_ -- `Contributing Guide `_ -- `Language File Translations `_ -- `Community Forums `_ -- `Community Wiki `_ -- `Community Slack Channel `_ +**Note:** The Composer method makes future updates easier with `composer update`, while the manual method requires downloading and replacing the system directory each time. -Report security issues to our `Security Panel `_ -or via our `page on HackerOne `_, thank you. +**Upgrading from Original CI3** -*************** -Acknowledgement -*************** +This fork is based on the unreleased CodeIgniter 3.2.0-dev. For most +applications the upgrade is straightforward: install via Composer, +update your `$system_path`, and review the upgrade guide. -The CodeIgniter team would like to thank EllisLab, all the -contributors to the CodeIgniter project and you, the CodeIgniter user. +The upgrade guide covers both 3.1.x and 3.2-dev users: +`upgrade_320.rst `_ diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php index 87dd868f966..ff3e150cbe3 100644 --- a/system/core/CodeIgniter.php +++ b/system/core/CodeIgniter.php @@ -56,7 +56,7 @@ * @var string * */ - const CI_VERSION = '3.2.0-dev'; + const CI_VERSION = '3.4.2'; /* * ------------------------------------------------------ diff --git a/system/core/Common.php b/system/core/Common.php index c7bb34549b5..bcb1f1aa36d 100644 --- a/system/core/Common.php +++ b/system/core/Common.php @@ -727,7 +727,7 @@ function remove_invisible_characters($str, $url_encoded = TRUE) do { - $str = preg_replace($non_displayables, '', $str, -1, $count); + $str = preg_replace($non_displayables, '', (string)$str, -1, $count); } while ($count); diff --git a/system/core/Config.php b/system/core/Config.php index 4efe1e1ec1b..e5476509efb 100644 --- a/system/core/Config.php +++ b/system/core/Config.php @@ -325,6 +325,20 @@ public function base_url($uri = '', $protocol = NULL) // ------------------------------------------------------------- + /** + * System URL + * + * @deprecated 3.0.0 + * @return string + */ + public function system_url() + { + $x = explode('/', preg_replace('|/*(.+?)/*$|', '\\1', BASEPATH)); + return $this->slash_item('base_url').end($x).'/'; + } + + // ------------------------------------------------------------- + /** * Build URI string * diff --git a/system/core/Controller.php b/system/core/Controller.php index aeccd60ee4c..3713ae06308 100644 --- a/system/core/Controller.php +++ b/system/core/Controller.php @@ -50,6 +50,7 @@ * @author EllisLab Dev Team * @link https://codeigniter.com/userguide3/general/controllers.html */ +#[AllowDynamicProperties] class CI_Controller { /** diff --git a/system/core/Exceptions.php b/system/core/Exceptions.php index 7244f3f28ea..4c3b63e4e97 100644 --- a/system/core/Exceptions.php +++ b/system/core/Exceptions.php @@ -62,18 +62,25 @@ class CI_Exceptions { * @var array */ public $levels = array( - E_ERROR => 'Error', - E_WARNING => 'Warning', - E_PARSE => 'Parsing Error', - E_NOTICE => 'Notice', - E_CORE_ERROR => 'Core Error', - E_CORE_WARNING => 'Core Warning', - E_COMPILE_ERROR => 'Compile Error', - E_COMPILE_WARNING => 'Compile Warning', - E_USER_ERROR => 'User Error', - E_USER_WARNING => 'User Warning', - E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice' + E_ERROR => 'Error', + E_RECOVERABLE_ERROR => 'Recoverable Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_DEPRECATED => 'Deprecated Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_USER_DEPRECATED => 'User Deprecated Notice', + + # 2048 is E_STRICT, but it's deprecated in PHP 8.4. + # We're keeping this here for backwards compatibility. + # If we're on older PHP, E_STRICT errors will be labelled correctly, and if we're on PHP 8.4+, this will be ignored. + 2048 => 'Strict Notice', ); /** diff --git a/system/core/Input.php b/system/core/Input.php index 62a1d89f87a..47e71f9ae95 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -662,6 +662,22 @@ public function is_ajax_request() // -------------------------------------------------------------------- + /** + * Is CLI request? + * + * Test to see if a request was made from the command line. + * + * @deprecated 3.0.0 Use is_cli() instead + * @see is_cli() + * @return bool + */ + public function is_cli_request() + { + return is_cli(); + } + + // -------------------------------------------------------------------- + /** * Get Request Method * diff --git a/system/core/Lang.php b/system/core/Lang.php index 18299060c09..95cc8039c46 100644 --- a/system/core/Lang.php +++ b/system/core/Lang.php @@ -190,7 +190,7 @@ public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE */ public function line($line, $log_errors = TRUE) { - $value = isset($this->language[$line]) ? $this->language[$line] : FALSE; + $value = ($line !== NULL && isset($this->language[$line])) ? $this->language[$line] : FALSE; // Because killer robots like unicorns! if ($value === FALSE && $log_errors === TRUE) diff --git a/system/core/Loader.php b/system/core/Loader.php index 648b7cfc7c2..b7380c05098 100644 --- a/system/core/Loader.php +++ b/system/core/Loader.php @@ -49,6 +49,7 @@ * @author EllisLab Dev Team * @link https://codeigniter.com/userguide3/libraries/loader.html */ +#[AllowDynamicProperties] class CI_Loader { // All these are set automatically. Don't mess with them. @@ -498,7 +499,20 @@ class_exists('CI_DB', FALSE) OR $this->database(); */ public function view($view, $vars = array(), $return = FALSE) { - return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_prepare_view_vars($vars), '_ci_return' => $return)); + $measure = "View - $view"; + $CI =& get_instance(); + + if (property_exists($CI, "debugbar") && $CI->debugbar) { + $CI->debugbar["time"]->startMeasure($measure); + } + + $result = $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_prepare_view_vars($vars), '_ci_return' => $return)); + + if (property_exists($CI, "debugbar") && $CI->debugbar && $CI->debugbar["time"]->hasStartedMeasure($measure)) { + $CI->debugbar["time"]->stopMeasure($measure); + } + + return $result; } // -------------------------------------------------------------------- diff --git a/system/core/Log.php b/system/core/Log.php index 99642e0c6a2..efee3fe4ccf 100644 --- a/system/core/Log.php +++ b/system/core/Log.php @@ -128,8 +128,19 @@ public function __construct() $this->_log_path = ($config['log_path'] !== '') ? rtrim($config['log_path'], '/\\').DIRECTORY_SEPARATOR : APPPATH.'logs'.DIRECTORY_SEPARATOR; - $this->_log_filename = (isset($config['log_filename']) && $config['log_filename'] !== '') - ? $config['log_filename'] : 'log-'.date('Y-m-d').'.php'; + if (isset($config['log_filename']) && $config['log_filename'] !== '') + { + $this->_log_filename = $config['log_filename']; + } + elseif (isset($config['log_file_extension']) && $config['log_file_extension'] !== '') + { + $ext = ltrim($config['log_file_extension'], '.'); + $this->_log_filename = 'log-'.date('Y-m-d').'.'.$ext; + } + else + { + $this->_log_filename = 'log-'.date('Y-m-d').'.php'; + } file_exists($this->_log_path) OR mkdir($this->_log_path, 0755, TRUE); diff --git a/system/core/Model.php b/system/core/Model.php index 1ba10fbb79d..5ca24e0a46f 100644 --- a/system/core/Model.php +++ b/system/core/Model.php @@ -47,8 +47,17 @@ * @author EllisLab Dev Team * @link https://codeigniter.com/userguide3/libraries/config.html */ +#[AllowDynamicProperties] class CI_Model { + /** + * Class constructor + * + * @link https://github.com/bcit-ci/CodeIgniter/issues/5332 + * @return void + */ + public function __construct() {} + /** * __get magic * diff --git a/system/core/Output.php b/system/core/Output.php index 02f3933f515..2109774932c 100644 --- a/system/core/Output.php +++ b/system/core/Output.php @@ -456,7 +456,7 @@ public function _display($output = NULL) $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end'); - if ($this->parse_exec_vars === TRUE) + if ($this->parse_exec_vars === TRUE && !empty($output)) { $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB'; $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output); diff --git a/system/core/Router.php b/system/core/Router.php index e0fb922f1a6..6dca57cf3d2 100644 --- a/system/core/Router.php +++ b/system/core/Router.php @@ -58,6 +58,13 @@ class CI_Router { */ public $config; + /** + * CI_URI class object + * + * @var object + */ + public $uri; + /** * List of routes * @@ -439,6 +446,19 @@ public function set_class($class) // -------------------------------------------------------------------- + /** + * Fetch the current class + * + * @deprecated 3.0.0 Read the 'class' property instead + * @return string + */ + public function fetch_class() + { + return $this->class; + } + + // -------------------------------------------------------------------- + /** * Set method name * @@ -452,6 +472,19 @@ public function set_method($method) // -------------------------------------------------------------------- + /** + * Fetch the current method + * + * @deprecated 3.0.0 Read the 'method' property instead + * @return string + */ + public function fetch_method() + { + return $this->method; + } + + // -------------------------------------------------------------------- + /** * Set directory name * @@ -470,4 +503,21 @@ public function set_directory($dir, $append = FALSE) $this->directory .= str_replace('.', '', trim($dir, '/')).'/'; } } + + // -------------------------------------------------------------------- + + /** + * Fetch directory + * + * Feches the sub-directory (if any) that contains the requested + * controller class. + * + * @deprecated 3.0.0 Read the 'directory' property instead + * @return string + */ + public function fetch_directory() + { + return $this->directory; + } + } diff --git a/system/core/URI.php b/system/core/URI.php index 1e948588354..95b9c926c96 100644 --- a/system/core/URI.php +++ b/system/core/URI.php @@ -51,6 +51,13 @@ */ class CI_URI { + /** + * CI_Config instance + * + * @var CI_Config + */ + public $config; + /** * List of cached URI segments * diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php index de03a418567..9e398c7101a 100644 --- a/system/database/DB_driver.php +++ b/system/database/DB_driver.php @@ -51,6 +51,7 @@ * @author EllisLab Dev Team * @link https://codeigniter.com/userguide3/database/ */ +#[AllowDynamicProperties] abstract class CI_DB_driver { /** @@ -1348,7 +1349,7 @@ public function escape_identifiers($item, $split = TRUE) return $item; } // Avoid breaking functions and literal values inside queries - elseif (ctype_digit($item) OR $item[0] === "'" OR ($this->_escape_char !== '"' && $item[0] === '"') OR strpos($item, '(') !== FALSE) + elseif (ctype_digit((string) $item) OR $item[0] === "'" OR ($this->_escape_char !== '"' && $item[0] === '"') OR strpos($item, '(') !== FALSE) { return $item; } diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php index 36679a4649d..26a591947a6 100644 --- a/system/database/DB_forge.php +++ b/system/database/DB_forge.php @@ -561,13 +561,18 @@ public function rename_table($table_name, $new_table_name) * @param array $field Column definition * @return bool */ - public function add_column($table, $field) + public function add_column($table, $field, $_after = NULL) { // Work-around for literal column definitions is_array($field) OR $field = array($field); foreach (array_keys($field) as $k) { + if ($_after !== NULL && is_array($field[$k]) && ! isset($field[$k]['after'])) + { + $field[$k]['after'] = $_after; + } + $this->add_field(array($k => $field[$k])); } diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php index de6aa04fc08..e3a4f16caf5 100644 --- a/system/database/DB_query_builder.php +++ b/system/database/DB_query_builder.php @@ -71,7 +71,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver { * * @var array */ - protected $qb_select = array(); + public $qb_select = array(); /** * QB DISTINCT flag @@ -141,7 +141,7 @@ abstract class CI_DB_query_builder extends CI_DB_driver { * * @var array */ - protected $qb_orderby = array(); + public $qb_orderby = array(); /** * QB data sets @@ -294,7 +294,7 @@ public function select($select = '*', $escape = NULL) foreach ($select as $val) { - $val = trim($val); + $val = trim((string)$val); if ($val !== '') { @@ -408,10 +408,10 @@ protected function _max_min_avg_sum($select = '', $alias = '', $type = 'MAX') if ($alias === '') { - $alias = $this->_create_alias_from_table(trim($select)); + $alias = $this->_create_alias_from_table(trim((string)$select)); } - $sql = $type.'('.$this->protect_identifiers(trim($select)).') AS '.$this->escape_identifiers(trim($alias)); + $sql = $type.'('.$this->protect_identifiers(trim((string)$select)).') AS '.$this->escape_identifiers(trim((string)$alias)); $this->qb_select[] = $sql; $this->qb_no_escape[] = NULL; @@ -478,7 +478,7 @@ public function from($from) { foreach (explode(',', $val) as $v) { - $v = trim($v); + $v = trim((string)$v); $this->_track_aliases($v); $this->qb_from[] = $v = $this->protect_identifiers($v, TRUE, NULL, FALSE); @@ -492,7 +492,7 @@ public function from($from) } else { - $val = trim($val); + $val = trim((string)$val); // Extract any aliases that might exist. We use this information // in the protect_identifiers to know whether to add a table prefix @@ -526,7 +526,15 @@ public function from($from) */ public function join($table, $cond, $type = '', $escape = NULL) { - $type = trim(strtoupper($type).' JOIN'); + if ($type === null) { + $type = ''; + } + + if (!is_string($type)) { + throw new InvalidArgumentException('join() expects parameter 3 to be a string, ' . gettype($type) . ' given'); + } + + $type = trim((string)strtoupper($type).' JOIN'); preg_match('#^(NATURAL\s+)?((LEFT|RIGHT|FULL)\s+)?((INNER|OUTER)\s+)?JOIN$#', $type) OR $type = 'JOIN'; // Extract any aliases that might exist. We use this information @@ -1209,7 +1217,7 @@ public function group_by($by, $escape = NULL) foreach ($by as $val) { - $val = trim($val); + $val = trim((string)$val); if ($val !== '') { @@ -1273,7 +1281,15 @@ public function or_having($key, $value = NULL, $escape = NULL) */ public function order_by($orderby, $direction = '', $escape = NULL) { - $direction = strtoupper(trim($direction)); + if ($direction === null) { + $direction = ''; + } + + if (!is_string($direction)) { + throw new InvalidArgumentException('order_by() expects parameter 2 to be a string, ' . gettype($direction) . ' given'); + } + + $direction = strtoupper(trim((string)$direction)); if ($direction === 'RANDOM') { @@ -1304,9 +1320,9 @@ public function order_by($orderby, $direction = '', $escape = NULL) $qb_orderby = array(); foreach (explode(',', $orderby) as $field) { - $qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) - ? array('field' => ltrim(substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE) - : array('field' => trim($field), 'direction' => $direction, 'escape' => TRUE); + $qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim((string)$field), $match, PREG_OFFSET_CAPTURE)) + ? array('field' => ltrim((string)substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE) + : array('field' => trim((string)$field), 'direction' => $direction, 'escape' => TRUE); } } @@ -2361,7 +2377,7 @@ protected function _track_aliases($table) $table = preg_replace('/\s+AS\s+/i', ' ', $table); // Grab the alias - $table = trim(strrchr($table, ' ')); + $table = trim((string)strrchr($table, ' ')); // Store the alias, if it doesn't already exist if ( ! in_array($table, $this->qb_aliased_tables, TRUE)) @@ -2504,12 +2520,12 @@ protected function _compile_wh($qb_key) if ( ! empty($matches[4])) { - $this->_is_literal($matches[4]) OR $matches[4] = $this->protect_identifiers(trim($matches[4])); + $this->_is_literal($matches[4]) OR $matches[4] = $this->protect_identifiers(trim((string)$matches[4])); $matches[4] = ' '.$matches[4]; } - $conditions[$ci] = $matches[1].$this->protect_identifiers(trim($matches[2])) - .' '.trim($matches[3]).$matches[4].$matches[5]; + $conditions[$ci] = $matches[1].$this->protect_identifiers(trim((string)$matches[2])) + .' '.trim((string)$matches[3]).$matches[4].$matches[5]; } $this->{$qb_key}[$i] = implode('', $conditions).(isset($this->{$qb_key}[$i]['value']) ? ' '.$this->{$qb_key}[$i]['value'] : ''); @@ -2780,7 +2796,7 @@ protected function _merge_cache() */ protected function _is_literal($str) { - $str = trim($str); + $str = trim((string)$str); if (empty($str) OR ctype_digit($str) OR (string) (float) $str === $str OR in_array(strtoupper($str), array('TRUE', 'FALSE'), TRUE)) { diff --git a/system/database/drivers/mysqli/mysqli_result.php b/system/database/drivers/mysqli/mysqli_result.php index 8c4f94d18fe..5c0c8673978 100644 --- a/system/database/drivers/mysqli/mysqli_result.php +++ b/system/database/drivers/mysqli/mysqli_result.php @@ -153,7 +153,6 @@ private static function _get_field_type($type) MYSQLI_TYPE_DATETIME => 'datetime', MYSQLI_TYPE_YEAR => 'year', MYSQLI_TYPE_NEWDATE => 'date', - MYSQLI_TYPE_INTERVAL => 'interval', MYSQLI_TYPE_ENUM => 'enum', MYSQLI_TYPE_SET => 'set', MYSQLI_TYPE_TINY_BLOB => 'tinyblob', diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php index 6f8b21d750f..7640ab41f5f 100644 --- a/system/database/drivers/oci8/oci8_driver.php +++ b/system/database/drivers/oci8/oci8_driver.php @@ -177,7 +177,7 @@ public function __construct($params) return; } elseif ($this->hostname !== '' && strpos($this->hostname, '/') === FALSE && strpos($this->hostname, ':') === FALSE - && (( ! empty($this->port) && ctype_digit($this->port)) OR $this->database !== '')) + && (( ! empty($this->port) && ctype_digit((string) $this->port)) OR $this->database !== '')) { /* If the hostname field isn't empty, doesn't contain * ':' and/or '/' and if port and/or database aren't @@ -187,7 +187,7 @@ public function __construct($params) * that the database field is a service name. */ $this->dsn = $this->hostname - .(( ! empty($this->port) && ctype_digit($this->port)) ? ':'.$this->port : '') + .(( ! empty($this->port) && ctype_digit((string) $this->port)) ? ':'.$this->port : '') .($this->database !== '' ? '/'.ltrim($this->database, '/') : ''); if (preg_match($valid_dsns['ec'], $this->dsn)) diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php index 559e865552c..a806b4427d8 100644 --- a/system/database/drivers/pdo/pdo_driver.php +++ b/system/database/drivers/pdo/pdo_driver.php @@ -36,6 +36,9 @@ * @since Version 2.1.0 * @filesource */ + +use DebugBar\DataCollector\PDO\TraceablePDO; + defined('BASEPATH') OR exit('No direct script access allowed'); /** @@ -65,7 +68,9 @@ class CI_DB_pdo_driver extends CI_DB { * * @var array */ - public $options = array(); + public $options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); // -------------------------------------------------------------------- @@ -127,6 +132,8 @@ public function __construct($params) */ public function db_connect($persistent = FALSE) { + global $debugbar; + if ($persistent === TRUE) { $this->options[PDO::ATTR_PERSISTENT] = TRUE; @@ -142,7 +149,13 @@ public function db_connect($persistent = FALSE) try { - return new PDO($this->dsn, $this->username, $this->password, $this->options); + if ($debugbar) { + $pdo = new TraceablePDO(new PDO($this->dsn, $this->username, $this->password, $this->options)); + $debugbar->addCollector(new DebugBar\DataCollector\PDO\PDOCollector($pdo)); + return $pdo; + } else { + return new PDO($this->dsn, $this->username, $this->password, $this->options); + } } catch (PDOException $e) { diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php index 1ad854da154..d3a24ae58d7 100644 --- a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php +++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php @@ -143,34 +143,65 @@ public function db_connect($persistent = FALSE) if ( ! empty($sql)) { - if (empty($this->options[PDO::MYSQL_ATTR_INIT_COMMAND])) + if (class_exists('Pdo\\Mysql')) { - $this->options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode = '.$sql; + $constant = constant('Pdo\\Mysql::ATTR_INIT_COMMAND'); + } else { + $constant = PDO::MYSQL_ATTR_INIT_COMMAND; + } + + if (empty($this->options[$constant])) + { + $this->options[$constant] = 'SET SESSION sql_mode = '.$sql; } else { - $this->options[PDO::MYSQL_ATTR_INIT_COMMAND] .= ', @@session.sql_mode = '.$sql; + $this->options[$constant] .= ', @@session.sql_mode = '.$sql; } } } if ($this->compress === TRUE) { - $this->options[PDO::MYSQL_ATTR_COMPRESS] = TRUE; + if (class_exists('Pdo\\Mysql')) + { + $constant = constant('Pdo\\Mysql::ATTR_COMPRESS'); + } else { + $constant = PDO::MYSQL_ATTR_COMPRESS; + } + + $this->options[$constant] = TRUE; } if (is_array($this->encrypt)) { + if (class_exists('Pdo\\Mysql')) + { + $sslKey = constant('Pdo\\Mysql::ATTR_SSL_KEY'); + $sslCert = constant('Pdo\\Mysql::ATTR_SSL_CERT'); + $sslCA = constant('Pdo\\Mysql::ATTR_SSL_CA'); + $sslCAPath = constant('Pdo\\Mysql::ATTR_SSL_CAPATH'); + $sslCipher = constant('Pdo\\Mysql::ATTR_SSL_CIPHER'); + $verify = constant('Pdo\\Mysql::ATTR_SSL_VERIFY_SERVER_CERT'); + } else { + $sslKey = PDO::MYSQL_ATTR_SSL_KEY; + $sslCert = PDO::MYSQL_ATTR_SSL_CERT; + $sslCA = PDO::MYSQL_ATTR_SSL_CA; + $sslCAPath = PDO::MYSQL_ATTR_SSL_CAPATH; + $sslCipher = PDO::MYSQL_ATTR_SSL_CIPHER; + $verify = defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT') ? PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT : null; + } + $ssl = array(); - empty($this->encrypt['ssl_key']) OR $ssl[PDO::MYSQL_ATTR_SSL_KEY] = $this->encrypt['ssl_key']; - empty($this->encrypt['ssl_cert']) OR $ssl[PDO::MYSQL_ATTR_SSL_CERT] = $this->encrypt['ssl_cert']; - empty($this->encrypt['ssl_ca']) OR $ssl[PDO::MYSQL_ATTR_SSL_CA] = $this->encrypt['ssl_ca']; - empty($this->encrypt['ssl_capath']) OR $ssl[PDO::MYSQL_ATTR_SSL_CAPATH] = $this->encrypt['ssl_capath']; - empty($this->encrypt['ssl_cipher']) OR $ssl[PDO::MYSQL_ATTR_SSL_CIPHER] = $this->encrypt['ssl_cipher']; + empty($this->encrypt['ssl_key']) OR $ssl[$sslKey] = $this->encrypt['ssl_key']; + empty($this->encrypt['ssl_cert']) OR $ssl[$sslCert] = $this->encrypt['ssl_cert']; + empty($this->encrypt['ssl_ca']) OR $ssl[$sslCA] = $this->encrypt['ssl_ca']; + empty($this->encrypt['ssl_capath']) OR $ssl[$sslCAPath] = $this->encrypt['ssl_capath']; + empty($this->encrypt['ssl_cipher']) OR $ssl[$sslCipher] = $this->encrypt['ssl_cipher']; - if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT') && isset($this->encrypt['ssl_verify'])) + if ($verify && isset($this->encrypt['ssl_verify'])) { - $ssl[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->encrypt['ssl_verify']; + $ssl[$verify] = $this->encrypt['ssl_verify']; } // DO NOT use array_merge() here! diff --git a/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php index cea20542715..305534253aa 100644 --- a/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php +++ b/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php @@ -99,7 +99,7 @@ public function __construct(&$db) if (version_compare($this->db->version(), '9.0', '>')) { - $this->create_table_if = 'CREATE TABLE IF NOT EXISTS'; + $this->_create_table_if = 'CREATE TABLE IF NOT EXISTS'; } } diff --git a/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php index 1cd473f2b5d..fe7750f6841 100644 --- a/system/database/drivers/postgre/postgre_driver.php +++ b/system/database/drivers/postgre/postgre_driver.php @@ -95,7 +95,7 @@ protected function _build_dsn() $this->hostname === '' OR $this->dsn = 'host='.$this->hostname.' '; - if ( ! empty($this->port) && ctype_digit($this->port)) + if ( ! empty($this->port) && ctype_digit((string) $this->port)) { $this->dsn .= 'port='.$this->port.' '; } diff --git a/system/database/drivers/postgre/postgre_forge.php b/system/database/drivers/postgre/postgre_forge.php index 2857fd51727..291a705baf4 100644 --- a/system/database/drivers/postgre/postgre_forge.php +++ b/system/database/drivers/postgre/postgre_forge.php @@ -87,7 +87,7 @@ public function __construct(&$db) if (version_compare($this->db->version(), '9.0', '>')) { - $this->create_table_if = 'CREATE TABLE IF NOT EXISTS'; + $this->_create_table_if = 'CREATE TABLE IF NOT EXISTS'; } } diff --git a/system/helpers/captcha_helper.php b/system/helpers/captcha_helper.php index e7e760a5f81..e5a1aa318a2 100644 --- a/system/helpers/captcha_helper.php +++ b/system/helpers/captcha_helper.php @@ -259,8 +259,8 @@ function create_captcha($data) // ----------------------------------- $length = strlen($word); $angle = ($length >= 6) ? mt_rand(-($length - 6), ($length - 6)) : 0; - $x_axis = mt_rand(6, (360 / $length)-16); - $y_axis = ($angle >= 0) ? mt_rand($img_height, $img_width) : mt_rand(6, $img_height); + $x_axis = mt_rand(6, (int) ((360 / $length) - 16)); + $y_axis = ($angle >= 0) ? mt_rand((int) $img_height, (int) $img_width) : mt_rand(6, (int) $img_height); // Create image // PHP.net recommends imagecreatetruecolor(), but it isn't always available @@ -315,13 +315,13 @@ function create_captcha($data) if ($use_font === FALSE) { ($font_size > 5) && $font_size = 5; - $x = mt_rand(0, $img_width / ($length / 3)); + $x = mt_rand(0, (int) ($img_width / ($length / 3))); $y = 0; } else { ($font_size > 30) && $font_size = 30; - $x = mt_rand(0, $img_width / ($length / 1.5)); + $x = mt_rand(0, (int) ($img_width / ($length / 1.5))); $y = $font_size + 2; } @@ -329,13 +329,13 @@ function create_captcha($data) { if ($use_font === FALSE) { - $y = mt_rand(0 , $img_height / 2); + $y = mt_rand(0, (int) ($img_height / 2)); imagestring($im, $font_size, $x, $y, $word[$i], $colors['text']); $x += ($font_size * 2); } else { - $y = mt_rand($img_height / 2, $img_height - 3); + $y = mt_rand((int) ($img_height / 2), (int) ($img_height - 3)); imagettftext($im, $font_size, $angle, $x, $y, $colors['text'], $font_path, $word[$i]); $x += $font_size; } @@ -375,7 +375,10 @@ function create_captcha($data) $img_class = (bool) strlen($img_class) ? 'class="'.$img_class.'" ' : ''; $img = ''; - ImageDestroy($im); + if (PHP_VERSION_ID < 80000) + { + imagedestroy($im); + } return array('word' => $word, 'time' => $now, 'image' => $img, 'filename' => $img_filename); } diff --git a/system/helpers/date_helper.php b/system/helpers/date_helper.php index 6ea9d82bd98..6cf1eee21e6 100644 --- a/system/helpers/date_helper.php +++ b/system/helpers/date_helper.php @@ -123,6 +123,42 @@ function mdate($datestr = '', $time = '') // ------------------------------------------------------------------------ +if ( ! function_exists('standard_date')) +{ + /** + * Standard Date + * + * Returns a date formatted according to the submitted standard. + * + * As of PHP 5.2, the DateTime extension provides constants that + * serve for the exact same purpose and are used internally by + * date() as well. + * + * @deprecated 3.1.3 Use PHP's native date() with DateTime constants + * + * @param string $fmt = 'DATE_RFC822' the chosen format + * @param int $time = NULL Unix timestamp + * @return string + */ + function standard_date($fmt = 'DATE_RFC822', $time = NULL) + { + if (empty($time)) + { + $time = now(); + } + + // Procedural style pre-defined constants from the DateTime extension + if (strpos($fmt, 'DATE_') !== 0 OR defined($fmt) === FALSE) + { + return FALSE; + } + + return date(constant($fmt), $time); + } +} + +// ------------------------------------------------------------------------ + if ( ! function_exists('timespan')) { /** @@ -451,6 +487,72 @@ function human_to_unix($datestr = '') // ------------------------------------------------------------------------ +if ( ! function_exists('nice_date')) +{ + /** + * Turns many "reasonably-date-like" strings into something + * that is actually useful. This only works for dates after unix epoch. + * + * @deprecated 3.1.3 Use DateTime::createFromFormat($input_format, $input)->format($output_format); + * @param string The terribly formatted date-like string + * @param string Date format to return (same as php date function) + * @return string + */ + function nice_date($bad_date = '', $format = FALSE) + { + if (empty($bad_date)) + { + return 'Unknown'; + } + elseif (empty($format)) + { + $format = 'U'; + } + + // Date like: YYYYMM + if (preg_match('/^\d{6}$/i', $bad_date)) + { + if (in_array(substr($bad_date, 0, 2), array('19', '20'))) + { + $year = substr($bad_date, 0, 4); + $month = substr($bad_date, 4, 2); + } + else + { + $month = substr($bad_date, 0, 2); + $year = substr($bad_date, 2, 4); + } + + return date($format, strtotime($year.'-'.$month.'-01')); + } + + // Date Like: YYYYMMDD + if (preg_match('/^\d{8}$/i', $bad_date, $matches)) + { + return DateTime::createFromFormat('Ymd', $bad_date)->format($format); + } + + // Date Like: MM-DD-YYYY __or__ M-D-YYYY (or anything in between) + if (preg_match('/^(\d{1,2})-(\d{1,2})-(\d{4})$/i', $bad_date, $matches)) + { + return date($format, strtotime($matches[3].'-'.$matches[1].'-'.$matches[2])); + } + + // Any other kind of string, when converted into UNIX time, + // produces "0 seconds after epoc..." is probably bad... + // return "Invalid Date". + if (date('U', strtotime($bad_date)) === '0') + { + return 'Invalid Date'; + } + + // It's probably a valid-ish date format already + return date($format, strtotime($bad_date)); + } +} + +// ------------------------------------------------------------------------ + if ( ! function_exists('timezone_menu')) { /** @@ -501,7 +603,7 @@ function timezone_menu($default = 'UTC', $class = '', $name = 'timezones', $attr * for various other ones in this library * * @param string timezone - * @return string + * @return string|array */ function timezones($tz = '') { diff --git a/system/helpers/email_helper.php b/system/helpers/email_helper.php new file mode 100644 index 00000000000..ec0c4207ed9 --- /dev/null +++ b/system/helpers/email_helper.php @@ -0,0 +1,85 @@ +', $count); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('nbs')) +{ + /** + * Generates non-breaking space entities based on number supplied + * + * @deprecated 3.0.0 Use str_repeat() instead + * @param int + * @return string + */ + function nbs($num = 1) + { + return str_repeat(' ', $num); + } +} diff --git a/system/helpers/security_helper.php b/system/helpers/security_helper.php index 54851a0947c..4e39fb40665 100644 --- a/system/helpers/security_helper.php +++ b/system/helpers/security_helper.php @@ -83,6 +83,29 @@ function sanitize_filename($filename) // ------------------------------------------------------------------------ +if ( ! function_exists('do_hash')) +{ + /** + * Hash encode a string + * + * @deprecated 3.0.0 Use PHP's native hash() instead. + * @param string $str + * @param string $type = 'sha1' + * @return string + */ + function do_hash($str, $type = 'sha1') + { + if ( ! in_array(strtolower($type), hash_algos())) + { + $type = 'md5'; + } + + return hash($type, $str); + } +} + +// ------------------------------------------------------------------------ + if ( ! function_exists('strip_image_tags')) { /** diff --git a/system/helpers/string_helper.php b/system/helpers/string_helper.php index 0cd87e91515..f2fc205c5b8 100644 --- a/system/helpers/string_helper.php +++ b/system/helpers/string_helper.php @@ -50,6 +50,32 @@ // ------------------------------------------------------------------------ +if ( ! function_exists('trim_slashes')) +{ + /** + * Trim Slashes + * + * Removes any leading/trailing slashes from a string: + * + * /this/that/theother/ + * + * becomes: + * + * this/that/theother + * + * @deprecated 3.0.0 This is just an alias for PHP's native trim() + * + * @param string + * @return string + */ + function trim_slashes($str) + { + return trim($str, '/'); + } +} + +// ------------------------------------------------------------------------ + if ( ! function_exists('strip_slashes')) { /** @@ -201,8 +227,10 @@ function random_string($type = 'alnum', $len = 8) break; } return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len); + case 'unique': case 'md5': return md5(uniqid(mt_rand())); + case 'encrypt': case 'sha1': return sha1(uniqid(mt_rand(), TRUE)); } @@ -254,3 +282,22 @@ function alternator() return $args[($i++ % count($args))]; } } + +// ------------------------------------------------------------------------ + +if ( ! function_exists('repeater')) +{ + /** + * Repeater function + * + * @deprecated 3.0.0 Use PHP's native str_repeat() instead + * + * @param string $data String to repeat + * @param int $num Number of repeats + * @return string + */ + function repeater($data, $num = 1) + { + return ($num > 0) ? str_repeat($data, $num) : ''; + } +} diff --git a/system/helpers/text_helper.php b/system/helpers/text_helper.php index 506d45aca70..979e1458044 100644 --- a/system/helpers/text_helper.php +++ b/system/helpers/text_helper.php @@ -423,6 +423,8 @@ function convert_accented_characters($str) $array_from = array_keys($foreign_characters); $array_to = array_values($foreign_characters); } + + $str = (gettype($str) === 'NULL') ? '' : $str; return preg_replace($array_from, $array_to, $str); } diff --git a/system/helpers/url_helper.php b/system/helpers/url_helper.php index e3c9bc0a4fa..1a932a7bb66 100644 --- a/system/helpers/url_helper.php +++ b/system/helpers/url_helper.php @@ -478,6 +478,15 @@ function prep_url($str = '') */ function url_title($str, $separator = '-', $lowercase = FALSE) { + if ($separator === 'dash') + { + $separator = '-'; + } + elseif ($separator === 'underscore') + { + $separator = '_'; + } + $q_separator = preg_quote($separator, '#'); $trans = array( diff --git a/system/libraries/Cache/drivers/Cache_redis.php b/system/libraries/Cache/drivers/Cache_redis.php index 19fbdc0cae4..27454329e81 100644 --- a/system/libraries/Cache/drivers/Cache_redis.php +++ b/system/libraries/Cache/drivers/Cache_redis.php @@ -163,7 +163,7 @@ public function get($key) { $data = $this->_redis->hMGet($key, array('__ci_type', '__ci_value')); - if ($value !== FALSE && $this->_redis->sIsMember('_ci_redis_serialized', $key)) + if ($data === FALSE || $this->_redis->sIsMember('_ci_redis_serialized', $key)) { return FALSE; } @@ -223,6 +223,7 @@ public function save($id, $data, $ttl = 60, $raw = FALSE) } else { + $this->_redis->expireAt($id, time() + $ttl); $this->_redis->{static::$_sRemove_name}('_ci_redis_serialized', $id); } diff --git a/system/libraries/Cart.php b/system/libraries/Cart.php new file mode 100644 index 00000000000..f8244b15377 --- /dev/null +++ b/system/libraries/Cart.php @@ -0,0 +1,568 @@ +CI =& get_instance(); + + // Are any config settings being passed manually? If so, set them + $config = is_array($params) ? $params : array(); + + // Load the Sessions class + $this->CI->load->driver('session', $config); + + // Grab the shopping cart array from the session table + $this->_cart_contents = $this->CI->session->userdata('cart_contents'); + if ($this->_cart_contents === NULL) + { + // No cart exists so we'll set some base values + $this->_cart_contents = array('cart_total' => 0, 'total_items' => 0); + } + + log_message('info', 'Cart Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Insert items into the cart and save it to the session table + * + * @param array + * @return bool + */ + public function insert($items = array()) + { + // Was any cart data passed? No? Bah... + if ( ! is_array($items) OR count($items) === 0) + { + log_message('error', 'The insert method must be passed an array containing data.'); + return FALSE; + } + + // You can either insert a single product using a one-dimensional array, + // or multiple products using a multi-dimensional one. The way we + // determine the array type is by looking for a required array key named "id" + // at the top level. If it's not found, we will assume it's a multi-dimensional array. + + $save_cart = FALSE; + if (isset($items['id'])) + { + if (($rowid = $this->_insert($items))) + { + $save_cart = TRUE; + } + } + else + { + foreach ($items as $val) + { + if (is_array($val) && isset($val['id'])) + { + if ($this->_insert($val)) + { + $save_cart = TRUE; + } + } + } + } + + // Save the cart data if the insert was successful + if ($save_cart === TRUE) + { + $this->_save_cart(); + return isset($rowid) ? $rowid : TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Insert + * + * @param array + * @return bool + */ + protected function _insert($items = array()) + { + // Was any cart data passed? No? Bah... + if ( ! is_array($items) OR count($items) === 0) + { + log_message('error', 'The insert method must be passed an array containing data.'); + return FALSE; + } + + // -------------------------------------------------------------------- + + // Does the $items array contain an id, quantity, price, and name? These are required + if ( ! isset($items['id'], $items['qty'], $items['price'], $items['name'])) + { + log_message('error', 'The cart array must contain a product ID, quantity, price, and name.'); + return FALSE; + } + + // -------------------------------------------------------------------- + + // Prep the quantity. It can only be a number. Duh... also trim any leading zeros + $items['qty'] = (float) $items['qty']; + + // If the quantity is zero or blank there's nothing for us to do + if ($items['qty'] == 0) + { + return FALSE; + } + + // -------------------------------------------------------------------- + + // Validate the product ID. It can only be alpha-numeric, dashes, underscores or periods + // Not totally sure we should impose this rule, but it seems prudent to standardize IDs. + // Note: These can be user-specified by setting the $this->product_id_rules variable. + if ( ! preg_match('/^['.$this->product_id_rules.']+$/i', $items['id'])) + { + log_message('error', 'Invalid product ID. The product ID can only contain alpha-numeric characters, dashes, and underscores'); + return FALSE; + } + + // -------------------------------------------------------------------- + + // Validate the product name. It can only be alpha-numeric, dashes, underscores, colons or periods. + // Note: These can be user-specified by setting the $this->product_name_rules variable. + if ($this->product_name_safe && ! preg_match('/^['.$this->product_name_rules.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $items['name'])) + { + log_message('error', 'An invalid name was submitted as the product name: '.$items['name'].' The name can only contain alpha-numeric characters, dashes, underscores, colons, and spaces'); + return FALSE; + } + + // -------------------------------------------------------------------- + + // Prep the price. Remove leading zeros and anything that isn't a number or decimal point. + $items['price'] = (float) $items['price']; + + // We now need to create a unique identifier for the item being inserted into the cart. + // Every time something is added to the cart it is stored in the master cart array. + // Each row in the cart array, however, must have a unique index that identifies not only + // a particular product, but makes it possible to store identical products with different options. + // For example, what if someone buys two identical t-shirts (same product ID), but in + // different sizes? The product ID (and other attributes, like the name) will be identical for + // both sizes because it's the same shirt. The only difference will be the size. + // Internally, we need to treat identical submissions, but with different options, as a unique product. + // Our solution is to convert the options array to a string and MD5 it along with the product ID. + // This becomes the unique "row ID" + if (isset($items['options']) && count($items['options']) > 0) + { + $rowid = md5($items['id'].serialize($items['options'])); + } + else + { + // No options were submitted so we simply MD5 the product ID. + // Technically, we don't need to MD5 the ID in this case, but it makes + // sense to standardize the format of array indexes for both conditions + $rowid = md5($items['id']); + } + + // -------------------------------------------------------------------- + + // Now that we have our unique "row ID", we'll add our cart items to the master array + // grab quantity if it's already there and add it on + $old_quantity = isset($this->_cart_contents[$rowid]['qty']) ? (int) $this->_cart_contents[$rowid]['qty'] : 0; + + // Re-create the entry, just to make sure our index contains only the data from this submission + $items['rowid'] = $rowid; + $items['qty'] += $old_quantity; + $this->_cart_contents[$rowid] = $items; + + return $rowid; + } + + // -------------------------------------------------------------------- + + /** + * Update the cart + * + * This function permits the quantity of a given item to be changed. + * Typically it is called from the "view cart" page if a user makes + * changes to the quantity before checkout. That array must contain the + * product ID and quantity for each item. + * + * @param array + * @return bool + */ + public function update($items = array()) + { + // Was any cart data passed? + if ( ! is_array($items) OR count($items) === 0) + { + return FALSE; + } + + // You can either update a single product using a one-dimensional array, + // or multiple products using a multi-dimensional one. The way we + // determine the array type is by looking for a required array key named "rowid". + // If it's not found we assume it's a multi-dimensional array + $save_cart = FALSE; + if (isset($items['rowid'])) + { + if ($this->_update($items) === TRUE) + { + $save_cart = TRUE; + } + } + else + { + foreach ($items as $val) + { + if (is_array($val) && isset($val['rowid'])) + { + if ($this->_update($val) === TRUE) + { + $save_cart = TRUE; + } + } + } + } + + // Save the cart data if the insert was successful + if ($save_cart === TRUE) + { + $this->_save_cart(); + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Update the cart + * + * This function permits changing item properties. + * Typically it is called from the "view cart" page if a user makes + * changes to the quantity before checkout. That array must contain the + * rowid and quantity for each item. + * + * @param array + * @return bool + */ + protected function _update($items = array()) + { + // Without these array indexes there is nothing we can do + if ( ! isset($items['rowid'], $this->_cart_contents[$items['rowid']])) + { + return FALSE; + } + + // Prep the quantity + if (isset($items['qty'])) + { + $items['qty'] = (float) $items['qty']; + // Is the quantity zero? If so we will remove the item from the cart. + // If the quantity is greater than zero we are updating + if ($items['qty'] == 0) + { + unset($this->_cart_contents[$items['rowid']]); + return TRUE; + } + } + + // find updatable keys + $keys = array_intersect(array_keys($this->_cart_contents[$items['rowid']]), array_keys($items)); + // if a price was passed, make sure it contains valid data + if (isset($items['price'])) + { + $items['price'] = (float) $items['price']; + } + + // product id & name shouldn't be changed + foreach (array_diff($keys, array('id', 'name')) as $key) + { + $this->_cart_contents[$items['rowid']][$key] = $items[$key]; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Save the cart array to the session DB + * + * @return bool + */ + protected function _save_cart() + { + // Let's add up the individual prices and set the cart sub-total + $this->_cart_contents['total_items'] = $this->_cart_contents['cart_total'] = 0; + foreach ($this->_cart_contents as $key => $val) + { + // We make sure the array contains the proper indexes + if ( ! is_array($val) OR ! isset($val['price'], $val['qty'])) + { + continue; + } + + $this->_cart_contents['cart_total'] += ($val['price'] * $val['qty']); + $this->_cart_contents['total_items'] += $val['qty']; + $this->_cart_contents[$key]['subtotal'] = ($this->_cart_contents[$key]['price'] * $this->_cart_contents[$key]['qty']); + } + + // Is our cart empty? If so we delete it from the session + if (count($this->_cart_contents) <= 2) + { + $this->CI->session->unset_userdata('cart_contents'); + + // Nothing more to do... coffee time! + return FALSE; + } + + // If we made it this far it means that our cart has data. + // Let's pass it to the Session class so it can be stored + $this->CI->session->set_userdata(array('cart_contents' => $this->_cart_contents)); + + // Woot! + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Cart Total + * + * @return int + */ + public function total() + { + return $this->_cart_contents['cart_total']; + } + + // -------------------------------------------------------------------- + + /** + * Remove Item + * + * Removes an item from the cart + * + * @param int + * @return bool + */ + public function remove($rowid) + { + // unset & save + unset($this->_cart_contents[$rowid]); + $this->_save_cart(); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Total Items + * + * Returns the total item count + * + * @return int + */ + public function total_items() + { + return $this->_cart_contents['total_items']; + } + + // -------------------------------------------------------------------- + + /** + * Cart Contents + * + * Returns the entire cart array + * + * @param bool + * @return array + */ + public function contents($newest_first = FALSE) + { + // do we want the newest first? + $cart = ($newest_first) ? array_reverse($this->_cart_contents) : $this->_cart_contents; + + // Remove these so they don't create a problem when showing the cart table + unset($cart['total_items']); + unset($cart['cart_total']); + + return $cart; + } + + // -------------------------------------------------------------------- + + /** + * Get cart item + * + * Returns the details of a specific item in the cart + * + * @param string $row_id + * @return array + */ + public function get_item($row_id) + { + return (in_array($row_id, array('total_items', 'cart_total'), TRUE) OR ! isset($this->_cart_contents[$row_id])) + ? FALSE + : $this->_cart_contents[$row_id]; + } + + // -------------------------------------------------------------------- + + /** + * Has options + * + * Returns TRUE if the rowid passed to this function correlates to an item + * that has options associated with it. + * + * @param string $row_id = '' + * @return bool + */ + public function has_options($row_id = '') + { + return (isset($this->_cart_contents[$row_id]['options']) && count($this->_cart_contents[$row_id]['options']) !== 0); + } + + // -------------------------------------------------------------------- + + /** + * Product options + * + * Returns the an array of options, for a particular product row ID + * + * @param string $row_id = '' + * @return array + */ + public function product_options($row_id = '') + { + return isset($this->_cart_contents[$row_id]['options']) ? $this->_cart_contents[$row_id]['options'] : array(); + } + + // -------------------------------------------------------------------- + + /** + * Format Number + * + * Returns the supplied number with commas and a decimal point. + * + * @param float + * @return string + */ + public function format_number($n = '') + { + return ($n === '') ? '' : number_format( (float) $n, 2, '.', ','); + } + + // -------------------------------------------------------------------- + + /** + * Destroy the cart + * + * Empties the cart and kills the session + * + * @return void + */ + public function destroy() + { + $this->_cart_contents = array('cart_total' => 0, 'total_items' => 0); + $this->CI->session->unset_userdata('cart_contents'); + } + +} diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php index 84f0b6c3e76..23f4d23f3d0 100644 --- a/system/libraries/Driver.php +++ b/system/libraries/Driver.php @@ -50,6 +50,7 @@ * @author EllisLab Dev Team * @link */ +#[AllowDynamicProperties] class CI_Driver_Library { /** diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php index 572cab3fcb1..b53dac2e830 100644 --- a/system/libraries/Encryption.php +++ b/system/libraries/Encryption.php @@ -366,10 +366,10 @@ public function create_key($length) * Encrypt * * @param string $data Input data - * @param array $params Input parameters + * @param array|null $params Input parameters * @return string */ - public function encrypt($data, array $params = NULL) + public function encrypt($data, $params = NULL) { if (($params = $this->_get_params($params)) === FALSE) { @@ -470,6 +470,11 @@ protected function _mcrypt_encrypt($data, $params) */ protected function _openssl_encrypt($data, $params) { + if ($data === NULL) + { + return NULL; + } + if (empty($params['handle'])) { return FALSE; @@ -501,10 +506,10 @@ protected function _openssl_encrypt($data, $params) * Decrypt * * @param string $data Encrypted data - * @param array $params Input parameters + * @param array|null $params Input parameters * @return string */ - public function decrypt($data, array $params = NULL) + public function decrypt($data, $params = NULL) { if (($params = $this->_get_params($params)) === FALSE) { @@ -626,6 +631,11 @@ protected function _mcrypt_decrypt($data, $params) */ protected function _openssl_decrypt($data, $params) { + if ($data === NULL) + { + return NULL; + } + if ($iv_size = openssl_cipher_iv_length($params['handle'])) { $iv = self::substr($data, 0, $iv_size); diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php index dd1685db193..71587f7cb29 100644 --- a/system/libraries/Form_validation.php +++ b/system/libraries/Form_validation.php @@ -105,6 +105,14 @@ class CI_Form_validation { */ protected $error_string = ''; + /** + * Whether the form data has been through validation and had errors + * + * @deprecated 3.0.6 + * @var bool + */ + protected $_safe_form_data = FALSE; + /** * Custom data to validate * @@ -479,6 +487,7 @@ public function run($config = NULL, &$data = NULL) if ( ! empty($this->_error_array)) { + $this->_safe_form_data = TRUE; return FALSE; } @@ -1524,6 +1533,38 @@ public function valid_base64($str) // -------------------------------------------------------------------- + /** + * Prep data for form + * + * This function allows HTML to be safely shown in a form. + * Special characters are converted. + * + * @deprecated 3.0.6 Not used anywhere within the framework and pretty much useless + * @param mixed $data Input data + * @return mixed + */ + public function prep_for_form($data) + { + if ($this->_safe_form_data === FALSE OR empty($data)) + { + return $data; + } + + if (is_array($data)) + { + foreach ($data as $key => $val) + { + $data[$key] = $this->prep_for_form($val); + } + + return $data; + } + + return str_replace(array("'", '"', '<', '>'), array(''', '"', '<', '>'), stripslashes($data)); + } + + // -------------------------------------------------------------------- + /** * Prep URL * diff --git a/system/libraries/Image_lib.php b/system/libraries/Image_lib.php index 4e5fc7be6fc..e933d38bf89 100644 --- a/system/libraries/Image_lib.php +++ b/system/libraries/Image_lib.php @@ -85,6 +85,14 @@ class CI_Image_lib { */ public $new_image = ''; + + /** + * Path to destination image + * + * @var string + */ + public $dest_image = ''; + /** * Image width * @@ -833,8 +841,11 @@ public function image_process_gd($action = 'resize') } // Kill the file handles - imagedestroy($dst_img); - imagedestroy($src_img); + if (PHP_VERSION_ID < 80000) + { + imagedestroy($dst_img); + imagedestroy($src_img); + } if ($this->dynamic_output !== TRUE) { @@ -1042,8 +1053,11 @@ public function image_rotate_gd() } // Kill the file handles - imagedestroy($dst_img); - imagedestroy($src_img); + if (PHP_VERSION_ID < 80000) + { + imagedestroy($dst_img); + imagedestroy($src_img); + } chmod($this->full_dst_path, $this->file_permissions); @@ -1121,7 +1135,10 @@ public function image_mirror_gd() } // Kill the file handles - imagedestroy($src_img); + if (PHP_VERSION_ID < 80000) + { + imagedestroy($src_img); + } chmod($this->full_dst_path, $this->file_permissions); @@ -1251,8 +1268,11 @@ public function overlay_watermark() return FALSE; } - imagedestroy($src_img); - imagedestroy($wm_img); + if (PHP_VERSION_ID < 80000) + { + imagedestroy($src_img); + imagedestroy($wm_img); + } return TRUE; } @@ -1422,7 +1442,10 @@ public function text_watermark() $this->image_save_gd($src_img); } - imagedestroy($src_img); + if (PHP_VERSION_ID < 80000) + { + imagedestroy($src_img); + } return TRUE; } diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php index 7f1ed7739c8..38ba8d9e735 100644 --- a/system/libraries/Pagination.php +++ b/system/libraries/Pagination.php @@ -358,6 +358,12 @@ public function __construct($params = array()) */ public function initialize(array $params = array()) { + if (isset($params['anchor_class'])) + { + empty($params['anchor_class']) OR $params['attributes']['class'] = $params['anchor_class']; + unset($params['anchor_class']); + } + if (isset($params['attributes']) && is_array($params['attributes'])) { $this->_parse_attributes($params['attributes']); diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php index d423c14811c..4c3537bd25b 100644 --- a/system/libraries/Profiler.php +++ b/system/libraries/Profiler.php @@ -73,6 +73,76 @@ class CI_Profiler { 'config' ); + /** + * Compile benchmarks + * + * @var bool + */ + protected $_compile_benchmarks; + + /** + * Compile GET data + * + * @var bool + */ + protected $_compile_get; + + /** + * Compile memory usage + * + * @var bool + */ + protected $_compile_memory_usage; + + /** + * Compile POST data + * + * @var bool + */ + protected $_compile_post; + + /** + * Compile URI string + * + * @var bool + */ + protected $_compile_uri_string; + + /** + * Compile controller info + * + * @var bool + */ + protected $_compile_controller_info; + + /** + * Compile queries + * + * @var bool + */ + protected $_compile_queries; + + /** + * Compile HTTP headers + * + * @var bool + */ + protected $_compile_http_headers; + + /** + * Compile session data + * + * @var bool + */ + protected $_compile_session_data; + + /** + * Compile config data + * + * @var bool + */ + protected $_compile_config; + /** * Number of queries to show before making the additional queries togglable * diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php index 2d55f822af9..e6715271254 100644 --- a/system/libraries/Session/Session.php +++ b/system/libraries/Session/Session.php @@ -417,7 +417,7 @@ protected function _configure_sid_length() { $bits_per_character = (int) ini_get('session.sid_bits_per_character'); $sid_length = (int) ini_get('session.sid_length'); - if (($bits = $sid_length * $bits_per_character) < 160) + if (($bits = $sid_length * $bits_per_character) < 160 && version_compare(PHP_VERSION, '8.4', '<')) { // Add as many more characters as necessary to reach at least 160 bits $sid_length += (int) ceil((160 % $bits) / $bits_per_character); diff --git a/system/libraries/Session/drivers/Session_redis_driver.php b/system/libraries/Session/drivers/Session_redis_driver.php index 43dd8415dce..22a27f2839d 100644 --- a/system/libraries/Session/drivers/Session_redis_driver.php +++ b/system/libraries/Session/drivers/Session_redis_driver.php @@ -217,9 +217,7 @@ public function open($save_path, $name) } else { - $this->_redis = $redis; - $this->php5_validate_id(); - return $this->_success; + log_message('error', 'Session: Unable to connect to Redis with the configured settings.'); } return $this->_failure; diff --git a/system/libraries/Table.php b/system/libraries/Table.php index a033ced212c..60b9fddcf84 100644 --- a/system/libraries/Table.php +++ b/system/libraries/Table.php @@ -490,12 +490,12 @@ protected function _compile_template() return; } - $this->temp = $this->_default_template(); + $temp = $this->_default_template(); foreach (array('table_open', 'thead_open', 'thead_close', 'heading_row_start', 'heading_row_end', 'heading_cell_start', 'heading_cell_end', 'tbody_open', 'tbody_close', 'row_start', 'row_end', 'cell_start', 'cell_end', 'row_alt_start', 'row_alt_end', 'cell_alt_start', 'cell_alt_end', 'table_close') as $val) { if ( ! isset($this->template[$val])) { - $this->template[$val] = $this->temp[$val]; + $this->template[$val] = $temp[$val]; } } } diff --git a/system/libraries/Upload.php b/system/libraries/Upload.php index 168211a9144..26bed6e5f5d 100644 --- a/system/libraries/Upload.php +++ b/system/libraries/Upload.php @@ -1231,7 +1231,10 @@ protected function _file_mime_type($file) if ($finfo !== FALSE) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system { $mime = @finfo_file($finfo, $file['tmp_name']); - finfo_close($finfo); + if (PHP_VERSION_ID < 80100) + { + finfo_close($finfo); + } /* According to the comments section of the PHP manual page, * it is possible that this function returns an empty string diff --git a/system/libraries/Xmlrpc.php b/system/libraries/Xmlrpc.php index 8587015e877..be4e34497c7 100644 --- a/system/libraries/Xmlrpc.php +++ b/system/libraries/Xmlrpc.php @@ -1151,8 +1151,7 @@ public function parseResponse($fp) //------------------------------------- $parser = xml_parser_create($this->xmlrpc_defencoding); - $pname = (string) $parser; - $this->xh[$pname] = array( + $this->xh = array( 'isf' => 0, 'ac' => '', 'headers' => array(), @@ -1161,10 +1160,9 @@ public function parseResponse($fp) 'isf_reason' => 0 ); - xml_set_object($parser, $this); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, TRUE); - xml_set_element_handler($parser, 'open_tag', 'closing_tag'); - xml_set_character_data_handler($parser, 'character_data'); + xml_set_element_handler($parser, [$this, 'open_tag'], [$this, 'closing_tag']); + xml_set_character_data_handler($parser, [$this, 'character_data']); //xml_set_default_handler($parser, 'default_handler'); // Get headers @@ -1175,7 +1173,7 @@ public function parseResponse($fp) { break; } - $this->xh[$pname]['headers'][] = $line; + $this->xh['headers'][] = $line; } $data = implode("\r\n", $lines); @@ -1193,18 +1191,18 @@ public function parseResponse($fp) xml_parser_free($parser); // Got ourselves some badness, it seems - if ($this->xh[$pname]['isf'] > 1) + if ($this->xh['isf'] > 1) { if ($this->debug === TRUE) { - echo "---Invalid Return---\n".$this->xh[$pname]['isf_reason']."---Invalid Return---\n\n"; + echo "---Invalid Return---\n".$this->xh['isf_reason']."---Invalid Return---\n\n"; } - return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return'].' '.$this->xh[$pname]['isf_reason']); + return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return'].' '.$this->xh['isf_reason']); } - elseif ( ! is_object($this->xh[$pname]['value'])) + elseif ( ! is_object($this->xh['value'])) { - return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return'].' '.$this->xh[$pname]['isf_reason']); + return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return'].' '.$this->xh['isf_reason']); } // Display XML content for debugging @@ -1212,10 +1210,10 @@ public function parseResponse($fp) { echo '
';
 
-			if (count($this->xh[$pname]['headers']) > 0)
+			if (count($this->xh['headers']) > 0)
 			{
 				echo "---HEADERS---\n";
-				foreach ($this->xh[$pname]['headers'] as $header)
+				foreach ($this->xh['headers'] as $header)
 				{
 					echo $header."\n";
 				}
@@ -1223,13 +1221,13 @@ public function parseResponse($fp)
 			}
 
 			echo "---DATA---\n".htmlspecialchars($data)."\n---END DATA---\n\n---PARSED---\n";
-			var_dump($this->xh[$pname]['value']);
+			var_dump($this->xh['value']);
 			echo "\n---END PARSED---
"; } // Send response - $v = $this->xh[$pname]['value']; - if ($this->xh[$pname]['isf']) + $v = $this->xh['value']; + if ($this->xh['isf']) { $errno_v = $v->me['struct']['faultCode']; $errstr_v = $v->me['struct']['faultString']; @@ -1248,7 +1246,7 @@ public function parseResponse($fp) $r = new XML_RPC_Response($v); } - $r->headers = $this->xh[$pname]['headers']; + $r->headers = $this->xh['headers']; return $r; } @@ -1279,26 +1277,24 @@ public function parseResponse($fp) */ public function open_tag($the_parser, $name) { - $the_parser = (string) $the_parser; - // If invalid nesting, then return - if ($this->xh[$the_parser]['isf'] > 1) return; + if ($this->xh['isf'] > 1) return; // Evaluate and check for correct nesting of XML elements - if (count($this->xh[$the_parser]['stack']) === 0) + if (count($this->xh['stack']) === 0) { if ($name !== 'METHODRESPONSE' && $name !== 'METHODCALL') { - $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = 'Top level XML-RPC element is missing'; + $this->xh['isf'] = 2; + $this->xh['isf_reason'] = 'Top level XML-RPC element is missing'; return; } } // not top level element: see if parent is OK - elseif ( ! in_array($this->xh[$the_parser]['stack'][0], $this->valid_parents[$name], TRUE)) + elseif ( ! in_array($this->xh['stack'][0], $this->valid_parents[$name], TRUE)) { - $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = 'XML-RPC element '.$name.' cannot be child of '.$this->xh[$the_parser]['stack'][0]; + $this->xh['isf'] = 2; + $this->xh['isf_reason'] = 'XML-RPC element '.$name.' cannot be child of '.$this->xh['stack'][0]; return; } @@ -1308,22 +1304,22 @@ public function open_tag($the_parser, $name) case 'ARRAY': // Creates array for child elements $cur_val = array('value' => array(), 'type' => $name); - array_unshift($this->xh[$the_parser]['valuestack'], $cur_val); + array_unshift($this->xh['valuestack'], $cur_val); break; case 'METHODNAME': case 'NAME': - $this->xh[$the_parser]['ac'] = ''; + $this->xh['ac'] = ''; break; case 'FAULT': - $this->xh[$the_parser]['isf'] = 1; + $this->xh['isf'] = 1; break; case 'PARAM': - $this->xh[$the_parser]['value'] = NULL; + $this->xh['value'] = NULL; break; case 'VALUE': - $this->xh[$the_parser]['vt'] = 'value'; - $this->xh[$the_parser]['ac'] = ''; - $this->xh[$the_parser]['lv'] = 1; + $this->xh['vt'] = 'value'; + $this->xh['ac'] = ''; + $this->xh['lv'] = 1; break; case 'I4': case 'INT': @@ -1332,23 +1328,23 @@ public function open_tag($the_parser, $name) case 'DOUBLE': case 'DATETIME.ISO8601': case 'BASE64': - if ($this->xh[$the_parser]['vt'] !== 'value') + if ($this->xh['vt'] !== 'value') { //two data elements inside a value: an error occurred! - $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = 'There is a '.$name.' element following a ' - .$this->xh[$the_parser]['vt'].' element inside a single value'; + $this->xh['isf'] = 2; + $this->xh['isf_reason'] = 'There is a '.$name.' element following a ' + .$this->xh['vt'].' element inside a single value'; return; } - $this->xh[$the_parser]['ac'] = ''; + $this->xh['ac'] = ''; break; case 'MEMBER': // Set name of to nothing to prevent errors later if no is found - $this->xh[$the_parser]['valuestack'][0]['name'] = ''; + $this->xh['valuestack'][0]['name'] = ''; // Set NULL value to check to see if value passed for this param/member - $this->xh[$the_parser]['value'] = NULL; + $this->xh['value'] = NULL; break; case 'DATA': case 'METHODCALL': @@ -1358,15 +1354,15 @@ public function open_tag($the_parser, $name) break; default: /// An Invalid Element is Found, so we have trouble - $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = 'Invalid XML-RPC element found: '.$name; + $this->xh['isf'] = 2; + $this->xh['isf_reason'] = 'Invalid XML-RPC element found: '.$name; break; } // Add current element name to stack, to allow validation of nesting - array_unshift($this->xh[$the_parser]['stack'], $name); + array_unshift($this->xh['stack'], $name); - $name === 'VALUE' OR $this->xh[$the_parser]['lv'] = 0; + $name === 'VALUE' OR $this->xh['lv'] = 0; } // -------------------------------------------------------------------- @@ -1380,27 +1376,25 @@ public function open_tag($the_parser, $name) */ public function closing_tag($the_parser, $name) { - $the_parser = (string) $the_parser; - - if ($this->xh[$the_parser]['isf'] > 1) return; + if ($this->xh['isf'] > 1) return; // Remove current element from stack and set variable // NOTE: If the XML validates, then we do not have to worry about // the opening and closing of elements. Nesting is checked on the opening // tag so we be safe there as well. - $curr_elem = array_shift($this->xh[$the_parser]['stack']); + $curr_elem = array_shift($this->xh['stack']); switch ($name) { case 'STRUCT': case 'ARRAY': - $cur_val = array_shift($this->xh[$the_parser]['valuestack']); - $this->xh[$the_parser]['value'] = isset($cur_val['values']) ? $cur_val['values'] : array(); - $this->xh[$the_parser]['vt'] = strtolower($name); + $cur_val = array_shift($this->xh['valuestack']); + $this->xh['value'] = isset($cur_val['values']) ? $cur_val['values'] : array(); + $this->xh['vt'] = strtolower($name); break; case 'NAME': - $this->xh[$the_parser]['valuestack'][0]['name'] = $this->xh[$the_parser]['ac']; + $this->xh['valuestack'][0]['name'] = $this->xh['ac']; break; case 'BOOLEAN': case 'I4': @@ -1409,87 +1403,87 @@ public function closing_tag($the_parser, $name) case 'DOUBLE': case 'DATETIME.ISO8601': case 'BASE64': - $this->xh[$the_parser]['vt'] = strtolower($name); + $this->xh['vt'] = strtolower($name); if ($name === 'STRING') { - $this->xh[$the_parser]['value'] = $this->xh[$the_parser]['ac']; + $this->xh['value'] = $this->xh['ac']; } elseif ($name === 'DATETIME.ISO8601') { - $this->xh[$the_parser]['vt'] = $this->xmlrpcDateTime; - $this->xh[$the_parser]['value'] = $this->xh[$the_parser]['ac']; + $this->xh['vt'] = $this->xmlrpcDateTime; + $this->xh['value'] = $this->xh['ac']; } elseif ($name === 'BASE64') { - $this->xh[$the_parser]['value'] = base64_decode($this->xh[$the_parser]['ac']); + $this->xh['value'] = base64_decode($this->xh['ac']); } elseif ($name === 'BOOLEAN') { // Translated BOOLEAN values to TRUE AND FALSE - $this->xh[$the_parser]['value'] = (bool) $this->xh[$the_parser]['ac']; + $this->xh['value'] = (bool) $this->xh['ac']; } elseif ($name=='DOUBLE') { // we have a DOUBLE // we must check that only 0123456789-. are characters here - $this->xh[$the_parser]['value'] = preg_match('/^[+-]?[eE0-9\t \.]+$/', $this->xh[$the_parser]['ac']) - ? (float) $this->xh[$the_parser]['ac'] + $this->xh['value'] = preg_match('/^[+-]?[eE0-9\t \.]+$/', $this->xh['ac']) + ? (float) $this->xh['ac'] : 'ERROR_NON_NUMERIC_FOUND'; } else { // we have an I4/INT // we must check that only 0123456789- are characters here - $this->xh[$the_parser]['value'] = preg_match('/^[+-]?[0-9\t ]+$/', $this->xh[$the_parser]['ac']) - ? (int) $this->xh[$the_parser]['ac'] + $this->xh['value'] = preg_match('/^[+-]?[0-9\t ]+$/', $this->xh['ac']) + ? (int) $this->xh['ac'] : 'ERROR_NON_NUMERIC_FOUND'; } - $this->xh[$the_parser]['ac'] = ''; - $this->xh[$the_parser]['lv'] = 3; // indicate we've found a value + $this->xh['ac'] = ''; + $this->xh['lv'] = 3; // indicate we've found a value break; case 'VALUE': // This if() detects if no scalar was inside - if ($this->xh[$the_parser]['vt'] == 'value') + if ($this->xh['vt'] == 'value') { - $this->xh[$the_parser]['value'] = $this->xh[$the_parser]['ac']; - $this->xh[$the_parser]['vt'] = $this->xmlrpcString; + $this->xh['value'] = $this->xh['ac']; + $this->xh['vt'] = $this->xmlrpcString; } // build the XML-RPC value out of the data received, and substitute it - $temp = new XML_RPC_Values($this->xh[$the_parser]['value'], $this->xh[$the_parser]['vt']); + $temp = new XML_RPC_Values($this->xh['value'], $this->xh['vt']); - if (count($this->xh[$the_parser]['valuestack']) && $this->xh[$the_parser]['valuestack'][0]['type'] === 'ARRAY') + if (count($this->xh['valuestack']) && $this->xh['valuestack'][0]['type'] === 'ARRAY') { // Array - $this->xh[$the_parser]['valuestack'][0]['values'][] = $temp; + $this->xh['valuestack'][0]['values'][] = $temp; } else { // Struct - $this->xh[$the_parser]['value'] = $temp; + $this->xh['value'] = $temp; } break; case 'MEMBER': - $this->xh[$the_parser]['ac'] = ''; + $this->xh['ac'] = ''; // If value add to array in the stack for the last element built - if ($this->xh[$the_parser]['value']) + if ($this->xh['value']) { - $this->xh[$the_parser]['valuestack'][0]['values'][$this->xh[$the_parser]['valuestack'][0]['name']] = $this->xh[$the_parser]['value']; + $this->xh['valuestack'][0]['values'][$this->xh['valuestack'][0]['name']] = $this->xh['value']; } break; case 'DATA': - $this->xh[$the_parser]['ac'] = ''; + $this->xh['ac'] = ''; break; case 'PARAM': - if ($this->xh[$the_parser]['value']) + if ($this->xh['value']) { - $this->xh[$the_parser]['params'][] = $this->xh[$the_parser]['value']; + $this->xh['params'][] = $this->xh['value']; } break; case 'METHODNAME': - $this->xh[$the_parser]['method'] = ltrim($this->xh[$the_parser]['ac']); + $this->xh['method'] = ltrim($this->xh['ac']); break; case 'PARAMS': case 'FAULT': @@ -1514,24 +1508,22 @@ public function closing_tag($the_parser, $name) */ public function character_data($the_parser, $data) { - $the_parser = (string) $the_parser; - - if ($this->xh[$the_parser]['isf'] > 1) return; // XML Fault found already + if ($this->xh['isf'] > 1) return; // XML Fault found already // If a value has not been found - if ($this->xh[$the_parser]['lv'] !== 3) + if ($this->xh['lv'] !== 3) { - if ($this->xh[$the_parser]['lv'] === 1) + if ($this->xh['lv'] === 1) { - $this->xh[$the_parser]['lv'] = 2; // Found a value + $this->xh['lv'] = 2; // Found a value } - if ( ! isset($this->xh[$the_parser]['ac'])) + if ( ! isset($this->xh['ac'])) { - $this->xh[$the_parser]['ac'] = ''; + $this->xh['ac'] = ''; } - $this->xh[$the_parser]['ac'] .= $data; + $this->xh['ac'] .= $data; } } diff --git a/system/libraries/Xmlrpcs.php b/system/libraries/Xmlrpcs.php index eb5a24c4917..557c9912ba4 100644 --- a/system/libraries/Xmlrpcs.php +++ b/system/libraries/Xmlrpcs.php @@ -234,9 +234,8 @@ public function parseRequest($data = '') $parser = xml_parser_create($this->xmlrpc_defencoding); $parser_object = new XML_RPC_Message('filler'); - $pname = (string) $parser; - $parser_object->xh[$pname] = array( + $parser_object->xh = array( 'isf' => 0, 'isf_reason' => '', 'params' => array(), @@ -245,10 +244,9 @@ public function parseRequest($data = '') 'method' => '' ); - xml_set_object($parser, $parser_object); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, TRUE); - xml_set_element_handler($parser, 'open_tag', 'closing_tag'); - xml_set_character_data_handler($parser, 'character_data'); + xml_set_element_handler($parser, [$parser_object, 'open_tag'], [$parser_object, 'closing_tag']); + xml_set_character_data_handler($parser, [$parser_object, 'character_data']); //xml_set_default_handler($parser, 'default_handler'); //------------------------------------- @@ -265,7 +263,7 @@ public function parseRequest($data = '') xml_get_current_line_number($parser))); xml_parser_free($parser); } - elseif ($parser_object->xh[$pname]['isf']) + elseif ($parser_object->xh['isf']) { return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return']); } @@ -273,17 +271,17 @@ public function parseRequest($data = '') { xml_parser_free($parser); - $m = new XML_RPC_Message($parser_object->xh[$pname]['method']); + $m = new XML_RPC_Message($parser_object->xh['method']); $plist = ''; - for ($i = 0, $c = count($parser_object->xh[$pname]['params']); $i < $c; $i++) + for ($i = 0, $c = count($parser_object->xh['params']); $i < $c; $i++) { if ($this->debug === TRUE) { - $plist .= $i.' - '.print_r(get_object_vars($parser_object->xh[$pname]['params'][$i]), TRUE).";\n"; + $plist .= $i.' - '.print_r(get_object_vars($parser_object->xh['params'][$i]), TRUE).";\n"; } - $m->addParam($parser_object->xh[$pname]['params'][$i]); + $m->addParam($parser_object->xh['params'][$i]); } if ($this->debug === TRUE) diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index ada6a5998b7..20fabc85db3 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -1,7 +1,12 @@ =')) { + error_reporting(E_ALL); +} else { + error_reporting(E_ALL | E_STRICT); +} $dir = realpath(dirname(__FILE__)); @@ -80,4 +85,4 @@ class_alias('org\bovigo\vfs\vfsStreamWrapper', 'vfsStreamWrapper'); include_once $dir.'/mocks/autoloader.php'; spl_autoload_register('autoload'); -unset($dir); \ No newline at end of file +unset($dir); diff --git a/tests/codeigniter/core/Input_test.php b/tests/codeigniter/core/Input_test.php index 93d1b7118c3..e1ffbe05e40 100644 --- a/tests/codeigniter/core/Input_test.php +++ b/tests/codeigniter/core/Input_test.php @@ -161,7 +161,10 @@ public function test_server() public function test_fetch_from_array() { $reflection = new ReflectionMethod($this->input, '_fetch_from_array'); - $reflection->setAccessible(TRUE); + if (PHP_VERSION_ID < 80100) + { + $reflection->setAccessible(TRUE); + } $data = array( 'foo' => 'bar', @@ -259,7 +262,10 @@ public function test_get_request_header() public function test_ip_address() { $reflection = new ReflectionProperty($this->input, 'ip_address'); - $reflection->setAccessible(TRUE); + if (PHP_VERSION_ID < 80100) + { + $reflection->setAccessible(TRUE); + } $reflection->setValue($this->input, '127.0.0.1'); $this->assertEquals('127.0.0.1', $this->input->ip_address()); diff --git a/tests/codeigniter/core/Loader_test.php b/tests/codeigniter/core/Loader_test.php index df9c9f44b68..6482765d0b9 100644 --- a/tests/codeigniter/core/Loader_test.php +++ b/tests/codeigniter/core/Loader_test.php @@ -36,7 +36,7 @@ public function test_library() // Test loading as an array. $this->assertInstanceOf('CI_Loader', $this->load->library(array($lib))); $this->assertTrue(class_exists($class), $class.' does not exist'); - $this->assertObjectHasAttribute($lib, $this->ci_obj); + $this->assertObjectHasPropertyShim($lib, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$lib); // Create library in VFS @@ -88,21 +88,21 @@ public function test_library_extension() $this->assertInstanceOf('CI_Loader', $this->load->library($lib)); $this->assertTrue(class_exists($class), $class.' does not exist'); $this->assertTrue(class_exists($ext), $ext.' does not exist'); - $this->assertObjectHasAttribute($name, $this->ci_obj); + $this->assertObjectHasPropertyShim($name, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$name); $this->assertInstanceOf($ext, $this->ci_obj->$name); // Test reloading with object name $obj = 'exttest'; $this->assertInstanceOf('CI_Loader', $this->load->library($lib, NULL, $obj)); - $this->assertObjectHasAttribute($obj, $this->ci_obj); + $this->assertObjectHasPropertyShim($obj, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$obj); $this->assertInstanceOf($ext, $this->ci_obj->$obj); // Test reloading unset($this->ci_obj->$name); $this->assertInstanceOf('CI_Loader', $this->load->library($lib)); - $this->assertObjectHasAttribute($name, $this->ci_obj); + $this->assertObjectHasPropertyShim($name, $this->ci_obj); // Create baseless library $name = 'ext_baseless_lib'; @@ -125,7 +125,7 @@ public function test_library_config() // Create library in VFS $lib = 'unit_test_config_lib'; $class = 'CI_'.ucfirst($lib); - $content = 'config = $params; } }'; + $content = "config = $params; } }'; $this->ci_vfs_create(ucfirst($lib), $content, $this->ci_base_root, 'libraries'); // Create config file @@ -140,7 +140,7 @@ public function test_library_config() $obj = 'testy'; $this->assertInstanceOf('CI_Loader', $this->load->library($lib, NULL, $obj)); $this->assertTrue(class_exists($class), $class.' does not exist'); - $this->assertObjectHasAttribute($obj, $this->ci_obj); + $this->assertObjectHasPropertyShim($obj, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$obj); $this->assertEquals($cfg, $this->ci_obj->$obj->config); @@ -172,7 +172,7 @@ public function test_load_library_in_application_dir() // Was the model class instantiated. $this->assertTrue(class_exists($class), $class.' does not exist'); - $this->assertObjectHasAttribute($lib, $this->ci_obj); + $this->assertObjectHasPropertyShim($lib, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$lib); } @@ -193,13 +193,13 @@ class_exists('CI_Driver_Library', TRUE); // Test loading as an array. $this->assertInstanceOf('CI_Loader', $this->load->driver(array($driver))); $this->assertTrue(class_exists($class), $class.' does not exist'); - $this->assertObjectHasAttribute($driver, $this->ci_obj); + $this->assertObjectHasPropertyShim($driver, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$driver); // Test loading as a library with a name $obj = 'testdrive'; $this->assertInstanceOf('CI_Loader', $this->load->library($driver, NULL, $obj)); - $this->assertObjectHasAttribute($obj, $this->ci_obj); + $this->assertObjectHasPropertyShim($obj, $this->ci_obj); $this->assertInstanceOf($class, $this->ci_obj->$obj); // Test a string given to params @@ -222,7 +222,7 @@ public function test_models() // Was the model class instantiated. $this->assertTrue(class_exists($model)); - $this->assertObjectHasAttribute($model, $this->ci_obj); + $this->assertObjectHasPropertyShim($model, $this->ci_obj); // Test no model given $this->assertInstanceOf('CI_Loader', $this->load->model('')); @@ -248,8 +248,8 @@ public function test_model_subdir() // Was the model class instantiated? $this->assertTrue(class_exists($model)); - $this->assertObjectHasAttribute($name, $this->ci_obj); - $this->assertObjectHasAttribute($name, $this->ci_obj); + $this->assertObjectHasPropertyShim($name, $this->ci_obj); + $this->assertObjectHasPropertyShim($name, $this->ci_obj); $this->assertInstanceOf($base, $this->ci_obj->$name); $this->assertInstanceOf($model, $this->ci_obj->$name); @@ -607,17 +607,17 @@ public function test_initialize() // Verify library $this->assertTrue(class_exists($lib_class), $lib_class.' does not exist'); - $this->assertObjectHasAttribute($lib, $this->ci_obj); + $this->assertObjectHasPropertyShim($lib, $this->ci_obj); $this->assertInstanceOf($lib_class, $this->ci_obj->$lib); // Verify driver $this->assertTrue(class_exists($drv_class), $drv_class.' does not exist'); - $this->assertObjectHasAttribute($drv, $this->ci_obj); + $this->assertObjectHasPropertyShim($drv, $this->ci_obj); $this->assertInstanceOf($drv_class, $this->ci_obj->$drv); // Verify model $this->assertTrue(class_exists($model), $model.' does not exist'); - $this->assertObjectHasAttribute($model, $this->ci_obj); + $this->assertObjectHasPropertyShim($model, $this->ci_obj); $this->assertInstanceOf($model, $this->ci_obj->$model); // Verify config calls diff --git a/tests/codeigniter/core/Log_test.php b/tests/codeigniter/core/Log_test.php index fc3e11e1fa5..a9f2dedb213 100644 --- a/tests/codeigniter/core/Log_test.php +++ b/tests/codeigniter/core/Log_test.php @@ -4,17 +4,20 @@ class Log_test extends CI_TestCase { public function test_configuration() { $path = new ReflectionProperty('CI_Log', '_log_path'); - $path->setAccessible(TRUE); $threshold = new ReflectionProperty('CI_Log', '_threshold'); - $threshold->setAccessible(TRUE); $date_fmt = new ReflectionProperty('CI_Log', '_date_fmt'); - $date_fmt->setAccessible(TRUE); $filename = new ReflectionProperty('CI_Log', '_log_filename'); - $filename->setAccessible(TRUE); $file_perms = new ReflectionProperty('CI_Log', '_file_permissions'); - $file_perms->setAccessible(TRUE); $enabled = new ReflectionProperty('CI_Log', '_enabled'); - $enabled->setAccessible(TRUE); + if (PHP_VERSION_ID < 80100) + { + $path->setAccessible(TRUE); + $threshold->setAccessible(TRUE); + $date_fmt->setAccessible(TRUE); + $filename->setAccessible(TRUE); + $file_perms->setAccessible(TRUE); + $enabled->setAccessible(TRUE); + } $this->ci_set_config('log_path', $this->ci_readonly_dir->url()); $this->ci_set_config('log_threshold', 'z'); @@ -45,7 +48,33 @@ public function test_configuration() $this->assertEquals($enabled->getValue($instance), TRUE); } - // -------------------------------------------------------------------- + public function test_log_file_extension_backward_compatibility() + { + $filename = new ReflectionProperty('CI_Log', '_log_filename'); + if (PHP_VERSION_ID < 80100) + { + $filename->setAccessible(TRUE); + } + + $this->ci_set_config('log_path', ''); + $this->ci_set_config('log_threshold', 0); + $this->ci_set_config('log_filename', ''); + $this->ci_set_config('log_file_extension', 'txt'); + $instance = new CI_Log(); + + $this->assertEquals($filename->getValue($instance), 'log-'.date('Y-m-d').'.txt'); + + $this->ci_set_config('log_file_extension', '.log'); + $instance = new CI_Log(); + + $this->assertEquals($filename->getValue($instance), 'log-'.date('Y-m-d').'.log'); + + $this->ci_set_config('log_filename', 'custom.log'); + $this->ci_set_config('log_file_extension', 'txt'); + $instance = new CI_Log(); + + $this->assertEquals($filename->getValue($instance), 'custom.log'); + } public function test_format_line() { @@ -54,7 +83,10 @@ public function test_format_line() $instance = new CI_Log(); $format_line = new ReflectionMethod($instance, '_format_line'); - $format_line->setAccessible(TRUE); + if (PHP_VERSION_ID < 80100) + { + $format_line->setAccessible(TRUE); + } $this->assertEquals( $format_line->invoke($instance, 'LEVEL', 'Timestamp', 'Message'), "LEVEL - Timestamp --> Message".PHP_EOL diff --git a/tests/codeigniter/helpers/text_helper_test.php b/tests/codeigniter/helpers/text_helper_test.php index 0713775da74..43024abdf32 100644 --- a/tests/codeigniter/helpers/text_helper_test.php +++ b/tests/codeigniter/helpers/text_helper_test.php @@ -101,7 +101,15 @@ public function test_censored_words() public function test_highlight_code() { - $expect = "\n<?php var_dump(\$this); ?> \n\n"; + // PHP 8.3 changed highlight_string() output format + if (PHP_VERSION_ID >= 80300) + { + $expect = "
<?php var_dump(\$this); ?> ?>
"; + } + else + { + $expect = "\n<?php var_dump(\$this); ?> \n\n"; + } $this->assertEquals($expect, highlight_code('')); } diff --git a/tests/codeigniter/libraries/Driver_test.php b/tests/codeigniter/libraries/Driver_test.php index ea5cfa235f8..f32080ae592 100644 --- a/tests/codeigniter/libraries/Driver_test.php +++ b/tests/codeigniter/libraries/Driver_test.php @@ -51,12 +51,12 @@ public function test_load_driver() $this->assertEquals($this->name, $this->lib->get_name()); // Was driver loaded? - $this->assertObjectHasAttribute($driver, $this->lib); + $this->assertObjectHasPropertyShim($driver, $this->lib); $this->assertInstanceOf($class, $this->lib->$driver); $this->assertInstanceOf('CI_Driver', $this->lib->$driver); // Was decorate called? - $this->assertObjectHasAttribute($prop, $this->lib->$driver); + $this->assertObjectHasPropertyShim($prop, $this->lib->$driver); $this->assertTrue($this->lib->$driver->$prop); // Do we get an error for an invalid driver? @@ -86,7 +86,7 @@ public function test_load_app_driver() $this->assertNotNull($this->lib->load_driver($driver)); // Was driver loaded? - $this->assertObjectHasAttribute($driver, $this->lib); + $this->assertObjectHasPropertyShim($driver, $this->lib); $this->assertInstanceOf($class, $this->lib->$driver); $this->assertInstanceOf('CI_Driver', $this->lib->$driver); @@ -120,7 +120,7 @@ public function test_load_driver_ext() $this->assertNotNull($this->lib->load_driver($driver)); // Was driver loaded? - $this->assertObjectHasAttribute($driver, $this->lib); + $this->assertObjectHasPropertyShim($driver, $this->lib); $this->assertInstanceOf($class, $this->lib->$driver); $this->assertInstanceOf($baseclass, $this->lib->$driver); $this->assertInstanceOf('CI_Driver', $this->lib->$driver); diff --git a/tests/codeigniter/libraries/Upload_test.php b/tests/codeigniter/libraries/Upload_test.php index 74a7d2c22df..0d069d9a9d0 100644 --- a/tests/codeigniter/libraries/Upload_test.php +++ b/tests/codeigniter/libraries/Upload_test.php @@ -27,7 +27,10 @@ public function test___construct_initialize() $reflection = new ReflectionClass($upload); $reflection = $reflection->getProperty('_file_name_override'); - $reflection->setAccessible(TRUE); + if (PHP_VERSION_ID < 80100) + { + $reflection->setAccessible(TRUE); + } $this->assertEquals('foo', $reflection->getValue($upload)); $this->assertTrue($upload->file_ext_tolower); @@ -59,9 +62,6 @@ function test_data() $data = array( 'file_name' => 'hello.txt', 'file_type' => 'text/plain', - 'file_path' => '/tmp/', - 'full_path' => '/tmp/hello.txt', - 'raw_name' => 'hello', 'orig_name' => 'hello.txt', 'client_name' => '', 'file_ext' => '.txt', @@ -80,6 +80,10 @@ function test_data() $this->upload->{$k} = $v; } + $data['file_path'] = '/tmp/'; + $data['full_path'] = '/tmp/hello.txt'; + $data['raw_name'] = 'hello'; + $this->assertEquals('hello.txt', $this->upload->data('file_name')); $this->assertEquals($data, $this->upload->data()); } diff --git a/tests/codeigniter/libraries/Xmlrpc_test.php b/tests/codeigniter/libraries/Xmlrpc_test.php new file mode 100644 index 00000000000..e767585f843 --- /dev/null +++ b/tests/codeigniter/libraries/Xmlrpc_test.php @@ -0,0 +1,133 @@ +input = new CI_Input($security); + + $this->input_lib_raw_stream = new ReflectionProperty($this->input, '_raw_input_stream'); + if (PHP_VERSION_ID < 80100) + { + $this->input_lib_raw_stream->setAccessible(TRUE); + } + + $this->ci_instance_var('input', $this->input); + $this->ci_instance_var('security', $security); + } + + // -------------------------------------------------------------------- + + public function test_xmlrpc_client() + { + $xmlrpc = new Mock_Libraries_Xmlrpc(); + $xmlrpc->server('http://rpc.test/'); + $xmlrpc->method('testcontroller.test'); + + $request = array('My Blog', 'http://www.myrpc.com/test/'); + $message = 'test'.time(); + $xml_response = $this->xml_response($message); + $xmlrpc->client->mock_response = "HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\nContent-Length: ".strlen($xml_response)."\r\n\r\n$xml_response"; + + // Perform in the same request multiple calls + for ($attempt = 1; $attempt <= 2; $attempt++) + { + $xmlrpc->request($request); + + $this->assertTrue($xmlrpc->send_request()); + + $response = $xmlrpc->display_response(); + + $this->assertEquals('theuser', $response['name']); + $this->assertEquals(123435, $response['member_id']); + $this->assertEquals($message, $response['request']); + } + } + + // -------------------------------------------------------------------- + + public function test_xmlrpc_server() + { + $xmlrpcs = new Mock_Libraries_Xmlrpcs(); + + $config['functions']['Testmethod'] = array('function' => __CLASS__.'.mock_method_new_entry'); + $config['object'] = $this; + + $xmlrpcs->initialize($config); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->input_lib_raw_stream->setValue($this->input, $this->xml_request()); + + $xmlrpcs->serve(); + + $this->assertEquals('Test', $this->method_param); + } + + // -------------------------------------------------------------------- + + /** + * @param XML_RPC_Message $param + */ + public function mock_method_new_entry($param) + { + $this->method_param = $param->params[0]->scalarval(); + + return new XML_RPC_Response(new XML_RPC_Values(true, 'boolean')); + } + + // -------------------------------------------------------------------- + + private function xml_response($message) + { + return ' + + + + + + +name + +theuser + + + +member_id + +123435 + + + +request + +'.$message.' + + + + + +'; + } + + // -------------------------------------------------------------------- + + public function xml_request() + { + return ' + +Testmethod + + + +Test + + + +'; + } +} diff --git a/tests/mocks/autoloader.php b/tests/mocks/autoloader.php index 4dd53d4af31..3e6b0b60432 100644 --- a/tests/mocks/autoloader.php +++ b/tests/mocks/autoloader.php @@ -33,6 +33,7 @@ function autoload($class) $ci_libraries = array( 'Calendar', + 'Cart', 'Driver_Library', 'Email', 'Encrypt', @@ -52,6 +53,7 @@ function autoload($class) 'Upload', 'User_agent', 'Xmlrpc', + 'Xmlrpcs', 'Zip' ); diff --git a/tests/mocks/ci_testcase.php b/tests/mocks/ci_testcase.php index 3ebb6b822e9..3f6de627bc7 100644 --- a/tests/mocks/ci_testcase.php +++ b/tests/mocks/ci_testcase.php @@ -1,5 +1,6 @@ assertObjectHasProperty($propertyName, $object, $message); + } + else + { + $this->assertObjectHasAttribute($propertyName, $object, $message); + } + } } diff --git a/tests/mocks/libraries/xmlrpc.php b/tests/mocks/libraries/xmlrpc.php new file mode 100644 index 00000000000..32f5f3765d5 --- /dev/null +++ b/tests/mocks/libraries/xmlrpc.php @@ -0,0 +1,33 @@ +client = new Mock_Libraries_XML_RPC_Client('/', $url, $port, $proxy, $proxy_port); + } +} + +class Mock_Libraries_XML_RPC_Client extends XML_RPC_Client { + public $mock_response = ''; + + /** + * @param XML_RPC_Message $msg + */ + public function sendPayload($msg) + { + if (empty($msg->payload)) + { + $msg->createPayload(); + } + + $fp = fopen('php://memory', 'rw+'); + fwrite($fp, $this->mock_response); + fseek($fp, 0); + + $parsed = $msg->parseResponse($fp); + fclose($fp); + + return $parsed; + } +} + diff --git a/tests/mocks/libraries/xmlrpcs.php b/tests/mocks/libraries/xmlrpcs.php new file mode 100644 index 00000000000..d93396c0189 --- /dev/null +++ b/tests/mocks/libraries/xmlrpcs.php @@ -0,0 +1,23 @@ +parseRequest(); + + $payload = 'xmlrpc_defencoding.'"?'.'>'."\n".$this->debug_msg.$r->prepare_response(); + + $this->mock_payload = "HTTP/1.1 200 OK\r\n"; + $this->mock_payload .= "Content-Type: text/xml\r\n"; + $this->mock_payload .= 'Content-Length: '.strlen($payload)."\r\n"; + + $this->mock_payload .= "\r\n"; + + $this->mock_payload .= $payload; + } +} diff --git a/user_guide_src/source/installation/upgrade_320.rst b/user_guide_src/source/installation/upgrade_320.rst index 6308fa7fa47..8ed23afa27a 100644 --- a/user_guide_src/source/installation/upgrade_320.rst +++ b/user_guide_src/source/installation/upgrade_320.rst @@ -1,6 +1,11 @@ -############################# -Upgrading from 3.1.x to 3.2.x -############################# +####################################### +Upgrading from 3.1.x or 3.2-dev to 3.2+ +####################################### + +This guide covers upgrading to this maintenance fork, which is based on +the unreleased CodeIgniter 3.2.0-dev. Many unnecessary breaking changes +from 3.2.0-dev have been reverted to preserve backward compatibility. +This is the same guide whether you are coming from 3.1.x or 3.2-dev. Before performing an update you should take your site offline by replacing the index.php file with a static one. @@ -8,7 +13,16 @@ replacing the index.php file with a static one. Step 1: Update your CodeIgniter files ===================================== -Replace all files and directories in your *system/* directory. +Install via Composer (recommended):: + + composer require pocketarc/codeigniter + +Then update the ``$system_path`` in your ``index.php``:: + + $system_path = 'vendor/pocketarc/codeigniter/system'; + +Alternatively, you can manually replace all files and directories in +your *system/* directory. .. note:: If you have any custom developed files in these directories, please make copies of them first. @@ -16,53 +30,21 @@ Replace all files and directories in your *system/* directory. Step 2: Check your PHP version ============================== -We recommend always running versions that are `currently supported -`_, which right now is at least PHP 5.6. - -PHP 5.3.x versions are now officially not supported by CodeIgniter, and while 5.4.8+ -may be at least runnable, we strongly discourage you from using any PHP versions below -the ones listed on the `PHP.net Supported Versions `_ -page. +This fork supports PHP 5.4 through 8.5+. We recommend running a +`currently supported `_ +PHP version (8.4 or newer). -Step 3: Remove calls to ``CI_Model::__construct()`` -=================================================== +Step 3: Calls to ``CI_Model::__construct()`` (optional cleanup) +=============================================================== The class constructor for ``CI_Model`` never contained vital code or useful logic, only a single line to log a message. A change in CodeIgniter 3.1.7 moved this log message elsewhere and that naturally made the constructor -completely unnecessary. However, it was left in place to avoid immedate BC -breaks in a minor release. +completely unnecessary. -In version 3.2.0, that constructor is entirely removed, which would result -in fatal errors on attempts to call it. Particularly in code like this: -:: - - class Some_model extends CI_Model { - - public function __construct() - { - parent::__construct(); // calls CI_Model::__construct() - - do_some_other_thing(); - } - } - -All you need to do is remove that ``parent::__construct()`` call. On a side -note, the following seems to be a very common practice: -:: - - class Some_class extends CI_Something { - - public function __construct() - { - parent::__construct(); - } - } - -Please, do NOT do this! It's pointless; it serves no purpose and doesn't do -anything. If a parent class has a ``__construct()`` method, it will be -inherited by all its child classes and will execute just fine - you DON'T -have to explicitly call it unless you want to extend its logic. +The constructor is kept as an empty method for backwards compatibility, so +existing code calling ``parent::__construct()`` will continue to work. +However, such calls are unnecessary and can be safely removed. Step 4: Change database connection handling =========================================== @@ -197,7 +179,7 @@ Step 10: Remove usage of previously deprecated functionalities ============================================================== The following is a list of functionalities deprecated in previous -CodeIgniter versions that have been removed in 3.2.0: +CodeIgniter versions that have been removed in 3.2+: - ``$config['allow_get_array']`` (use ``$_GET = array();`` instead) - ``$config['standardize_newlines']`` @@ -205,38 +187,10 @@ CodeIgniter versions that have been removed in 3.2.0: - 'sqlite' database driver (no longer shipped with PHP 5.4+; 'sqlite3' is still available) -- ``CI_Input::is_cli_request()`` (use :php:func:`is_cli()` instead) -- ``CI_Router::fetch_directory()`` (use ``CI_Router::$directory`` instead) -- ``CI_Router::fetch_class()`` (use ``CI_Router::$class`` instead) -- ``CI_Router::fetch_method()`` (use ``CI_Router::$method`` instead) -- ``CI_Config::system_url()`` (encourages insecure practices) -- ``CI_Form_validation::prep_for_form()`` (the *prep_for_form* rule) - -- ``standard_date()`` :doc:`Date Helper <../helpers/date_helper>` function (use ``date()`` instead) -- ``nice_date()`` :doc:`Date Helper <../helpers/date_helper>` function (use ``DateTime::format()`` instead) -- ``do_hash()`` :doc:`Security Helper <../helpers/security_helper>` function (use ``hash()`` instead) -- ``br()`` :doc:`HTML Helper <../helpers/html_helper>` function (use ``str_repeat()`` with ``'
'`` instead) -- ``nbs()`` :doc:`HTML Helper <../helpers/html_helper>` function (use ``str_repeat()`` with ``' '`` instead) -- ``trim_slashes()`` :doc:`String Helper <../helpers/string_helper>` function (use ``trim()`` with ``'/'`` instead) -- ``repeater()`` :doc:`String Helper <../helpers/string_helper>` function (use ``str_repeat()`` instead) -- ``read_file()`` :doc:`File Helper <../helpers/file_helper>` function (use ``file_get_contents()`` instead) -- ``form_prep()`` :doc:`Form Helper <../helpers/form_helper>` function (use :php:func:`html_escape()` instead) - - The entire *Encrypt Library* (the newer :doc:`Encryption Library <../libraries/encryption>` is still available) -- The entire *Cart Library* (an archived version is available on GitHub: `bcit-ci/ci3-cart-library `_) - The entire *Javascript Library* (it was always experimental in the first place) - -- The entire *Email Helper*, which only had two functions: - - - ``valid_email()`` (use ``filter_var($email, FILTER_VALIDATE_EMAIL)`` instead) - - ``send_email()`` (use ``mail()`` instead) - - The entire *Smiley Helper* (an archived version is available on GitHub: `bcit-ci/ci3-smiley-helper `_) -- The ``$_after`` parameter from :doc:`Database Forge <../database/forge>` method ``add_column()``. -- The ``anchor_class`` option from :doc:`Pagination Library <../libraries/pagination>` (use ``class`` instead). -- The ``unique`` and ``encrypt`` options from :doc:`String Helper <../helpers/string_helper>` function ``random_string()``. -- The ``underscore`` and ``dash`` options from :doc:`URL Helper <../helpers/url_helper>`` function :php:func:`url_title()`. - The ``$img_path``, ``$img_url`` and ``$font_path`` parameters from :doc:`CAPCHA Helper <../helpers/captcha_helper>` function :php:func:`create_captcha()` (pass as array options instead). @@ -277,14 +231,12 @@ The ``$curs_id`` property is also removed. If you were using those, you can create your own cursors via ``oci_new_cursor()`` and the publicly accessible ``$conn_id``. -Stop 14: Replace $config['log_file_extension'] with $config['log_filename'] in application/config/config.php -============================================================================================================ +Step 14: Check log filename configuration in application/config/config.php +========================================================================== You can now specify the full log filename via ``$config['log_filename']``. Add this configuration option to your **application/config/config.php**, if you haven't copied the new one over. -The previously existing ``$config['log_file_extension']`` option has been -removed and no longer works. However, its functionality is essentially -integrated into the new ``$config['log_filename']``, since it includes the -filename extension in itself. +The ``$config['log_file_extension']`` option still works as a fallback, +but ``$config['log_filename']`` takes precedence when set. diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst index 6924dde485d..c87fea5e065 100644 --- a/user_guide_src/source/installation/upgrading.rst +++ b/user_guide_src/source/installation/upgrading.rst @@ -8,7 +8,7 @@ upgrading from. .. toctree:: :titlesonly: - Upgrading from 3.1.12+ to 3.2.x + Upgrading from 3.1.x or 3.2-dev to 3.2+ Upgrading from 3.1.13 to 3.1.14 Upgrading from 3.1.12 to 3.1.13 Upgrading from 3.1.11 to 3.1.12 diff --git a/user_guide_src/source/libraries/cart.rst b/user_guide_src/source/libraries/cart.rst new file mode 100644 index 00000000000..be343320db8 --- /dev/null +++ b/user_guide_src/source/libraries/cart.rst @@ -0,0 +1,398 @@ +################### +Shopping Cart Class +################### + +The Cart Class permits items to be added to a session that stays active +while a user is browsing your site. These items can be retrieved and +displayed in a standard "shopping cart" format, allowing the user to +update the quantity or remove items from the cart. + +.. important:: The Cart library is DEPRECATED and should not be used. + It is currently only kept for backwards compatibility. + +Please note that the Cart Class ONLY provides the core "cart" +functionality. It does not provide shipping, credit card authorization, +or other processing components. + +.. contents:: + :local: + +.. raw:: html + +
+ +******************** +Using the Cart Class +******************** + +Initializing the Shopping Cart Class +==================================== + +.. important:: The Cart class utilizes CodeIgniter's :doc:`Session + Class ` to save the cart information to a database, so + before using the Cart class you must set up a database table as + indicated in the :doc:`Session Documentation `, and set the + session preferences in your application/config/config.php file to + utilize a database. + +To initialize the Shopping Cart Class in your controller constructor, +use the ``$this->load->library()`` method:: + + $this->load->library('cart'); + +Once loaded, the Cart object will be available using:: + + $this->cart + +.. note:: The Cart Class will load and initialize the Session Class + automatically, so unless you are using sessions elsewhere in your + application, you do not need to load the Session class. + +Adding an Item to The Cart +========================== + +To add an item to the shopping cart, simply pass an array with the +product information to the ``$this->cart->insert()`` method, as shown +below:: + + $data = array( + 'id' => 'sku_123ABC', + 'qty' => 1, + 'price' => 39.95, + 'name' => 'T-Shirt', + 'options' => array('Size' => 'L', 'Color' => 'Red') + ); + + $this->cart->insert($data); + +.. important:: The first four array indexes above (id, qty, price, and + name) are **required**. If you omit any of them the data will not be + saved to the cart. The fifth index (options) is optional. It is intended + to be used in cases where your product has options associated with it. + Use an array for options, as shown above. + +The five reserved indexes are: + +- **id** - Each product in your store must have a unique identifier. + Typically this will be an "sku" or other such identifier. +- **qty** - The quantity being purchased. +- **price** - The price of the item. +- **name** - The name of the item. +- **options** - Any additional attributes that are needed to identify + the product. These must be passed via an array. + +In addition to the five indexes above, there are two reserved words: +rowid and subtotal. These are used internally by the Cart class, so +please do NOT use those words as index names when inserting data into +the cart. + +Your array may contain additional data. Anything you include in your +array will be stored in the session. However, it is best to standardize +your data among all your products in order to make displaying the +information in a table easier. + +:: + + $data = array( + 'id' => 'sku_123ABC', + 'qty' => 1, + 'price' => 39.95, + 'name' => 'T-Shirt', + 'coupon' => 'XMAS-50OFF' + ); + + $this->cart->insert($data); + +The ``insert()`` method will return the $rowid if you successfully insert a +single item. + +Adding Multiple Items to The Cart +================================= + +By using a multi-dimensional array, as shown below, it is possible to +add multiple products to the cart in one action. This is useful in cases +where you wish to allow people to select from among several items on the +same page. + +:: + + $data = array( + array( + 'id' => 'sku_123ABC', + 'qty' => 1, + 'price' => 39.95, + 'name' => 'T-Shirt', + 'options' => array('Size' => 'L', 'Color' => 'Red') + ), + array( + 'id' => 'sku_567ZYX', + 'qty' => 1, + 'price' => 9.95, + 'name' => 'Coffee Mug' + ), + array( + 'id' => 'sku_965QRS', + 'qty' => 1, + 'price' => 29.95, + 'name' => 'Shot Glass' + ) + ); + + $this->cart->insert($data); + +Displaying the Cart +=================== + +To display the cart you will create a :doc:`view +file ` with code similar to the one shown below. + +Please note that this example uses the :doc:`form +helper `. + +:: + + + + + + + + + + + + + + + cart->contents() as $items): ?> + + + + + + + + + + + + + + + + + + + + +
QTYItem DescriptionItem PriceSub-Total
$i.'[qty]', 'value' => $items['qty'], 'maxlength' => '3', 'size' => '5')); ?> + + + cart->has_options($items['rowid']) == TRUE): ?> + +

+ cart->product_options($items['rowid']) as $option_name => $option_value): ?> + + :
+ + +

+ + + +
cart->format_number($items['price']); ?>$cart->format_number($items['subtotal']); ?>
 Total$cart->format_number($this->cart->total()); ?>
+ +

+ +Updating The Cart +================= + +To update the information in your cart, you must pass an array +containing the Row ID and one or more pre-defined properties to the +``$this->cart->update()`` method. + +.. note:: If the quantity is set to zero, the item will be removed from + the cart. + +:: + + $data = array( + 'rowid' => 'b99ccdf16028f015540f341130b6d8ec', + 'qty' => 3 + ); + + $this->cart->update($data); + + // Or a multi-dimensional array + + $data = array( + array( + 'rowid' => 'b99ccdf16028f015540f341130b6d8ec', + 'qty' => 3 + ), + array( + 'rowid' => 'xw82g9q3r495893iajdh473990rikw23', + 'qty' => 4 + ), + array( + 'rowid' => 'fh4kdkkkaoe30njgoe92rkdkkobec333', + 'qty' => 2 + ) + ); + + $this->cart->update($data); + +You may also update any property you have previously defined when +inserting the item such as options, price or other custom fields. + +:: + + $data = array( + 'rowid' => 'b99ccdf16028f015540f341130b6d8ec', + 'qty' => 1, + 'price' => 49.95, + 'coupon' => NULL + ); + + $this->cart->update($data); + +What is a Row ID? +***************** + +The row ID is a unique identifier that is generated by the cart code +when an item is added to the cart. The reason a unique ID is created +is so that identical products with different options can be managed +by the cart. + +For example, let's say someone buys two identical t-shirts (same product +ID), but in different sizes. The product ID (and other attributes) will +be identical for both sizes because it's the same shirt. The only +difference will be the size. The cart must therefore have a means of +identifying this difference so that the two sizes of shirts can be +managed independently. It does so by creating a unique "row ID" based on +the product ID and any options associated with it. + +In nearly all cases, updating the cart will be something the user does +via the "view cart" page, so as a developer, it is unlikely that you +will ever have to concern yourself with the "row ID", other than making +sure your "view cart" page contains this information in a hidden form +field, and making sure it gets passed to the ``update()`` method when +the update form is submitted. Please examine the construction of the +"view cart" page above for more information. + + +*************** +Class Reference +*************** + +.. php:class:: CI_Cart + + .. attribute:: $product_id_rules = '\.a-z0-9_-' + + These are the regular expression rules that we use to validate the product + ID - alpha-numeric, dashes, underscores, or periods by default + + .. attribute:: $product_name_rules = '\w \-\.\:' + + These are the regular expression rules that we use to validate the product ID and product name - alpha-numeric, dashes, underscores, colons or periods by + default + + .. attribute:: $product_name_safe = TRUE + + Whether or not to only allow safe product names. Default TRUE. + + + .. php:method:: insert([$items = array()]) + + :param array $items: Items to insert into the cart + :returns: TRUE on success, FALSE on failure + :rtype: bool + + Insert items into the cart and save it to the session table. Returns TRUE + on success and FALSE on failure. + + + .. php:method:: update([$items = array()]) + + :param array $items: Items to update in the cart + :returns: TRUE on success, FALSE on failure + :rtype: bool + + This method permits changing the properties of a given item. + Typically it is called from the "view cart" page if a user makes changes + to the quantity before checkout. That array must contain the rowid + for each item. + + .. php:method:: remove($rowid) + + :param int $rowid: ID of the item to remove from the cart + :returns: TRUE on success, FALSE on failure + :rtype: bool + + Allows you to remove an item from the shopping cart by passing it the + ``$rowid``. + + .. php:method:: total() + + :returns: Total amount + :rtype: int + + Displays the total amount in the cart. + + + .. php:method:: total_items() + + :returns: Total amount of items in the cart + :rtype: int + + Displays the total number of items in the cart. + + + .. php:method:: contents([$newest_first = FALSE]) + + :param bool $newest_first: Whether to order the array with newest items first + :returns: An array of cart contents + :rtype: array + + Returns an array containing everything in the cart. You can sort the + order by which the array is returned by passing it TRUE where the contents + will be sorted from newest to oldest, otherwise it is sorted from oldest + to newest. + + .. php:method:: get_item($row_id) + + :param int $row_id: Row ID to retrieve + :returns: Array of item data + :rtype: array + + Returns an array containing data for the item matching the specified row + ID, or FALSE if no such item exists. + + .. php:method:: has_options($row_id = '') + + :param int $row_id: Row ID to inspect + :returns: TRUE if options exist, FALSE otherwise + :rtype: bool + + Returns TRUE (boolean) if a particular row in the cart contains options. + This method is designed to be used in a loop with ``contents()``, since + you must pass the rowid to this method, as shown in the Displaying + the Cart example above. + + .. php:method:: product_options([$row_id = '']) + + :param int $row_id: Row ID + :returns: Array of product options + :rtype: array + + Returns an array of options for a particular product. This method is + designed to be used in a loop with ``contents()``, since you + must pass the rowid to this method, as shown in the Displaying the + Cart example above. + + .. php:method:: destroy() + + :rtype: void + + Permits you to destroy the cart. This method will likely be called + when you are finished processing the customer's order. \ No newline at end of file