diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 81ddccc..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 CodeIgniter 4 web framework - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 03626c7..275f015 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ including the user guide. It has been built from the [development repository](https://github.com/codeigniter4/CodeIgniter4). -**This is pre-release code and should not be used in production sites.** - More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. The user guide corresponding to this version of the framework can be found diff --git a/app/Config/Kint.php b/app/Config/Kint.php index 9562447..09db83d 100644 --- a/app/Config/Kint.php +++ b/app/Config/Kint.php @@ -1,7 +1,7 @@ 0644, + + /* + * Logging Directory Path + * + * By default, logs are written to WRITEPATH . 'logs/' + * Specify a different destination here, if desired. + */ + 'path' => '', ], /** diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index a570ef5..41014d4 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -516,11 +516,7 @@ foreach (static::$mimes as $ext => $types) { - if (is_string($types) && $types === $type) - { - return $ext; - } - else if (is_array($types) && in_array($type, $types)) + if ((is_string($types) && $types === $type) || (is_array($types) && in_array($type, $types))) { return $ext; } diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 4832736..a2a9654 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -1,7 +1,7 @@ + } ?>

$

@@ -235,7 +235,7 @@ + } ?> @@ -287,15 +287,15 @@ - $value) : ?> + + } ?> + } ?> getName(), 'html') ?> diff --git a/composer.json b/composer.json index d8adb48..a8045d3 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "ext-curl": "*", "ext-intl": "*", "ext-json": "*", + "ext-mbstring": "*", "kint-php/kint": "^3.3", "psr/log": "^1.1", "laminas/laminas-escaper": "^2.6" diff --git a/contributing.md b/contributing.md deleted file mode 100644 index f0cf482..0000000 --- a/contributing.md +++ /dev/null @@ -1,94 +0,0 @@ -# Contributing to CodeIgniter4 - - -## Contributions - -We expect all contributions to conform to our [style guide](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/styleguide.rst), be commented (inside the PHP source files), -be documented (in the [user guide](https://codeigniter4.github.io/userguide/)), and unit tested (in the [test folder](https://github.com/codeigniter4/CodeIgniter4/tree/develop/tests)). -There is a [Contributing to CodeIgniter](./contributing/README.rst) section in the repository which describes the contribution process; this page is an overview. - -Note, we expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works. If pull requests are not accompanied by relevant tests, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work on the framework than you do. Please make it as painless for your contributions to be included as possible. If you need help with getting tests running on your local machines, ask for help on the forums. We would be happy to help out. - -The [Open Source Guide](https://opensource.guide/) is a good first read for those new to contributing to open source! -## Issues - -Issues are a quick way to point out a bug. If you find a bug or documentation error in CodeIgniter then please make sure that: - -1. There is not already an open [Issue](https://github.com/codeigniter4/CodeIgniter4/issues) -2. The Issue has not already been fixed (check the develop branch or look for [closed Issues](https://github.com/codeigniter4/CodeIgniter4/issues?q=is%3Aissue+is%3Aclosed)) -3. It's not something really obvious that you can fix yourself - -Reporting Issues is helpful, but an even [better approach](./contributing/workflow.rst) is to send a [Pull Request](https://help.github.com/en/articles/creating-a-pull-request), which is done by [Forking](https://help.github.com/en/articles/fork-a-repo) the main repository and making a [Commit](https://help.github.com/en/desktop/contributing-to-projects/committing-and-reviewing-changes-to-your-project) to your own copy of the project. This will require you to use the version control system called [Git](https://git-scm.com/). - -## Guidelines - -Before we look into how to contribute to CodeIgniter4, here are some guidelines. If your Pull Requests fail -to pass these guidelines, they will be declined, and you will need to re-submit -when you’ve made the changes. This might sound a bit tough, but it is required -for us to maintain the quality of the codebase. - -### PHP Style - -All code must meet the [Style Guide](./contributing/styleguide.rst). -This makes certain that all submitted code is of the same format as the existing code and ensures that the codebase will be as readable as possible. - -### Documentation - -If you change anything that requires a change to documentation, then you will need to add to the documentation. New classes, methods, parameters, changing default values, etc. are all changes that require a change to documentation. Also, the [changelog](https://codeigniter4.github.io/CodeIgniter4/changelogs/index.html) must be updated for every change, and [PHPDoc](https://github.com/codeigniter4/CodeIgniter4/blob/develop/phpdoc.dist.xml) blocks must be maintained. - -### Compatibility - -CodeIgniter4 requires [PHP 7.2](https://php.net/releases/7_2_0.php). - -### Branching - -CodeIgniter4 uses the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model which requires all -Pull Requests to be sent to the "develop" branch; this is where the next planned version will be developed. -The "master" branch will always contain the latest stable version and is kept clean so a "hotfix" (e.g. an -emergency security patch) can be applied to the "master" branch to create a new version, without worrying -about other features holding it up. For this reason, all commits need to be made to the "develop" branch, -and any sent to the "master" branch will be closed automatically. If you have multiple changes to submit, -please place all changes into their own branch on your fork. - -**One thing at a time:** A pull request should only contain one change. That does not mean only one commit, -but one change - however many commits it took. The reason for this is that if you change X and Y, -but send a pull request for both at the same time, we might really want X but disagree with Y, -meaning we cannot merge the request. Using the Git-Flow branching model you can create new -branches for both of these features and send two requests. - -A reminder: **please use separate branches for each of your PRs** - it will make it easier for you to keep changes separate from -each other and from whatever else you are doing with your repository! - -### Signing - -You must [GPG-sign](./contributing/signing.rst) your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open-source project. This is *not* just a "signed-off-by" commit, but instead, a digitally signed one. - -## How-to Guide - -The best way to contribute is to fork the CodeIgniter4 repository, and "clone" that to your development area. That sounds like some jargon, but "forking" on GitHub means "making a copy of that repo to your account" and "cloning" means "copying that code to your environment so you can work on it". - -1. Set up Git ([Windows](https://git-scm.com/download/win), [Mac](https://git-scm.com/download/mac), & [Linux](https://git-scm.com/download/linux)). -2. Go to the [CodeIgniter4 repository](https://github.com/codeigniter4/CodeIgniter4). -3. [Fork](https://help.github.com/en/articles/fork-a-repo) it (to your Github account). -4. [Clone](https://help.github.com/en/articles/cloning-a-repository) your CodeIgniter repository: `git@github.com:\/CodeIgniter4.git` -5. Create a new [branch](https://help.github.com/en/articles/about-branches) in your project for each set of changes you want to make. -6. Fix existing bugs on the [Issue tracker](https://github.com/codeigniter4/CodeIgniter4/issues) after confirming that no one else is working on them. -7. [Commit](https://help.github.com/en/desktop/contributing-to-projects/committing-and-reviewing-changes-to-your-project) the changed files in your contribution branch. -8. [Push](https://help.github.com/en/articles/pushing-to-a-remote) your contribution branch to your fork. -9. Send a [pull request](http://help.github.com/send-pull-requests/). - -The codebase maintainers will now be alerted to the submission and someone from the team will respond. If your change fails to meet the guidelines, it will be rejected or feedback will be provided to help you improve it. - -Once the maintainer handling your pull request is satisfied with it they will approve the pull request and merge it into the "develop" branch; your patch will now be part of the next release! - -### Keeping your fork up-to-date - -Unlike systems like Subversion, Git can have multiple remotes. A remote is the name for the URL of a Git repository. By default, your fork will have a remote named "origin", which points to your fork, but you can add another remote named "codeigniter", which points to `git://github.com/codeigniter4/CodeIgniter4.git`. This is a read-only remote, but you can pull from this develop branch to update your own. - -If you are using the command-line, you can do the following to update your fork to the latest changes: - -1. `git remote add codeigniter git://github.com/codeigniter4/CodeIgniter4.git` -2. `git pull codeigniter develop` -3. `git push origin develop` - -Your fork is now up to date. This should be done regularly and, at the least, before you submit a pull request. diff --git a/public/index.php b/public/index.php index 5b9e912..3eaa592 100644 --- a/public/index.php +++ b/public/index.php @@ -13,7 +13,7 @@ // Location of the Paths config file. // This is the line that might need to be changed, depending on your folder structure. -$pathsPath = FCPATH . '../app/Config/Paths.php'; +$pathsPath = realpath(FCPATH . '../app/Config/Paths.php'); // ^^^ Change this if you move your application folder /* diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index f16b284..49248eb 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -39,8 +39,8 @@ namespace CodeIgniter\API; -use Config\Format; use CodeIgniter\HTTP\Response; +use Config\Format; /** * Response trait. @@ -56,7 +56,6 @@ */ trait ResponseTrait { - /** * Allows child classes to override the * status code that is used in their API. @@ -66,6 +65,7 @@ protected $codes = [ 'created' => 201, 'deleted' => 200, + 'updated' => 200, 'no_content' => 204, 'invalid_request' => 400, 'unsupported_response_type' => 400, @@ -93,6 +93,15 @@ 'not_implemented' => 501, ]; + /** + * How to format the response data. + * Either 'json' or 'xml'. If blank will be + * determine through content negotiation. + * + * @var string + */ + protected $format = 'json'; + //-------------------------------------------------------------------- /** @@ -190,6 +199,19 @@ return $this->respond($data, $this->codes['deleted'], $message); } + /** + * Used after a resource has been successfully updated. + * + * @param mixed $data Data. + * @param string $message Message. + * + * @return mixed + */ + public function respondUpdated($data = null, string $message = '') + { + return $this->respond($data, $this->codes['updated'], $message); + } + //-------------------------------------------------------------------- /** @@ -364,9 +386,14 @@ return $data; } - // Determine correct response type through content negotiation $config = new Format(); - $format = $this->request->negotiate('media', $config->supportedResponseFormats, false); + $format = "application/$this->format"; + + // Determine correct response type through content negotiation if not explicitly declared + if (empty($this->format) || ! in_array($this->format, ['json', 'xml'])) + { + $format = $this->request->negotiate('media', $config->supportedResponseFormats, false); + } $this->response->setContentType($format); @@ -387,4 +414,17 @@ return $this->formatter->format($data); } + /** + * Sets the format the response should be in. + * + * @param string $format + * + * @return $this + */ + public function setResponseFormat(string $format = null) + { + $this->format = strtolower($format); + + return $this; + } } diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 4663107..fa5c9a2 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -392,7 +392,7 @@ // be a path. // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_278 // Modified to allow backslash and colons for on Windows machines. - $filename = preg_replace('/[^a-zA-Z0-9\s\/\-\_\.\:\\\\]/', '', $filename); + $filename = preg_replace('/[^0-9\p{L}\s\/\-\_\.\:\\\\]/u', '', $filename); // Clean up our filename edges. $filename = trim($filename, '.-_'); diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 0b78e9c..5aa217e 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -353,7 +353,11 @@ // Remove the file extension (.php) $className = mb_substr($className, 0, -4); - return $className; + // Check if this exists + if (class_exists($className)) + { + return $className; + } } } diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index e60036c..476c08d 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -437,7 +437,7 @@ // Do it once or more, write with empty string gives us a new line for ($i = 0; $i < $num; $i ++) { - static::write(''); + static::write(); } } @@ -504,9 +504,7 @@ $string .= "\033[4m"; } - $string .= $text . "\033[0m"; - - return $string; + return $string . ($text . "\033[0m"); } //-------------------------------------------------------------------- @@ -702,24 +700,17 @@ */ protected static function parseCommandLine() { - $optionsFound = false; - // start picking segments off from #1, ignoring the invoking program for ($i = 1; $i < $_SERVER['argc']; $i ++) { // If there's no '-' at the beginning of the argument // then add it to our segments. - if (! $optionsFound && mb_strpos($_SERVER['argv'][$i], '-') === false) + if (mb_strpos($_SERVER['argv'][$i], '-') === false) { static::$segments[] = $_SERVER['argv'][$i]; continue; } - // We set $optionsFound here so that we know to - // skip the next argument since it's likely the - // value belonging to this option. - $optionsFound = true; - $arg = str_replace('-', '', $_SERVER['argv'][$i]); $value = null; @@ -731,10 +722,6 @@ } static::$options[$arg] = $value; - - // Reset $optionsFound so it can collect segments - // past any options. - $optionsFound = false; } } @@ -958,7 +945,7 @@ } } - fwrite(STDOUT, $table); + static::write($table); } //-------------------------------------------------------------------- diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php index b1e6103..2e1a8e1 100644 --- a/system/CLI/CommandRunner.php +++ b/system/CLI/CommandRunner.php @@ -40,8 +40,8 @@ namespace CodeIgniter\CLI; -use Config\Services; use CodeIgniter\Controller; +use Config\Services; /** * Command runner @@ -104,7 +104,7 @@ if (is_null($command)) { - $command = 'help'; + $command = 'list'; } return $this->runCommand($command, $params); diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index 737db09..86e7090 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -154,7 +154,7 @@ { $key = $this->prefix . $key; - return is_file($this->path . $key) ? unlink($this->path . $key) : false; + return is_file($this->path . $key) && unlink($this->path . $key); } //-------------------------------------------------------------------- diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 26a6803..c708a27 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -39,20 +39,20 @@ namespace CodeIgniter; use Closure; +use CodeIgniter\Debug\Timer; +use CodeIgniter\Events\Events; +use CodeIgniter\Exceptions\PageNotFoundException; +use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\DownloadResponse; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\Request; -use CodeIgniter\HTTP\ResponseInterface; -use Config\Services; -use Config\Cache; -use CodeIgniter\HTTP\URI; -use CodeIgniter\Debug\Timer; -use CodeIgniter\Events\Events; use CodeIgniter\HTTP\Response; -use CodeIgniter\HTTP\CLIRequest; +use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\HTTP\URI; use CodeIgniter\Router\Exceptions\RedirectException; use CodeIgniter\Router\RouteCollectionInterface; -use CodeIgniter\Exceptions\PageNotFoundException; +use Config\Cache; +use Config\Services; use Exception; /** @@ -66,7 +66,7 @@ /** * The current version of CodeIgniter Framework */ - const CI_VERSION = '4.0.2'; + const CI_VERSION = '4.0.3'; /** * App startup time. @@ -194,7 +194,9 @@ if (! CI_DEBUG) { + // @codeCoverageIgnoreStart \Kint::$enabled_mode = false; + // @codeCoverageIgnoreEnd } } @@ -496,9 +498,11 @@ } else { + // @codeCoverageIgnoreStart header('HTTP/1.1 503 Service Unavailable.', true, 503); echo 'The application environment is not set correctly.'; exit(1); // EXIT_ERROR + // @codeCoverageIgnoreEnd } } @@ -550,9 +554,11 @@ return; } - if (is_cli() && ! (ENVIRONMENT === 'testing')) + if (is_cli() && ENVIRONMENT !== 'testing') { + // @codeCoverageIgnoreStart $this->request = Services::clirequest($this->config); + // @codeCoverageIgnoreEnd } else { @@ -747,9 +753,7 @@ { $this->totalTime = $this->benchmark->getElapsedTime('total_execution'); - $output = str_replace('{elapsed_time}', $this->totalTime, $output); - - return $output; + return str_replace('{elapsed_time}', $this->totalTime, $output); } //-------------------------------------------------------------------- @@ -958,10 +962,12 @@ if (ENVIRONMENT !== 'testing') { + // @codeCoverageIgnoreStart if (ob_get_level() > 0) { ob_end_flush(); } + // @codeCoverageIgnoreEnd } else { @@ -972,7 +978,7 @@ } } - throw PageNotFoundException::forPageNotFound($e->getMessage()); + throw PageNotFoundException::forPageNotFound(ENVIRONMENT !== 'production' || is_cli() ? $e->getMessage() : ''); } //-------------------------------------------------------------------- @@ -1110,7 +1116,9 @@ */ protected function callExit($code) { + // @codeCoverageIgnoreStart exit($code); + // @codeCoverageIgnoreEnd } //-------------------------------------------------------------------- diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php index 19d87a4..3983d67 100644 --- a/system/Commands/Database/CreateMigration.php +++ b/system/Commands/Database/CreateMigration.php @@ -40,8 +40,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use Config\Autoload; -use Config\Migrations; +use Config\Services; /** * Creates a new migration file. @@ -124,15 +123,14 @@ if (! empty($ns)) { - // Get all namespaces from PSR4 paths. - $config = new Autoload(); - $namespaces = $config->psr4; + // Get all namespaces + $namespaces = Services::autoloader()->getNamespace(); foreach ($namespaces as $namespace => $path) { if ($namespace === $ns) { - $homepath = realpath($path); + $homepath = realpath(reset($path)); break; } } @@ -178,7 +176,7 @@ helper('filesystem'); if (! write_file($path, $template)) { - CLI::error(lang('Migrations.writeError')); + CLI::error(lang('Migrations.writeError', [$path])); return; } diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php index e0ec669..bf6b4f8 100644 --- a/system/Commands/Database/MigrateRefresh.php +++ b/system/Commands/Database/MigrateRefresh.php @@ -40,6 +40,7 @@ namespace CodeIgniter\Commands\Database; use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\CLI; /** * Does a rollback followed by a latest to refresh the current state @@ -95,6 +96,7 @@ '-n' => 'Set migration namespace', '-g' => 'Set database group', '-all' => 'Set latest for all namespace, will ignore (-n) option', + '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', ]; /** @@ -105,7 +107,20 @@ */ public function run(array $params = []) { - $this->call('migrate:rollback', ['-b' => 0]); + $params = ['-b' => 0]; + + if (ENVIRONMENT === 'production') + { + $force = $params['-f'] ?? CLI::getOption('f'); + if (is_null($force) && CLI::prompt(lang('Migrations.refreshConfirm'), ['y', 'n']) === 'n') + { + return; + } + + $params['-f'] = ''; + } + + $this->call('migrate:rollback', $params); $this->call('migrate'); } diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php index ba25989..b2dca1c 100644 --- a/system/Commands/Database/MigrateRollback.php +++ b/system/Commands/Database/MigrateRollback.php @@ -42,7 +42,6 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use Config\Services; -use Config\Autoload; /** * Runs all of the migrations in reverse order, until they have @@ -97,6 +96,7 @@ protected $options = [ '-b' => 'Specify a batch to roll back to; e.g. "3" to return to batch #3 or "-2" to roll back twice', '-g' => 'Set database group', + '-f' => 'Force command - this option allows you to bypass the confirmation question when running this command in a production environment', ]; /** @@ -107,6 +107,15 @@ */ public function run(array $params = []) { + if (ENVIRONMENT === 'production') + { + $force = $params['-f'] ?? CLI::getOption('f'); + if (is_null($force) && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') + { + return; + } + } + $runner = Services::migrations(); $group = $params['-g'] ?? CLI::getOption('g'); diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index 8e71436..94ad3ea 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -42,7 +42,6 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use Config\Services; -use Config\Autoload; /** * Displays a list of all migrations and whether they've been run or not. @@ -106,6 +105,10 @@ 'CodeIgniter', 'Config', 'Tests\Support', + 'Kint', + 'Laminas\ZendFrameworkBridge', + 'Laminas\Escaper', + 'Psr\Log', ]; /** @@ -124,9 +127,11 @@ $runner->setGroup($group); } - // Get all namespaces from PSR4 paths. - $config = new Autoload(); - $namespaces = $config->psr4; + // Get all namespaces + $namespaces = Services::autoloader()->getNamespace(); + + // Determines whether any migrations were found + $found = false; // Loop for all $namespaces foreach ($namespaces as $namespace => $path) @@ -138,16 +143,17 @@ $runner->setNamespace($namespace); $migrations = $runner->findMigrations(); - $history = $runner->getHistory(); - - CLI::write($namespace); if (empty($migrations)) { - CLI::error(lang('Migrations.noneFound')); continue; } + $found = true; + $history = $runner->getHistory(); + + CLI::write($namespace); + ksort($migrations); $max = 0; @@ -176,6 +182,11 @@ CLI::write(str_pad(' ' . $migration->name, $max + 6) . ($date ? $date : '---')); } } + + if (! $found) + { + CLI::error(lang('Migrations.noneFound')); + } } } diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php index bae8e73..c20fe32 100644 --- a/system/Commands/ListCommands.php +++ b/system/Commands/ListCommands.php @@ -188,9 +188,8 @@ $max += $extra + $indent; $item = str_repeat(' ', $indent) . $item; - $item = str_pad($item, $max); - return $item; + return str_pad($item, $max); } //-------------------------------------------------------------------- diff --git a/system/Commands/Server/Serve.php b/system/Commands/Server/Serve.php index 9fb68fe..9e65862 100644 --- a/system/Commands/Server/Serve.php +++ b/system/Commands/Server/Serve.php @@ -97,14 +97,14 @@ /** * The current port offset. * - * @var int + * @var integer */ protected $portOffset = 0; /** * The max number of ports to attempt to serve from * - * @var int + * @var integer */ protected $tries = 10; @@ -131,12 +131,14 @@ // Valid PHP Version? if (phpversion() < $this->minPHPVersion) { + // @codeCoverageIgnoreStart die('Your PHP version must be ' . $this->minPHPVersion . ' or higher to run CodeIgniter. Current version: ' . phpversion()); + // @codeCoverageIgnoreEnd } // Collect any user-supplied options and apply them. - $php = CLI::getOption('php') ?? PHP_BINARY; + $php = escapeshellarg(CLI::getOption('php') ?? PHP_BINARY); $host = CLI::getOption('host') ?? 'localhost'; $port = (int) (CLI::getOption('port') ?? '8080') + $this->portOffset; @@ -155,7 +157,8 @@ // to ensure our environment is set and it simulates basic mod_rewrite. passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot . ' ' . $rewrite, $status); - if ($status && $this->portOffset < $this->tries) { + if ($status && $this->portOffset < $this->tries) + { $this->portOffset += 1; $this->run($params); diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php index 15c7476..ca26bfa 100644 --- a/system/Commands/Sessions/CreateMigration.php +++ b/system/Commands/Sessions/CreateMigration.php @@ -126,7 +126,7 @@ helper('filesystem'); if (! write_file($path, $template)) { - CLI::error(lang('Migrations.migWriteError')); + CLI::error(lang('Migrations.writeError', [$path])); return; } diff --git a/system/Common.php b/system/Common.php index a14bc66..2e3fcd7 100644 --- a/system/Common.php +++ b/system/Common.php @@ -37,19 +37,20 @@ * @filesource */ -use Config\App; -use Config\Logger; -use Config\Database; -use Config\Services; -use CodeIgniter\HTTP\URI; -use Laminas\Escaper\Escaper; use CodeIgniter\Config\Config; -use CodeIgniter\Test\TestLogger; +use CodeIgniter\Database\ConnectionInterface; +use CodeIgniter\Files\Exceptions\FileNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Database\ConnectionInterface; -use CodeIgniter\Files\Exceptions\FileNotFoundException; +use CodeIgniter\HTTP\URI; +use CodeIgniter\Test\TestLogger; +use Config\App; +use Config\Database; +use Config\Logger; +use Config\Services; +use Config\View; +use Laminas\Escaper\Escaper; /** * Common Functions @@ -246,9 +247,11 @@ */ function dd(...$vars) { + // @codeCoverageIgnoreStart Kint::$aliases[] = 'dd'; Kint::dump(...$vars); exit; + // @codeCoverageIgnoreEnd } } @@ -319,7 +322,7 @@ { if (is_array($data)) { - foreach ($data as $key => &$value) + foreach ($data as &$value) { $value = esc($value, $context); } @@ -384,10 +387,7 @@ * @param RequestInterface $request * @param ResponseInterface $response * - * Not testable, as it will exit! - * - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException - * @codeCoverageIgnore + * @throws \CodeIgniter\HTTP\Exceptions\HTTPException */ function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) { @@ -400,25 +400,33 @@ $response = Services::response(null, true); } - if (is_cli() || $request->isSecure()) + if (ENVIRONMENT !== 'testing' && (is_cli() || $request->isSecure())) { + // @codeCoverageIgnoreStart return; + // @codeCoverageIgnoreEnd } - // If the session library is loaded, we should regenerate + // If the session status is active, we should regenerate // the session ID for safety sake. - if (class_exists('Session', false)) + if (ENVIRONMENT !== 'testing' && session_status() === PHP_SESSION_ACTIVE) { + // @codeCoverageIgnoreStart Services::session(null, true) ->regenerate(); + // @codeCoverageIgnoreEnd } - $uri = $request->uri; - $uri->setScheme('https'); + $baseURL = config(App::class)->baseURL; + + if (strpos($baseURL, 'http://') === 0) + { + $baseURL = (string) substr($baseURL, strlen('http://')); + } $uri = URI::createURIString( - $uri->getScheme(), $uri->getAuthority(true), $uri->getPath(), // Absolute URIs should use a "/" for an empty path - $uri->getQuery(), $uri->getFragment() + 'https', $baseURL, $request->uri->getPath(), // Absolute URIs should use a "/" for an empty path + $request->uri->getQuery(), $request->uri->getFragment() ); // Set an HSTS header @@ -426,7 +434,12 @@ $response->redirect($uri); $response->sendHeaders(); - exit(); + if (ENVIRONMENT !== 'testing') + { + // @codeCoverageIgnoreStart + exit(); + // @codeCoverageIgnoreEnd + } } } @@ -747,7 +760,9 @@ // Ensure the session is loaded if (session_status() === PHP_SESSION_NONE && ENVIRONMENT !== 'testing') { + // @codeCoverageIgnoreStart session(); + // @codeCoverageIgnoreEnd } $request = Services::request(); @@ -1060,8 +1075,9 @@ */ $renderer = Services::renderer(); - $saveData = true; - if (array_key_exists('saveData', $options) && $options['saveData'] === true) + $saveData = config(View::class)->saveData; + + if (array_key_exists('saveData', $options)) { $saveData = (bool) $options['saveData']; unset($options['saveData']); diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php index d53108f..6ebb122 100644 --- a/system/ComposerScripts.php +++ b/system/ComposerScripts.php @@ -90,7 +90,9 @@ if (empty($source)) { + // @codeCoverageIgnoreStart die('Cannot move file. Source path invalid.'); + // @codeCoverageIgnoreEnd } if (! is_file($source)) @@ -203,7 +205,9 @@ { if (! static::moveFile($source, $dest)) { + // @codeCoverageIgnoreStart die('Error moving: ' . $source); + // @codeCoverageIgnoreEnd } } } diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php index 9a0100f..ceec8ba 100644 --- a/system/Config/BaseConfig.php +++ b/system/Config/BaseConfig.php @@ -164,16 +164,12 @@ { case array_key_exists("{$shortPrefix}.{$property}", $_ENV): return $_ENV["{$shortPrefix}.{$property}"]; - break; case array_key_exists("{$shortPrefix}.{$property}", $_SERVER): return $_SERVER["{$shortPrefix}.{$property}"]; - break; case array_key_exists("{$prefix}.{$property}", $_ENV): return $_ENV["{$prefix}.{$property}"]; - break; case array_key_exists("{$prefix}.{$property}", $_SERVER): return $_SERVER["{$prefix}.{$property}"]; - break; default: $value = getenv($property); return $value === false ? null : $value; diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index 541334a..4220d77 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -128,7 +128,7 @@ if (strpos($line, '=') !== false) { list($name, $value) = $this->normaliseVariable($line); - $vars[$name] = $value; + $vars[$name] = $value; } } @@ -314,10 +314,8 @@ { case array_key_exists($name, $_ENV): return $_ENV[$name]; - break; case array_key_exists($name, $_SERVER): return $_SERVER[$name]; - break; default: $value = getenv($name); diff --git a/system/Config/Services.php b/system/Config/Services.php index 6f166a6..afb6512 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Config; use CodeIgniter\Cache\CacheFactory; +use CodeIgniter\Database\ConnectionInterface; +use CodeIgniter\Database\MigrationRunner; use CodeIgniter\Debug\Exceptions; use CodeIgniter\Debug\Iterator; use CodeIgniter\Debug\Timer; @@ -70,10 +72,8 @@ use CodeIgniter\Validation\Validation; use CodeIgniter\View\Cell; use CodeIgniter\View\Parser; -use Config\App; -use CodeIgniter\Database\ConnectionInterface; -use CodeIgniter\Database\MigrationRunner; use CodeIgniter\View\RendererInterface; +use Config\App; use Config\Cache; use Config\Images; use Config\Logger; @@ -207,8 +207,7 @@ { $config = new \Config\Email(); } - $email = new \CodeIgniter\Email\Email($config); - return $email; + return new \CodeIgniter\Email\Email($config); } /** @@ -232,8 +231,7 @@ } $encryption = new Encryption($config); - $encrypter = $encryption->initialize($config); - return $encrypter; + return $encryption->initialize($config); } //-------------------------------------------------------------------- @@ -502,7 +500,7 @@ if (empty($config)) { - $config = new \Config\Pager(); + $config = config('Pager'); } if (! $view instanceof RendererInterface) @@ -542,7 +540,7 @@ $viewPath = $paths->viewDirectory; } - return new Parser($config, $viewPath, static::locator(true), CI_DEBUG, static::logger(true)); + return new Parser($config, $viewPath, static::locator(), CI_DEBUG, static::logger()); } //-------------------------------------------------------------------- @@ -577,7 +575,7 @@ $viewPath = $paths->viewDirectory; } - return new \CodeIgniter\View\View($config, $viewPath, static::locator(true), CI_DEBUG, static::logger(true)); + return new \CodeIgniter\View\View($config, $viewPath, static::locator(), CI_DEBUG, static::logger()); } //-------------------------------------------------------------------- @@ -705,7 +703,7 @@ if (empty($routes)) { - $routes = static::routes(true); + $routes = static::routes(); } return new Router($routes, $request); @@ -759,7 +757,7 @@ $config = config(App::class); } - $logger = static::logger(true); + $logger = static::logger(); $driverName = $config->sessionDriver; $driver = new $driverName($config, static::request()->getIpAddress()); diff --git a/system/Controller.php b/system/Controller.php index 172104e..985d828 100644 --- a/system/Controller.php +++ b/system/Controller.php @@ -39,11 +39,11 @@ namespace CodeIgniter; -use Config\Services; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Validation\Validation; use CodeIgniter\Validation\Exceptions\ValidationException; +use CodeIgniter\Validation\Validation; +use Config\Services; use Psr\Log\LoggerInterface; /** @@ -117,7 +117,6 @@ $this->request = $request; $this->response = $response; $this->logger = $logger; - $this->logger->info('Controller "' . get_class($this) . '" loaded.'); if ($this->forceHTTPS > 0) { @@ -195,7 +194,7 @@ // If you replace the $rules array with the name of the group if (is_string($rules)) { - $validation = new \Config\Validation(); + $validation = config('Validation'); // If the rule wasn't found in the \Config\Validation, we // should throw an exception so the developer can find it. @@ -214,12 +213,10 @@ $rules = $validation->$rules; } - $success = $this->validator + return $this->validator ->withRequest($this->request) ->setRules($rules, $messages) ->run(); - - return $success; } //-------------------------------------------------------------------- diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 800247f..33e7004 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Database; +use Closure; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Exceptions\DataException; -use Closure; /** * Class BaseBuilder @@ -103,7 +103,7 @@ * * @var array */ - protected $QBGroupBy = []; + public $QBGroupBy = []; /** * QB HAVING data @@ -214,6 +214,14 @@ protected $binds = []; /** + * Collects the key count for named parameters + * in the Query object. + * + * @var array + */ + protected $bindsKeyCount = []; + + /** * Some databases, like SQLite, do not by default * allow limiting of delete clauses. * @@ -388,7 +396,7 @@ */ public function selectMax(string $select = '', string $alias = '') { - return $this->maxMinAvgSum($select, $alias, 'MAX'); + return $this->maxMinAvgSum($select, $alias); } //-------------------------------------------------------------------- @@ -973,20 +981,38 @@ * @used-by whereNotIn() * @used-by orWhereNotIn() * - * @param string $key The field to search - * @param array|Closure $values The values searched on, or anonymous function with subquery - * @param boolean $not If the statement would be IN or NOT IN - * @param string $type - * @param boolean $escape - * @param string $clause (Internal use only) + * @param string $key The field to search + * @param array|Closure $values The values searched on, or anonymous function with subquery + * @param boolean $not If the statement would be IN or NOT IN + * @param string $type + * @param boolean $escape + * @param string $clause (Internal use only) + * @throws InvalidArgumentException * * @return BaseBuilder */ protected function _whereIn(string $key = null, $values = null, bool $not = false, string $type = 'AND ', bool $escape = null, string $clause = 'QBWhere') { - if ($key === null || $values === null || (! is_array($values) && ! ($values instanceof Closure))) + if (empty($key) || ! is_string($key)) { + if (CI_DEBUG) + { + throw new \InvalidArgumentException(sprintf('%s() expects $key to be a non-empty string', debug_backtrace(0, 2)[1]['function'])); + } + // @codeCoverageIgnoreStart return $this; + // @codeCoverageIgnoreEnd + } + + if ($values === null || (! is_array($values) && ! ($values instanceof Closure))) + { + if (CI_DEBUG) + { + throw new \InvalidArgumentException(sprintf('%s() expects $values to be of type array or closure', debug_backtrace(0, 2)[1]['function'])); + } + // @codeCoverageIgnoreStart + return $this; + // @codeCoverageIgnoreEnd } is_bool($escape) || $escape = $this->db->protectIdentifiers; @@ -1301,7 +1327,7 @@ */ public function groupStart() { - return $this->groupStartPrepare('', 'AND ', 'QBWhere'); + return $this->groupStartPrepare(); } //-------------------------------------------------------------------- @@ -1313,7 +1339,7 @@ */ public function orGroupStart() { - return $this->groupStartPrepare('', 'OR ', 'QBWhere'); + return $this->groupStartPrepare('', 'OR '); } //-------------------------------------------------------------------- @@ -1325,7 +1351,7 @@ */ public function notGroupStart() { - return $this->groupStartPrepare('NOT ', 'AND ', 'QBWhere'); + return $this->groupStartPrepare('NOT '); } //-------------------------------------------------------------------- @@ -1337,7 +1363,7 @@ */ public function orNotGroupStart() { - return $this->groupStartPrepare('NOT ', 'OR ', 'QBWhere'); + return $this->groupStartPrepare('NOT ', 'OR '); } //-------------------------------------------------------------------- @@ -1349,7 +1375,7 @@ */ public function groupEnd() { - return $this->groupEndPrepare('QBWhere'); + return $this->groupEndPrepare(); } // -------------------------------------------------------------------- @@ -1682,9 +1708,9 @@ * * @return string */ - protected function _limit(string $sql): string + protected function _limit(string $sql, bool $offsetIgnore = false): string { - return $sql . ' LIMIT ' . ($this->QBOffset ? $this->QBOffset . ', ' : '') . $this->QBLimit; + return $sql . ' LIMIT ' . (false === $offsetIgnore && $this->QBOffset ? $this->QBOffset . ', ' : '') . $this->QBLimit; } //-------------------------------------------------------------------- @@ -1897,7 +1923,7 @@ $limit = $this->QBLimit; $this->QBLimit = false; - $sql = ($this->QBDistinct === true) + $sql = ($this->QBDistinct === true || ! empty($this->QBGroupBy)) ? $this->countString . $this->db->protectIdentifiers('numrows') . "\nFROM (\n" . $this->compileSelect() . "\n) CI_count_all_results" : @@ -2012,8 +2038,9 @@ { throw new DatabaseException('You must use the "set" method to update an entry.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } } else @@ -2024,8 +2051,9 @@ { throw new DatabaseException('insertBatch() called with no data'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $this->setInsertBatch($set, '', $escape); @@ -2234,8 +2262,9 @@ { throw new DatabaseException('You must use the "set" method to update an entry.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } return true; @@ -2284,7 +2313,9 @@ { throw new DatabaseException('You must use the "set" method to update an entry.'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $table = $this->QBFrom[0]; @@ -2445,7 +2476,7 @@ return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) . $this->compileWhereHaving('QBWhere') . $this->compileOrderBy() - . ($this->QBLimit ? $this->_limit(' ') : ''); + . ($this->QBLimit ? $this->_limit(' ', true) : ''); } //-------------------------------------------------------------------- @@ -2468,8 +2499,9 @@ { throw new DatabaseException('You must use the "set" method to update an entry.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } return true; @@ -2497,8 +2529,9 @@ { throw new DatabaseException('You must specify an index to match on for batch updates.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } if ($set === null) @@ -2509,7 +2542,9 @@ { throw new DatabaseException('You must use the "set" method to update an entry.'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } } else @@ -2520,7 +2555,9 @@ { throw new DatabaseException('updateBatch() called with no data'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $this->setUpdateBatch($set, $index); @@ -2573,7 +2610,7 @@ $ids = []; $final = []; - foreach ($values as $key => $val) + foreach ($values as $val) { $ids[] = $val[$index]; @@ -2622,7 +2659,7 @@ is_bool($escape) || $escape = $this->db->protectIdentifiers; - foreach ($key as $k => $v) + foreach ($key as $v) { $index_set = false; $clean = []; @@ -2769,8 +2806,9 @@ { throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.'); } - + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $sql = $this->_delete($table); @@ -2787,7 +2825,7 @@ throw new DatabaseException('SQLite3 does not allow LIMITs on DELETE queries.'); } - $sql = $this->_limit($sql); + $sql = $this->_limit($sql, true); } if ($reset_data) @@ -2849,8 +2887,7 @@ */ protected function _delete(string $table): string { - return 'DELETE ' . $this->compileIgnore('delete') . 'FROM ' . $table . $this->compileWhereHaving('QBWhere') - . ($this->QBLimit ? ' LIMIT ' . $this->QBLimit : ''); + return 'DELETE ' . $this->compileIgnore('delete') . 'FROM ' . $table . $this->compileWhereHaving('QBWhere'); } //-------------------------------------------------------------------- @@ -3304,6 +3341,12 @@ { $this->db->setAliasedTables([]); } + + // Reset QBFrom part + if (! empty($this->QBFrom)) + { + $this->from(array_shift($this->QBFrom), true); + } } //-------------------------------------------------------------------- @@ -3402,12 +3445,11 @@ return $key; } - $count = 0; - - while (array_key_exists($key . $count, $this->binds)) + if (! array_key_exists($key, $this->bindsKeyCount)) { - ++$count; + $this->bindsKeyCount[$key] = 0; } + $count = $this->bindsKeyCount[$key]++; $this->binds[$key . $count] = [ $value, diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index de9d510..844cf7b 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database; -use CodeIgniter\Events\Events; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Events\Events; /** * Class BaseConnection @@ -1002,7 +1002,7 @@ $this->initialize(); } - $this->pretend(true); + $this->pretend(); $sql = $func($this); @@ -1381,9 +1381,7 @@ { if (is_array($str)) { - $str = array_map([&$this, 'escape'], $str); - - return $str; + return array_map([&$this, 'escape'], $str); } else if (is_string($str) || ( is_object($str) && method_exists($str, '__toString'))) { diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php index b9d586a..5e6abe3 100644 --- a/system/Database/BaseResult.php +++ b/system/Database/BaseResult.php @@ -39,6 +39,8 @@ namespace CodeIgniter\Database; +use CodeIgniter\Entity; + /** * Class BaseResult */ @@ -187,12 +189,12 @@ return $this->customResultObject[$className]; } - is_null($this->rowData) || $this->dataSeek(0); + is_null($this->rowData) || $this->dataSeek(); $this->customResultObject[$className] = []; while ($row = $this->fetchObject($className)) { - if (method_exists($row, 'syncOriginal')) + if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) { $row->syncOriginal(); } @@ -237,7 +239,7 @@ return $this->resultArray; } - is_null($this->rowData) || $this->dataSeek(0); + is_null($this->rowData) || $this->dataSeek(); while ($row = $this->fetchAssoc()) { $this->resultArray[] = $row; @@ -280,10 +282,10 @@ return $this->resultObject; } - is_null($this->rowData) || $this->dataSeek(0); + is_null($this->rowData) || $this->dataSeek(); while ($row = $this->fetchObject()) { - if (method_exists($row, 'syncOriginal')) + if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) { $row->syncOriginal(); } @@ -312,7 +314,7 @@ if (! is_numeric($n)) { // We cache the row data for subsequent uses - is_array($this->rowData) || $this->rowData = $this->getRowArray(0); + is_array($this->rowData) || $this->rowData = $this->getRowArray(); // array_key_exists() instead of isset() to allow for NULL values if (empty($this->rowData) || ! array_key_exists($n, $this->rowData)) @@ -433,7 +435,7 @@ // We cache the row data for subsequent uses if (! is_array($this->rowData)) { - $this->rowData = $this->getRowArray(0); + $this->rowData = $this->getRowArray(); } if (is_array($key)) diff --git a/system/Database/BaseUtils.php b/system/Database/BaseUtils.php index a2ba2b0..e9a4dd6 100644 --- a/system/Database/BaseUtils.php +++ b/system/Database/BaseUtils.php @@ -356,7 +356,7 @@ 'tables' => [], 'ignore' => [], 'filename' => '', - 'format' => 'gzip', // gzip, zip, txt + 'format' => 'gzip', // gzip, txt 'add_drop' => true, 'add_insert' => true, 'newline' => "\n", @@ -383,15 +383,14 @@ } // Validate the format - if (! in_array($prefs['format'], ['gzip', 'zip', 'txt'], true)) + if (! in_array($prefs['format'], ['gzip', 'txt'], true)) { $prefs['format'] = 'txt'; } // Is the encoder supported? If not, we'll either issue an // error or use plain text depending on the debug settings - if (($prefs['format'] === 'gzip' && ! function_exists('gzencode')) - || ( $prefs['format'] === 'zip' && ! function_exists('gzcompress'))) + if ($prefs['format'] === 'gzip' && ! function_exists('gzencode')) { if ($this->db->DBDebug) { @@ -401,46 +400,12 @@ $prefs['format'] = 'txt'; } - // Was a Zip file requested? - if ($prefs['format'] === 'zip') - { - // Set the filename if not provided (only needed with Zip files) - if ($prefs['filename'] === '') - { - $prefs['filename'] = (count($prefs['tables']) === 1 ? $prefs['tables'] : $this->db->database) - . date('Y-m-d_H-i', time()) . '.sql'; - } - else - { - // If they included the .zip file extension we'll remove it - if (preg_match('|.+?\.zip$|', $prefs['filename'])) - { - $prefs['filename'] = str_replace('.zip', '', $prefs['filename']); - } - - // Tack on the ".sql" file extension if needed - if (! preg_match('|.+?\.sql$|', $prefs['filename'])) - { - $prefs['filename'] .= '.sql'; - } - } - - // Load the Zip class and output it - // $CI =& get_instance(); - // $CI->load->library('zip'); - // $CI->zip->add_data($prefs['filename'], $this->_backup($prefs)); - // return $CI->zip->get_zip(); - } - elseif ($prefs['format'] === 'txt') // Was a text file requested? + if ($prefs['format'] === 'txt') // Was a text file requested? { return $this->_backup($prefs); } - elseif ($prefs['format'] === 'gzip') // Was a Gzip file requested? - { - return gzencode($this->_backup($prefs)); - } - return; + return gzencode($this->_backup($prefs)); } //-------------------------------------------------------------------- diff --git a/system/Database/Database.php b/system/Database/Database.php index a8d26b1..adb0063 100644 --- a/system/Database/Database.php +++ b/system/Database/Database.php @@ -109,9 +109,7 @@ $db->initialize(); } - $class = new $className($db); - - return $class; + return new $className($db); } //-------------------------------------------------------------------- @@ -133,9 +131,7 @@ $db->initialize(); } - $class = new $className($db); - - return $class; + return new $className($db); } //-------------------------------------------------------------------- diff --git a/system/Database/Forge.php b/system/Database/Forge.php index b40dc5e..e38d505 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -110,14 +110,14 @@ * * @var string */ - protected $createDatabaseIfStr = null; + protected $createDatabaseIfStr; /** * CHECK DATABASE EXIST statement * * @var string */ - protected $checkDatabaseExistStr = null; + protected $checkDatabaseExistStr; /** * DROP DATABASE statement @@ -717,9 +717,7 @@ } } - $sql = $sql . ' ' . $this->db->escapeIdentifiers($table); - - return $sql; + return $sql . ' ' . $this->db->escapeIdentifiers($table); } //-------------------------------------------------------------------- diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 6d4f0e2..55350c8 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -38,10 +38,10 @@ namespace CodeIgniter\Database; -use Config\Services; use CodeIgniter\CLI\CLI; use CodeIgniter\Config\BaseConfig; use CodeIgniter\Exceptions\ConfigException; +use Config\Services; /** * Class MigrationRunner @@ -1027,6 +1027,15 @@ // Determine DBGroup to use $group = $instance->getDBGroup() ?? config('Database')->defaultGroup; + // Skip tests db group when not running in testing environment + if (ENVIRONMENT !== 'testing' && $group === 'tests' && $this->groupFilter !== 'tests') + { + // @codeCoverageIgnoreStart + $this->groupSkip = true; + return true; + // @codeCoverageIgnoreEnd + } + // Skip migration if group filtering was set if ($direction === 'up' && ! is_null($this->groupFilter) && $this->groupFilter !== $group) { diff --git a/system/Database/ModelFactory.php b/system/Database/ModelFactory.php index 6b7d514..b2b4362 100644 --- a/system/Database/ModelFactory.php +++ b/system/Database/ModelFactory.php @@ -13,14 +13,6 @@ static private $instances = []; /** - * The Database connection to use, - * if other than default. - * - * @var ConnectionInterface - */ - static private $connection = null; - - /** * Create new configuration instances or return * a shared instance * diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 26b213c..9bda5b0 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -326,8 +326,19 @@ $res->free(); } } - - return $this->connID->query($this->prepQuery($sql)); + try + { + return $this->connID->query($this->prepQuery($sql)); + } + catch (\mysqli_sql_exception $e) + { + log_message('error', $e); + if ($this->DBDebug) + { + throw $e; + } + } + return false; } //-------------------------------------------------------------------- @@ -424,8 +435,6 @@ '\\' . '_', ], $str ); - - return $str; } //-------------------------------------------------------------------- diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 35d87b1..3ef4104 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database\MySQLi; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for MySQLi @@ -116,9 +116,7 @@ // Bind it $this->statement->bind_param($bindTypes, ...$data); - $success = $this->statement->execute(); - - return $success; + return $this->statement->execute(); } //-------------------------------------------------------------------- diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index bf20a3d..fa45c45 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -40,7 +40,6 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Exceptions\DatabaseException; -use http\Encoding\Stream\Inflate; /** * Builder for Postgre @@ -196,7 +195,9 @@ { throw new DatabaseException('You must use the "set" method to update an entry.'); } + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } $table = $this->QBFrom[0]; @@ -269,7 +270,7 @@ * * @return string */ - protected function _limit(string $sql): string + protected function _limit(string $sql, bool $offsetIgnore = false): string { return $sql . ' LIMIT ' . $this->QBLimit . ($this->QBOffset ? " OFFSET {$this->QBOffset}" : ''); } @@ -316,7 +317,7 @@ protected function _updateBatch(string $table, array $values, string $index): string { $ids = []; - foreach ($values as $key => $val) + foreach ($values as $val) { $ids[] = $val[$index]; diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index b239232..c3d6db6 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -187,11 +187,23 @@ * * @param string $sql * - * @return resource + * @return mixed */ public function execute(string $sql) { - return pg_query($this->connID, $sql); + try + { + return pg_query($this->connID, $sql); + } + catch (\ErrorException $e) + { + log_message('error', $e); + if ($this->DBDebug) + { + throw $e; + } + } + return false; } //-------------------------------------------------------------------- diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php index 7b3bff3..27e3da2 100644 --- a/system/Database/Postgre/PreparedQuery.php +++ b/system/Database/Postgre/PreparedQuery.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database\Postgre; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for Postgre @@ -148,12 +148,10 @@ // Track our current value $count = 0; - $sql = preg_replace_callback('/\?/', function ($matches) use (&$count) { + return preg_replace_callback('/\?/', function ($matches) use (&$count) { $count ++; return "\${$count}"; }, $sql); - - return $sql; } //-------------------------------------------------------------------- diff --git a/system/Database/Query.php b/system/Database/Query.php index 301185b..dae2eeb 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -368,7 +368,7 @@ { $sql = $this->finalQueryString; - $hasNamedBinds = strpos($sql, ':') !== false; + $hasNamedBinds = strpos($sql, ':') !== false && strpos($sql, ':=') === false; if (empty($this->binds) || empty($this->bindMarker) || (strpos($sql, $this->bindMarker) === false && @@ -440,9 +440,7 @@ $replacers[":{$placeholder}:"] = $escapedValue; } - $sql = strtr($sql, $replacers); - - return $sql; + return strtr($sql, $replacers); } //-------------------------------------------------------------------- diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index ae83cfc..d3ac1cb 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -165,9 +165,21 @@ */ public function execute(string $sql) { - return $this->isWriteType($sql) - ? $this->connID->exec($sql) - : $this->connID->query($sql); + try + { + return $this->isWriteType($sql) + ? $this->connID->exec($sql) + : $this->connID->query($sql); + } + catch (\ErrorException $e) + { + log_message('error', $e); + if ($this->DBDebug) + { + throw $e; + } + } + return false; } //-------------------------------------------------------------------- @@ -323,8 +335,8 @@ $retVal[$i]->type = $query[$i]->type; $retVal[$i]->max_length = null; $retVal[$i]->default = $query[$i]->dflt_value; - $retVal[$i]->primary_key = isset($query[$i]->pk) ? (bool)$query[$i]->pk : false; - $retVal[$i]->nullable = isset($query[$i]->notnull) ? ! (bool)$query[$i]->notnull : false; + $retVal[$i]->primary_key = isset($query[$i]->pk) && (bool)$query[$i]->pk; + $retVal[$i]->nullable = isset($query[$i]->notnull) && ! (bool)$query[$i]->notnull; } return $retVal; diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php index 338c6e4..bb6f482 100644 --- a/system/Database/SQLite3/Forge.php +++ b/system/Database/SQLite3/Forge.php @@ -167,7 +167,6 @@ ->run(); return ''; - break; case 'CHANGE': $sqlTable = new Table($this->db, $this); @@ -176,7 +175,6 @@ ->run(); return null; - break; default: return parent::_alterTable($alter_type, $table, $field); } diff --git a/system/Database/SQLite3/PreparedQuery.php b/system/Database/SQLite3/PreparedQuery.php index fbd6fea..22202c3 100644 --- a/system/Database/SQLite3/PreparedQuery.php +++ b/system/Database/SQLite3/PreparedQuery.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Database\SQLite3; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for SQLite3 diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php index 682b7ce..97eb414 100644 --- a/system/Database/Seeder.php +++ b/system/Database/Seeder.php @@ -122,6 +122,8 @@ } $this->db = & $db; + + $this->forge = \Config\Database::forge($this->DBGroup); } //-------------------------------------------------------------------- diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index 7cc3043..7f7ca2c 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -142,6 +142,7 @@ */ public function exceptionHandler(Throwable $exception) { + // @codeCoverageIgnoreStart $codes = $this->determineCodes($exception); $statusCode = $codes[0]; $exitCode = $codes[1]; @@ -171,6 +172,7 @@ $this->render($exception, $statusCode); exit($exitCode); + // @codeCoverageIgnoreEnd } //-------------------------------------------------------------------- @@ -186,11 +188,10 @@ * @param string $message * @param string|null $file * @param integer|null $line - * @param null $context * * @throws \ErrorException */ - public function errorHandler(int $severity, string $message, string $file = null, int $line = null, $context = null) + public function errorHandler(int $severity, string $message, string $file = null, int $line = null) { if (! (error_reporting() & $severity)) { @@ -371,17 +372,20 @@ */ public static function cleanPath(string $file): string { - if (strpos($file, APPPATH) === 0) + switch (true) { - $file = 'APPPATH/' . substr($file, strlen(APPPATH)); - } - elseif (strpos($file, SYSTEMPATH) === 0) - { - $file = 'SYSTEMPATH/' . substr($file, strlen(SYSTEMPATH)); - } - elseif (strpos($file, FCPATH) === 0) - { - $file = 'FCPATH/' . substr($file, strlen(FCPATH)); + case strpos($file, APPPATH) === 0: + $file = 'APPPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(APPPATH)); + break; + case strpos($file, SYSTEMPATH) === 0: + $file = 'SYSTEMPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(SYSTEMPATH)); + break; + case strpos($file, FCPATH) === 0: + $file = 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH)); + break; + case defined('VENDORPATH') && strpos($file, VENDORPATH) === 0; + $file = 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(VENDORPATH)); + break; } return $file; diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index 02e50fc..ff2be35 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -118,7 +118,7 @@ $data['startTime'] = $startTime; $data['totalTime'] = $totalTime * 1000; $data['totalMemory'] = number_format((memory_get_peak_usage()) / 1024 / 1024, 3); - $data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7, 5); + $data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7); $data['segmentCount'] = (int) ceil($data['totalTime'] / $data['segmentDuration']); $data['CI_VERSION'] = \CodeIgniter\CodeIgniter::CI_VERSION; $data['collectors'] = []; @@ -167,7 +167,7 @@ $data['vars']['post'][esc($name)] = is_array($value) ? '
' . esc(print_r($value, true)) . '
' : esc($value); } - foreach ($request->getHeaders() as $header => $value) + foreach ($request->getHeaders() as $value) { if (empty($value)) { @@ -415,6 +415,7 @@ return; } + // @codeCoverageIgnoreStart $request = Services::request(); // If the request contains '?debugbar then we're @@ -459,6 +460,7 @@ http_response_code(404); exit; // Exit here is needed to avoid load the index page } + // @codeCoverageIgnoreEnd } /** diff --git a/system/Debug/Toolbar/Collectors/Config.php b/system/Debug/Toolbar/Collectors/Config.php index 1ae809c..82ef634 100644 --- a/system/Debug/Toolbar/Collectors/Config.php +++ b/system/Debug/Toolbar/Collectors/Config.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; +use CodeIgniter\CodeIgniter; use Config\App; use Config\Services; -use CodeIgniter\CodeIgniter; /** * Debug toolbar configuration diff --git a/system/Debug/Toolbar/Collectors/Events.php b/system/Debug/Toolbar/Collectors/Events.php index 285d87d..41c9f00 100644 --- a/system/Debug/Toolbar/Collectors/Events.php +++ b/system/Debug/Toolbar/Collectors/Events.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; -use Config\Services; use CodeIgniter\View\RendererInterface; +use Config\Services; /** * Views collector @@ -111,7 +111,7 @@ $rows = $this->viewer->getPerformanceData(); - foreach ($rows as $name => $info) + foreach ($rows as $info) { $data[] = [ 'name' => 'View: ' . $info['view'], diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php index 8bcec56..bf0c110 100644 --- a/system/Debug/Toolbar/Collectors/Views.php +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -39,8 +39,8 @@ namespace CodeIgniter\Debug\Toolbar\Collectors; -use Config\Services; use CodeIgniter\View\RendererInterface; +use Config\Services; /** * Views collector @@ -126,7 +126,7 @@ $rows = $this->viewer->getPerformanceData(); - foreach ($rows as $name => $info) + foreach ($rows as $info) { $data[] = [ 'name' => 'View: ' . $info['view'], diff --git a/system/Debug/Toolbar/Views/toolbarloader.js.php b/system/Debug/Toolbar/Views/toolbarloader.js.php index 4ecdeeb..af69338 100644 --- a/system/Debug/Toolbar/Views/toolbarloader.js.php +++ b/system/Debug/Toolbar/Views/toolbarloader.js.php @@ -75,9 +75,11 @@ var debugbarTime = realXHR.getResponseHeader('Debugbar-Time'); if (debugbarTime) { var h2 = document.querySelector('#ci-history > h2'); - h2.innerHTML = 'History You have new debug data. '; - var badge = document.querySelector('a[data-tab="ci-history"] > span > .badge'); - badge.className += ' active'; + if(h2) { + h2.innerHTML = 'History You have new debug data. '; + var badge = document.querySelector('a[data-tab="ci-history"] > span > .badge'); + badge.className += ' active'; + } } } }, false); diff --git a/system/Encryption/Encryption.php b/system/Encryption/Encryption.php index 1f047f6..4c2d333 100644 --- a/system/Encryption/Encryption.php +++ b/system/Encryption/Encryption.php @@ -38,10 +38,8 @@ namespace CodeIgniter\Encryption; -use Config\Encryption as EncryptionConfig; -use CodeIgniter\Encryption\Exceptions\EncryptionException; use CodeIgniter\Config\BaseConfig; -use Config\Services; +use CodeIgniter\Encryption\Exceptions\EncryptionException; /** * CodeIgniter Encryption Manager diff --git a/system/Encryption/Handlers/OpenSSLHandler.php b/system/Encryption/Handlers/OpenSSLHandler.php index 72b6023..50622d1 100644 --- a/system/Encryption/Handlers/OpenSSLHandler.php +++ b/system/Encryption/Handlers/OpenSSLHandler.php @@ -64,7 +64,7 @@ * * @param BaseConfig $config * - * @throws \CodeIgniter\Encryption\EncryptionException + * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException */ public function __construct(BaseConfig $config = null) { @@ -77,7 +77,7 @@ * @param string $data Input data * @param array $params Over-ridden parameters, specifically the key * @return string - * @throws \CodeIgniter\Encryption\EncryptionException + * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException */ public function encrypt($data, $params = null) { @@ -114,9 +114,8 @@ $result = $iv . $data; $hmacKey = \hash_hmac($this->digest, $result, $secret, true); - $result = $hmacKey . $result; - return $result; + return $hmacKey . $result; } // -------------------------------------------------------------------- @@ -127,7 +126,7 @@ * @param string $data Encrypted data * @param array $params Over-ridden parameters, specifically the key * @return string - * @throws \CodeIgniter\Encryption\EncryptionException + * @throws \CodeIgniter\Encryption\Exceptions\EncryptionException */ public function decrypt($data, $params = null) { @@ -145,7 +144,7 @@ } if (empty($this->key)) { - throw EncryptionException::forStarterKeyNeeded(); + throw EncryptionException::forNeedsStarterKey(); } // derive a secret key diff --git a/system/Entity.php b/system/Entity.php index 94153d0..1ccd6d2 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -39,14 +39,13 @@ namespace CodeIgniter; -use CodeIgniter\Exceptions\EntityException; -use CodeIgniter\I18n\Time; use CodeIgniter\Exceptions\CastException; +use CodeIgniter\I18n\Time; /** * Entity encapsulation, for use with CodeIgniter\Model */ -class Entity +class Entity implements \JsonSerializable { /** * Maps names used in sets and gets against unique @@ -165,7 +164,7 @@ // allow our magic methods a chance to do their thing. foreach ($this->attributes as $key => $value) { - if (substr($key, 0, 1) === '_') + if (strpos($key, '_') === 0) { continue; } @@ -353,7 +352,7 @@ if (array_key_exists($key, $this->casts)) { - $isNullable = substr($this->casts[$key], 0, 1) === '?'; + $isNullable = strpos($this->casts[$key], '?') === 0; $castTo = $isNullable ? substr($this->casts[$key], 1) : $this->casts[$key]; } @@ -530,7 +529,7 @@ protected function castAs($value, string $type) { - if (substr($type, 0, 1) === '?') + if (strpos($type, '?') === 0) { if ($value === null) { @@ -570,17 +569,15 @@ $value = (array)$value; break; case 'json': - $value = $this->castAsJson($value, false); + $value = $this->castAsJson($value); break; case 'json-array': $value = $this->castAsJson($value, true); break; case 'datetime': - return new \DateTime($value); - break; + return $this->mutateDate($value); case 'timestamp': return strtotime($value); - break; } return $value; @@ -614,4 +611,15 @@ } return $tmp; } + + /** + * Support for json_encode() + * + * @return array|mixed + * @throws \Exception + */ + public function jsonSerialize() + { + return $this->toArray(); + } } diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php index a5bb065..ddcb717 100644 --- a/system/Exceptions/CastException.php +++ b/system/Exceptions/CastException.php @@ -19,22 +19,17 @@ switch($error) { case JSON_ERROR_DEPTH: - throw new static(lang('Cast.jsonErrorDepth')); - break; + return new static(lang('Cast.jsonErrorDepth')); case JSON_ERROR_STATE_MISMATCH: - throw new static(lang('Cast.jsonErrorStateMismatch')); - break; + return new static(lang('Cast.jsonErrorStateMismatch')); case JSON_ERROR_CTRL_CHAR: - throw new static(lang('Cast.jsonErrorCtrlChar')); - break; + return new static(lang('Cast.jsonErrorCtrlChar')); case JSON_ERROR_SYNTAX: - throw new static(lang('Cast.jsonErrorSyntax')); - break; + return new static(lang('Cast.jsonErrorSyntax')); case JSON_ERROR_UTF8: - throw new static(lang('Cast.jsonErrorUtf8')); - break; + return new static(lang('Cast.jsonErrorUtf8')); default: - throw new static(lang('Cast.jsonErrorUnknown')); + return new static(lang('Cast.jsonErrorUnknown')); } } diff --git a/system/Exceptions/ConfigException.php b/system/Exceptions/ConfigException.php index dc36377..964c521 100644 --- a/system/Exceptions/ConfigException.php +++ b/system/Exceptions/ConfigException.php @@ -16,6 +16,6 @@ public static function forDisabledMigrations() { - throw new static(lang('Migrations.disabled')); + return new static(lang('Migrations.disabled')); } } diff --git a/system/Files/File.php b/system/Files/File.php index 8b5fc2e..1f59795 100644 --- a/system/Files/File.php +++ b/system/Files/File.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Files; -use SplFileInfo; use CodeIgniter\Files\Exceptions\FileException; use CodeIgniter\Files\Exceptions\FileNotFoundException; +use SplFileInfo; /** * Wrapper for PHP's built-in SplFileInfo, with goodies. diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 853e135..451a2da 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -39,9 +39,9 @@ namespace CodeIgniter\Filters; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Filters\Exceptions\FilterException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Filters\Exceptions\FilterException; /** * Filters @@ -150,47 +150,59 @@ throw FilterException::forNoAlias($alias); } - $class = new $this->config->aliases[$alias](); - - if (! $class instanceof FilterInterface) + if (is_array($this->config->aliases[$alias])) { - throw FilterException::forIncorrectInterface(get_class($class)); + $classNames = $this->config->aliases[$alias]; + } + else + { + $classNames = [$this->config->aliases[$alias]]; } - if ($position === 'before') + foreach ($classNames as $className) { - $result = $class->before($this->request, $this->arguments[$alias] ?? null); + $class = new $className(); - if ($result instanceof RequestInterface) + if (! $class instanceof FilterInterface) { - $this->request = $result; - continue; + throw FilterException::forIncorrectInterface(get_class($class)); } - // If the response object was sent back, - // then send it and quit. - if ($result instanceof ResponseInterface) + if ($position === 'before') { - // short circuit - bypass any other filters + $result = $class->before($this->request, $this->arguments[$alias] ?? null); + + if ($result instanceof RequestInterface) + { + $this->request = $result; + continue; + } + + // If the response object was sent back, + // then send it and quit. + if ($result instanceof ResponseInterface) + { + // short circuit - bypass any other filters + return $result; + } + + // Ignore an empty result + if (empty($result)) + { + continue; + } + return $result; } - - // Ignore an empty result - if (empty($result)) + elseif ($position === 'after') { - continue; - } + $result = $class->after($this->request, $this->response); - return $result; - } - elseif ($position === 'after') - { - $result = $class->after($this->request, $this->response); - - if ($result instanceof ResponseInterface) - { - $this->response = $result; - continue; + if ($result instanceof ResponseInterface) + { + $this->response = $result; + continue; + } } } } diff --git a/system/Filters/Honeypot.php b/system/Filters/Honeypot.php index aea08c3..a5807e4 100644 --- a/system/Filters/Honeypot.php +++ b/system/Filters/Honeypot.php @@ -38,10 +38,10 @@ namespace CodeIgniter\Filters; +use CodeIgniter\Honeypot\Exceptions\HoneypotException; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Config\Services; -use CodeIgniter\Honeypot\Exceptions\HoneypotException; /** * Honeypot filter diff --git a/system/Format/JSONFormatter.php b/system/Format/JSONFormatter.php index 50caf39..823f3ca 100644 --- a/system/Format/JSONFormatter.php +++ b/system/Format/JSONFormatter.php @@ -56,13 +56,13 @@ */ public function format($data) { - $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; + $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; $options = ENVIRONMENT === 'production' ? $options : $options | JSON_PRETTY_PRINT; $result = json_encode($data, $options, 512); - if (json_last_error() !== JSON_ERROR_NONE) + if ( ! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION])) { throw FormatException::forInvalidJSON(json_last_error_msg()); } diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index c5506ac..d1a8e94 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -536,9 +536,7 @@ // Have content? if ($size === null || $size > 0) { - $curl_options = $this->applyBody($curl_options); - - return $curl_options; + return $this->applyBody($curl_options); } if ($method === 'PUT' || $method === 'POST') diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 713312f..910f700 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -136,7 +136,7 @@ * * @var string */ - protected $reportURI = null; + protected $reportURI; /** * Used for security enforcement diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 0b363cd..b7c04df 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -427,7 +427,7 @@ // Use $_POST directly here, since filter_has_var only // checks the initial POST data, not anything that might // have been added since. - return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet($index, $filter, $flags); + return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : (isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost()); } //-------------------------------------------------------------------- @@ -446,7 +446,7 @@ // Use $_GET directly here, since filter_has_var only // checks the initial GET data, not anything that might // have been added since. - return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost($index, $filter, $flags); + return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : (isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet()); } //-------------------------------------------------------------------- @@ -616,7 +616,6 @@ $this->uri->setScheme(parse_url($baseURL, PHP_URL_SCHEME)); $this->uri->setHost(parse_url($baseURL, PHP_URL_HOST)); $this->uri->setPort(parse_url($baseURL, PHP_URL_PORT)); - $this->uri->resolveRelativeURI(parse_url($baseURL, PHP_URL_PATH)); // Ensure we have any query vars $this->uri->setQuery($_SERVER['QUERY_STRING'] ?? ''); @@ -722,7 +721,7 @@ $query = $parts['query'] ?? ''; $uri = $parts['path'] ?? ''; - if (isset($_SERVER['SCRIPT_NAME'][0])) + if (isset($_SERVER['SCRIPT_NAME'][0]) && pathinfo($_SERVER['SCRIPT_NAME'], PATHINFO_EXTENSION) === 'php') { // strip the script name from the beginning of the URI if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) diff --git a/system/HTTP/Message.php b/system/HTTP/Message.php index 1afb1d5..6d7a5cc 100644 --- a/system/HTTP/Message.php +++ b/system/HTTP/Message.php @@ -316,7 +316,9 @@ { $orig_name = $this->getHeaderName($name); - $this->headers[$orig_name]->appendValue($value); + array_key_exists($orig_name, $this->headers) + ? $this->headers[$orig_name]->appendValue($value) + : $this->setHeader($name, $value); return $this; } diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 6ac2d06..74aa470 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -179,7 +179,7 @@ */ public function language(array $supported): string { - return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language')); + return $this->getBestMatch($supported, $this->request->getHeaderLine('accept-language'), false, false, true); } //-------------------------------------------------------------------- @@ -198,10 +198,11 @@ * @param boolean $enforceTypes If TRUE, will compare media types and sub-types. * @param boolean $strictMatch If TRUE, will return empty string on no match. * If FALSE, will return the first supported element. + * @param boolean $matchLocales If TRUE, will match locale sub-types to a broad type (fr-FR = fr) * * @return string Best match */ - protected function getBestMatch(array $supported, string $header = null, bool $enforceTypes = false, bool $strictMatch = false): string + protected function getBestMatch(array $supported, string $header = null, bool $enforceTypes = false, bool $strictMatch = false, bool $matchLocales = false): string { if (empty($supported)) { @@ -232,7 +233,7 @@ // If an acceptable value is supported, return it foreach ($supported as $available) { - if ($this->match($accept, $available, $enforceTypes)) + if ($this->match($accept, $available, $enforceTypes, $matchLocales)) { return $available; } @@ -337,12 +338,14 @@ /** * Match-maker * - * @param array $acceptable - * @param string $supported - * @param boolean $enforceTypes + * @param array $acceptable + * @param string $supported + * @param boolean $enforceTypes + * @param boolean $matchLocales + * * @return boolean */ - protected function match(array $acceptable, string $supported, bool $enforceTypes = false): bool + protected function match(array $acceptable, string $supported, bool $enforceTypes = false, $matchLocales = false): bool { $supported = $this->parseHeader($supported); if (is_array($supported) && count($supported) === 1) @@ -363,6 +366,12 @@ return $this->matchTypes($acceptable, $supported); } + // Do we need to match locales against broader locales? + if ($matchLocales) + { + return $this->matchLocales($acceptable, $supported); + } + return false; } @@ -409,8 +418,14 @@ */ public function matchTypes(array $acceptable, array $supported): bool { - list($aType, $aSubType) = explode('/', $acceptable['value']); - list($sType, $sSubType) = explode('/', $supported['value']); + [ + $aType, + $aSubType, + ] = explode('/', $acceptable['value']); + [ + $sType, + $sSubType, + ] = explode('/', $supported['value']); // If the types don't match, we're done. if ($aType !== $sType) @@ -429,4 +444,25 @@ } //-------------------------------------------------------------------- + + /** + * Will match locales against their broader pairs, so that fr-FR would + * match a supported localed of fr + * + * @param array $acceptable + * @param array $supported + * + * @return boolean + */ + public function matchLocales(array $acceptable, array $supported): bool + { + $aBroad = mb_strpos($acceptable['value'], '-') > 0 + ? mb_substr($acceptable['value'], 0, mb_strpos($acceptable['value'], '-')) + : $acceptable['value']; + $sBroad = mb_strpos($supported['value'], '-') > 0 + ? mb_substr($supported['value'], 0, mb_strpos($supported['value'], '-')) + : $supported['value']; + + return strtolower($aBroad) === strtolower($sBroad); + } } diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index a6d3c5e..0d8320b 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -40,10 +40,10 @@ namespace CodeIgniter\HTTP; -use Config\App; -use Config\Format; use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\Pager\PagerInterface; +use Config\App; +use Config\Format; /** * Representation of an outgoing, getServer-side response. @@ -986,6 +986,7 @@ $name = $prefix . $name; + $cookieHasFlag = false; foreach ($this->cookies as &$cookie) { if ($cookie['name'] === $name) @@ -1000,11 +1001,16 @@ } $cookie['value'] = ''; $cookie['expires'] = ''; - + $cookieHasFlag = true; break; } } + if (! $cookieHasFlag) + { + $this->setCookie($name, '', '', $domain, $path, $prefix); + } + return $this; } diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 001b0a9..09aa3cd 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -566,7 +566,7 @@ $uri .= $authority; } - if ($path) + if ($path !== '') { $uri .= substr($uri, -1, 1) !== '/' ? '/' . ltrim($path, '/') : $path; } @@ -597,7 +597,12 @@ { $parts = parse_url($str); - if (empty($parts['host']) && ! empty($parts['path'])) + if (! isset($parts['path'])) + { + $parts['path'] = $this->getPath(); + } + + if (empty($parts['host']) && $parts['path'] !== '') { $parts['host'] = $parts['path']; unset($parts['path']); @@ -705,7 +710,9 @@ { $this->path = $this->filterPath($path); - $this->segments = explode('/', $this->path); + $tempPath = trim($this->path, '/'); + + $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); return $this; } @@ -721,7 +728,9 @@ { $this->path = $this->filterPath(implode('/', $this->segments)); - $this->segments = explode('/', $this->path); + $tempPath = trim($this->path, '/'); + + $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); return $this; } @@ -913,7 +922,7 @@ { $this->user = $parts['user']; } - if (! empty($parts['path'])) + if (isset($parts['path']) && $parts['path'] !== '') { $this->path = $this->filterPath($parts['path']); } @@ -953,9 +962,11 @@ } // Populate our segments array - if (! empty($parts['path'])) + if (isset($parts['path']) && $parts['path'] !== '') { - $this->segments = explode('/', trim($parts['path'], '/')); + $tempPath = trim($parts['path'], '/'); + + $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); } } @@ -1048,14 +1059,14 @@ */ protected function mergePaths(URI $base, URI $reference): string { - if (! empty($base->getAuthority()) && empty($base->getPath())) + if (! empty($base->getAuthority()) && $base->getPath() === '') { return '/' . ltrim($reference->getPath(), '/ '); } $path = explode('/', $base->getPath()); - if (empty($path[0])) + if ($path[0] === '') { unset($path[0]); } @@ -1082,7 +1093,7 @@ */ public function removeDotSegments(string $path): string { - if (empty($path) || $path === '/') + if ($path === '' || $path === '/') { return $path; } @@ -1091,7 +1102,7 @@ $input = explode('/', $path); - if (empty($input[0])) + if ($input[0] === '') { unset($input[0]); $input = array_values($input); diff --git a/system/HTTP/UserAgent.php b/system/HTTP/UserAgent.php index 6e5b596..c6873fd 100644 --- a/system/HTTP/UserAgent.php +++ b/system/HTTP/UserAgent.php @@ -51,7 +51,7 @@ * * @var string */ - protected $agent = null; + protected $agent; /** * Flag for if the user-agent belongs to a browser diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 9c5d8f6..c114df9 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -79,7 +79,7 @@ ? array_shift($indexes) : null; - if (empty($currentIndex) || (! isset($array[$currentIndex]) && $currentIndex !== '*')) + if ((empty($currentIndex) && intval($currentIndex) !== 0) || (! isset($array[$currentIndex]) && $currentIndex !== '*')) { return null; } @@ -90,7 +90,7 @@ // If $array has more than 1 item, we have to loop over each. if (is_array($array)) { - foreach ($array as $key => $value) + foreach ($array as $value) { $answer = _array_search_dot($indexes, $value); diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index d85f644..50ab392 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -96,9 +96,8 @@ $request = \Config\Services::request(); $filter = true === $xssClean ? FILTER_SANITIZE_STRING : null; - $cookie = $request->getCookie($prefix . $index, $filter); - return $cookie; + return $request->getCookie($prefix . $index, $filter); } } diff --git a/system/Helpers/date_helper.php b/system/Helpers/date_helper.php index 6cf362a..1951d25 100644 --- a/system/Helpers/date_helper.php +++ b/system/Helpers/date_helper.php @@ -96,8 +96,7 @@ $selected = ($timezone === $default) ? 'selected' : ''; $buffer .= "" . PHP_EOL; } - $buffer .= '' . PHP_EOL; - return $buffer; + return $buffer . ('' . PHP_EOL); } } diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php index 1ead233..2556e86 100644 --- a/system/Helpers/filesystem_helper.php +++ b/system/Helpers/filesystem_helper.php @@ -209,45 +209,54 @@ * Reads the specified directory and builds an array containing the filenames. * Any sub-folders contained within the specified path are read as well. * - * @param string $source_dir Path to source - * @param boolean $include_path Whether to include the path as part of the filename - * @param boolean $recursion Internal variable to determine recursion status - do not use in calls + * @param string $source_dir Path to source + * @param boolean|null $include_path Whether to include the path as part of the filename; false for no path, null for a relative path, true for full path + * @param boolean $hidden Whether to include hidden files (files beginning with a period) * * @return array */ - function get_filenames(string $source_dir, bool $include_path = false, bool $recursion = false): array + function get_filenames(string $source_dir, ?bool $include_path = false, bool $hidden = false): array { - static $fileData = []; + $files = []; + + $source_dir = realpath($source_dir) ?: $source_dir; + $source_dir = rtrim($source_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; try { - $fp = opendir($source_dir); - // reset the array and make sure $source_dir has a trailing slash on the initial call - if ($recursion === false) + foreach (new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($source_dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::SELF_FIRST + ) as $name => $object) { - $fileData = []; - $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - } + $basename = pathinfo($name, PATHINFO_BASENAME); - while (false !== ($file = readdir($fp))) - { - if (is_dir($source_dir . $file) && $file[0] !== '.') + if (! $hidden && $basename[0] === '.') { - get_filenames($source_dir . $file . DIRECTORY_SEPARATOR, $include_path, true); + continue; } - elseif ($file[0] !== '.') + elseif ($include_path === false) { - $fileData[] = ($include_path === true) ? $source_dir . $file : $file; + $files[] = $basename; + } + elseif (is_null($include_path)) + { + $files[] = str_replace($source_dir, '', $name); + } + else + { + $files[] = $name; } } - - closedir($fp); - return $fileData; } - catch (\Exception $fe) + catch (\Throwable $e) { return []; } + + sort($files); + + return $files; } } diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index 82c809c..e505e65 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -69,6 +69,12 @@ } // If an action is not a full URL then turn it into one elseif (strpos($action, '://') === false) { + // If an action has {locale} + if (strpos($action, '{locale}') !== false) + { + $action = str_replace('{locale}', Services::request()->getLocale(), $action); + } + $action = site_url($action); } @@ -180,7 +186,7 @@ if (! is_array($value)) { - $form .= '\n"; + $form .= '\n"; } else { @@ -408,7 +414,7 @@ { $sel = in_array($optgroup_key, $selected) ? ' selected="selected"' : ''; $form .= '\n"; + . $optgroup_val . "\n"; } $form .= "\n"; } @@ -416,7 +422,7 @@ { $form .= '\n"; + . $val . "\n"; } } @@ -645,9 +651,7 @@ $out .= "