From 460264d3897a7da9abceec6fb99d599a779c06eb Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 26 Jun 2016 01:21:35 +0900 Subject: [PATCH 0001/1807] Update travis-ci link Signed-off-by: Kenji Suzuki --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8499227158d..e5d1348b01aa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CodeIgniter 4 Development -[![Build Status](https://travis-ci.org/lonnieezell/CodeIgniter4.svg?branch=develop)](https://travis-ci.org/lonnieezell/CodeIgniter4) +[![Build Status](https://travis-ci.org/bcit-ci/CodeIgniter4.svg?branch=develop)](https://travis-ci.org/bcit-ci/CodeIgniter4)
[![StyleCI](https://styleci.io/repos/41463886/shield)](https://styleci.io/repos/41463886) From 1dd277a95533e3ed7a16b87c625cb3ee61a2ad73 Mon Sep 17 00:00:00 2001 From: "Instructor, Computer Systems Technology" Date: Sat, 25 Jun 2016 12:58:05 -0700 Subject: [PATCH 0002/1807] Update README.md Add a note about index.php, since this seems to be causing no end of confusion on the forum! --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index e5d1348b01aa..7aac60f31d89 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,15 @@ while still keeping as many of the things intact that has made people love the f More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. +## Important Change with index.php + +index.php is no longer in the root of the project! It has been moved inside the *public* folder, +for better security and separation of components. + +This means that you should configure your web server to "point" to your project's *public* folder, and +not to the project root. A better practice would be to configure a virtual host to point there. A poor practice would be to point your web server to the project root and expect to enter *public/...*, as the rest of your logic and the +framework are exposed. + ## Repository Management We use Github issues to track **BUGS** and to track approved **DEVELOPMENT** work packages. We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss From fc44ee6523c71c65d3defea80487a378a738ffa0 Mon Sep 17 00:00:00 2001 From: "Instructor, Computer Systems Technology" Date: Sat, 25 Jun 2016 12:59:42 -0700 Subject: [PATCH 0003/1807] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7aac60f31d89..a9c0c0e75d17 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,9 @@ This means that you should configure your web server to "point" to your project' not to the project root. A better practice would be to configure a virtual host to point there. A poor practice would be to point your web server to the project root and expect to enter *public/...*, as the rest of your logic and the framework are exposed. +**Please** read the user guide for a better explanation of how CI4 works! +The user guide updating and deployment is a bit awkward at the moment, but we are working on it! + ## Repository Management We use Github issues to track **BUGS** and to track approved **DEVELOPMENT** work packages. We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss From 8296832a3847e2c00ea015da5633d7aff7276062 Mon Sep 17 00:00:00 2001 From: "Instructor, Computer Systems Technology" Date: Sat, 25 Jun 2016 14:49:04 -0700 Subject: [PATCH 0004/1807] Create index.html Provide a helpful message for those trying to "run" the project "out of the box". --- index.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 000000000000..7e7cda4bbf72 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + CodeIgniter 4 ... almost! + + + + +

If you see this message, you have not configured your web server properly.

+

You need to set your "document root" to the public folder + inside your project. This could be your default setting, or that of + a virtual host, depending on how you set up your local development + environment.

+ + From 1341152691fd408995bfaffa9043bd1787cfae62 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Sat, 25 Jun 2016 23:51:19 -0700 Subject: [PATCH 0005/1807] gh-pages mods to user guide makefile Signed-off-by:Master Yoda --- user_guide_src/Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/user_guide_src/Makefile b/user_guide_src/Makefile index 519e1136ff54..425a2ef113a5 100644 --- a/user_guide_src/Makefile +++ b/user_guide_src/Makefile @@ -6,13 +6,14 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build +GHBUILDDIR = ../../CodeIgniter4-guide # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest ghpages help: @echo "Please use \`make ' where is one of" @@ -128,3 +129,9 @@ doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." + +ghpages: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(GHBUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(GHBUILDDIR)/html." + From e6f1519df10f21299da5160b1d8b573de8a78664 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 26 Jun 2016 16:59:56 +0900 Subject: [PATCH 0006/1807] Fix white spaces Signed-off-by: Kenji Suzuki --- system/Commands/MigrationsCommand.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/system/Commands/MigrationsCommand.php b/system/Commands/MigrationsCommand.php index 49a0d8db5f3d..ed4e76ae0992 100644 --- a/system/Commands/MigrationsCommand.php +++ b/system/Commands/MigrationsCommand.php @@ -62,7 +62,7 @@ class MigrationsCommand extends \CodeIgniter\Controller */ public function __construct() { - $this->runner = Services::migrations(); + $this->runner = Services::migrations(); } //-------------------------------------------------------------------- @@ -73,12 +73,12 @@ public function __construct() public function index() { CLI::write('Migration Commands', 'white'); - CLI::write(CLI::color('latest', 'yellow'). "\t\tMigrates database to latest available migration."); - CLI::write(CLI::color('current', 'yellow'). "\t\tMigrates database to version set as 'current' in configuration."); - CLI::write(CLI::color('version [v]', 'yellow'). "\tMigrates database to version {v}."); - CLI::write(CLI::color('rollback', 'yellow'). "\tRuns all migrations 'down' to version 0."); - CLI::write(CLI::color('refresh', 'yellow'). "\t\tUninstalls and re-runs all migrations to freshen database."); - CLI::write(CLI::color('seed [name]', 'yellow'). "\tRuns the seeder named [name]."); + CLI::write(CLI::color('latest', 'yellow'). "\t\tMigrates database to latest available migration."); + CLI::write(CLI::color('current', 'yellow'). "\t\tMigrates database to version set as 'current' in configuration."); + CLI::write(CLI::color('version [v]', 'yellow'). "\tMigrates database to version {v}."); + CLI::write(CLI::color('rollback', 'yellow'). "\tRuns all migrations 'down' to version 0."); + CLI::write(CLI::color('refresh', 'yellow'). "\t\tUninstalls and re-runs all migrations to freshen database."); + CLI::write(CLI::color('seed [name]', 'yellow'). "\tRuns the seeder named [name]."); } //-------------------------------------------------------------------- From 6f6290c129141c6a87cd254c3e04a98f956a212d Mon Sep 17 00:00:00 2001 From: "Instructor, Computer Systems Technology" Date: Sun, 26 Jun 2016 11:06:30 -0700 Subject: [PATCH 0007/1807] Update welcome_message.php Fixed incorrect user guide link! --- application/Views/welcome_message.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Views/welcome_message.php b/application/Views/welcome_message.php index 1aaaec2d1b69..1d566b41473a 100644 --- a/application/Views/welcome_message.php +++ b/application/Views/welcome_message.php @@ -125,7 +125,7 @@

If you are exploring CodeIgniter for the very first time, you should start by reading the (in progress) - User Guide.

+ User Guide.

From 07d27ab57f30a670a003fa0525926084df46a01a Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 26 Jun 2016 14:00:55 -0500 Subject: [PATCH 0008/1807] Removing StyleCI badge. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a9c0c0e75d17..c59b1ea652b9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Build Status](https://travis-ci.org/bcit-ci/CodeIgniter4.svg?branch=develop)](https://travis-ci.org/bcit-ci/CodeIgniter4)
-[![StyleCI](https://styleci.io/repos/41463886/shield)](https://styleci.io/repos/41463886) ## What is CodeIgniter? CodeIgniter is a PHP full-stack web framework that is light, fast, flexible, and secure. From 4735011481ba62f46afe891f174ebea74c48e0ff Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 26 Jun 2016 22:12:48 -0500 Subject: [PATCH 0009/1807] Adding :hash placeholder for routes. Fixes #130 --- system/Router/RouteCollection.php | 1 + user_guide_src/source/general/routing.rst | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index c4988523673b..ab56b3f2994e 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -120,6 +120,7 @@ class RouteCollection implements RouteCollectionInterface 'num' => '[0-9]+', 'alpha' => '[a-zA-Z]+', 'alphanum' => '[a-zA-Z0-9]+', + 'hash' => '[^/]+', ]; /** diff --git a/user_guide_src/source/general/routing.rst b/user_guide_src/source/general/routing.rst index 28eac82202d7..d1ebabe8164c 100644 --- a/user_guide_src/source/general/routing.rst +++ b/user_guide_src/source/general/routing.rst @@ -62,7 +62,9 @@ The following placeholders are available for you to use in your routes: * **(:segment)** will match any character except for a forward slash (/) restricting the result to a single segment. * **(:num)** will match any integer. * **(:alpha)** will match any string of alphabetic characters -* **(alphanum)** will match any string of alphabetic characters or integers, or any combination of the two. +* **(:alphanum)** will match any string of alphabetic characters or integers, or any combination of the two. +* **(:hash)** is the same as **:segment**, but can be used to easily see which routes use hashed ids (see the +:doc:`Model ` docs. Examples ======== From ba54dc0932b409015b9c5f2ddd26fb51812e0659 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 00:54:04 -0500 Subject: [PATCH 0010/1807] Initial work on porting the cache library over. --- application/Config/Cache.php | 84 +++++ application/Config/Services.php | 27 +- system/Cache/CacheFactory.php | 62 ++++ system/Cache/CacheInterface.php | 108 ++++++ system/Cache/Handlers/DummyHandler.php | 135 +++++++ system/Cache/Handlers/FileHandler.php | 474 +++++++++++++++++++++++++ system/Common.php | 2 +- 7 files changed, 888 insertions(+), 4 deletions(-) create mode 100644 application/Config/Cache.php create mode 100644 system/Cache/CacheFactory.php create mode 100644 system/Cache/CacheInterface.php create mode 100644 system/Cache/Handlers/DummyHandler.php create mode 100644 system/Cache/Handlers/FileHandler.php diff --git a/application/Config/Cache.php b/application/Config/Cache.php new file mode 100644 index 000000000000..582c5be77d91 --- /dev/null +++ b/application/Config/Cache.php @@ -0,0 +1,84 @@ + \CodeIgniter\Cache\Handlers\DummyHandler::class, +// 'file' => \CodeIgniter\Cache\Handlers\FileHandler::class, +// 'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class, +// 'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class, +// 'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class, + ]; +} diff --git a/application/Config/Services.php b/application/Config/Services.php index 9df755ef2b0e..65df1940c124 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -49,6 +49,27 @@ public static function autoloader($getShared = true) //-------------------------------------------------------------------- + /** + * The cache class provides a simple way to store and retrieve + * complex data for later. + */ + public static function cache(\Config\Cache $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('cache', $config); + } + + if (! is_object($config)) + { + $config = new \Config\Cache(); + } + + return \CodeIgniter\Cache\CacheFactory::getHandler($config); + } + + //-------------------------------------------------------------------- + /** * The CLI Request class provides for ways to interact with * a command line request. @@ -189,10 +210,10 @@ public static function migrations(BaseConfig $config = null, ConnectionInterface return new MigrationRunner($config, $db); } - + //-------------------------------------------------------------------- - - + + /** * The Negotiate class provides the content negotiation features for * working the request to determine correct language, encoding, charset, diff --git a/system/Cache/CacheFactory.php b/system/Cache/CacheFactory.php new file mode 100644 index 000000000000..41b4d2ee1297 --- /dev/null +++ b/system/Cache/CacheFactory.php @@ -0,0 +1,62 @@ +validHandlers) || ! is_array($config->validHandlers)) + { + throw new \InvalidArgumentException('Cache config must have an array of $validHandlers.'); + } + + if (! isset($config->handler) || ! isset($config->backupHandler)) + { + throw new \InvalidArgumentException('Cache config must have a handler and backupHanlder set.'); + } + + $handler = ! empty($handler) ? $handler : $config->handler; + $backup = ! empty($backup) ? $backup : $config->backupHandler; + + if (! in_array($handler, $config->validHandlers) || ! in_array($backup, $config->validHandlers)) + { + throw new \InvalidArgumentException('Cache config has an invalid handler or backup handler specified.'); + } + + // Get an instance of our handler. + $adapter = new $config->validHandlers[$handler]($config); + + if (! $adapter->isSupported()) + { + $adapter = new $config->validHandlers[$backup]($config); + + if (! $adapter->isSupported()) + { + // Log stuff here, don't throw exception. No need to raise a fuss. + + // Fall back to the dummy adapter. + $adapter = new $config->validHandler['dummy'](); + } + } + + return $adapter; + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file diff --git a/system/Cache/CacheInterface.php b/system/Cache/CacheInterface.php new file mode 100644 index 000000000000..35ae33f270fd --- /dev/null +++ b/system/Cache/CacheInterface.php @@ -0,0 +1,108 @@ +prefix = $config->prefix ?: ''; + $this->path = ! empty($config->path) + ? $config->path + : WRITEPATH.'cache'; + + $this->path = rtrim($this->path, '/').'/'; + } + + //-------------------------------------------------------------------- + + + /** + * Attempts to fetch an item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function get(string $key) + { + $key = $this->prefix.$key; + + $data = $this->getItem($key); + + return is_array($data) ? $data['data'] : null; + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by Mamcache in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param bool $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, $ttl = null, $raw = false) + { + $key = $this->prefix.$key; + + $contents = [ + 'time' => time(), + 'ttl' => $ttl, + 'data' => $value, + ]; + + if ($this->writeFile($this->path.$key, serialize($contents))) + { + chmod($this->path.$key, 0640); + + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + $key = $this->prefix.$key; + + return file_exists($this->path.$key) + ? unlink($this->path.$key) + : false; + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + $data = $this->getItem($key); + + if ($data === false) + { + $data = ['data' => 0, 'ttl' => 60]; + } + elseif (! is_int($data['data'])) + { + return false; + } + + $new_value = $data['data']+$offset; + + return $this->save($key, $new_value, $data['ttl']) + ? $new_value + : false; + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + $data = $this->getItem($key); + + if ($data === false) + { + $data = ['data' => 0, 'ttl' => 60]; + } + elseif (! is_int($data['data'])) + { + return false; + } + + $new_value = $data['data']-$offset; + + return $this->save($key, $new_value, $data['ttl']) + ? $new_value + : false; + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + return $this->deleteFiles($this->path, false, true); + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return $this->getDirFileInfo($this->path); + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + $key = $this->prefix.$key; + + if ( ! file_exists($this->path.$key)) + { + return FALSE; + } + + $data = unserialize(file_get_contents($this->path.$key)); + + if (is_array($data)) + { + $mtime = filemtime($this->path.$key); + + if ( ! isset($data['ttl'])) + { + return FALSE; + } + + return array( + 'expire' => $mtime + $data['ttl'], + 'mtime' => $mtime + ); + } + + return FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return is_writable($this->path); + } + + //-------------------------------------------------------------------- + + /** + * Does the heavy lifting of actually retrieving the file and + * verifying it's age. + * + * @param string $key + * + * @return bool|mixed + */ + protected function getItem(string $key) + { + if (! is_file($this->path.$key)) + { + return null; + } + + $data = unserialize(file_get_contents($this->path.$key)); + + if ($data['ttl'] > 0 && time() > $data['time']+$data['ttl']) + { + unlink($this->path.$key); + + return null; + } + + return $data; + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // SUPPORT METHODS FOR FILES + //-------------------------------------------------------------------- + + /** + * Writes a file to disk, or returns false if not successful. + * + * @param $path + * @param $data + * @param string $mode + * + * @return bool + */ + protected function writeFile($path, $data, $mode = 'wb') + { + if (! $fp = @fopen($path, $mode)) + { + return false; + } + + flock($fp, LOCK_EX); + + for ($result = $written = 0, $length = strlen($data); $written < $length; $written += $result) + { + if (($result = fwrite($fp, substr($data, $written))) === false) + { + break; + } + } + + flock($fp, LOCK_UN); + fclose($fp); + + return is_int($result); + } + + //-------------------------------------------------------------------- + + /** + * Delete Files + * + * Deletes all files contained in the supplied directory path. + * Files must be writable or owned by the system in order to be deleted. + * If the second parameter is set to TRUE, any directories contained + * within the supplied base directory will be nuked as well. + * + * @param string $path File path + * @param bool $del_dir Whether to delete any directories found in the path + * @param bool $htdocs Whether to skip deleting .htaccess and index page files + * @param int $_level Current directory depth level (default: 0; internal use only) + * + * @return bool + */ + protected function deleteFiles($path, $del_dir = false, $htdocs = false, $_level = 0) + { + // Trim the trailing slash + $path = rtrim($path, '/\\'); + + if (! $current_dir = @opendir($path)) + { + return false; + } + + while (false !== ($filename = @readdir($current_dir))) + { + if ($filename !== '.' && $filename !== '..') + { + if (is_dir($path.DIRECTORY_SEPARATOR.$filename) && $filename[0] !== '.') + { + $this->deleteFiles($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $htdocs, $_level+1); + } + elseif ($htdocs !== true || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) + { + @unlink($path.DIRECTORY_SEPARATOR.$filename); + } + } + } + + closedir($current_dir); + + return ($del_dir === true && $_level > 0) + ? @rmdir($path) + : true; + } + + //-------------------------------------------------------------------- + + /** + * Get Directory File Information + * + * Reads the specified directory and builds an array containing the filenames, + * filesize, dates, and permissions + * + * Any sub-folders contained within the specified path are read as well. + * + * @param string path to source + * @param bool Look only at the top level directory specified? + * @param bool internal variable to determine recursion status - do not use in calls + * + * @return array + */ + protected function getDirFileInfo($source_dir, $top_level_only = true, $_recursion = false) + { + static $_filedata = []; + $relative_path = $source_dir; + + if ($fp = @opendir($source_dir)) + { + // reset the array and make sure $source_dir has a trailing slash on the initial call + if ($_recursion === false) + { + $_filedata = []; + $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + } + + // Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast + while (false !== ($file = readdir($fp))) + { + if (is_dir($source_dir.$file) && $file[0] !== '.' && $top_level_only === false) + { + $this->getDirFileInfo($source_dir.$file.DIRECTORY_SEPARATOR, $top_level_only, true); + } + elseif ($file[0] !== '.') + { + $_filedata[$file] = $this->getFileInfo($source_dir.$file); + $_filedata[$file]['relative_path'] = $relative_path; + } + } + + closedir($fp); + + return $_filedata; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Get File Info + * + * Given a file and path, returns the name, path, size, date modified + * Second parameter allows you to explicitly declare what information you want returned + * Options are: name, server_path, size, date, readable, writable, executable, fileperms + * Returns FALSE if the file cannot be found. + * + * @param string path to file + * @param mixed array or comma separated string of information returned + * + * @return array + */ + protected function getFileInfo($file, $returned_values = ['name', 'server_path', 'size', 'date']) + { + if (! file_exists($file)) + { + return false; + } + + if (is_string($returned_values)) + { + $returned_values = explode(',', $returned_values); + } + + foreach ($returned_values as $key) + { + switch ($key) + { + case 'name': + $fileinfo['name'] = basename($file); + break; + case 'server_path': + $fileinfo['server_path'] = $file; + break; + case 'size': + $fileinfo['size'] = filesize($file); + break; + case 'date': + $fileinfo['date'] = filemtime($file); + break; + case 'readable': + $fileinfo['readable'] = is_readable($file); + break; + case 'writable': + $fileinfo['writable'] = is_writable($file); + break; + case 'executable': + $fileinfo['executable'] = is_executable($file); + break; + case 'fileperms': + $fileinfo['fileperms'] = fileperms($file); + break; + } + } + + return $fileinfo; + } + + //-------------------------------------------------------------------- +} \ No newline at end of file diff --git a/system/Common.php b/system/Common.php index bfaeb8ae1257..aaabf1dd1247 100644 --- a/system/Common.php +++ b/system/Common.php @@ -244,7 +244,7 @@ function service(string $name, ...$params) { /** * Allow cleaner access to shared services - * + * * @param string $name * @param array|null $params * @return type From 0242b6a00e64be9d62bd2e1967ea817c0e641ae6 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 22:31:49 -0500 Subject: [PATCH 0011/1807] Memcached cache driver --- application/Config/Cache.php | 18 ++ system/Cache/Handlers/MemcachedHandler.php | 293 +++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 system/Cache/Handlers/MemcachedHandler.php diff --git a/application/Config/Cache.php b/application/Config/Cache.php index 582c5be77d91..b164bccf4e14 100644 --- a/application/Config/Cache.php +++ b/application/Config/Cache.php @@ -65,6 +65,24 @@ class Cache extends BaseConfig */ public $prefix = ''; + /* + | ------------------------------------------------------------------------- + | Memcached settings + | ------------------------------------------------------------------------- + | Your Memcached servers can be specified below, if you are using + | the Memcached drivers. + | + | See: https://codeigniter.com/user_guide/libraries/caching.html#memcached + | + */ + public $memcached = [ + 'default' => [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1 + ] + ]; + /* |-------------------------------------------------------------------------- | Available Cache Handlers diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php new file mode 100644 index 000000000000..3fa91662723e --- /dev/null +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -0,0 +1,293 @@ + [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1, + ], + ]; + + //-------------------------------------------------------------------- + + public function __construct($config) + { + $this->prefix = $config->prefix ?: ''; + + if (isset($config->memcached)) + { + $this->config = $config->memcached; + } + + $defaults = $this->config['defaults']; + + if (class_exists('Memcached')) + { + $this->memcached = new Memcached(); + } + elseif (class_exists('Memcache')) + { + $this->memcached = new Memcache(); + } + else + { +// log_message('error', 'Cache: Failed to create Memcache(d) object; extension not loaded?'); + + return; + } + + foreach ($this->config as $cacheName => $cacheServer) + { + if (! isset($cacheServer['hostname'])) + { +// log_message('debug', 'Cache: Memcache(d) configuration "'.$cacheName.'" doesn\'t include a hostname; ignoring.'); + continue; + } + elseif ($cacheServer['hostname'][0] === '/') + { + $cacheServer['port'] = 0; + } + elseif (empty($cacheServer['port'])) + { + $cacheServer['port'] = $defaults['port']; + } + + isset($cacheServer['weight']) OR $cacheServer['weight'] = $defaults['weight']; + + if ($this->memcached instanceof Memcache) + { + // Third parameter is persistance and defaults to TRUE. + $this->memcached->addServer( + $cacheServer['hostname'], + $cacheServer['port'], + true, + $cacheServer['weight'] + ); + } + elseif ($this->memcached instanceof Memcached) + { + $this->memcached->addServer( + $cacheServer['hostname'], + $cacheServer['port'], + $cacheServer['weight'] + ); + } + } + } + + //-------------------------------------------------------------------- + + + /** + * Attempts to fetch an item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function get(string $key) + { + $key = $this->prefix.$key; + + $data = $this->memcached->get($key); + + return is_array($data) ? $data[0] : $data; + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by Mamcache in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param bool $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, $ttl = null, $raw = false) + { + $key = $this->prefix.$key; + + if ($raw !== true) + { + $value = [$value, time(), $ttl]; + } + + if ($this->memcached instanceof Memcached) + { + return $this->memcached->set($key, $value, $ttl); + } + elseif ($this->memcached instanceof Memcache) + { + return $this->memcached->set($key, $value, 0, $ttl); + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + $key = $this->prefix.$key; + + return $this->memcached->delete($key); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + return $this->memcached->increment($key, $offset); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + return $this->memcached->decrement($key, $offset); + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + return $this->memcached->flush(); + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return $this->memcached->getStats(); + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + $key = $this->prefix.$key; + + $stored = $this->memcached->get($key); + + if (count($stored) !== 3) + { + return FALSE; + } + + list($data, $time, $ttl) = $stored; + + return array( + 'expire' => $time + $ttl, + 'mtime' => $time, + 'data' => $data + ); + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return (extension_loaded('memcached') OR extension_loaded('memcache')); + } + + //-------------------------------------------------------------------- + + /** + * Class destructor + * + * Closes the connection to Memcache(d) if present. + */ + public function __destruct() + { + if ($this->memcached instanceof Memcache) + { + $this->memcached->close(); + } + elseif ($this->memcached instanceof Memcached) + { + $this->memcached->quit(); + } + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file From fbf39cfe05a46ba695c6aa66c0639a35497d1944 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 22:48:57 -0500 Subject: [PATCH 0012/1807] Redis and Wincache Drivers --- application/Config/Cache.php | 15 ++ system/Cache/Handlers/RedisHandler.php | 292 ++++++++++++++++++++++ system/Cache/Handlers/WincacheHandler.php | 188 ++++++++++++++ 3 files changed, 495 insertions(+) create mode 100644 system/Cache/Handlers/RedisHandler.php create mode 100644 system/Cache/Handlers/WincacheHandler.php diff --git a/application/Config/Cache.php b/application/Config/Cache.php index b164bccf4e14..4b937aeefc46 100644 --- a/application/Config/Cache.php +++ b/application/Config/Cache.php @@ -83,6 +83,21 @@ class Cache extends BaseConfig ] ]; + /* + | ------------------------------------------------------------------------- + | Redis settings + | ------------------------------------------------------------------------- + | Your Redis server can be specified below, if you are using + | the Redis drivers. + | + */ + public $redis = [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + ]; + /* |-------------------------------------------------------------------------- | Available Cache Handlers diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php new file mode 100644 index 000000000000..75259c057f79 --- /dev/null +++ b/system/Cache/Handlers/RedisHandler.php @@ -0,0 +1,292 @@ + '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + ]; + + /** + * Redis connection + * + * @var Redis + */ + protected $redis; + + //-------------------------------------------------------------------- + + public function __construct($config) + { + $this->prefix = $config->prefix ?: ''; + + if (! $this->isSupported()) + { +// log_message('error', 'Cache: Failed to create Redis object; extension not loaded?'); + return; + } + + $config = $this->defaultConfig; + + if (isset($config->redis)) + { + $config = array_merge($this->defaultConfig, $config->redis); + } + + $this->redis = new Redis(); + + try + { + if (! $this->redis->connect($config['host'], ($config['host'][0] === '/' ? 0 + : $config['port']), $config['timeout']) + ) + { +// log_message('error', 'Cache: Redis connection failed. Check your configuration.'); + } + + if (isset($config['password']) && ! $this->redis->auth($config['password'])) + { +// log_message('error', 'Cache: Redis authentication failed.'); + } + } + catch (RedisException $e) + { +// log_message('error', 'Cache: Redis connection refused ('.$e->getMessage().')'); + } + } + + //-------------------------------------------------------------------- + + /** + * Attempts to fetch an item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function get(string $key) + { + $key = $this->prefix.$key; + + $data = $this->redis->hMGet($key, ['__ci_type', '__ci_value']); + + if (! isset($data['__ci_type'], $data['__ci_value']) OR $data['__ci_value'] === false) + { + return null; + } + + switch ($data['__ci_type']) + { + case 'array': + case 'object': + return unserialize($data['__ci_value']); + case 'boolean': + case 'integer': + case 'double': // Yes, 'double' is returned and NOT 'float' + case 'string': + case 'NULL': + return settype($data['__ci_value'], $data['__ci_type']) + ? $data['__ci_value'] + : null; + case 'resource': + default: + return null; + } + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by Mamcache in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param bool $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, $ttl = null, $raw = false) + { + $key = $this->prefix.$key; + + switch ($data_type = gettype($value)) + { + case 'array': + case 'object': + $value = serialize($value); + break; + case 'boolean': + case 'integer': + case 'double': // Yes, 'double' is returned and NOT 'float' + case 'string': + case 'NULL': + break; + case 'resource': + default: + return false; + } + + if (! $this->redis->hMSet($key, ['__ci_type' => $data_type, '__ci_value' => $value])) + { + return false; + } + elseif ($ttl) + { + $this->redis->expireAt($key, time()+$ttl); + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + $key = $this->prefix.$key; + + return ($this->redis->delete($key) === 1); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + return $this->redis->hIncrBy($key, 'data', $offset); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + return $this->redis->hIncrBy($key, 'data', -$offset); + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + return $this->redis->flushDB(); + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return $this->redis->info(); + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + $key = $this->prefix.$key; + + $value = $this->get($key); + + if ($value !== FALSE) + { + return array( + 'expire' => time() + $this->redis->ttl($key), + 'data' => $value + ); + } + + return FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return extension_loaded('redis'); + } + + //-------------------------------------------------------------------- + + /** + * Class destructor + * + * Closes the connection to Memcache(d) if present. + */ + public function __destruct() + { + if ($this->redis) + { + $this->redis->close(); + } + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php new file mode 100644 index 000000000000..c60101ca789a --- /dev/null +++ b/system/Cache/Handlers/WincacheHandler.php @@ -0,0 +1,188 @@ +prefix = $config->prefix ?: ''; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to fetch an item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function get(string $key) + { + $key = $this->prefix.$key; + + $success = FALSE; + $data = wincache_ucache_get($key, $success); + + // Success returned by reference from wincache_ucache_get() + return ($success) ? $data : FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by Mamcache in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param bool $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, $ttl = 60, $raw = false) + { + $key = $this->prefix.$key; + + return wincache_ucache_set($key, $value, $ttl); + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + $key = $this->prefix.$key; + + return wincache_ucache_delete($key); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + $success = FALSE; + $value = wincache_ucache_inc($key, $offset, $success); + + return ($success === TRUE) ? $value : FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, $offset = 1) + { + $key = $this->prefix.$key; + + $success = FALSE; + $value = wincache_ucache_dec($key, $offset, $success); + + return ($success === TRUE) ? $value : FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + return wincache_ucache_clear(); + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return wincache_ucache_info(TRUE); + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + $key = $this->prefix.$key; + + if ($stored = wincache_ucache_info(FALSE, $key)) + { + $age = $stored['ucache_entries'][1]['age_seconds']; + $ttl = $stored['ucache_entries'][1]['ttl_seconds']; + $hitcount = $stored['ucache_entries'][1]['hitcount']; + + return array( + 'expire' => $ttl - $age, + 'hitcount' => $hitcount, + 'age' => $age, + 'ttl' => $ttl + ); + } + + return FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return (extension_loaded('wincache') && ini_get('wincache.ucenabled')); + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file From 346513fb94ee0cec728db9095f1ee7e57086c16c Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 22:51:21 -0500 Subject: [PATCH 0013/1807] Default TTL for all cache handlers --- system/Cache/Handlers/DummyHandler.php | 2 +- system/Cache/Handlers/FileHandler.php | 2 +- system/Cache/Handlers/MemcachedHandler.php | 2 +- system/Cache/Handlers/RedisHandler.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Cache/Handlers/DummyHandler.php b/system/Cache/Handlers/DummyHandler.php index e301a9e57f7e..cc8aadaee099 100644 --- a/system/Cache/Handlers/DummyHandler.php +++ b/system/Cache/Handlers/DummyHandler.php @@ -29,7 +29,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = null, $raw = false) + public function save(string $key, $value, $ttl = 60, $raw = false) { return true; } diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index 8e5f3acd9cd5..e2139f93b9da 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -62,7 +62,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = null, $raw = false) + public function save(string $key, $value, $ttl = 60, $raw = false) { $key = $this->prefix.$key; diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index 3fa91662723e..e21ef2cd9bd1 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -130,7 +130,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = null, $raw = false) + public function save(string $key, $value, $ttl = 60, $raw = false) { $key = $this->prefix.$key; diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index 75259c057f79..0691476e6a0f 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -124,7 +124,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = null, $raw = false) + public function save(string $key, $value, $ttl = 60, $raw = false) { $key = $this->prefix.$key; From e05df8784a3eceb0e3375c1d0bd5eb16d52d2ec1 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 23:03:35 -0500 Subject: [PATCH 0014/1807] Refactoring cache handler init. Consistent type hinting --- system/Cache/CacheFactory.php | 2 ++ system/Cache/Handlers/DummyHandler.php | 16 ++++++++++--- system/Cache/Handlers/FileHandler.php | 15 ++++++++++--- system/Cache/Handlers/MemcachedHandler.php | 14 +++++++++--- system/Cache/Handlers/RedisHandler.php | 26 ++++++++++++---------- system/Cache/Handlers/WincacheHandler.php | 18 +++++++++++---- 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/system/Cache/CacheFactory.php b/system/Cache/CacheFactory.php index 41b4d2ee1297..2fca1243b71c 100644 --- a/system/Cache/CacheFactory.php +++ b/system/Cache/CacheFactory.php @@ -54,6 +54,8 @@ public static function getHandler($config, string $handler = null, string $backu } } + $adapter->initialize(); + return $adapter; } diff --git a/system/Cache/Handlers/DummyHandler.php b/system/Cache/Handlers/DummyHandler.php index cc8aadaee099..5d95688461ac 100644 --- a/system/Cache/Handlers/DummyHandler.php +++ b/system/Cache/Handlers/DummyHandler.php @@ -2,6 +2,16 @@ class DummyHandler implements CacheInterface { + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize() + { + // Nothing to see here... + } + + //-------------------------------------------------------------------- + /** * Attempts to fetch an item from the cache store. * @@ -29,7 +39,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = 60, $raw = false) + public function save(string $key, $value, int $ttl = 60, bool $raw = false) { return true; } @@ -58,7 +68,7 @@ public function delete(string $key) * * @return mixed */ - public function increment(string $key, $offset = 1) + public function increment(string $key, int $offset = 1) { return true; } @@ -73,7 +83,7 @@ public function increment(string $key, $offset = 1) * * @return mixed */ - public function decrement(string $key, $offset = 1) + public function decrement(string $key, int $offset = 1) { return true; } diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index e2139f93b9da..3eaddce939e4 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -30,6 +30,15 @@ public function __construct($config) //-------------------------------------------------------------------- + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize() + { + // Not to see here... + } + + //-------------------------------------------------------------------- /** * Attempts to fetch an item from the cache store. @@ -62,7 +71,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = 60, $raw = false) + public function save(string $key, $value, int $ttl = 60, bool $raw = false) { $key = $this->prefix.$key; @@ -110,7 +119,7 @@ public function delete(string $key) * * @return mixed */ - public function increment(string $key, $offset = 1) + public function increment(string $key, int $offset = 1) { $key = $this->prefix.$key; @@ -142,7 +151,7 @@ public function increment(string $key, $offset = 1) * * @return mixed */ - public function decrement(string $key, $offset = 1) + public function decrement(string $key, int $offset = 1) { $key = $this->prefix.$key; diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index e21ef2cd9bd1..22f81159b545 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -39,7 +39,15 @@ public function __construct($config) { $this->config = $config->memcached; } + } + + //-------------------------------------------------------------------- + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize() + { $defaults = $this->config['defaults']; if (class_exists('Memcached')) @@ -130,7 +138,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = 60, $raw = false) + public function save(string $key, $value, int $ttl = 60, bool $raw = false) { $key = $this->prefix.$key; @@ -177,7 +185,7 @@ public function delete(string $key) * * @return mixed */ - public function increment(string $key, $offset = 1) + public function increment(string $key, int $offset = 1) { $key = $this->prefix.$key; @@ -194,7 +202,7 @@ public function increment(string $key, $offset = 1) * * @return mixed */ - public function decrement(string $key, $offset = 1) + public function decrement(string $key, int $offset = 1) { $key = $this->prefix.$key; diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index 0691476e6a0f..3e44eb4fcec5 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -15,7 +15,7 @@ class RedisHandler implements CacheInterface * @static * @var array */ - protected $defaultConfig = [ + protected $config = [ 'host' => '127.0.0.1', 'password' => null, 'port' => 6379, @@ -35,18 +35,20 @@ public function __construct($config) { $this->prefix = $config->prefix ?: ''; - if (! $this->isSupported()) + if (isset($config->redis)) { -// log_message('error', 'Cache: Failed to create Redis object; extension not loaded?'); - return; + $this->config = array_merge($this->config, $config->redis); } + } - $config = $this->defaultConfig; + //-------------------------------------------------------------------- - if (isset($config->redis)) - { - $config = array_merge($this->defaultConfig, $config->redis); - } + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize() + { + $config = $this->config; $this->redis = new Redis(); @@ -124,7 +126,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = 60, $raw = false) + public function save(string $key, $value, int $ttl = 60, bool $raw = false) { $key = $this->prefix.$key; @@ -183,7 +185,7 @@ public function delete(string $key) * * @return mixed */ - public function increment(string $key, $offset = 1) + public function increment(string $key, int $offset = 1) { $key = $this->prefix.$key; @@ -200,7 +202,7 @@ public function increment(string $key, $offset = 1) * * @return mixed */ - public function decrement(string $key, $offset = 1) + public function decrement(string $key, int $offset = 1) { $key = $this->prefix.$key; diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php index c60101ca789a..803f281c6a87 100644 --- a/system/Cache/Handlers/WincacheHandler.php +++ b/system/Cache/Handlers/WincacheHandler.php @@ -17,7 +17,17 @@ public function __construct($config) } //-------------------------------------------------------------------- - + + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize() + { + // Nothing to see here... + } + + //-------------------------------------------------------------------- + /** * Attempts to fetch an item from the cache store. * @@ -51,7 +61,7 @@ public function get(string $key) * * @return mixed */ - public function save(string $key, $value, $ttl = 60, $raw = false) + public function save(string $key, $value, int $ttl = 60, bool $raw = false) { $key = $this->prefix.$key; @@ -84,7 +94,7 @@ public function delete(string $key) * * @return mixed */ - public function increment(string $key, $offset = 1) + public function increment(string $key, int $offset = 1) { $key = $this->prefix.$key; @@ -104,7 +114,7 @@ public function increment(string $key, $offset = 1) * * @return mixed */ - public function decrement(string $key, $offset = 1) + public function decrement(string $key, int $offset = 1) { $key = $this->prefix.$key; From 26251ae72952be7dfb13188cfe341803c225b98e Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 23:24:34 -0500 Subject: [PATCH 0015/1807] Missed use statement --- system/Cache/CacheFactory.php | 4 ++-- system/Cache/CacheInterface.php | 15 +++++++++++---- system/Cache/Handlers/DummyHandler.php | 2 ++ system/Cache/Handlers/FileHandler.php | 4 +++- system/Cache/Handlers/MemcachedHandler.php | 2 ++ system/Cache/Handlers/RedisHandler.php | 2 ++ system/Cache/Handlers/WincacheHandler.php | 2 ++ 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/system/Cache/CacheFactory.php b/system/Cache/CacheFactory.php index 2fca1243b71c..cee74ebea577 100644 --- a/system/Cache/CacheFactory.php +++ b/system/Cache/CacheFactory.php @@ -27,13 +27,13 @@ public static function getHandler($config, string $handler = null, string $backu if (! isset($config->handler) || ! isset($config->backupHandler)) { - throw new \InvalidArgumentException('Cache config must have a handler and backupHanlder set.'); + throw new \InvalidArgumentException('Cache config must have a handler and backupHandler set.'); } $handler = ! empty($handler) ? $handler : $config->handler; $backup = ! empty($backup) ? $backup : $config->backupHandler; - if (! in_array($handler, $config->validHandlers) || ! in_array($backup, $config->validHandlers)) + if (! array_key_exists($handler, $config->validHandlers) || ! array_key_exists($backup, $config->validHandlers)) { throw new \InvalidArgumentException('Cache config has an invalid handler or backup handler specified.'); } diff --git a/system/Cache/CacheInterface.php b/system/Cache/CacheInterface.php index 35ae33f270fd..4a68d273f588 100644 --- a/system/Cache/CacheInterface.php +++ b/system/Cache/CacheInterface.php @@ -2,6 +2,13 @@ interface CacheInterface { + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize(); + + //-------------------------------------------------------------------- + /** * Attempts to fetch an item from the cache store. * @@ -26,7 +33,7 @@ public function get(string $key); * * @return mixed */ - public function save(string $key, $value, $ttl = null, $raw = false); + public function save(string $key, $value, int $ttl = 60, bool $raw = false); //-------------------------------------------------------------------- @@ -49,7 +56,7 @@ public function delete(string $key); * * @return mixed */ - public function increment(string $key, $offset = 1); + public function increment(string $key, int $offset = 1); //-------------------------------------------------------------------- @@ -61,7 +68,7 @@ public function increment(string $key, $offset = 1); * * @return mixed */ - public function decrement(string $key, $offset = 1); + public function decrement(string $key, int $offset = 1); //-------------------------------------------------------------------- @@ -96,7 +103,7 @@ public function getCacheInfo(); public function getMetaData(string $key); //-------------------------------------------------------------------- - + /** * Determines if the driver is supported on this system. * diff --git a/system/Cache/Handlers/DummyHandler.php b/system/Cache/Handlers/DummyHandler.php index 5d95688461ac..5a6f40dfc528 100644 --- a/system/Cache/Handlers/DummyHandler.php +++ b/system/Cache/Handlers/DummyHandler.php @@ -1,5 +1,7 @@ getItem($key); - return is_array($data) ? $data['data'] : null; + return is_array($data) ? $data['data'] : false; } //-------------------------------------------------------------------- diff --git a/system/Cache/Handlers/MemcachedHandler.php b/system/Cache/Handlers/MemcachedHandler.php index 22f81159b545..50452f357e51 100644 --- a/system/Cache/Handlers/MemcachedHandler.php +++ b/system/Cache/Handlers/MemcachedHandler.php @@ -1,5 +1,7 @@ Date: Mon, 27 Jun 2016 23:24:55 -0500 Subject: [PATCH 0016/1807] Adding Cache files --- system/Config/AutoloadConfig.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index d06770b2dfb2..b50af3eb9790 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -39,7 +39,7 @@ /** * AUTO-LOADER - * + * * This file defines the namespaces and class maps so the Autoloader * can find the files as needed. */ @@ -47,13 +47,13 @@ class AutoloadConfig { /** * Array of namespaces for autoloading. - * @var type + * @var type */ public $psr4 = []; /** * Map of class names and locations - * @var type + * @var type */ public $classmap = []; @@ -114,6 +114,13 @@ public function __construct() 'CodeIgniter\CodeIgniter' => BASEPATH.'CodeIgniter.php', 'CodeIgniter\CLI\CLI' => BASEPATH.'CLI/CLI.php', 'CodeIgniter\Loader' => BASEPATH.'Loader.php', + 'CodeIgniter\Cache\CacheFactory' => BASEPATH.'Cache/CacheFactory.php', + 'CodeIgniter\Cache\CacheInterface' => BASEPATH.'Cache/CacheInterface.php', + 'CodeIgniter\Cache\Handlers\DummyHandler' => BASEPATH.'Cache/Handlers/DummyHandler.php', + 'CodeIgniter\Cache\Handlers\FileHandler' => BASEPATH.'Cache/Handlers/FileHandler.php', + 'CodeIgniter\Cache\Handlers\MemcachedHandler' => BASEPATH.'Cache/Handlers/MemcachedHandler.php', + 'CodeIgniter\Cache\Handlers\RedisHandler' => BASEPATH.'Cache/Handlers/RedisHandler.php', + 'CodeIgniter\Cache\Handlers\WincacheHandler' => BASEPATH.'Cache/Handlers/WincacheHandler.php', 'CodeIgniter\Controller' => BASEPATH.'Controller.php', 'CodeIgniter\Config\AutoloadConfig' => BASEPATH.'Config/Autoload.php', 'CodeIgniter\Config\BaseConfig' => BASEPATH.'Config/BaseConfig.php', From 4512606f7a6ac2d4c930118e5e74e9a2ae02ce6f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 23:25:13 -0500 Subject: [PATCH 0017/1807] Don't allow setting 'path' --- system/Config/BaseConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php index 28cc3f7e0dfb..e3dbad46baf7 100644 --- a/system/Config/BaseConfig.php +++ b/system/Config/BaseConfig.php @@ -108,7 +108,7 @@ protected function getEnvValue(string $property, string $prefix, string $shortPr { return $value; } - elseif (($value = getenv($property)) !== false) + elseif (($value = getenv($property)) !== false && $property != 'path') { return $value; } From 574bfa23fe1e555e2e91a55ceafdc98d00531e69 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 23:34:44 -0500 Subject: [PATCH 0018/1807] Return false on no values from Caches::get --- application/Config/Cache.php | 14 ++++---- system/Cache/Handlers/RedisHandler.php | 6 ++-- system/Cache/Handlers/WincacheHandler.php | 42 +++++++++++------------ 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/application/Config/Cache.php b/application/Config/Cache.php index 4b937aeefc46..e297929b42a4 100644 --- a/application/Config/Cache.php +++ b/application/Config/Cache.php @@ -1,5 +1,7 @@ \CodeIgniter\Cache\Handlers\DummyHandler::class, -// 'file' => \CodeIgniter\Cache\Handlers\FileHandler::class, -// 'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class, -// 'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class, -// 'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class, + 'file' => \CodeIgniter\Cache\Handlers\FileHandler::class, + 'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class, + 'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class, + 'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class, ]; } diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index 1ba0589c4d37..3563a6f5d8eb 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -91,7 +91,7 @@ public function get(string $key) if (! isset($data['__ci_type'], $data['__ci_value']) OR $data['__ci_value'] === false) { - return null; + return false; } switch ($data['__ci_type']) @@ -106,10 +106,10 @@ public function get(string $key) case 'NULL': return settype($data['__ci_value'], $data['__ci_type']) ? $data['__ci_value'] - : null; + : false; case 'resource': default: - return null; + return false; } } diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php index 781182f0fde2..adac0d91a129 100644 --- a/system/Cache/Handlers/WincacheHandler.php +++ b/system/Cache/Handlers/WincacheHandler.php @@ -25,7 +25,7 @@ public function __construct($config) */ public function initialize() { - // Nothing to see here... + // Nothing to see here... } //-------------------------------------------------------------------- @@ -41,11 +41,11 @@ public function get(string $key) { $key = $this->prefix.$key; - $success = FALSE; - $data = wincache_ucache_get($key, $success); + $success = false; + $data = wincache_ucache_get($key, $success); // Success returned by reference from wincache_ucache_get() - return ($success) ? $data : FALSE; + return ($success) ? $data : false; } //-------------------------------------------------------------------- @@ -100,10 +100,10 @@ public function increment(string $key, int $offset = 1) { $key = $this->prefix.$key; - $success = FALSE; - $value = wincache_ucache_inc($key, $offset, $success); + $success = false; + $value = wincache_ucache_inc($key, $offset, $success); - return ($success === TRUE) ? $value : FALSE; + return ($success === true) ? $value : false; } //-------------------------------------------------------------------- @@ -120,10 +120,10 @@ public function decrement(string $key, int $offset = 1) { $key = $this->prefix.$key; - $success = FALSE; - $value = wincache_ucache_dec($key, $offset, $success); + $success = false; + $value = wincache_ucache_dec($key, $offset, $success); - return ($success === TRUE) ? $value : FALSE; + return ($success === true) ? $value : false; } //-------------------------------------------------------------------- @@ -150,7 +150,7 @@ public function clean() */ public function getCacheInfo() { - return wincache_ucache_info(TRUE); + return wincache_ucache_info(true); } //-------------------------------------------------------------------- @@ -166,21 +166,21 @@ public function getMetaData(string $key) { $key = $this->prefix.$key; - if ($stored = wincache_ucache_info(FALSE, $key)) + if ($stored = wincache_ucache_info(false, $key)) { - $age = $stored['ucache_entries'][1]['age_seconds']; - $ttl = $stored['ucache_entries'][1]['ttl_seconds']; + $age = $stored['ucache_entries'][1]['age_seconds']; + $ttl = $stored['ucache_entries'][1]['ttl_seconds']; $hitcount = $stored['ucache_entries'][1]['hitcount']; - return array( - 'expire' => $ttl - $age, - 'hitcount' => $hitcount, - 'age' => $age, - 'ttl' => $ttl - ); + return [ + 'expire' => $ttl-$age, + 'hitcount' => $hitcount, + 'age' => $age, + 'ttl' => $ttl, + ]; } - return FALSE; + return false; } //-------------------------------------------------------------------- From ee0cbacb6f580d324f37cc939acbb63bdc4306c7 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 23:35:32 -0500 Subject: [PATCH 0019/1807] Add cache common function --- system/Common.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/system/Common.php b/system/Common.php index aaabf1dd1247..6e3544588beb 100644 --- a/system/Common.php +++ b/system/Common.php @@ -53,6 +53,38 @@ // Services Convenience Functions //-------------------------------------------------------------------- +if (! function_exists('cache')) +{ + /** + * A convenience method that provides access to the Cache + * object. If no parameter is provided, will return the object, + * otherwise, will attempt to return the cached value. + * + * Examples: + * cache()->save('foo', 'bar'); + * $foo = cache('bar'); + * + * @param string|null $key + * + * @return mixed + */ + function cache(string $key = null) + { + $cache = \Config\Services::cache(); + + // No params - return cache object + if (is_null($key)) + { + return $cache; + } + + // Still here? Retrieve the value. + return $cache->get($key); + } +} + +//-------------------------------------------------------------------- + if ( ! function_exists('view')) { /** From 54975893a6d25ef91e70115eebbd5c609339d745 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 27 Jun 2016 23:44:29 -0500 Subject: [PATCH 0020/1807] [ci skip] Adding docs for cache common function --- .../source/general/common_functions.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst index e3daf5491af8..07c448be1437 100644 --- a/user_guide_src/source/general/common_functions.rst +++ b/user_guide_src/source/general/common_functions.rst @@ -15,6 +15,21 @@ Global Functions Service Accessors ================= +.. php:function:: cache ( [$key] ) + + :param string $key: The cache name of the item to retrieve from cache (Optional) + :returns: Either the cache object, or the item retrieved from the cache + :rtype: mixed + + If no $key is provided, will return the Cache engine instance. If a $key + is provided, will return the value of $key as stored in the cache currently, + or false if no value is found. + + Examples:: + + $foo = cache('foo'); + $cache = cache(); + .. php:function:: esc ( $data, $context='html' [, $encoding]) :param string|array $data: The information to be escaped. From 738ff0b7c984a4e1aadde125274758b7f63d92f5 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 28 Jun 2016 00:06:47 -0500 Subject: [PATCH 0021/1807] [ci skip] Cache library documentation --- system/Cache/Handlers/RedisHandler.php | 3 +- user_guide_src/source/libraries/caching.rst | 246 ++++++++++++++++++++ 2 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 user_guide_src/source/libraries/caching.rst diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index 3563a6f5d8eb..725b14e2ed02 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -1,6 +1,7 @@ getMessage().')'); + throw new CriticalError('Cache: Redis connection refused ('.$e->getMessage().')'); } } diff --git a/user_guide_src/source/libraries/caching.rst b/user_guide_src/source/libraries/caching.rst new file mode 100644 index 000000000000..4f204e2ae2f5 --- /dev/null +++ b/user_guide_src/source/libraries/caching.rst @@ -0,0 +1,246 @@ +############## +Caching Driver +############## + +CodeIgniter features wrappers around some of the most popular forms of +fast and dynamic caching. All but file-based caching require specific +server requirements, and a Fatal Exception will be thrown if server +requirements are not met. + +.. contents:: +:local: + +************* +Example Usage +************* + +The following example shows a common usage pattern within your controllers. + +:: + + if ( ! $foo = cache('foo')) + { + echo 'Saving to the cache!
'; + $foo = 'foobarbaz!'; + + // Save into the cache for 5 minutes + cache()->save('foo', $foo, 300); + } + + echo $foo; + +You can grab an instance of the cache engine directly through the Services class:: + + $cache = \Config\Services::cache(); + + $foo = $cache->get('foo'); + +===================== +Configuring the Cache +===================== + +All configuration for the cache engine is done in **application/Config/Cache.php**. In that file, +the following items are available. + +**$handler** + +The is the name of the handler that should be used as the primary handler when starting up the engine. +Available names are: dummy, file, memcached, redis, wincache. + +**$backupHandler** + +In the case that the first choice $hanlder is not available, this is the next cache handler to load. +This is commonly the **file** handler since the file system is always available, but may not fit +more complex, multi-server setups. + +**$prefix** + +If you have more than one application using the same cache storage, you can add a custom prefix +here that is prepended to all key names. + +**$path** + +This is used by the ``file`` handler to show where it should save the cache files to. + +**$memcached** + +This is an array of servers that will be used when using the ``Memcache(d)`` handler. + +**$redis** + +The settings for the Redis server that you wish to use when using the ``Redis`` handler. + +=============== +Class Reference +=============== + + .. php:method:: isSupported($handler) + + :param string $handler: the name of the caching handler + :returns: TRUE if supported, FALSE if not + :rtype: bool + + .. php:method:: get($id) + + :param string $id: Cache item name + :returns: Item value or FALSE if not found + :rtype: mixed + + This method will attempt to fetch an item from the cache store. If the + item does not exist, the method will return FALSE. + :: + + $foo = $cache->get('my_cached_item'); + + .. php:method:: save($id, $data[, $ttl = 60[, $raw = FALSE]]) + + :param string $id: Cache item name + :param mixed $data: the data to save + :param int $ttl: Time To Live, in seconds (default 60) + :param bool $raw: Whether to store the raw value + :returns: TRUE on success, FALSE on failure + :rtype: string + + This method will save an item to the cache store. If saving fails, the + method will return FALSE. + :: + + $cache->save('cache_item_id', 'data_to_cache'); + + .. note:: The ``$raw`` parameter is only utilized by Memcache, +in order to allow usage of ``increment()`` and ``decrement()``. + + .. php:method:: delete($id) + + :param string $id: name of cached item + :returns: TRUE on success, FALSE on failure + :rtype: bool + + This method will delete a specific item from the cache store. If item + deletion fails, the method will return FALSE. + :: + + $cache->delete('cache_item_id'); + + .. php:method:: increment($id[, $offset = 1]) + + :param string $id: Cache ID + :param int $offset: Step/value to add + :returns: New value on success, FALSE on failure + :rtype: mixed + + Performs atomic incrementation of a raw stored value. + :: + + // 'iterator' has a value of 2 + + $cache->increment('iterator'); // 'iterator' is now 3 + + $cache->increment('iterator', 3); // 'iterator' is now 6 + + .. php:method:: decrement($id[, $offset = 1]) + + :param string $id: Cache ID + :param int $offset: Step/value to reduce by + :returns: New value on success, FALSE on failure + :rtype: mixed + + Performs atomic decrementation of a raw stored value. + :: + + // 'iterator' has a value of 6 + + $cache->decrement('iterator'); // 'iterator' is now 5 + + $cache->decrement('iterator', 2); // 'iterator' is now 3 + + .. php:method:: clean() + + :returns: TRUE on success, FALSE on failure + :rtype: bool + + This method will 'clean' the entire cache. If the deletion of the + cache files fails, the method will return FALSE. + :: + + $cache->clean(); + + .. php:method:: cache_info() + + :returns: Information on the entire cache database + :rtype: mixed + + This method will return information on the entire cache. + :: + + var_dump($cache->cache_info()); + + .. note:: The information returned and the structure of the data is dependent +on which adapter is being used. + + .. php:method:: getMetadata($id) + + :param string $id: Cache item name + :returns: Metadata for the cached item + :rtype: mixed + + This method will return detailed information on a specific item in the + cache. + :: + + var_dump($cache->getMetadata('my_cached_item')); + + .. note:: The information returned and the structure of the data is dependent +on which adapter is being used. + +******* +Drivers +******* + +File-based Caching +================== + +Unlike caching from the Output Class, the driver file-based caching +allows for pieces of view files to be cached. Use this with care, and +make sure to benchmark your application, as a point can come where disk +I/O will negate positive gains by caching. + +Memcached Caching +================= + +Multiple Memcached servers can be specified in the cache configuration file. + +For more information on Memcached, please see +`http://php.net/memcached `_. + +WinCache Caching +================ + +Under Windows, you can also utilize the WinCache driver. + +For more information on WinCache, please see +`http://php.net/wincache `_. + +Redis Caching +============= + +Redis is an in-memory key-value store which can operate in LRU cache mode. +To use it, you need `Redis server and phpredis PHP extension `_. + +Config options to connect to redis server must be stored in the application/config/redis.php file. +Available options are:: + + $config['host'] = '127.0.0.1'; + $config['password'] = NULL; + $config['port'] = 6379; + $config['timeout'] = 0; + +For more information on Redis, please see +`http://redis.io `_. + +Dummy Cache +=========== + +This is a caching backend that will always 'miss.' It stores no data, +but lets you keep your caching code in place in environments that don't +support your chosen cache. \ No newline at end of file From 59fb85c2fbe236025e0522bc0b7f791f836116ec Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 28 Jun 2016 21:59:55 -0500 Subject: [PATCH 0022/1807] Tutorial corrections --- user_guide_src/source/tutorial/news_section.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/user_guide_src/source/tutorial/news_section.rst b/user_guide_src/source/tutorial/news_section.rst index b25eb63acf11..33ec3dabba04 100644 --- a/user_guide_src/source/tutorial/news_section.rst +++ b/user_guide_src/source/tutorial/news_section.rst @@ -65,7 +65,7 @@ following code to your model. { if ($slug === false) { - return $this->findAll(); + $this->findAll(); } return $this->asArray() @@ -195,13 +195,13 @@ add some code to the controller and create a new view. Go back to the if (empty($data['news'])) { - throw new \CodeIgniter\PageNotFoundException('Cannot page the page: '. $slug); + throw new \CodeIgniter\PageNotFoundException('Cannot find the page: '. $slug); } - $data['title'] = $data['news'][0]['title']; + $data['title'] = $data['news']['title']; echo view('Templates/Header', $data); - echo view('News/Index', $data); + echo view('News/View', $data); echo view('Templates/Footer'); } @@ -214,7 +214,7 @@ The only things left to do is create the corresponding view at '.$news['title'].''; - echo $$news['text']; + echo $news['text']; Routing ------- From 7061d4ec9db46191c1c999cfa0a4aa0c2a8a8650 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 28 Jun 2016 22:04:41 -0500 Subject: [PATCH 0023/1807] Defaulting controllers to be namespaced to encourage use of Namespaces now. --- application/Config/Routes.php | 2 +- application/Controllers/Home.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/Config/Routes.php b/application/Config/Routes.php index de43652a760a..d10976275bfd 100644 --- a/application/Config/Routes.php +++ b/application/Config/Routes.php @@ -58,7 +58,7 @@ * Controllers when no specific route has been defined. If false, * only routes that have been defined here will be available. */ -$routes->setDefaultNamespace(''); +$routes->setDefaultNamespace('App\Controllers'); $routes->setDefaultController('Home'); $routes->setDefaultMethod('index'); $routes->setTranslateURIDashes(false); diff --git a/application/Controllers/Home.php b/application/Controllers/Home.php index 9c5dd52c7d63..85981149e2ed 100644 --- a/application/Controllers/Home.php +++ b/application/Controllers/Home.php @@ -1,4 +1,4 @@ - Date: Tue, 28 Jun 2016 22:44:37 -0500 Subject: [PATCH 0024/1807] Added sample database entries to env.example --- application/env.example | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/application/env.example b/application/env.example index 73c2384fa03e..6086d9cc89fc 100644 --- a/application/env.example +++ b/application/env.example @@ -39,6 +39,22 @@ # app.CSPEnabled = false +#-------------------------------------------------------------------- +# DATABASE +#-------------------------------------------------------------------- + +# database.default.hostname = localhost +# database.default.database = ci4 +# database.default.username = root +# database.default.password = root +# database.default.DBDriver = MySQLi + +# database.tests.hostname = localhost +# database.tests.database = ci4 +# database.tests.username = root +# database.tests.password = root +# database.tests.DBDriver = MySQLi + #-------------------------------------------------------------------- # CONTENT SECURITY POLICY #-------------------------------------------------------------------- From f343fd2fdb444824941a160e9d6555b7b6990577 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 28 Jun 2016 23:02:52 -0500 Subject: [PATCH 0025/1807] Fixing bug with routes and default namespaces. --- system/CodeIgniter.php | 12 ++++++------ system/Router/RouteCollection.php | 12 ++++++++++++ system/Router/Router.php | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 6edf0b6d8690..0b47f53a9540 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -78,21 +78,21 @@ class CodeIgniter /** * Current request. - * + * * @var \CodeIgniter\HTTP\Request */ protected $request; /** * Current response. - * + * * @var \CodeIgniter\HTTP\Response */ protected $response; /** * Router to use. - * + * * @var \CodeIgniter\Router\Router */ protected $router; @@ -105,7 +105,7 @@ class CodeIgniter /** * Controller method to invoke. - * + * * @var string */ protected $method; @@ -138,7 +138,7 @@ public function __construct(int $startMemory, float $startTime, App $config) * The class entry point. This is where the magic happens and all * of the framework pieces are pulled together and shown how to * make beautiful music together. Or something like that. :) - * + * * @param RouteCollectionInterface $routes */ public function run(RouteCollectionInterface $routes = null) @@ -216,7 +216,7 @@ public function run(RouteCollectionInterface $routes = null) /** * Start the Benchmark - * + * * The timer is used to display total script execution both in the * debug toolbar, and potentially on the displayed page. */ diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index ab56b3f2994e..4302a1c79500 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -373,6 +373,18 @@ public function getDefaultMethod(): string //-------------------------------------------------------------------- + /** + * Returns the default namespace as set in the Routes config file. + * + * @return string + */ + public function getDefaultNamespace(): string + { + return $this->defaultNamespace; + } + + //-------------------------------------------------------------------- + /** * Returns the current value of the translateURIDashses setting. * diff --git a/system/Router/Router.php b/system/Router/Router.php index 32698c5e6686..076a0b665c5d 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -139,7 +139,9 @@ public function handle(string $uri = null) // everything runs off of it's default settings. if (empty($uri)) { - return $this->controller; + return strpos($this->controller, '\\') === false + ? $this->collection->getDefaultNamespace().$this->controller + : $this->controller; } if ($this->checkRoutes($uri)) From c2649da19784d88dd1849ed9c0c3e15c70b2f1bf Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 28 Jun 2016 23:04:22 -0500 Subject: [PATCH 0026/1807] updated test --- tests/system/CodeIgniterTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index 47ba2607a695..8bdcddffb99f 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -10,6 +10,8 @@ class CodeIgniterTest extends \CIUnitTestCase */ protected $codeigniter; + protected $routes; + //-------------------------------------------------------------------- public function setUp() @@ -39,6 +41,9 @@ public function testRunDefaultRoute() //-------------------------------------------------------------------- + /** + * @group route + */ public function testRunEmptyDefaultRoute() { $_SERVER['argv'] = [ From e7ace124e858f6005222cac4999ab431a92cc5f8 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 28 Jun 2016 23:42:14 -0500 Subject: [PATCH 0027/1807] Adding note about namespaced views. --- user_guide_src/source/general/views.rst | 33 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/user_guide_src/source/general/views.rst b/user_guide_src/source/general/views.rst index a368f3cd350f..0091093503a4 100644 --- a/user_guide_src/source/general/views.rst +++ b/user_guide_src/source/general/views.rst @@ -35,13 +35,13 @@ To load a particular view file you will use the following function:: view('name'); -Where _name_ is the name of your view file. +Where _name_ is the name of your view file. .. important:: The .php file extension does not need to be specified, but all views are expected to end with the .php extension. Now, open the controller file you made earlier called ``Blog.php``, and replace the echo statement with the view function:: - class Blog extends \CodeIgniter\Controller + class Blog extends \CodeIgniter\Controller { public function index() { @@ -53,6 +53,19 @@ If you visit your site using the URL you did earlier you should see your new vie example.com/index.php/blog/ +Namespaced Views +---------------- + +You can store views under a **View** directory that is namespaced, and load that view as if it was namespaced. While +PHP does not support loading non-class files from a namespace, CodeIgniter provides this feature to make it possible +to package your views together in a module-like fashion for easy re-use or distribution. + +If you have ``Blog`` directory that has a PSR-4 mapping setup in the :doc:`Autoloader ` living +under the namespace ``Example\Blog``, you could retrieve view files as if they were namespaced also. Following this +example, you could load the **BlogView** file from **/blog/views** by prepending the namespace to the view name:: + + echo view('Example\Blog\BlogView'); + Loading Multiple Views ====================== @@ -74,7 +87,7 @@ content view, and a footer view. That might look something like this:: echo view('footer'); } } - + In the example above, we are using "dynamically added data", which you will see below. Storing Views within Sub-directories @@ -96,7 +109,7 @@ Here's an example:: 'heading' => 'My Heading', 'message' => 'My Message' ]; - + echo view('blogview', $data); Let's try it with your controller file. Open it and add this code:: @@ -107,11 +120,11 @@ Let's try it with your controller file. Open it and add this code:: { $data['title'] = "My Real Title"; $data['heading'] = "My Real Heading"; - + echo view('blogview', $data); } } - + Now open your view file and change the text to variables that correspond to the array keys in your data:: @@ -147,11 +160,11 @@ sets the data, and renders the view. While this is often exactly what you want, want to work with it more directly. In that case you can access the View service directly:: $renderer = \Config\Services::renderer(); - + .. important:: You should create services only within controllers. If you need access to the View class from a library, you should set that as a dependency in the constructor. -Then you can use any of the three standard methods that it provides. +Then you can use any of the three standard methods that it provides. * **render('view_name', array $options)** Performs the rendering of the view and its data. The $options array is unused by default, but provided for third-party libraries to use when integrating with different template engines. @@ -192,11 +205,11 @@ or in an href attribute, you would need different escaping rules to be effective context as the second parameter. Valid contexts are 'html', 'js', 'css', 'url', and 'attr':: Some Link - + - + + +*************** +Class Reference +*************** + +.. php:interface:: CodeIgniter\\View\\RendererableInterface + + .. php:method:: render($view[, $options[, $saveData=false]]]) + + :param string $view: File name of the view source + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string + + Builds the output based upon a file name and any data that has already been set:: + + echo $renderer->render('myview'); + + Options supported: + + - ``cache`` - the time in seconds, to save a view's results + - ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath + - ``saveData`` - true if the view data parameter should be retained for subsequent calls + + + .. php:method:: setData([$data[, $context=null]]) + + :param array $data: Array of view data strings, as key/value pairs + :param string $context: The context to use for data escaping. + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RenderableInterface. + + Sets several pieces of view data at once:: + + $renderer->setData(['name'=>'George', 'position'=>'Boss']); + + Supported escape contexts: html, css, js, url. + If 'raw', no escaping will happen. + + .. php:method:: setVar($name[, $value=null[, $context=null]]) + + :param string $name: Name of the view data variable + :param mixed $value: The value of this view data + :param string $context: The context to use for data escaping. + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RenderableInterface. + + Sets a single piece of view data:: + + $renderer->setVar('name','Joe','html'); + + Supported escape contexts: html, css, js, url, or raw. + If 'raw', no escaping will happen. + diff --git a/user_guide_src/source/general/views.rst b/user_guide_src/source/general/views.rst index f56a7137962a..6fd9bc2eceaa 100644 --- a/user_guide_src/source/general/views.rst +++ b/user_guide_src/source/general/views.rst @@ -18,12 +18,12 @@ Creating a View Using your text editor, create a file called ``BlogView.php`` and put this in it:: - - My Blog - - -

Welcome to my Blog!

- + + My Blog + + +

Welcome to my Blog!

+ Then save the file in your **application/Views** directory. @@ -65,7 +65,7 @@ content view, and a footer view. That might look something like this:: public function index() { $data = [ - 'page_title' = 'Your title' + 'page_title' => 'Your title' ]; echo view('header'); @@ -143,12 +143,12 @@ Let's try it with your controller file. Open it and add this code:: Now open your view file and change the text to variables that correspond to the array keys in your data:: - - <?= $title ?> - - -

- + + <?= $title ?> + + +

+ Then load the page at the URL you've been using and you should see the variables replaced. @@ -167,70 +167,6 @@ into the `$option` array in the third parameter. echo view('blogview', $data, ['saveData' => true]); -Direct Access To View Class -=========================== - -The ``view()`` function is a convenience method that grabs an instance of the ``renderer`` service, -sets the data, and renders the view. While this is often exactly what you want, you may find times where you -want to work with it more directly. In that case you can access the View service directly:: - - $renderer = \Config\Services::renderer(); - -.. important:: You should create services only within controllers. If you need access to the View class - from a library, you should set that as a dependency in the constructor. - -Then you can use any of the three standard methods that it provides. - -* **render('view_name', array $options)** Performs the rendering of the view and its data. The $options array is - unused by default, but provided for third-party libraries to use when integrating with different template engines. -* **setVar('name', 'value', $context=null)** Sets a single piece of dynamic data. $context specifies the context - to escape for. Defaults to no escaping. Set to empty value to skip escaping. -* **setData($array, $context=null)** Takes an array of key/value pairs for dynamic data and optionally escapes it. - $context specifies the context to escape for. Defaults to no escaping. Set to empty value to skip escaping. - -The `setVar()` and `setData()` methods are chainable, allowing you to combine a number of different calls together in a chain:: - - service('renderer')->setVar('one', $one) - ->setVar('two', $two) - ->render('myView'); - -Escaping Data -============= - -When you pass data to the ``setVar()`` and ``setData()`` functions you have the option to escape the data to protect -against cross-site scripting attacks. As the last parameter in either method, you can pass the desired context to -escape the data for. See below for context descriptions. - -If you don't want the data to be escaped, you can pass `null` or `raw` as the final parameter to each function:: - - $renderer->setVar('one', $one, 'raw'); - -If you choose not to escape data, or you are passing in an object instance, you can manually escape the data within -the view with the ``esc()`` function. The first parameter is the string to escape. The second parameter is the -context to escape the data for (see below):: - - getStat()) ?> - -Escaping Contexts ------------------ - -By default, the ``esc()`` and, in turn, the ``setVar()`` and ``setData()`` functions assume that the data you want to -escape is intended to be used within standard HTML. However, if the data is intended for use in Javascript, CSS, -or in an href attribute, you would need different escaping rules to be effective. You can pass in the name of the -context as the second parameter. Valid contexts are 'html', 'js', 'css', 'url', and 'attr':: - - Some Link - - - - - Creating Loops ============== @@ -276,70 +212,3 @@ Now open your view file and create a loop:: -View Cells -========== - -View Cells allow you to insert HTML that is generated outside of your controller. It simply calls the specified -class and method, which must return valid HTML. This method could be in an callable method, found in any class -that the autoloader can locate. The only restriction is that the class can not have any constructor parameters. -This is intended to be used within views, and is a great aid to modularizing your code. -:: - - - -In this example, the class ``App\Libraries\Blog`` is loaded, and the method ``recentPosts()`` is ran. That method -must return a string with the generated HTML. The method used can be either a static method or not. Either way works. - -Cell Parameters ---------------- - -You can further refine the call by passing a string with a list of parameters in the second parameter that are passed -to the method as an array of key/value pairs, or a comma-seperated string of key/value pairs:: - - // Passing Parameter Array - 'codeigniter', 'limit' => 5]) ?> - - // Passing Parameter String - - - public function recentPosts(array $params=[]) - { - $posts = $this->blogModel->where('category', $params['category']) - ->orderBy('published_on', 'desc') - ->limit($params['limit']) - ->get(); - - return view('recentPosts', ['posts' => $posts]); - } - -Additionally, you can use parameter names that match the parameter variables in the method for better readability. -When you use it this way, all of the parameters must always be specified in the view cell call:: - - - - public function recentPosts(int $limit, string $category) - { - $posts = $this->blogModel->where('category', $category) - ->orderBy('published_on', 'desc') - ->limit($limit) - ->get(); - - return view('recentPosts', ['posts' => $posts]); - } - -Cell Caching ------------- - -You can cache the results of the view cell call by passing the number of seconds to cache the data for as the -third parameter. This will use the currently configured cache engine. -:: - - // Cache the view for 5 minutes - - -You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name -as the fourth parameter.:: - - // Cache the view for 5 minutes - - diff --git a/user_guide_src/source/libraries/response.rst b/user_guide_src/source/libraries/response.rst index ea6e8b924667..727377894b92 100644 --- a/user_guide_src/source/libraries/response.rst +++ b/user_guide_src/source/libraries/response.rst @@ -1,6 +1,6 @@ -************** +============== HTTP Responses -************** +============== The Response class extends the :doc:`HTTP Message Class ` with methods only appropriate for a server responding to the client that called it. From a14ef03eb9cfa0b6b01e4fbdef43fd891fa0aa6f Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Thu, 21 Jul 2016 15:57:46 -0700 Subject: [PATCH 0117/1807] Added attr context to writeup --- user_guide_src/source/general/view_renderer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/general/view_renderer.rst b/user_guide_src/source/general/view_renderer.rst index ebd6187863a0..f2afa255509a 100644 --- a/user_guide_src/source/general/view_renderer.rst +++ b/user_guide_src/source/general/view_renderer.rst @@ -96,7 +96,7 @@ Class Reference $renderer->setData(['name'=>'George', 'position'=>'Boss']); - Supported escape contexts: html, css, js, url. + Supported escape contexts: html, css, js, url, or attr or raw. If 'raw', no escaping will happen. .. php:method:: setVar($name[, $value=null[, $context=null]]) @@ -111,6 +111,6 @@ Class Reference $renderer->setVar('name','Joe','html'); - Supported escape contexts: html, css, js, url, or raw. + Supported escape contexts: html, css, js, url, attr or raw. If 'raw', no escaping will happen. From 9703fd8ff91c2ad22f53f6016c2711df8af6ca81 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 21 Jul 2016 22:26:30 -0500 Subject: [PATCH 0118/1807] [ci skip] Add new App namespace to PSR4 to keep utility classes, like Filters, working even with custom namespaces. Fixes #188 --- application/Config/Autoload.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/Config/Autoload.php b/application/Config/Autoload.php index 097844155456..67042c8f7d8d 100644 --- a/application/Config/Autoload.php +++ b/application/Config/Autoload.php @@ -51,8 +51,8 @@ public function __construct() */ $psr4 = [ 'Config' => APPPATH.'Config', - APP_NAMESPACE.'\Controllers' => APPPATH.'Controllers', - APP_NAMESPACE => realpath(APPPATH), + APP_NAMESPACE => APPPATH, // For custom namespace + 'App' => APPPATH, // To ensure filters, etc still found ]; /** From d4822062bebaa86d5bdb700a5c89b98e1b7bdece Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Fri, 22 Jul 2016 22:12:02 -0700 Subject: [PATCH 0119/1807] Aligning View & Parser --- system/View/Parser.php | 250 ++++++++++++++++++++++++++++ system/View/RenderableInterface.php | 19 ++- system/View/View.php | 66 +++++++- tests/system/View/ParserTest.php | 99 +++++++++++ 4 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 system/View/Parser.php create mode 100644 tests/system/View/ParserTest.php diff --git a/system/View/Parser.php b/system/View/Parser.php new file mode 100644 index 000000000000..3adf56304a81 --- /dev/null +++ b/system/View/Parser.php @@ -0,0 +1,250 @@ + delimiters + * + * @package CodeIgniter\View + */ + +class Parser extends View { + + /** + * Left delimiter character for pseudo vars + * + * @var string + */ + public $leftDelimiter = '{'; + + /** + * Right delimiter character for pseudo vars + * + * @var string + */ + public $rightDelimiter = '}'; + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * @param string $viewPath + * @param type $loader + * @param bool $debug + * @param Logger $logger + */ + public function __construct(string $viewPath=null, $loader=null, bool $debug = null, Logger $logger = null) + { + parent::__construct($viewPath,$loader,$debug,$logger); + } + + // -------------------------------------------------------------------- + + /** + * Parse a template + * + * Parses pseudo-variables contained in the specified template view, + * replacing them with the data in the second param + * + * @param string $view + * @param array $options + * @param bool $saveData + * + * @return string + */ + public function render(string $view, array $options=null, bool $saveData=false) : string + { + // get the view template file + $template = parent::render($template); + return $this->_parse($template, $data, $options); + } + + // -------------------------------------------------------------------- + + /** + * Parse a String + * + * Parses pseudo-variables contained in the specified string, + * replacing them with the data in the second param + * + * @param string $template + * @param array $options + * @param bool $saveData + * + * @return string + */ + public function renderString(string $template, array $options=null, bool $saveData=false) : string + { + return $this->_parse($template, $options, $saveData); + } + + // -------------------------------------------------------------------- + + /** + * Parse a template + * + * Parses pseudo-variables contained in the specified template, + * replacing them with the data in the second param + * + * @param string $template + * @param array $data + * @param array $options + * @return string + */ + protected function _parse($template, $data, $return = FALSE) + { + if ($template === '') + { + return FALSE; + } + + $replace = array(); + foreach ($data as $key => $val) + { + $replace = array_merge( + $replace, + is_array($val) + ? $this->_parse_pair($key, $val, $template) + : $this->_parse_single($key, (string) $val, $template) + ); + } + + unset($data); + $template = strtr($template, $replace); + + if ($return === FALSE) + { + $this->CI->output->append_output($template); + } + + return $template; + } + + // -------------------------------------------------------------------- + + /** + * Set the left/right variable delimiters + * + * @param string + * @param string + * @return void + */ + public function setDelimiters($l = '{', $r = '}') + { + $this->leftDelimiter = $l; + $this->rightDelimiter = $r; + } + + // -------------------------------------------------------------------- + + /** + * Parse a single key/value + * + * @param string + * @param string + * @param string + * @return string + */ + protected function _parseSingle($key, $val, $string) + { + return array($this->leftDelimiter.$key.$this->rightDelimiter => (string) $val); + } + + // -------------------------------------------------------------------- + + /** + * Parse a tag pair + * + * Parses tag pairs: {some_tag} string... {/some_tag} + * + * @param string + * @param array + * @param string + * @return string + */ + protected function _parsePair($variable, $data, $string) + { + $replace = array(); + preg_match_all( + '#'.preg_quote($this->leftDelimiter.$variable.$this->rightDelimiter).'(.+?)'.preg_quote($this->leftDelimiter.'/'.$variable.$this->rightDelimiter).'#s', + $string, + $matches, + PREG_SET_ORDER + ); + + foreach ($matches as $match) + { + $str = ''; + foreach ($data as $row) + { + $temp = array(); + foreach ($row as $key => $val) + { + if (is_array($val)) + { + $pair = $this->_parse_pair($key, $val, $match[1]); + if ( ! empty($pair)) + { + $temp = array_merge($temp, $pair); + } + + continue; + } + + $temp[$this->leftDelimiter.$key.$this->rightDelimiter] = $val; + } + + $str .= strtr($match[1], $temp); + } + + $replace[$match[0]] = $str; + } + + return $replace; + } + +} diff --git a/system/View/RenderableInterface.php b/system/View/RenderableInterface.php index f920fe02a88e..4cb79439f9d7 100644 --- a/system/View/RenderableInterface.php +++ b/system/View/RenderableInterface.php @@ -58,7 +58,24 @@ interface RenderableInterface { * * @return string */ - public function render(string $view, array $options=null, $saveData=false): string; + public function render(string $view, array $options=null, bool $saveData=false): string; + + //-------------------------------------------------------------------- + + /** + * Builds the output based upon a stringand any + * data that has already been set. + * + * @param string $view The view contents + * @param array $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param bool $saveData If true, will save data for use with any other calls, + * if false, will clean the data after displaying the view. + * + * @return string + */ + public function renderString(string $view, array $options=null, bool $saveData=false): string; //-------------------------------------------------------------------- diff --git a/system/View/View.php b/system/View/View.php index f2ab5557c0a9..7b1573a61d99 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -120,12 +120,12 @@ public function __construct(string $viewPath=null, $loader=null, bool $debug = n * - cache_name Name to use for cache * * @param string $view - * @param array $options // Unused in this implementation + * @param array $options * @param bool $saveData * * @return string */ - public function render(string $view, array $options=null, $saveData=false): string + public function render(string $view, array $options=null, bool $saveData=false): string { $start = microtime(true); @@ -184,6 +184,68 @@ public function render(string $view, array $options=null, $saveData=false): stri //-------------------------------------------------------------------- + /** + * Builds the output based upon a file name and any + * data that has already been set. + * + * Valid $options: + * - cache number of seconds to cache for + * - cache_name Name to use for cache + * + * @param string $view + * @param array $options + * @param bool $saveData + * + * @return string + */ + public function renderString(string $view, array $options=null, bool $saveData=false): string + { + $start = microtime(true); + + $view = str_replace('.php', '', $view).'.php'; + + // Was it cached? + if (isset($options['cache'])) + { + $cacheName = $options['cache_name'] ?: str_replace('.php', '', $view); + + if ($output = cache($cacheName)) + { + $this->logPerformance($start, microtime(true), $view); + return $output; + } + } + + $file = $this->viewPath.$view; + + // Make our view data available to the view. + extract($this->data); + + if (! $saveData) + { + $this->data = []; + } + + ob_start(); + + include($file); + + $output = ob_get_contents(); + @ob_end_clean(); + + $this->logPerformance($start, microtime(true), $view); + + // Should we cache? + if (isset($options['cache'])) + { + cache()->save($cacheName, $output, (int)$options['cache']); + } + + return $output; + } + + //-------------------------------------------------------------------- + /** * Sets several pieces of view data at once. * diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php new file mode 100644 index 000000000000..1fed20a5a549 --- /dev/null +++ b/tests/system/View/ParserTest.php @@ -0,0 +1,99 @@ +loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); + $this->viewsDir = __DIR__.'/Views'; + $this->parser = new Parser($this->viewsDir, $this->loader); + } + + // -------------------------------------------------------------------- + + public function testSetDelimiters() + { + + // Make sure default delimiters are there + $this->assertEquals('{', $this->parser->leftDelimiter); + $this->assertEquals('}', $this->parser->rightDelimiter); + + // Change them to square brackets + $this->parser->setDelimiters('[', ']'); + + // Make sure they changed + $this->assertEquals('[', $this->parser->leftDelimiter); + $this->assertEquals(']', $this->parser->rightDelimiter); + + // Reset them + $this->parser->setDelimiters(); + + // Make sure default delimiters are there + $this->assertEquals('{', $this->parser->leftDelimiter); + $this->assertEquals('}', $this->parser->rightDelimiter); + } + + // -------------------------------------------------------------------- + + public function testParseString() + { + $data = array( + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.' + ); + + $template = "{title}\n{body}"; + + $result = implode("\n", $data); + + $this->assertEquals($result, $this->parser->renderString($template, $data, TRUE)); + } + + // -------------------------------------------------------------------- + + public function testParse() + { + $this->_parseNoTemplate(); + $this->_parseVarPair(); + $this->_mismatchedVarPair(); + } + + // -------------------------------------------------------------------- + + private function _parseNoTemplate() + { + $this->assertFalse($this->parser->renderString('', '', TRUE)); + } + + // -------------------------------------------------------------------- + + private function _parseVarPair() + { + $data = array( + 'title' => 'Super Heroes', + 'powers' => array(array('invisibility' => 'yes', 'flying' => 'no')) + ); + + $template = "{title}\n{powers}{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{/powers}"; + + $this->assertEquals("Super Heroes\nyes\nno\nsecond: yes no", $this->parser->renderString($template, $data, TRUE)); + } + + // -------------------------------------------------------------------- + + private function _mismatchedVarPair() + { + $data = array( + 'title' => 'Super Heroes', + 'powers' => array(array('invisibility' => 'yes', 'flying' => 'no')) + ); + + $template = "{title}\n{powers}{invisibility}\n{flying}"; + $result = "Super Heroes\n{powers}{invisibility}\n{flying}"; + + $this->assertEquals($result, $this->parser->renderString($template, $data, TRUE)); + } + +} \ No newline at end of file From 75c0a1b319cee7b89ca96c3273e269442ca55d6e Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Fri, 22 Jul 2016 22:18:16 -0700 Subject: [PATCH 0120/1807] Sync --- application/Config/App.php | 4 +- application/Config/Autoload.php | 4 +- application/Config/Filters.php | 7 +- application/Controllers/Home.php | 2 +- application/Filters/DebugToolbar.php | 56 + phpcbf-fixed.diff | 8093 +++++++++++++++++ system/CodeIgniter.php | 90 +- system/Database/BaseBuilder.php | 55 +- system/Database/Postgre/Builder.php | 26 + system/Debug/Toolbar.php | 4 +- system/HTTP/ResponseInterface.php | 75 +- system/HTTP/URI.php | 110 +- system/Helpers/url_helper.php | 500 +- system/ThirdParty/ZendEscaper/Escaper.php | 42 +- tests/system/Database/Builder/LikeTest.php | 18 + tests/system/Database/Live/LikeTest.php | 11 + tests/system/HTTP/URITest.php | 49 +- tests/system/Helpers/URLHelperTest.php | 638 +- .../source/contributing/styleguide.rst | 50 +- .../source/database/query_builder.rst | 5 + user_guide_src/source/general/controllers.rst | 3 +- user_guide_src/source/general/debugging.rst | 9 +- user_guide_src/source/general/index.rst | 5 +- user_guide_src/source/general/view_cells.rst | 68 + user_guide_src/source/general/view_parser.rst | 6 + .../source/general/view_renderer.rst | 116 + user_guide_src/source/general/views.rst | 164 +- user_guide_src/source/helpers/index.rst | 8 +- user_guide_src/source/helpers/url_helper.rst | 339 + user_guide_src/source/libraries/response.rst | 4 +- 30 files changed, 10161 insertions(+), 400 deletions(-) create mode 100644 application/Filters/DebugToolbar.php create mode 100644 phpcbf-fixed.diff create mode 100644 user_guide_src/source/general/view_cells.rst create mode 100644 user_guide_src/source/general/view_parser.rst create mode 100644 user_guide_src/source/general/view_renderer.rst create mode 100644 user_guide_src/source/helpers/url_helper.rst diff --git a/application/Config/App.php b/application/Config/App.php index 763857f82bdd..cd0a2034f870 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -207,10 +207,8 @@ class App extends BaseConfig | The Debug Toolbar provides a way to see information about the performance | and state of your application during that page display. By default it will | NOT be displayed under production environments, and will only display if - | CI_DEBIG is true, since if it's not, there's not much to display anyway. + | CI_DEBUG is true, since if it's not, there's not much to display anyway. */ - public $toolbarEnabled = (ENVIRONMENT != 'production' && CI_DEBUG); - public $toolbarCollectors = [ 'CodeIgniter\Debug\Toolbar\Collectors\Timers', 'CodeIgniter\Debug\Toolbar\Collectors\Database', diff --git a/application/Config/Autoload.php b/application/Config/Autoload.php index 097844155456..67042c8f7d8d 100644 --- a/application/Config/Autoload.php +++ b/application/Config/Autoload.php @@ -51,8 +51,8 @@ public function __construct() */ $psr4 = [ 'Config' => APPPATH.'Config', - APP_NAMESPACE.'\Controllers' => APPPATH.'Controllers', - APP_NAMESPACE => realpath(APPPATH), + APP_NAMESPACE => APPPATH, // For custom namespace + 'App' => APPPATH, // To ensure filters, etc still found ]; /** diff --git a/application/Config/Filters.php b/application/Config/Filters.php index 944fba5b8f03..cb16f8137b64 100644 --- a/application/Config/Filters.php +++ b/application/Config/Filters.php @@ -7,7 +7,8 @@ class Filters extends BaseConfig // Makes reading things below nicer, // and simpler to change out script that's used. public $aliases = [ - 'csrf' => \App\Filters\CSRF::class + 'csrf' => \App\Filters\CSRF::class, + 'toolbar' => \App\Filters\DebugToolbar::class, ]; // Always applied before every request @@ -15,7 +16,9 @@ class Filters extends BaseConfig 'before' => [ // 'csrf' ], - 'after' => [] + 'after' => [ + 'toolbar' + ] ]; // Works on all of a particular HTTP method diff --git a/application/Controllers/Home.php b/application/Controllers/Home.php index 0b02edfb09eb..47526fa99975 100644 --- a/application/Controllers/Home.php +++ b/application/Controllers/Home.php @@ -4,7 +4,7 @@ class Home extends \CodeIgniter\Controller { public function index() { - echo view('welcome_message'); + return view('welcome_message'); } //-------------------------------------------------------------------- diff --git a/application/Filters/DebugToolbar.php b/application/Filters/DebugToolbar.php new file mode 100644 index 000000000000..99d7128599df --- /dev/null +++ b/application/Filters/DebugToolbar.php @@ -0,0 +1,56 @@ +getPerfomanceStats(); + + return $response->appendBody( + $toolbar->run( + $stats['startTime'], + $stats['totalTime'], + $stats['startMemory'], + $request, + $response + ) + ); + } + } + + //-------------------------------------------------------------------- +} diff --git a/phpcbf-fixed.diff b/phpcbf-fixed.diff new file mode 100644 index 000000000000..9b325a41e33f --- /dev/null +++ b/phpcbf-fixed.diff @@ -0,0 +1,8093 @@ +--- vendor/symfony/polyfill-mbstring/bootstrap.php ++++ PHP_CodeSniffer +@@ -16,36 +16,132 @@ + define('MB_CASE_LOWER', 1); + define('MB_CASE_TITLE', 2); + +- function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } +- function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } +- function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } +- function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } +- function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } +- function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } +- function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +- function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +- function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } +- function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } +- function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } +- function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } +- function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } +- function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } +- function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } +- function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } +- function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } +- function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } +- function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } +- function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } +- function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } +- function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } +- function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } +- function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } +- function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } +- function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +- function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } +- function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } +- function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } +- function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } +- function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } +- function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $v0, $a, $b, $c, $d, $e, $f); } ++ function mb_convert_encoding($s, $to, $from = null) ++ { ++ return p\Mbstring::mb_convert_encoding($s, $to, $from); ++ } ++ function mb_decode_mimeheader($s) ++ { ++ return p\Mbstring::mb_decode_mimeheader($s); ++ } ++ function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) ++ { ++ return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); ++ } ++ function mb_convert_case($s, $mode, $enc = null) ++ { ++ return p\Mbstring::mb_convert_case($s, $mode, $enc); ++ } ++ function mb_internal_encoding($enc = null) ++ { ++ return p\Mbstring::mb_internal_encoding($enc); ++ } ++ function mb_language($lang = null) ++ { ++ return p\Mbstring::mb_language($lang); ++ } ++ function mb_list_encodings() ++ { ++ return p\Mbstring::mb_list_encodings(); ++ } ++ function mb_encoding_aliases($encoding) ++ { ++ return p\Mbstring::mb_encoding_aliases($encoding); ++ } ++ function mb_check_encoding($var = null, $encoding = null) ++ { ++ return p\Mbstring::mb_check_encoding($var, $encoding); ++ } ++ function mb_detect_encoding($str, $encodingList = null, $strict = false) ++ { ++ return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); ++ } ++ function mb_detect_order($encodingList = null) ++ { ++ return p\Mbstring::mb_detect_order($encodingList); ++ } ++ function mb_parse_str($s, &$result = array()) ++ { ++ parse_str($s, $result); ++ } ++ function mb_strlen($s, $enc = null) ++ { ++ return p\Mbstring::mb_strlen($s, $enc); ++ } ++ function mb_strpos($s, $needle, $offset = 0, $enc = null) ++ { ++ return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); ++ } ++ function mb_strtolower($s, $enc = null) ++ { ++ return p\Mbstring::mb_strtolower($s, $enc); ++ } ++ function mb_strtoupper($s, $enc = null) ++ { ++ return p\Mbstring::mb_strtoupper($s, $enc); ++ } ++ function mb_substitute_character($char = null) ++ { ++ return p\Mbstring::mb_substitute_character($char); ++ } ++ function mb_substr($s, $start, $length = 2147483647, $enc = null) ++ { ++ return p\Mbstring::mb_substr($s, $start, $length, $enc); ++ } ++ function mb_stripos($s, $needle, $offset = 0, $enc = null) ++ { ++ return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); ++ } ++ function mb_stristr($s, $needle, $part = false, $enc = null) ++ { ++ return p\Mbstring::mb_stristr($s, $needle, $part, $enc); ++ } ++ function mb_strrchr($s, $needle, $part = false, $enc = null) ++ { ++ return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); ++ } ++ function mb_strrichr($s, $needle, $part = false, $enc = null) ++ { ++ return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); ++ } ++ function mb_strripos($s, $needle, $offset = 0, $enc = null) ++ { ++ return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); ++ } ++ function mb_strrpos($s, $needle, $offset = 0, $enc = null) ++ { ++ return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); ++ } ++ function mb_strstr($s, $needle, $part = false, $enc = null) ++ { ++ return p\Mbstring::mb_strstr($s, $needle, $part, $enc); ++ } ++ function mb_get_info($type = 'all') ++ { ++ return p\Mbstring::mb_get_info($type); ++ } ++ function mb_http_output($enc = null) ++ { ++ return p\Mbstring::mb_http_output($enc); ++ } ++ function mb_strwidth($s, $enc = null) ++ { ++ return p\Mbstring::mb_strwidth($s, $enc); ++ } ++ function mb_substr_count($haystack, $needle, $enc = null) ++ { ++ return p\Mbstring::mb_substr_count($haystack, $needle, $enc); ++ } ++ function mb_output_handler($contents, $status) ++ { ++ return p\Mbstring::mb_output_handler($contents, $status); ++ } ++ function mb_http_input($type = '') ++ { ++ return p\Mbstring::mb_http_input($type); ++ } ++ function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) ++ { ++ return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $v0, $a, $b, $c, $d, $e, $f); ++ } + } + +--- vendor/symfony/polyfill-mbstring/Mbstring.php ++++ PHP_CodeSniffer +@@ -115,11 +115,13 @@ + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; +- array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { +- if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { +- $ok = false; ++ array_walk_recursive( ++ $vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { ++ if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { ++ $ok = false; ++ } + } +- }); ++ ); + + return $ok ? $fromEncoding : false; + } +@@ -229,11 +231,11 @@ + } + + switch ($lang = strtolower($lang)) { +- case 'uni': +- case 'neutral': +- self::$language = $lang; ++ case 'uni': ++ case 'neutral': ++ self::$language = $lang; + +- return true; ++ return true; + } + + return false; +@@ -247,9 +249,9 @@ + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { +- case 'UTF8': +- case 'UTF-8': +- return array('utf8'); ++ case 'UTF8': ++ case 'UTF-8': ++ return array('utf8'); + } + + return false; +@@ -280,23 +282,23 @@ + + foreach ($encodingList as $enc) { + switch ($enc) { +- case 'ASCII': +- if (!preg_match('/[\x80-\xFF]/', $str)) { +- return $enc; +- } +- break; ++ case 'ASCII': ++ if (!preg_match('/[\x80-\xFF]/', $str)) { ++ return $enc; ++ } ++ break; + +- case 'UTF8': +- case 'UTF-8': +- if (preg_match('//u', $str)) { +- return 'UTF-8'; +- } +- break; ++ case 'UTF8': ++ case 'UTF-8': ++ if (preg_match('//u', $str)) { ++ return 'UTF-8'; ++ } ++ break; + +- default: +- if (0 === strncmp($enc, 'ISO-8859-', 9)) { +- return $enc; +- } ++ default: ++ if (0 === strncmp($enc, 'ISO-8859-', 9)) { ++ return $enc; ++ } + } + } + +@@ -316,13 +318,13 @@ + + foreach ($encodingList as $enc) { + switch ($enc) { +- default: +- if (strncmp($enc, 'ISO-8859-', 9)) { +- return false; +- } +- case 'ASCII': +- case 'UTF8': +- case 'UTF-8': ++ default: ++ if (strncmp($enc, 'ISO-8859-', 9)) { ++ return false; ++ } ++ case 'ASCII': ++ case 'UTF8': ++ case 'UTF-8': + } + } + +@@ -578,7 +580,7 @@ + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { +- return require $file; ++ return include $file; + } + + return false; + +--- vendor/symfony/translation/PluralizationRules.php ++++ PHP_CodeSniffer +@@ -55,135 +55,135 @@ + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + switch ($locale) { +- case 'az': +- case 'bo': +- case 'dz': +- case 'id': +- case 'ja': +- case 'jv': +- case 'ka': +- case 'km': +- case 'kn': +- case 'ko': +- case 'ms': +- case 'th': +- case 'tr': +- case 'vi': +- case 'zh': +- return 0; ++ case 'az': ++ case 'bo': ++ case 'dz': ++ case 'id': ++ case 'ja': ++ case 'jv': ++ case 'ka': ++ case 'km': ++ case 'kn': ++ case 'ko': ++ case 'ms': ++ case 'th': ++ case 'tr': ++ case 'vi': ++ case 'zh': ++ return 0; + break; + +- case 'af': +- case 'bn': +- case 'bg': +- case 'ca': +- case 'da': +- case 'de': +- case 'el': +- case 'en': +- case 'eo': +- case 'es': +- case 'et': +- case 'eu': +- case 'fa': +- case 'fi': +- case 'fo': +- case 'fur': +- case 'fy': +- case 'gl': +- case 'gu': +- case 'ha': +- case 'he': +- case 'hu': +- case 'is': +- case 'it': +- case 'ku': +- case 'lb': +- case 'ml': +- case 'mn': +- case 'mr': +- case 'nah': +- case 'nb': +- case 'ne': +- case 'nl': +- case 'nn': +- case 'no': +- case 'om': +- case 'or': +- case 'pa': +- case 'pap': +- case 'ps': +- case 'pt': +- case 'so': +- case 'sq': +- case 'sv': +- case 'sw': +- case 'ta': +- case 'te': +- case 'tk': +- case 'ur': +- case 'zu': +- return ($number == 1) ? 0 : 1; +- +- case 'am': +- case 'bh': +- case 'fil': +- case 'fr': +- case 'gun': +- case 'hi': +- case 'hy': +- case 'ln': +- case 'mg': +- case 'nso': +- case 'xbr': +- case 'ti': +- case 'wa': +- return (($number == 0) || ($number == 1)) ? 0 : 1; +- +- case 'be': +- case 'bs': +- case 'hr': +- case 'ru': +- case 'sr': +- case 'uk': +- return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); +- +- case 'cs': +- case 'sk': +- return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); +- +- case 'ga': +- return ($number == 1) ? 0 : (($number == 2) ? 1 : 2); +- +- case 'lt': +- return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); +- +- case 'sl': +- return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3)); +- +- case 'mk': +- return ($number % 10 == 1) ? 0 : 1; +- +- case 'mt': +- return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); +- +- case 'lv': +- return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2); +- +- case 'pl': +- return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); +- +- case 'cy': +- return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3)); ++ case 'af': ++ case 'bn': ++ case 'bg': ++ case 'ca': ++ case 'da': ++ case 'de': ++ case 'el': ++ case 'en': ++ case 'eo': ++ case 'es': ++ case 'et': ++ case 'eu': ++ case 'fa': ++ case 'fi': ++ case 'fo': ++ case 'fur': ++ case 'fy': ++ case 'gl': ++ case 'gu': ++ case 'ha': ++ case 'he': ++ case 'hu': ++ case 'is': ++ case 'it': ++ case 'ku': ++ case 'lb': ++ case 'ml': ++ case 'mn': ++ case 'mr': ++ case 'nah': ++ case 'nb': ++ case 'ne': ++ case 'nl': ++ case 'nn': ++ case 'no': ++ case 'om': ++ case 'or': ++ case 'pa': ++ case 'pap': ++ case 'ps': ++ case 'pt': ++ case 'so': ++ case 'sq': ++ case 'sv': ++ case 'sw': ++ case 'ta': ++ case 'te': ++ case 'tk': ++ case 'ur': ++ case 'zu': ++ return ($number == 1) ? 0 : 1; ++ ++ case 'am': ++ case 'bh': ++ case 'fil': ++ case 'fr': ++ case 'gun': ++ case 'hi': ++ case 'hy': ++ case 'ln': ++ case 'mg': ++ case 'nso': ++ case 'xbr': ++ case 'ti': ++ case 'wa': ++ return (($number == 0) || ($number == 1)) ? 0 : 1; ++ ++ case 'be': ++ case 'bs': ++ case 'hr': ++ case 'ru': ++ case 'sr': ++ case 'uk': ++ return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); ++ ++ case 'cs': ++ case 'sk': ++ return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); ++ ++ case 'ga': ++ return ($number == 1) ? 0 : (($number == 2) ? 1 : 2); ++ ++ case 'lt': ++ return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); ++ ++ case 'sl': ++ return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3)); ++ ++ case 'mk': ++ return ($number % 10 == 1) ? 0 : 1; ++ ++ case 'mt': ++ return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); ++ ++ case 'lv': ++ return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2); ++ ++ case 'pl': ++ return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); ++ ++ case 'cy': ++ return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3)); + +- case 'ro': +- return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); ++ case 'ro': ++ return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + +- case 'ar': +- return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); ++ case 'ar': ++ return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); + +- default: +- return 0; ++ default: ++ return 0; + } + } + + +--- vendor/symfony/translation/Dumper/IcuResFileDumper.php ++++ PHP_CodeSniffer +@@ -46,8 +46,7 @@ + + $data .= pack('V', strlen($target)) + .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8') +- .$this->writePadding($data) +- ; ++ .$this->writePadding($data); + } + + $resOffset = $this->getPosition($data); +@@ -60,7 +59,8 @@ + + $bundleTop = $this->getPosition($data); + +- $root = pack('V7', ++ $root = pack( ++ 'V7', + $resOffset + (2 << 28), // Resource Offset + Resource Type + 6, // Index length + $keyTop, // Index keys top +@@ -70,7 +70,8 @@ + 0 // Index attributes + ); + +- $header = pack('vC2v4C12@32', ++ $header = pack( ++ 'vC2v4C12@32', + 32, // Header size + 0xDA, 0x27, // Magic number 1 and 2 + 20, 0, 0, 2, // Rest of the header, ..., Size of a char + +--- vendor/symfony/translation/Dumper/FileDumper.php ++++ PHP_CodeSniffer +@@ -114,10 +114,12 @@ + */ + private function getRelativePath($domain, $locale) + { +- return strtr($this->relativePathTemplate, array( ++ return strtr( ++ $this->relativePathTemplate, array( + '%domain%' => $domain, + '%locale%' => $locale, + '%extension%' => $this->getExtension(), +- )); ++ ) ++ ); + } + } + +--- vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php ++++ PHP_CodeSniffer +@@ -140,8 +140,7 @@ + $translator = $this + ->getMockBuilder('Symfony\Component\Translation\DataCollectorTranslator') + ->disableOriginalConstructor() +- ->getMock() +- ; ++ ->getMock(); + + return $translator; + } + +--- vendor/symfony/translation/Tests/Dumper/CsvFileDumperTest.php ++++ PHP_CodeSniffer +@@ -19,8 +19,10 @@ + public function testFormatCatalogue() + { + $catalogue = new MessageCatalogue('en'); +- $catalogue->add(array('foo' => 'bar', 'bar' => 'foo +-foo', 'foo;foo' => 'bar')); ++ $catalogue->add( ++ array('foo' => 'bar', 'bar' => 'foo ++foo', 'foo;foo' => 'bar') ++ ); + + $dumper = new CsvFileDumper(); + + +--- vendor/symfony/translation/Tests/Dumper/YamlFileDumperTest.php ++++ PHP_CodeSniffer +@@ -23,11 +23,12 @@ + array( + 'foo.bar1' => 'value1', + 'foo.bar2' => 'value2', +- )); ++ ) ++ ); + +- $dumper = new YamlFileDumper(); ++ $dumper = new YamlFileDumper(); + +- $this->assertStringEqualsFile(__DIR__.'/../fixtures/messages.yml', $dumper->formatCatalogue($catalogue, 'messages', array('as_tree' => true, 'inline' => 999))); ++ $this->assertStringEqualsFile(__DIR__.'/../fixtures/messages.yml', $dumper->formatCatalogue($catalogue, 'messages', array('as_tree' => true, 'inline' => 999))); + } + + public function testLinearFormatCatalogue() +@@ -37,10 +38,11 @@ + array( + 'foo.bar1' => 'value1', + 'foo.bar2' => 'value2', +- )); ++ ) ++ ); + +- $dumper = new YamlFileDumper(); ++ $dumper = new YamlFileDumper(); + +- $this->assertStringEqualsFile(__DIR__.'/../fixtures/messages_linear.yml', $dumper->formatCatalogue($catalogue, 'messages')); ++ $this->assertStringEqualsFile(__DIR__.'/../fixtures/messages_linear.yml', $dumper->formatCatalogue($catalogue, 'messages')); + } + } + +--- vendor/symfony/translation/Tests/Dumper/XliffFileDumperTest.php ++++ PHP_CodeSniffer +@@ -19,11 +19,13 @@ + public function testFormatCatalogue() + { + $catalogue = new MessageCatalogue('en_US'); +- $catalogue->add(array( ++ $catalogue->add( ++ array( + 'foo' => 'bar', + 'key' => '', + 'key.with.cdata' => ' & ', +- )); ++ ) ++ ); + $catalogue->setMetadata('foo', array('notes' => array(array('priority' => 1, 'from' => 'bar', 'content' => 'baz')))); + $catalogue->setMetadata('key', array('notes' => array(array('content' => 'baz'), array('content' => 'qux')))); + +@@ -38,11 +40,13 @@ + public function testFormatCatalogueXliff2() + { + $catalogue = new MessageCatalogue('en_US'); +- $catalogue->add(array( ++ $catalogue->add( ++ array( + 'foo' => 'bar', + 'key' => '', + 'key.with.cdata' => ' & ', +- )); ++ ) ++ ); + $catalogue->setMetadata('key', array('target-attributes' => array('order' => 1))); + + $dumper = new XliffFileDumper(); +@@ -74,9 +78,11 @@ + public function testFormatCatalogueWithTargetAttributesMetadata() + { + $catalogue = new MessageCatalogue('en_US'); +- $catalogue->add(array( ++ $catalogue->add( ++ array( + 'foo' => 'bar', +- )); ++ ) ++ ); + $catalogue->setMetadata('foo', array('target-attributes' => array('state' => 'needs-translation'))); + + $dumper = new XliffFileDumper(); + +--- vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php ++++ PHP_CodeSniffer +@@ -43,9 +43,11 @@ + public function testGetResultFromSingleDomain() + { + $this->assertEquals( +- new MessageCatalogue('en', array( ++ new MessageCatalogue( ++ 'en', array( + 'messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'), +- )), ++ ) ++ ), + $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + +--- vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php ++++ PHP_CodeSniffer +@@ -43,9 +43,11 @@ + public function testGetResultFromSingleDomain() + { + $this->assertEquals( +- new MessageCatalogue('en', array( ++ new MessageCatalogue( ++ 'en', array( + 'messages' => array('a' => 'old_a', 'c' => 'new_c'), +- )), ++ ) ++ ), + $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + +--- vendor/symfony/translation/Tests/LoggingTranslatorTest.php ++++ PHP_CodeSniffer +@@ -22,8 +22,7 @@ + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->exactly(2)) + ->method('warning') +- ->with('Translation not found.') +- ; ++ ->with('Translation not found.'); + + $translator = new Translator('ar'); + $loggableTranslator = new LoggingTranslator($translator, $logger); +@@ -36,8 +35,7 @@ + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->once()) + ->method('debug') +- ->with('Translation use fallback catalogue.') +- ; ++ ->with('Translation use fallback catalogue.'); + + $translator = new Translator('ar'); + $translator->setFallbackLocales(array('en')); + +--- vendor/symfony/translation/Tests/TranslatorCacheTest.php ++++ PHP_CodeSniffer +@@ -94,13 +94,14 @@ + $catalogue = new MessageCatalogue($locale, array()); + $catalogue->addResource(new StaleResource()); // better use a helper class than a mock, because it gets serialized in the cache and re-loaded + +- /** @var LoaderInterface|\PHPUnit_Framework_MockObject_MockObject $loader */ ++ /** ++ * @var LoaderInterface|\PHPUnit_Framework_MockObject_MockObject $loader ++*/ + $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); + $loader + ->expects($this->exactly(2)) + ->method('load') +- ->will($this->returnValue($catalogue)) +- ; ++ ->will($this->returnValue($catalogue)); + + // 1st pass + $translator = new Translator($locale, null, $this->tmpDir, true); + +--- vendor/symfony/translation/Loader/PhpFileLoader.php ++++ PHP_CodeSniffer +@@ -23,6 +23,6 @@ + */ + protected function loadResource($resource) + { +- return require $resource; ++ return include $resource; + } + } + +--- vendor/symfony/translation/Loader/JsonFileLoader.php ++++ PHP_CodeSniffer +@@ -47,18 +47,18 @@ + private function getJSONErrorMessage($errorCode) + { + switch ($errorCode) { +- case JSON_ERROR_DEPTH: +- return 'Maximum stack depth exceeded'; +- case JSON_ERROR_STATE_MISMATCH: +- return 'Underflow or the modes mismatch'; +- case JSON_ERROR_CTRL_CHAR: +- return 'Unexpected control character found'; +- case JSON_ERROR_SYNTAX: +- return 'Syntax error, malformed JSON'; +- case JSON_ERROR_UTF8: +- return 'Malformed UTF-8 characters, possibly incorrectly encoded'; +- default: +- return 'Unknown error'; ++ case JSON_ERROR_DEPTH: ++ return 'Maximum stack depth exceeded'; ++ case JSON_ERROR_STATE_MISMATCH: ++ return 'Underflow or the modes mismatch'; ++ case JSON_ERROR_CTRL_CHAR: ++ return 'Unexpected control character found'; ++ case JSON_ERROR_SYNTAX: ++ return 'Syntax error, malformed JSON'; ++ case JSON_ERROR_UTF8: ++ return 'Malformed UTF-8 characters, possibly incorrectly encoded'; ++ default: ++ return 'Unknown error'; + } + } + } + +--- vendor/symfony/translation/Loader/XliffFileLoader.php ++++ PHP_CodeSniffer +@@ -239,7 +239,8 @@ + { + $errors = array(); + foreach (libxml_get_errors() as $error) { +- $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', ++ $errors[] = sprintf( ++ '[%s %s] %s (in %s - line %d, column %d)', + LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), +@@ -267,7 +268,9 @@ + */ + private function getVersionNumber(\DOMDocument $dom) + { +- /** @var \DOMNode $xliff */ ++ /** ++ * @var \DOMNode $xliff ++*/ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + +--- vendor/symfony/translation/Interval.php ++++ PHP_CodeSniffer +@@ -29,7 +29,7 @@ + * + * @author Fabien Potencier + * +- * @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation ++ * @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation + */ + class Interval + { + +--- vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php ++++ PHP_CodeSniffer +@@ -74,10 +74,14 @@ + } + + if (!isset($event['method'])) { +- $event['method'] = 'on'.preg_replace_callback(array( ++ $event['method'] = 'on'.preg_replace_callback( ++ array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', +- ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); ++ ), function ($matches) { ++ return strtoupper($matches[0]); ++ }, $event['event'] ++ ); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + +--- vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php ++++ PHP_CodeSniffer +@@ -72,7 +72,11 @@ + */ + public function testAddListenerDisallowed() + { +- $this->dispatcher->addListener('event', function () { return 'foo'; }); ++ $this->dispatcher->addListener( ++ 'event', function () { ++ return 'foo'; ++ } ++ ); + } + + /** +@@ -90,7 +94,11 @@ + */ + public function testRemoveListenerDisallowed() + { +- $this->dispatcher->removeListener('event', function () { return 'foo'; }); ++ $this->dispatcher->removeListener( ++ 'event', function () { ++ return 'foo'; ++ } ++ ); + } + + /** + +--- vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php ++++ PHP_CodeSniffer +@@ -25,7 +25,10 @@ + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + +- $tdispatcher->addListener('foo', $listener = function () {}); ++ $tdispatcher->addListener( ++ 'foo', $listener = function () { ++ } ++ ); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame($listener, $listeners[0]); +@@ -39,7 +42,10 @@ + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + +- $tdispatcher->addListener('foo', $listener = function () {}); ++ $tdispatcher->addListener( ++ 'foo', $listener = function () { ++ } ++ ); + $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo')); + } + +@@ -51,7 +57,10 @@ + $this->assertFalse($dispatcher->hasListeners('foo')); + $this->assertFalse($tdispatcher->hasListeners('foo')); + +- $tdispatcher->addListener('foo', $listener = function () {}); ++ $tdispatcher->addListener( ++ 'foo', $listener = function () { ++ } ++ ); + $this->assertTrue($dispatcher->hasListeners('foo')); + $this->assertTrue($tdispatcher->hasListeners('foo')); + } +@@ -61,7 +70,10 @@ + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + +- $tdispatcher->addListener('foo', function () {}, 123); ++ $tdispatcher->addListener( ++ 'foo', function () { ++ }, 123 ++ ); + + $listeners = $dispatcher->getListeners('foo'); + $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0])); +@@ -77,7 +89,10 @@ + { + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $traceableEventDispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); +- $traceableEventDispatcher->addListener('foo', function () {}, 123); ++ $traceableEventDispatcher->addListener( ++ 'foo', function () { ++ }, 123 ++ ); + $listeners = $traceableEventDispatcher->getListeners('foo'); + + $this->assertSame(0, $traceableEventDispatcher->getListenerPriority('foo', $listeners[0])); +@@ -103,7 +118,10 @@ + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); +- $tdispatcher->addListener('foo', $listener = function () {}); ++ $tdispatcher->addListener( ++ 'foo', $listener = function () { ++ } ++ ); + + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 0)), $tdispatcher->getNotCalledListeners()); +@@ -118,11 +136,16 @@ + { + $tdispatcher = null; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); +- $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) { +- $tdispatcher = $dispatcher; +- $dispatcher->dispatch('bar'); +- }); +- $dispatcher->addListener('bar', function (Event $event) {}); ++ $dispatcher->addListener( ++ 'foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) { ++ $tdispatcher = $dispatcher; ++ $dispatcher->dispatch('bar'); ++ } ++ ); ++ $dispatcher->addListener( ++ 'bar', function (Event $event) { ++ } ++ ); + $dispatcher->dispatch('foo'); + $this->assertSame($dispatcher, $tdispatcher); + $this->assertCount(2, $dispatcher->getCalledListeners()); +@@ -134,8 +157,14 @@ + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); +- $tdispatcher->addListener('foo', $listener1 = function () {}); +- $tdispatcher->addListener('foo', $listener2 = function () {}); ++ $tdispatcher->addListener( ++ 'foo', $listener1 = function () { ++ } ++ ); ++ $tdispatcher->addListener( ++ 'foo', $listener2 = function () { ++ } ++ ); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".'); +@@ -149,8 +178,15 @@ + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); +- $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); +- $tdispatcher->addListener('foo', $listener2 = function () {}); ++ $tdispatcher->addListener( ++ 'foo', $listener1 = function (Event $event) { ++ $event->stopPropagation(); ++ } ++ ); ++ $tdispatcher->addListener( ++ 'foo', $listener2 = function () { ++ } ++ ); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".'); +@@ -165,8 +201,16 @@ + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); +- $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10); +- $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20); ++ $tdispatcher->addListener( ++ 'foo', function () use (&$called) { ++ $called[] = 'foo1'; ++ }, 10 ++ ); ++ $tdispatcher->addListener( ++ 'foo', function () use (&$called) { ++ $called[] = 'foo2'; ++ }, 20 ++ ); + + $tdispatcher->dispatch('foo'); + +@@ -177,12 +221,14 @@ + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; +- $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { +- ++$loop; +- if (2 == $loop) { +- $dispatcher->dispatch('foo'); ++ $dispatcher->addListener( ++ 'foo', $listener1 = function () use ($dispatcher, &$loop) { ++ ++$loop; ++ if (2 == $loop) { ++ $dispatcher->dispatch('foo'); ++ } + } +- }); ++ ); + + $dispatcher->dispatch('foo'); + } +@@ -191,12 +237,16 @@ + { + $nestedCall = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); +- $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) { +- $dispatcher->dispatch('bar', $e); +- }); +- $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) { +- $nestedCall = true; +- }); ++ $dispatcher->addListener( ++ 'foo', function (Event $e) use ($dispatcher) { ++ $dispatcher->dispatch('bar', $e); ++ } ++ ); ++ $dispatcher->addListener( ++ 'bar', function (Event $e) use (&$nestedCall) { ++ $nestedCall = true; ++ } ++ ); + + $this->assertFalse($nestedCall); + $dispatcher->dispatch('foo'); +@@ -210,7 +260,10 @@ + $dispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); +- $eventDispatcher->addListener('foo', function () {}); ++ $eventDispatcher->addListener( ++ 'foo', function () { ++ } ++ ); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + +--- vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php ++++ PHP_CodeSniffer +@@ -35,8 +35,7 @@ + $service + ->expects($this->once()) + ->method('onEvent') +- ->with($event) +- ; ++ ->with($event); + + $container = new Container(); + $container->set('service.listener', $service); +@@ -56,20 +55,17 @@ + $service + ->expects($this->once()) + ->method('onEvent') +- ->with($event) +- ; ++ ->with($event); + + $service + ->expects($this->once()) + ->method('onEventWithPriority') +- ->with($event) +- ; ++ ->with($event); + + $service + ->expects($this->once()) + ->method('onEventNested') +- ->with($event) +- ; ++ ->with($event); + + $container = new Container(); + $container->set('service.subscriber', $service); +@@ -91,8 +87,7 @@ + $service + ->expects($this->once()) + ->method('onEvent') +- ->with($event) +- ; ++ ->with($event); + + $container = new Container(); + $container->set('service.listener', $service); +@@ -138,8 +133,7 @@ + $service1 + ->expects($this->exactly(2)) + ->method('onEvent') +- ->with($event) +- ; ++ ->with($event); + + $scope = new Scope('scope'); + $container = new Container(); +@@ -157,8 +151,7 @@ + $service2 + ->expects($this->once()) + ->method('onEvent') +- ->with($event) +- ; ++ ->with($event); + + $container->enterScope('scope'); + $container->set('service.listener', $service2, 'scope'); +@@ -188,8 +181,7 @@ + $service + ->expects($this->once()) + ->method('onEvent') +- ->with($event) +- ; ++ ->with($event); + + $this->assertTrue($dispatcher->hasListeners()); + + +--- vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php ++++ PHP_CodeSniffer +@@ -119,7 +119,12 @@ + $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1)); + $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2)); +- $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {})); ++ $this->assertNull( ++ $this->dispatcher->getListenerPriority( ++ 'pre.foo', function () { ++ } ++ ) ++ ); + } + + public function testDispatch() +@@ -269,9 +274,11 @@ + public function testLegacyEventReceivesTheDispatcherInstance() + { + $dispatcher = null; +- $this->dispatcher->addListener('test', function ($event) use (&$dispatcher) { +- $dispatcher = $event->getDispatcher(); +- }); ++ $this->dispatcher->addListener( ++ 'test', function ($event) use (&$dispatcher) { ++ $dispatcher = $event->getDispatcher(); ++ } ++ ); + $this->dispatcher->dispatch('test'); + $this->assertSame($this->dispatcher, $dispatcher); + } +@@ -299,13 +306,17 @@ + { + $dispatcher = $this->createEventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); +- $dispatcher->removeListener('bug.62976', function () {}); ++ $dispatcher->removeListener( ++ 'bug.62976', function () { ++ } ++ ); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } + + public function testHasListenersWhenAddedCallbackListenerIsRemoved() + { +- $listener = function () {}; ++ $listener = function () { ++ }; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertFalse($this->dispatcher->hasListeners()); +@@ -313,7 +324,8 @@ + + public function testGetListenersWhenAddedCallbackListenerIsRemoved() + { +- $listener = function () {}; ++ $listener = function () { ++ }; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertSame(array(), $this->dispatcher->getListeners()); + +--- vendor/symfony/validator/PropertyMetadataInterface.php ++++ PHP_CodeSniffer +@@ -22,7 +22,7 @@ + * + * @author Bernhard Schussek + * +- * @see MetadataInterface ++ * @see MetadataInterface + * @deprecated since version 2.5, to be removed in 3.0. + * Use {@link Mapping\PropertyMetadataInterface} instead. + */ + +--- vendor/symfony/validator/Context/ExecutionContextInterface.php ++++ PHP_CodeSniffer +@@ -56,7 +56,7 @@ + * cannot store a context and expect that the methods still return the same + * results later on. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/Context/ExecutionContextFactoryInterface.php ++++ PHP_CodeSniffer +@@ -19,7 +19,7 @@ + * You can use a custom factory if you want to customize the execution context + * that is passed through the validation run. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/Context/ExecutionContext.php ++++ PHP_CodeSniffer +@@ -27,7 +27,7 @@ + /** + * The context used and created by {@link ExecutionContextFactory}. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * +@@ -196,23 +196,24 @@ + ->setInvalidValue($invalidValue) + ->setPlural($plural) + ->setCode($code) +- ->addViolation() +- ; ++ ->addViolation(); + + return; + } + +- $this->violations->add(new ConstraintViolation( +- $this->translator->trans($message, $parameters, $this->translationDomain), +- $message, +- $parameters, +- $this->root, +- $this->propertyPath, +- $this->value, +- null, +- null, +- $this->constraint +- )); ++ $this->violations->add( ++ new ConstraintViolation( ++ $this->translator->trans($message, $parameters, $this->translationDomain), ++ $message, ++ $parameters, ++ $this->root, ++ $this->propertyPath, ++ $this->value, ++ null, ++ null, ++ $this->constraint ++ ) ++ ); + } + + /** +@@ -327,8 +328,7 @@ + ->setInvalidValue($invalidValue) + ->setPlural($plural) + ->setCode($code) +- ->addViolation() +- ; ++ ->addViolation(); + + return; + } +@@ -336,8 +336,7 @@ + $this + ->buildViolation($message, $parameters) + ->atPath($subPath) +- ->addViolation() +- ; ++ ->addViolation(); + } + + /** +@@ -355,8 +354,7 @@ + ->getValidator() + ->inContext($this) + ->atPath($subPath) +- ->validate($value, $constraint, $groups) +- ; ++ ->validate($value, $constraint, $groups); + } + + if ($traverse && $value instanceof \Traversable) { +@@ -366,16 +364,14 @@ + ->getValidator() + ->inContext($this) + ->atPath($subPath) +- ->validate($value, $constraint, $groups) +- ; ++ ->validate($value, $constraint, $groups); + } + + return $this + ->getValidator() + ->inContext($this) + ->atPath($subPath) +- ->validate($value, null, $groups) +- ; ++ ->validate($value, null, $groups); + } + + /** +@@ -389,8 +385,7 @@ + ->getValidator() + ->inContext($this) + ->atPath($subPath) +- ->validate($value, $constraints, $groups) +- ; ++ ->validate($value, $constraints, $groups); + } + + /** + +--- vendor/symfony/validator/Context/ExecutionContextFactory.php ++++ PHP_CodeSniffer +@@ -17,7 +17,7 @@ + /** + * Creates new {@link ExecutionContext} instances. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Context/LegacyExecutionContextFactory.php ++++ PHP_CodeSniffer +@@ -22,7 +22,7 @@ + * + * Implemented for backward compatibility with Symfony < 2.5. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Context/LegacyExecutionContext.php ++++ PHP_CodeSniffer +@@ -20,7 +20,7 @@ + /** + * An execution context that is compatible with the legacy API (< 2.5). + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Violation/ConstraintViolationBuilder.php ++++ PHP_CodeSniffer +@@ -20,7 +20,7 @@ + /** + * Default implementation of {@link ConstraintViolationBuilderInterface}. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * +@@ -199,7 +199,7 @@ + $this->message, + $this->plural, + $this->parameters, +- $this->translationDomain# ++ $this->translationDomain// + ); + } catch (\InvalidArgumentException $e) { + $translatedMessage = $this->translator->trans( +@@ -210,17 +210,19 @@ + } + } + +- $this->violations->add(new ConstraintViolation( +- $translatedMessage, +- $this->message, +- $this->parameters, +- $this->root, +- $this->propertyPath, +- $this->invalidValue, +- $this->plural, +- $this->code, +- $this->constraint, +- $this->cause +- )); ++ $this->violations->add( ++ new ConstraintViolation( ++ $translatedMessage, ++ $this->message, ++ $this->parameters, ++ $this->root, ++ $this->propertyPath, ++ $this->invalidValue, ++ $this->plural, ++ $this->code, ++ $this->constraint, ++ $this->cause ++ ) ++ ); + } + } + +--- vendor/symfony/validator/Violation/ConstraintViolationBuilderInterface.php ++++ PHP_CodeSniffer +@@ -19,7 +19,7 @@ + * Finally, call {@link addViolation()} to add the violation to the current + * execution context. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/Tests/ConstraintViolationListTest.php ++++ PHP_CodeSniffer +@@ -102,13 +102,15 @@ + + public function testToString() + { +- $this->list = new ConstraintViolationList(array( ++ $this->list = new ConstraintViolationList( ++ array( + $this->getViolation('Error 1', 'Root'), + $this->getViolation('Error 2', 'Root', 'foo.bar'), + $this->getViolation('Error 3', 'Root', '[baz]'), + $this->getViolation('Error 4', '', 'foo.bar'), + $this->getViolation('Error 5', '', '[baz]'), +- )); ++ ) ++ ); + + $expected = <<<'EOF' + Root: + +--- vendor/symfony/validator/Tests/ConstraintTest.php ++++ PHP_CodeSniffer +@@ -23,10 +23,12 @@ + { + public function testSetProperties() + { +- $constraint = new ConstraintA(array( ++ $constraint = new ConstraintA( ++ array( + 'property1' => 'foo', + 'property2' => 'bar', +- )); ++ ) ++ ); + + $this->assertEquals('foo', $constraint->property1); + $this->assertEquals('bar', $constraint->property2); +@@ -36,9 +38,11 @@ + { + $this->setExpectedException('Symfony\Component\Validator\Exception\InvalidOptionsException'); + +- new ConstraintA(array( ++ new ConstraintA( ++ array( + 'foo' => 'bar', +- )); ++ ) ++ ); + } + + public function testMagicPropertiesAreNotAllowed() +@@ -54,10 +58,12 @@ + { + $this->setExpectedException('Symfony\Component\Validator\Exception\InvalidOptionsException'); + +- new ConstraintC(array( ++ new ConstraintC( ++ array( + 'option1' => 'default', + 'foo' => 'bar', +- )); ++ ) ++ ); + } + + public function testSetDefaultProperty() +@@ -158,10 +164,12 @@ + + public function testSerialize() + { +- $constraint = new ConstraintA(array( ++ $constraint = new ConstraintA( ++ array( + 'property1' => 'foo', + 'property2' => 'bar', +- )); ++ ) ++ ); + + $restoredConstraint = unserialize(serialize($constraint)); + +@@ -170,29 +178,35 @@ + + public function testSerializeInitializesGroupsOptionToDefault() + { +- $constraint = new ConstraintA(array( ++ $constraint = new ConstraintA( ++ array( + 'property1' => 'foo', + 'property2' => 'bar', +- )); ++ ) ++ ); + + $constraint = unserialize(serialize($constraint)); + +- $expected = new ConstraintA(array( ++ $expected = new ConstraintA( ++ array( + 'property1' => 'foo', + 'property2' => 'bar', + 'groups' => 'Default', +- )); ++ ) ++ ); + + $this->assertEquals($expected, $constraint); + } + + public function testSerializeKeepsCustomGroups() + { +- $constraint = new ConstraintA(array( ++ $constraint = new ConstraintA( ++ array( + 'property1' => 'foo', + 'property2' => 'bar', + 'groups' => 'MyGroup', +- )); ++ ) ++ ); + + $constraint = unserialize(serialize($constraint)); + + +--- vendor/symfony/validator/Tests/Constraints/TimeValidatorTest.php ++++ PHP_CodeSniffer +@@ -80,9 +80,11 @@ + */ + public function testInvalidTimes($time, $code) + { +- $constraint = new Time(array( ++ $constraint = new Time( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($time, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/DateTimeValidatorTest.php ++++ PHP_CodeSniffer +@@ -80,9 +80,11 @@ + */ + public function testInvalidDateTimes($dateTime, $code) + { +- $constraint = new DateTime(array( ++ $constraint = new DateTime( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($dateTime, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CompositeTest.php ++++ PHP_CodeSniffer +@@ -40,10 +40,12 @@ + { + public function testMergeNestedGroupsIfNoExplicitParentGroup() + { +- $constraint = new ConcreteComposite(array( ++ $constraint = new ConcreteComposite( ++ array( + new NotNull(array('groups' => 'Default')), + new NotBlank(array('groups' => array('Default', 'Strict'))), +- )); ++ ) ++ ); + + $this->assertEquals(array('Default', 'Strict'), $constraint->groups); + $this->assertEquals(array('Default'), $constraint->constraints[0]->groups); +@@ -52,13 +54,15 @@ + + public function testSetImplicitNestedGroupsIfExplicitParentGroup() + { +- $constraint = new ConcreteComposite(array( ++ $constraint = new ConcreteComposite( ++ array( + 'constraints' => array( + new NotNull(), + new NotBlank(), + ), + 'groups' => array('Default', 'Strict'), +- )); ++ ) ++ ); + + $this->assertEquals(array('Default', 'Strict'), $constraint->groups); + $this->assertEquals(array('Default', 'Strict'), $constraint->constraints[0]->groups); +@@ -67,13 +71,15 @@ + + public function testExplicitNestedGroupsMustBeSubsetOfExplicitParentGroups() + { +- $constraint = new ConcreteComposite(array( ++ $constraint = new ConcreteComposite( ++ array( + 'constraints' => array( + new NotNull(array('groups' => 'Default')), + new NotBlank(array('groups' => 'Strict')), + ), + 'groups' => array('Default', 'Strict'), +- )); ++ ) ++ ); + + $this->assertEquals(array('Default', 'Strict'), $constraint->groups); + $this->assertEquals(array('Default'), $constraint->constraints[0]->groups); +@@ -85,20 +91,24 @@ + */ + public function testFailIfExplicitNestedGroupsNotSubsetOfExplicitParentGroups() + { +- new ConcreteComposite(array( ++ new ConcreteComposite( ++ array( + 'constraints' => array( + new NotNull(array('groups' => array('Default', 'Foobar'))), + ), + 'groups' => array('Default', 'Strict'), +- )); ++ ) ++ ); + } + + public function testImplicitGroupNamesAreForwarded() + { +- $constraint = new ConcreteComposite(array( ++ $constraint = new ConcreteComposite( ++ array( + new NotNull(array('groups' => 'Default')), + new NotBlank(array('groups' => 'Strict')), +- )); ++ ) ++ ); + + $constraint->addImplicitGroupName('ImplicitGroup'); + +@@ -120,10 +130,12 @@ + */ + public function testFailIfNoConstraint() + { +- new ConcreteComposite(array( ++ new ConcreteComposite( ++ array( + new NotNull(array('groups' => 'Default')), + 'NotBlank', +- )); ++ ) ++ ); + } + + /** +@@ -131,8 +143,10 @@ + */ + public function testValidCantBeNested() + { +- new ConcreteComposite(array( ++ new ConcreteComposite( ++ array( + new Valid(), +- )); ++ ) ++ ); + } + } + +--- vendor/symfony/validator/Tests/Constraints/ImageValidatorTest.php ++++ PHP_CodeSniffer +@@ -77,9 +77,11 @@ + public function testFileNotFound() + { + // Check that the logic from FileValidator still works +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'notFoundMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('foobar', $constraint); + +@@ -91,12 +93,14 @@ + + public function testValidSize() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minWidth' => 1, + 'maxWidth' => 2, + 'minHeight' => 1, + 'maxHeight' => 2, +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -105,10 +109,12 @@ + + public function testWidthTooSmall() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minWidth' => 3, + 'minWidthMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -121,10 +127,12 @@ + + public function testWidthTooBig() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxWidth' => 1, + 'maxWidthMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -137,10 +145,12 @@ + + public function testHeightTooSmall() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minHeight' => 3, + 'minHeightMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -153,10 +163,12 @@ + + public function testHeightTooBig() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxHeight' => 1, + 'maxHeightMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -172,9 +184,11 @@ + */ + public function testInvalidMinWidth() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minWidth' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + } +@@ -184,9 +198,11 @@ + */ + public function testInvalidMaxWidth() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxWidth' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + } +@@ -196,9 +212,11 @@ + */ + public function testInvalidMinHeight() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minHeight' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + } +@@ -208,19 +226,23 @@ + */ + public function testInvalidMaxHeight() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxHeight' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + } + + public function testRatioTooSmall() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minRatio' => 2, + 'minRatioMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -233,10 +255,12 @@ + + public function testRatioTooBig() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxRatio' => 0.5, + 'maxRatioMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -249,9 +273,11 @@ + + public function testMaxRatioUsesTwoDecimalsOnly() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxRatio' => 1.33, +- )); ++ ) ++ ); + + $this->validator->validate($this->image4By3, $constraint); + +@@ -263,9 +289,11 @@ + */ + public function testInvalidMinRatio() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'minRatio' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + } +@@ -275,19 +303,23 @@ + */ + public function testInvalidMaxRatio() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'maxRatio' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + } + + public function testSquareNotAllowed() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'allowSquare' => false, + 'allowSquareMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->image, $constraint); + +@@ -300,10 +332,12 @@ + + public function testLandscapeNotAllowed() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'allowLandscape' => false, + 'allowLandscapeMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->imageLandscape, $constraint); + +@@ -316,10 +350,12 @@ + + public function testPortraitNotAllowed() + { +- $constraint = new Image(array( ++ $constraint = new Image( ++ array( + 'allowPortrait' => false, + 'allowPortraitMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->imagePortrait, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/LengthValidatorTest.php ++++ PHP_CodeSniffer +@@ -135,10 +135,12 @@ + */ + public function testInvalidValuesMin($value) + { +- $constraint = new Length(array( ++ $constraint = new Length( ++ array( + 'min' => 4, + 'minMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -156,10 +158,12 @@ + */ + public function testInvalidValuesMax($value) + { +- $constraint = new Length(array( ++ $constraint = new Length( ++ array( + 'max' => 4, + 'maxMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -177,11 +181,13 @@ + */ + public function testInvalidValuesExactLessThanFour($value) + { +- $constraint = new Length(array( ++ $constraint = new Length( ++ array( + 'min' => 4, + 'max' => 4, + 'exactMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -199,11 +205,13 @@ + */ + public function testInvalidValuesExactMoreThanFour($value) + { +- $constraint = new Length(array( ++ $constraint = new Length( ++ array( + 'min' => 4, + 'max' => 4, + 'exactMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -221,12 +229,14 @@ + */ + public function testOneCharset($value, $charset, $isValid) + { +- $constraint = new Length(array( ++ $constraint = new Length( ++ array( + 'min' => 1, + 'max' => 1, + 'charset' => $charset, + 'charsetMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/AbstractConstraintValidatorTest.php ++++ PHP_CodeSniffer +@@ -101,24 +101,24 @@ + $contextualValidator = $this->getMock('Symfony\Component\Validator\Validator\ContextualValidatorInterface'); + + switch ($this->getApiVersion()) { +- case Validation::API_VERSION_2_5: +- $context = new ExecutionContext( +- $validator, +- $this->root, +- $translator +- ); +- break; +- case Validation::API_VERSION_2_4: +- case Validation::API_VERSION_2_5_BC: +- $context = new LegacyExecutionContext( +- $validator, +- $this->root, +- $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface'), +- $translator +- ); +- break; +- default: +- throw new \RuntimeException('Invalid API version'); ++ case Validation::API_VERSION_2_5: ++ $context = new ExecutionContext( ++ $validator, ++ $this->root, ++ $translator ++ ); ++ break; ++ case Validation::API_VERSION_2_4: ++ case Validation::API_VERSION_2_5_BC: ++ $context = new LegacyExecutionContext( ++ $validator, ++ $this->root, ++ $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface'), ++ $translator ++ ); ++ break; ++ default: ++ throw new \RuntimeException('Invalid API version'); + } + + $context->setGroup($this->group); + +--- vendor/symfony/validator/Tests/Constraints/NotNullValidatorTest.php ++++ PHP_CodeSniffer +@@ -49,9 +49,11 @@ + + public function testNullIsInvalid() + { +- $constraint = new NotNull(array( ++ $constraint = new NotNull( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(null, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CollectionValidatorTest.php ++++ PHP_CodeSniffer +@@ -35,9 +35,13 @@ + + public function testNullIsValid() + { +- $this->validator->validate(null, new Collection(array('fields' => array( +- 'foo' => new Range(array('min' => 4)), +- )))); ++ $this->validator->validate( ++ null, new Collection( ++ array('fields' => array( ++ 'foo' => new Range(array('min' => 4)), ++ )) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -50,9 +54,13 @@ + + $this->expectValidateValueAt(0, '[foo]', $data['foo'], array($constraint)); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => $constraint, +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => $constraint, ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -62,9 +70,13 @@ + */ + public function testThrowsExceptionIfNotTraversable() + { +- $this->validator->validate('foobar', new Collection(array('fields' => array( +- 'foo' => new Range(array('min' => 4)), +- )))); ++ $this->validator->validate( ++ 'foobar', new Collection( ++ array('fields' => array( ++ 'foo' => new Range(array('min' => 4)), ++ )) ++ ) ++ ); + } + + public function testWalkSingleConstraint() +@@ -84,12 +96,16 @@ + + $data = $this->prepareTestData($array); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, + 'bar' => $constraint, +- ), +- ))); ++ ), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -114,12 +130,16 @@ + + $data = $this->prepareTestData($array); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraints, + 'bar' => $constraints, +- ), +- ))); ++ ), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -128,19 +148,25 @@ + { + $constraint = new Range(array('min' => 4)); + +- $data = $this->prepareTestData(array( ++ $data = $this->prepareTestData( ++ array( + 'foo' => 5, + 'baz' => 6, +- )); ++ ) ++ ); + + $this->expectValidateValueAt(0, '[foo]', $data['foo'], array($constraint)); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, +- ), +- 'extraFieldsMessage' => 'myMessage', +- ))); ++ ), ++ 'extraFieldsMessage' => 'myMessage', ++ ) ++ ) ++ ); + + $this->buildViolation('myMessage') + ->setParameter('{{ field }}', '"baz"') +@@ -153,40 +179,52 @@ + // bug fix + public function testNullNotConsideredExtraField() + { +- $data = $this->prepareTestData(array( ++ $data = $this->prepareTestData( ++ array( + 'foo' => null, +- )); ++ ) ++ ); + + $constraint = new Range(array('min' => 4)); + + $this->expectValidateValueAt(0, '[foo]', $data['foo'], array($constraint)); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, +- ), +- ))); ++ ), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } + + public function testExtraFieldsAllowed() + { +- $data = $this->prepareTestData(array( ++ $data = $this->prepareTestData( ++ array( + 'foo' => 5, + 'bar' => 6, +- )); ++ ) ++ ); + + $constraint = new Range(array('min' => 4)); + + $this->expectValidateValueAt(0, '[foo]', $data['foo'], array($constraint)); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, +- ), +- 'allowExtraFields' => true, +- ))); ++ ), ++ 'allowExtraFields' => true, ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -197,12 +235,16 @@ + + $constraint = new Range(array('min' => 4)); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, +- ), +- 'missingFieldsMessage' => 'myMessage', +- ))); ++ ), ++ 'missingFieldsMessage' => 'myMessage', ++ ) ++ ) ++ ); + + $this->buildViolation('myMessage') + ->setParameter('{{ field }}', '"foo"') +@@ -218,25 +260,35 @@ + + $constraint = new Range(array('min' => 4)); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, +- ), +- 'allowMissingFields' => true, +- ))); ++ ), ++ 'allowMissingFields' => true, ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } + + public function testOptionalFieldPresent() + { +- $data = $this->prepareTestData(array( ++ $data = $this->prepareTestData( ++ array( + 'foo' => null, +- )); ++ ) ++ ); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Optional(), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Optional(), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -245,9 +297,13 @@ + { + $data = $this->prepareTestData(array()); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Optional(), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Optional(), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -264,9 +320,13 @@ + + $data = $this->prepareTestData($array); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Optional($constraint), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Optional($constraint), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -286,22 +346,32 @@ + + $data = $this->prepareTestData($array); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Optional($constraints), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Optional($constraints), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } + + public function testRequiredFieldPresent() + { +- $data = $this->prepareTestData(array( ++ $data = $this->prepareTestData( ++ array( + 'foo' => null, +- )); ++ ) ++ ); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Required(), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Required(), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -310,12 +380,16 @@ + { + $data = $this->prepareTestData(array()); + +- $this->validator->validate($data, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'fields' => array( + 'foo' => new Required(), +- ), +- 'missingFieldsMessage' => 'myMessage', +- ))); ++ ), ++ 'missingFieldsMessage' => 'myMessage', ++ ) ++ ) ++ ); + + $this->buildViolation('myMessage') + ->setParameter('{{ field }}', '"foo"') +@@ -337,9 +411,13 @@ + + $data = $this->prepareTestData($array); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Required($constraint), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Required($constraint), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -359,31 +437,43 @@ + + $data = $this->prepareTestData($array); + +- $this->validator->validate($data, new Collection(array( +- 'foo' => new Required($constraints), +- ))); ++ $this->validator->validate( ++ $data, new Collection( ++ array( ++ 'foo' => new Required($constraints), ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } + + public function testObjectShouldBeLeftUnchanged() + { +- $value = new \ArrayObject(array( ++ $value = new \ArrayObject( ++ array( + 'foo' => 3, +- )); ++ ) ++ ); + + $constraint = new Range(array('min' => 2)); + + $this->expectValidateValueAt(0, '[foo]', $value['foo'], array($constraint)); + +- $this->validator->validate($value, new Collection(array( +- 'fields' => array( ++ $this->validator->validate( ++ $value, new Collection( ++ array( ++ 'fields' => array( + 'foo' => $constraint, +- ), +- ))); ++ ), ++ ) ++ ) ++ ); + +- $this->assertEquals(array( ++ $this->assertEquals( ++ array( + 'foo' => 3, +- ), (array) $value); ++ ), (array) $value ++ ); + } + } + +--- vendor/symfony/validator/Tests/Constraints/ChoiceValidatorTest.php ++++ PHP_CodeSniffer +@@ -42,10 +42,12 @@ + */ + public function testExpectArrayIfMultipleIsTrue() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array('foo', 'bar'), + 'multiple' => true, +- )); ++ ) ++ ); + + $this->validator->validate('asdf', $constraint); + } +@@ -93,9 +95,11 @@ + + public function testValidChoiceCallbackClosure() + { +- $constraint = new Choice(array('callback' => function () { +- return array('foo', 'bar'); +- })); ++ $constraint = new Choice( ++ array('callback' => function () { ++ return array('foo', 'bar'); ++ }) ++ ); + + $this->validator->validate('bar', $constraint); + +@@ -125,10 +129,12 @@ + + public function testMultipleChoices() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array('foo', 'bar', 'baz'), + 'multiple' => true, +- )); ++ ) ++ ); + + $this->validator->validate(array('baz', 'bar'), $constraint); + +@@ -137,10 +143,12 @@ + + public function testInvalidChoice() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array('foo', 'bar'), + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('baz', $constraint); + +@@ -152,12 +160,14 @@ + + public function testInvalidChoiceEmptyChoices() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + // May happen when the choices are provided dynamically, e.g. from + // the DB or the model + 'choices' => array(), + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('baz', $constraint); + +@@ -169,11 +179,13 @@ + + public function testInvalidChoiceMultiple() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array('foo', 'bar'), + 'multipleMessage' => 'myMessage', + 'multiple' => true, +- )); ++ ) ++ ); + + $this->validator->validate(array('foo', 'baz'), $constraint); + +@@ -186,12 +198,14 @@ + + public function testTooFewChoices() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array('foo', 'bar', 'moo', 'maa'), + 'multiple' => true, + 'min' => 2, + 'minMessage' => 'myMessage', +- )); ++ ) ++ ); + + $value = array('foo'); + +@@ -209,12 +223,14 @@ + + public function testTooManyChoices() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array('foo', 'bar', 'moo', 'maa'), + 'multiple' => true, + 'max' => 2, + 'maxMessage' => 'myMessage', +- )); ++ ) ++ ); + + $value = array('foo', 'bar', 'moo'); + +@@ -232,10 +248,12 @@ + + public function testNonStrict() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array(1, 2), + 'strict' => false, +- )); ++ ) ++ ); + + $this->validator->validate('2', $constraint); + $this->validator->validate(2, $constraint); +@@ -245,10 +263,12 @@ + + public function testStrictAllowsExactValue() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array(1, 2), + 'strict' => true, +- )); ++ ) ++ ); + + $this->validator->validate(2, $constraint); + +@@ -257,11 +277,13 @@ + + public function testStrictDisallowsDifferentType() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array(1, 2), + 'strict' => true, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('2', $constraint); + +@@ -273,11 +295,13 @@ + + public function testNonStrictWithMultipleChoices() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array(1, 2, 3), + 'multiple' => true, + 'strict' => false, +- )); ++ ) ++ ); + + $this->validator->validate(array('2', 3), $constraint); + +@@ -286,12 +310,14 @@ + + public function testStrictWithMultipleChoices() + { +- $constraint = new Choice(array( ++ $constraint = new Choice( ++ array( + 'choices' => array(1, 2, 3), + 'multiple' => true, + 'strict' => true, + 'multipleMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(array(2, '3'), $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CollectionTest.php ++++ PHP_CodeSniffer +@@ -27,9 +27,11 @@ + */ + public function testRejectInvalidFieldsOption() + { +- new Collection(array( ++ new Collection( ++ array( + 'fields' => 'foo', +- )); ++ ) ++ ); + } + + /** +@@ -37,9 +39,11 @@ + */ + public function testRejectNonConstraints() + { +- new Collection(array( ++ new Collection( ++ array( + 'foo' => 'bar', +- )); ++ ) ++ ); + } + + /** +@@ -47,9 +51,11 @@ + */ + public function testRejectValidConstraint() + { +- new Collection(array( ++ new Collection( ++ array( + 'foo' => new Valid(), +- )); ++ ) ++ ); + } + + /** +@@ -57,9 +63,11 @@ + */ + public function testRejectValidConstraintWithinOptional() + { +- new Collection(array( ++ new Collection( ++ array( + 'foo' => new Optional(new Valid()), +- )); ++ ) ++ ); + } + + /** +@@ -67,45 +75,55 @@ + */ + public function testRejectValidConstraintWithinRequired() + { +- new Collection(array( ++ new Collection( ++ array( + 'foo' => new Required(new Valid()), +- )); ++ ) ++ ); + } + + public function testAcceptOptionalConstraintAsOneElementArray() + { +- $collection1 = new Collection(array( ++ $collection1 = new Collection( ++ array( + 'fields' => array( + 'alternate_email' => array( + new Optional(new Email()), + ), + ), +- )); ++ ) ++ ); + +- $collection2 = new Collection(array( ++ $collection2 = new Collection( ++ array( + 'fields' => array( + 'alternate_email' => new Optional(new Email()), + ), +- )); ++ ) ++ ); + + $this->assertEquals($collection1, $collection2); + } + + public function testAcceptRequiredConstraintAsOneElementArray() + { +- $collection1 = new Collection(array( ++ $collection1 = new Collection( ++ array( + 'fields' => array( + 'alternate_email' => array( + new Required(new Email()), + ), + ), +- )); ++ ) ++ ); + +- $collection2 = new Collection(array( ++ $collection2 = new Collection( ++ array( + 'fields' => array( + 'alternate_email' => new Required(new Email()), + ), +- )); ++ ) ++ ); + + $this->assertEquals($collection1, $collection2); + } + +--- vendor/symfony/validator/Tests/Constraints/CardSchemeValidatorTest.php ++++ PHP_CodeSniffer +@@ -56,10 +56,12 @@ + */ + public function testInvalidNumbers($scheme, $number, $code) + { +- $constraint = new CardScheme(array( ++ $constraint = new CardScheme( ++ array( + 'schemes' => $scheme, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($number, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/IsNullValidatorTest.php ++++ PHP_CodeSniffer +@@ -39,9 +39,11 @@ + */ + public function testInvalidValues($value, $valueAsString) + { +- $constraint = new IsNull(array( ++ $constraint = new IsNull( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/LuhnValidatorTest.php ++++ PHP_CodeSniffer +@@ -80,9 +80,11 @@ + */ + public function testInvalidNumbers($number, $code) + { +- $constraint = new Luhn(array( ++ $constraint = new Luhn( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($number, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/IbanValidatorTest.php ++++ PHP_CodeSniffer +@@ -424,9 +424,11 @@ + + private function assertViolationRaised($iban, $code) + { +- $constraint = new Iban(array( ++ $constraint = new Iban( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($iban, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/IsbnValidatorTest.php ++++ PHP_CodeSniffer +@@ -156,9 +156,11 @@ + */ + public function testValidIsbn10($isbn) + { +- $constraint = new Isbn(array( ++ $constraint = new Isbn( ++ array( + 'type' => 'isbn10', +- )); ++ ) ++ ); + + $this->validator->validate($isbn, $constraint); + +@@ -170,10 +172,12 @@ + */ + public function testInvalidIsbn10($isbn, $code) + { +- $constraint = new Isbn(array( ++ $constraint = new Isbn( ++ array( + 'type' => 'isbn10', + 'isbn10Message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($isbn, $constraint); + +@@ -200,10 +204,12 @@ + */ + public function testInvalidIsbn13($isbn, $code) + { +- $constraint = new Isbn(array( ++ $constraint = new Isbn( ++ array( + 'type' => 'isbn13', + 'isbn13Message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($isbn, $constraint); + +@@ -230,9 +236,11 @@ + */ + public function testInvalidIsbnAnyIsbn10($isbn, $code) + { +- $constraint = new Isbn(array( ++ $constraint = new Isbn( ++ array( + 'bothIsbnMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($isbn, $constraint); + +@@ -252,9 +260,11 @@ + */ + public function testInvalidIsbnAnyIsbn13($isbn, $code) + { +- $constraint = new Isbn(array( ++ $constraint = new Isbn( ++ array( + 'bothIsbnMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($isbn, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CallbackValidatorTest.php ++++ PHP_CodeSniffer +@@ -102,11 +102,13 @@ + public function testClosure() + { + $object = new CallbackValidatorTest_Object(); +- $constraint = new Callback(function ($object, ExecutionContextInterface $context) { +- $context->addViolation('My message', array('{{ value }}' => 'foobar')); ++ $constraint = new Callback( ++ function ($object, ExecutionContextInterface $context) { ++ $context->addViolation('My message', array('{{ value }}' => 'foobar')); + +- return false; +- }); ++ return false; ++ } ++ ); + + $this->validator->validate($object, $constraint); + +@@ -117,11 +119,13 @@ + + public function testClosureNullObject() + { +- $constraint = new Callback(function ($object, ExecutionContextInterface $context) { +- $context->addViolation('My message', array('{{ value }}' => 'foobar')); ++ $constraint = new Callback( ++ function ($object, ExecutionContextInterface $context) { ++ $context->addViolation('My message', array('{{ value }}' => 'foobar')); + +- return false; +- }); ++ return false; ++ } ++ ); + + $this->validator->validate(null, $constraint); + +@@ -133,13 +137,15 @@ + public function testClosureExplicitName() + { + $object = new CallbackValidatorTest_Object(); +- $constraint = new Callback(array( ++ $constraint = new Callback( ++ array( + 'callback' => function ($object, ExecutionContextInterface $context) { + $context->addViolation('My message', array('{{ value }}' => 'foobar')); + + return false; + }, +- )); ++ ) ++ ); + + $this->validator->validate($object, $constraint); + +@@ -174,9 +180,11 @@ + public function testArrayCallableExplicitName() + { + $object = new CallbackValidatorTest_Object(); +- $constraint = new Callback(array( ++ $constraint = new Callback( ++ array( + 'callback' => array(__CLASS__.'_Class', 'validateCallback'), +- )); ++ ) ++ ); + + $this->validator->validate($object, $constraint); + +@@ -242,9 +250,11 @@ + public function testLegacyMultipleMethodsBcExplicitName() + { + $object = new CallbackValidatorTest_Object(); +- $constraint = new Callback(array( ++ $constraint = new Callback( ++ array( + 'methods' => array('validate', 'validateStatic'), +- )); ++ ) ++ ); + + $this->validator->validate($object, $constraint); + +@@ -262,9 +272,11 @@ + public function testLegacySingleStaticMethodBc() + { + $object = new CallbackValidatorTest_Object(); +- $constraint = new Callback(array( ++ $constraint = new Callback( ++ array( + array(__CLASS__.'_Class', 'validateCallback'), +- )); ++ ) ++ ); + + $this->validator->validate($object, $constraint); + +@@ -280,9 +292,11 @@ + public function testLegacySingleStaticMethodBcExplicitName() + { + $object = new CallbackValidatorTest_Object(); +- $constraint = new Callback(array( ++ $constraint = new Callback( ++ array( + 'methods' => array(array(__CLASS__.'_Class', 'validateCallback')), +- )); ++ ) ++ ); + + $this->validator->validate($object, $constraint); + +@@ -319,10 +333,14 @@ + { + $object = new CallbackValidatorTest_Object(); + +- $this->validator->validate($object, new Callback(array( +- 'callback' => 'validate', +- 'methods' => array('validateStatic'), +- ))); ++ $this->validator->validate( ++ $object, new Callback( ++ array( ++ 'callback' => 'validate', ++ 'methods' => array('validateStatic'), ++ ) ++ ) ++ ); + } + + public function testConstraintGetTargets() + +--- vendor/symfony/validator/Tests/Constraints/RangeValidatorTest.php ++++ PHP_CodeSniffer +@@ -107,10 +107,12 @@ + */ + public function testInvalidValuesMin($value, $formattedValue) + { +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'min' => 10, + 'minMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -126,10 +128,12 @@ + */ + public function testInvalidValuesMax($value, $formattedValue) + { +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'max' => 20, + 'maxMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -145,12 +149,14 @@ + */ + public function testInvalidValuesCombinedMax($value, $formattedValue) + { +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'min' => 10, + 'max' => 20, + 'minMessage' => 'myMinMessage', + 'maxMessage' => 'myMaxMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -166,12 +172,14 @@ + */ + public function testInvalidValuesCombinedMin($value, $formattedValue) + { +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'min' => 10, + 'max' => 20, + 'minMessage' => 'myMinMessage', + 'maxMessage' => 'myMaxMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -289,10 +297,12 @@ + // Make sure we have the correct version loaded + IntlTestHelper::requireIntl($this); + +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'min' => 'March 10, 2014', + 'minMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -312,10 +322,12 @@ + // Make sure we have the correct version loaded + IntlTestHelper::requireIntl($this); + +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'max' => 'March 20, 2014', + 'maxMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -335,12 +347,14 @@ + // Make sure we have the correct version loaded + IntlTestHelper::requireIntl($this); + +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'min' => 'March 10, 2014', + 'max' => 'March 20, 2014', + 'minMessage' => 'myMinMessage', + 'maxMessage' => 'myMaxMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -360,12 +374,14 @@ + // Make sure we have the correct version loaded + IntlTestHelper::requireIntl($this); + +- $constraint = new Range(array( ++ $constraint = new Range( ++ array( + 'min' => 'March 10, 2014', + 'max' => 'March 20, 2014', + 'minMessage' => 'myMinMessage', + 'maxMessage' => 'myMaxMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -389,11 +405,15 @@ + + public function testNonNumeric() + { +- $this->validator->validate('abcd', new Range(array( +- 'min' => 10, +- 'max' => 20, +- 'invalidMessage' => 'myMessage', +- ))); ++ $this->validator->validate( ++ 'abcd', new Range( ++ array( ++ 'min' => 10, ++ 'max' => 20, ++ 'invalidMessage' => 'myMessage', ++ ) ++ ) ++ ); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"abcd"') + +--- vendor/symfony/validator/Tests/Constraints/ExpressionValidatorTest.php ++++ PHP_CodeSniffer +@@ -31,10 +31,12 @@ + + public function testExpressionIsEvaluatedWithNullValue() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'false', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(null, $constraint); + +@@ -46,10 +48,12 @@ + + public function testExpressionIsEvaluatedWithEmptyStringValue() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'false', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('', $constraint); + +@@ -75,10 +79,12 @@ + + public function testFailingExpressionAtObjectLevel() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'this.data == 1', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $object = new Entity(); + $object->data = '2'; +@@ -111,10 +117,12 @@ + + public function testFailingExpressionAtPropertyLevel() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'value == this.data', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $object = new Entity(); + $object->data = '1'; +@@ -153,10 +161,12 @@ + + public function testFailingExpressionAtNestedPropertyLevel() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'value == this.data', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $object = new Entity(); + $object->data = '1'; +@@ -200,10 +210,12 @@ + */ + public function testFailingExpressionAtPropertyLevelWithoutRoot() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'value == "1"', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->setRoot('2'); + $this->setPropertyPath(''); +@@ -220,20 +232,26 @@ + + public function testExpressionLanguageUsage() + { +- $constraint = new Expression(array( ++ $constraint = new Expression( ++ array( + 'expression' => 'false', +- )); ++ ) ++ ); + + $expressionLanguage = $this->getMock('Symfony\Component\ExpressionLanguage\ExpressionLanguage'); + + $used = false; + + $expressionLanguage->method('evaluate') +- ->will($this->returnCallback(function () use (&$used) { +- $used = true; +- +- return true; +- })); ++ ->will( ++ $this->returnCallback( ++ function () use (&$used) { ++ $used = true; ++ ++ return true; ++ } ++ ) ++ ); + + $validator = new ExpressionValidator(null, $expressionLanguage); + $validator->initialize($this->createContext()); + +--- vendor/symfony/validator/Tests/Constraints/NotBlankValidatorTest.php ++++ PHP_CodeSniffer +@@ -50,9 +50,11 @@ + + public function testNullIsInvalid() + { +- $constraint = new NotBlank(array( ++ $constraint = new NotBlank( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(null, $constraint); + +@@ -64,9 +66,11 @@ + + public function testBlankIsInvalid() + { +- $constraint = new NotBlank(array( ++ $constraint = new NotBlank( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('', $constraint); + +@@ -78,9 +82,11 @@ + + public function testFalseIsInvalid() + { +- $constraint = new NotBlank(array( ++ $constraint = new NotBlank( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(false, $constraint); + +@@ -92,9 +98,11 @@ + + public function testEmptyArrayIsInvalid() + { +- $constraint = new NotBlank(array( ++ $constraint = new NotBlank( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(array(), $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CountValidatorTest.php ++++ PHP_CodeSniffer +@@ -112,10 +112,12 @@ + */ + public function testTooManyValues($value) + { +- $constraint = new Count(array( ++ $constraint = new Count( ++ array( + 'max' => 4, + 'maxMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -133,10 +135,12 @@ + */ + public function testTooFewValues($value) + { +- $constraint = new Count(array( ++ $constraint = new Count( ++ array( + 'min' => 4, + 'minMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -154,11 +158,13 @@ + */ + public function testTooManyValuesExact($value) + { +- $constraint = new Count(array( ++ $constraint = new Count( ++ array( + 'min' => 4, + 'max' => 4, + 'exactMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + +@@ -176,11 +182,13 @@ + */ + public function testTooFewValuesExact($value) + { +- $constraint = new Count(array( ++ $constraint = new Count( ++ array( + 'min' => 4, + 'max' => 4, + 'exactMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/EmailValidatorTest.php ++++ PHP_CodeSniffer +@@ -77,9 +77,11 @@ + */ + public function testInvalidEmails($email) + { +- $constraint = new Email(array( ++ $constraint = new Email( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($email, $constraint); + +@@ -116,10 +118,12 @@ + { + DnsMock::withMockedHosts(array('example.com' => array(array('type' => $violation ? false : $type)))); + +- $constraint = new Email(array( ++ $constraint = new Email( ++ array( + 'message' => 'myMessage', + 'MX' === $type ? 'checkMX' : 'checkHost' => true, +- )); ++ ) ++ ); + + $this->validator->validate('foo@example.com', $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CurrencyValidatorTest.php ++++ PHP_CodeSniffer +@@ -90,9 +90,11 @@ + */ + public function testInvalidCurrencies($currency) + { +- $constraint = new Currency(array( ++ $constraint = new Currency( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($currency, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/TypeValidatorTest.php ++++ PHP_CodeSniffer +@@ -49,10 +49,12 @@ + + public function testEmptyIsInvalidIfNoString() + { +- $constraint = new Type(array( ++ $constraint = new Type( ++ array( + 'type' => 'integer', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('', $constraint); + +@@ -117,10 +119,12 @@ + */ + public function testInvalidValues($value, $type, $valueAsString) + { +- $constraint = new Type(array( ++ $constraint = new Type( ++ array( + 'type' => $type, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/DateValidatorTest.php ++++ PHP_CodeSniffer +@@ -80,9 +80,11 @@ + */ + public function testInvalidDates($date, $code) + { +- $constraint = new Date(array( ++ $constraint = new Date( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($date, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/BicValidatorTest.php ++++ PHP_CodeSniffer +@@ -63,9 +63,11 @@ + */ + public function testInvalidBics($bic, $code) + { +- $constraint = new Bic(array( ++ $constraint = new Bic( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($bic, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/FileValidatorTest.php ++++ PHP_CodeSniffer +@@ -169,10 +169,12 @@ + fwrite($this->file, '0'); + fclose($this->file); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'maxSize' => $limit, + 'maxSizeMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->getFile($this->path), $constraint); + +@@ -217,10 +219,12 @@ + fwrite($this->file, '0'); + fclose($this->file); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'maxSize' => $limit, + 'maxSizeMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->getFile($this->path), $constraint); + +@@ -232,9 +236,11 @@ + */ + public function testInvalidMaxSize() + { +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'maxSize' => '1abc', +- )); ++ ) ++ ); + + $this->validator->validate($this->path, $constraint); + } +@@ -270,11 +276,13 @@ + fwrite($this->file, '0'); + fclose($this->file); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'maxSize' => $limit, + 'binaryFormat' => $binaryFormat, + 'maxSizeMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->getFile($this->path), $constraint); + +@@ -302,9 +310,11 @@ + ->method('getMimeType') + ->will($this->returnValue('image/jpg')); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'mimeTypes' => array('image/png', 'image/jpg'), +- )); ++ ) ++ ); + + $this->validator->validate($file, $constraint); + +@@ -326,9 +336,11 @@ + ->method('getMimeType') + ->will($this->returnValue('image/jpg')); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'mimeTypes' => array('image/*'), +- )); ++ ) ++ ); + + $this->validator->validate($file, $constraint); + +@@ -350,10 +362,12 @@ + ->method('getMimeType') + ->will($this->returnValue('application/pdf')); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'mimeTypes' => array('image/png', 'image/jpg'), + 'mimeTypesMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($file, $constraint); + +@@ -380,10 +394,12 @@ + ->method('getMimeType') + ->will($this->returnValue('application/pdf')); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'mimeTypes' => array('image/*', 'image/jpg'), + 'mimeTypesMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($file, $constraint); + +@@ -399,9 +415,11 @@ + { + ftruncate($this->file, 0); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'disallowEmptyMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($this->getFile($this->path), $constraint); + +@@ -418,10 +436,12 @@ + { + $file = new UploadedFile('/path/to/file', 'originalName', 'mime', 0, $error); + +- $constraint = new File(array( ++ $constraint = new File( ++ array( + $message => 'myMessage', + 'maxSize' => $maxSize, +- )); ++ ) ++ ); + + $this->validator->validate($file, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/BlankValidatorTest.php ++++ PHP_CodeSniffer +@@ -46,9 +46,11 @@ + */ + public function testInvalidValues($value, $valueAsString) + { +- $constraint = new Blank(array( ++ $constraint = new Blank( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/UrlValidatorTest.php ++++ PHP_CodeSniffer +@@ -135,9 +135,11 @@ + */ + public function testInvalidUrls($url) + { +- $constraint = new Url(array( ++ $constraint = new Url( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($url, $constraint); + +@@ -175,9 +177,11 @@ + */ + public function testCustomProtocolIsValid($url) + { +- $constraint = new Url(array( ++ $constraint = new Url( ++ array( + 'protocols' => array('ftp', 'file', 'git'), +- )); ++ ) ++ ); + + $this->validator->validate($url, $constraint); + +@@ -201,10 +205,12 @@ + { + DnsMock::withMockedHosts(array('example.com' => array(array('type' => $violation ? '' : 'A')))); + +- $constraint = new Url(array( ++ $constraint = new Url( ++ array( + 'checkDNS' => true, + 'dnsMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('http://example.com', $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/RegexValidatorTest.php ++++ PHP_CodeSniffer +@@ -75,10 +75,12 @@ + */ + public function testInvalidValues($value) + { +- $constraint = new Regex(array( ++ $constraint = new Regex( ++ array( + 'pattern' => '/^[0-9]+$/', + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($value, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/CountryValidatorTest.php ++++ PHP_CodeSniffer +@@ -74,9 +74,11 @@ + */ + public function testInvalidCountries($country) + { +- $constraint = new Country(array( ++ $constraint = new Country( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($country, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/AllTest.php ++++ PHP_CodeSniffer +@@ -24,9 +24,11 @@ + */ + public function testRejectNonConstraints() + { +- new All(array( ++ new All( ++ array( + 'foo', +- )); ++ ) ++ ); + } + + /** +@@ -34,8 +36,10 @@ + */ + public function testRejectValidConstraint() + { +- new All(array( ++ new All( ++ array( + new Valid(), +- )); ++ ) ++ ); + } + } + +--- vendor/symfony/validator/Tests/Constraints/UuidValidatorTest.php ++++ PHP_CodeSniffer +@@ -85,9 +85,11 @@ + */ + public function testInvalidStrictUuids($uuid, $code, $versions = null) + { +- $constraint = new Uuid(array( ++ $constraint = new Uuid( ++ array( + 'message' => 'testMessage', +- )); ++ ) ++ ); + + if (null !== $versions) { + $constraint->versions = $versions; +@@ -152,9 +154,11 @@ + */ + public function testValidNonStrictUuids($uuid) + { +- $constraint = new Uuid(array( ++ $constraint = new Uuid( ++ array( + 'strict' => false, +- )); ++ ) ++ ); + + $this->validator->validate($uuid, $constraint); + +@@ -183,10 +187,12 @@ + */ + public function testInvalidNonStrictUuids($uuid, $code) + { +- $constraint = new Uuid(array( ++ $constraint = new Uuid( ++ array( + 'strict' => false, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($uuid, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/LocaleValidatorTest.php ++++ PHP_CodeSniffer +@@ -76,9 +76,11 @@ + */ + public function testInvalidLocales($locale) + { +- $constraint = new Locale(array( ++ $constraint = new Locale( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($locale, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/RegexTest.php ++++ PHP_CodeSniffer +@@ -65,10 +65,12 @@ + */ + public function testGetHtmlPattern($pattern, $htmlPattern, $match = true) + { +- $constraint = new Regex(array( ++ $constraint = new Regex( ++ array( + 'pattern' => $pattern, + 'match' => $match, +- )); ++ ) ++ ); + + $this->assertSame($pattern, $constraint->pattern); + $this->assertSame($htmlPattern, $constraint->getHtmlPattern()); +@@ -76,10 +78,12 @@ + + public function testGetCustomHtmlPattern() + { +- $constraint = new Regex(array( ++ $constraint = new Regex( ++ array( + 'pattern' => '((?![0-9]$|[a-z]+).)*', + 'htmlPattern' => 'foobar', +- )); ++ ) ++ ); + + $this->assertSame('((?![0-9]$|[a-z]+).)*', $constraint->pattern); + $this->assertSame('foobar', $constraint->getHtmlPattern()); + +--- vendor/symfony/validator/Tests/Constraints/IpValidatorTest.php ++++ PHP_CodeSniffer +@@ -54,9 +54,11 @@ + */ + public function testInvalidValidatorVersion() + { +- new Ip(array( ++ new Ip( ++ array( + 'version' => 666, +- )); ++ ) ++ ); + } + + /** +@@ -64,9 +66,13 @@ + */ + public function testValidIpsV4($ip) + { +- $this->validator->validate($ip, new Ip(array( +- 'version' => Ip::V4, +- ))); ++ $this->validator->validate( ++ $ip, new Ip( ++ array( ++ 'version' => Ip::V4, ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -90,9 +96,13 @@ + */ + public function testValidIpsV6($ip) + { +- $this->validator->validate($ip, new Ip(array( +- 'version' => Ip::V6, +- ))); ++ $this->validator->validate( ++ $ip, new Ip( ++ array( ++ 'version' => Ip::V6, ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -127,9 +137,13 @@ + */ + public function testValidIpsAll($ip) + { +- $this->validator->validate($ip, new Ip(array( +- 'version' => Ip::ALL, +- ))); ++ $this->validator->validate( ++ $ip, new Ip( ++ array( ++ 'version' => Ip::ALL, ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } +@@ -144,10 +158,12 @@ + */ + public function testInvalidIpsV4($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V4, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -177,10 +193,12 @@ + */ + public function testInvalidPrivateIpsV4($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V4_NO_PRIV, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -204,10 +222,12 @@ + */ + public function testInvalidReservedIpsV4($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V4_NO_RES, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -231,10 +251,12 @@ + */ + public function testInvalidPublicIpsV4($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V4_ONLY_PUBLIC, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -254,10 +276,12 @@ + */ + public function testInvalidIpsV6($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V6, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -291,10 +315,12 @@ + */ + public function testInvalidPrivateIpsV6($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V6_NO_PRIV, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -318,10 +344,12 @@ + */ + public function testInvalidReservedIpsV6($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V6_NO_RES, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -344,10 +372,12 @@ + */ + public function testInvalidPublicIpsV6($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::V6_ONLY_PUBLIC, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -367,10 +397,12 @@ + */ + public function testInvalidIpsAll($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::ALL, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -390,10 +422,12 @@ + */ + public function testInvalidPrivateIpsAll($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::ALL_NO_PRIV, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -413,10 +447,12 @@ + */ + public function testInvalidReservedIpsAll($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::ALL_NO_RES, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + +@@ -436,10 +472,12 @@ + */ + public function testInvalidPublicIpsAll($ip) + { +- $constraint = new Ip(array( ++ $constraint = new Ip( ++ array( + 'version' => Ip::ALL_ONLY_PUBLIC, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($ip, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/IssnValidatorTest.php ++++ PHP_CodeSniffer +@@ -125,10 +125,12 @@ + */ + public function testCaseSensitiveIssns($issn) + { +- $constraint = new Issn(array( ++ $constraint = new Issn( ++ array( + 'caseSensitive' => true, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($issn, $constraint); + +@@ -143,10 +145,12 @@ + */ + public function testRequireHyphenIssns($issn) + { +- $constraint = new Issn(array( ++ $constraint = new Issn( ++ array( + 'requireHyphen' => true, + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($issn, $constraint); + +@@ -173,9 +177,11 @@ + */ + public function testInvalidIssn($issn, $code) + { +- $constraint = new Issn(array( ++ $constraint = new Issn( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($issn, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/IsTrueValidatorTest.php ++++ PHP_CodeSniffer +@@ -43,9 +43,11 @@ + + public function testFalseIsInvalid() + { +- $constraint = new IsTrue(array( ++ $constraint = new IsTrue( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(false, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/LanguageValidatorTest.php ++++ PHP_CodeSniffer +@@ -74,9 +74,11 @@ + */ + public function testInvalidLanguages($language) + { +- $constraint = new Language(array( ++ $constraint = new Language( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate($language, $constraint); + +@@ -101,9 +103,13 @@ + \Locale::setDefault('fr_FR'); + $existingLanguage = 'en'; + +- $this->validator->validate($existingLanguage, new Language(array( +- 'message' => 'aMessage', +- ))); ++ $this->validator->validate( ++ $existingLanguage, new Language( ++ array( ++ 'message' => 'aMessage', ++ ) ++ ) ++ ); + + $this->assertNoViolation(); + } + +--- vendor/symfony/validator/Tests/Constraints/IsFalseValidatorTest.php ++++ PHP_CodeSniffer +@@ -43,9 +43,11 @@ + + public function testTrueIsInvalid() + { +- $constraint = new IsFalse(array( ++ $constraint = new IsFalse( ++ array( + 'message' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate(true, $constraint); + + +--- vendor/symfony/validator/Tests/Constraints/FileValidatorPathTest.php ++++ PHP_CodeSniffer +@@ -22,9 +22,11 @@ + + public function testFileNotFound() + { +- $constraint = new File(array( ++ $constraint = new File( ++ array( + 'notFoundMessage' => 'myMessage', +- )); ++ ) ++ ); + + $this->validator->validate('foobar', $constraint); + + +--- vendor/symfony/validator/Tests/ValidatorBuilderTest.php ++++ PHP_CodeSniffer +@@ -33,9 +33,11 @@ + + public function testAddObjectInitializer() + { +- $this->assertSame($this->builder, $this->builder->addObjectInitializer( +- $this->getMock('Symfony\Component\Validator\ObjectInitializerInterface') +- )); ++ $this->assertSame( ++ $this->builder, $this->builder->addObjectInitializer( ++ $this->getMock('Symfony\Component\Validator\ObjectInitializerInterface') ++ ) ++ ); + } + + public function testAddObjectInitializers() +@@ -85,22 +87,28 @@ + + public function testSetMetadataCache() + { +- $this->assertSame($this->builder, $this->builder->setMetadataCache( +- $this->getMock('Symfony\Component\Validator\Mapping\Cache\CacheInterface')) ++ $this->assertSame( ++ $this->builder, $this->builder->setMetadataCache( ++ $this->getMock('Symfony\Component\Validator\Mapping\Cache\CacheInterface') ++ ) + ); + } + + public function testSetConstraintValidatorFactory() + { +- $this->assertSame($this->builder, $this->builder->setConstraintValidatorFactory( +- $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface')) ++ $this->assertSame( ++ $this->builder, $this->builder->setConstraintValidatorFactory( ++ $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface') ++ ) + ); + } + + public function testSetTranslator() + { +- $this->assertSame($this->builder, $this->builder->setTranslator( +- $this->getMock('Symfony\Component\Translation\TranslatorInterface')) ++ $this->assertSame( ++ $this->builder, $this->builder->setTranslator( ++ $this->getMock('Symfony\Component\Translation\TranslatorInterface') ++ ) + ); + } + + +--- vendor/symfony/validator/Tests/LegacyExecutionContextTest.php ++++ PHP_CodeSniffer +@@ -98,16 +98,20 @@ + + $this->context->addViolation('Error', array('foo' => 'bar'), 'invalid'); + +- $this->assertEquals(new ConstraintViolationList(array( +- new ConstraintViolation( +- 'Translated error', +- 'Error', +- array('foo' => 'bar'), +- 'Root', +- 'foo.bar', +- 'invalid' +- ), +- )), $this->context->getViolations()); ++ $this->assertEquals( ++ new ConstraintViolationList( ++ array( ++ new ConstraintViolation( ++ 'Translated error', ++ 'Error', ++ array('foo' => 'bar'), ++ 'Root', ++ 'foo.bar', ++ 'invalid' ++ ), ++ ) ++ ), $this->context->getViolations() ++ ); + } + + public function testAddViolationUsesPreconfiguredValueIfNotPassed() +@@ -119,16 +123,20 @@ + + $this->context->addViolation('Error'); + +- $this->assertEquals(new ConstraintViolationList(array( +- new ConstraintViolation( +- 'Translated error', +- 'Error', +- array(), +- 'Root', +- 'foo.bar', +- 'currentValue' +- ), +- )), $this->context->getViolations()); ++ $this->assertEquals( ++ new ConstraintViolationList( ++ array( ++ new ConstraintViolation( ++ 'Translated error', ++ 'Error', ++ array(), ++ 'Root', ++ 'foo.bar', ++ 'currentValue' ++ ), ++ ) ++ ), $this->context->getViolations() ++ ); + } + + public function testAddViolationUsesPassedNullValue() +@@ -146,25 +154,29 @@ + $this->context->addViolation('Error', array('foo1' => 'bar1'), null); + $this->context->addViolation('Choice error', array('foo2' => 'bar2'), null, 1); + +- $this->assertEquals(new ConstraintViolationList(array( +- new ConstraintViolation( +- 'Translated error', +- 'Error', +- array('foo1' => 'bar1'), +- 'Root', +- 'foo.bar', +- null +- ), +- new ConstraintViolation( +- 'Translated choice error', +- 'Choice error', +- array('foo2' => 'bar2'), +- 'Root', +- 'foo.bar', +- null, +- 1 +- ), +- )), $this->context->getViolations()); ++ $this->assertEquals( ++ new ConstraintViolationList( ++ array( ++ new ConstraintViolation( ++ 'Translated error', ++ 'Error', ++ array('foo1' => 'bar1'), ++ 'Root', ++ 'foo.bar', ++ null ++ ), ++ new ConstraintViolation( ++ 'Translated choice error', ++ 'Choice error', ++ array('foo2' => 'bar2'), ++ 'Root', ++ 'foo.bar', ++ null, ++ 1 ++ ), ++ ) ++ ), $this->context->getViolations() ++ ); + } + + public function testAddViolationAt() +@@ -177,16 +189,20 @@ + // override preconfigured property path + $this->context->addViolationAt('bam.baz', 'Error', array('foo' => 'bar'), 'invalid'); + +- $this->assertEquals(new ConstraintViolationList(array( +- new ConstraintViolation( +- 'Translated error', +- 'Error', +- array('foo' => 'bar'), +- 'Root', +- 'foo.bar.bam.baz', +- 'invalid' +- ), +- )), $this->context->getViolations()); ++ $this->assertEquals( ++ new ConstraintViolationList( ++ array( ++ new ConstraintViolation( ++ 'Translated error', ++ 'Error', ++ array('foo' => 'bar'), ++ 'Root', ++ 'foo.bar.bam.baz', ++ 'invalid' ++ ), ++ ) ++ ), $this->context->getViolations() ++ ); + } + + public function testAddViolationAtUsesPreconfiguredValueIfNotPassed() +@@ -198,16 +214,20 @@ + + $this->context->addViolationAt('bam.baz', 'Error'); + +- $this->assertEquals(new ConstraintViolationList(array( +- new ConstraintViolation( +- 'Translated error', +- 'Error', +- array(), +- 'Root', +- 'foo.bar.bam.baz', +- 'currentValue' +- ), +- )), $this->context->getViolations()); ++ $this->assertEquals( ++ new ConstraintViolationList( ++ array( ++ new ConstraintViolation( ++ 'Translated error', ++ 'Error', ++ array(), ++ 'Root', ++ 'foo.bar.bam.baz', ++ 'currentValue' ++ ), ++ ) ++ ), $this->context->getViolations() ++ ); + } + + public function testAddViolationAtUsesPassedNullValue() +@@ -225,25 +245,29 @@ + $this->context->addViolationAt('bam.baz', 'Error', array('foo' => 'bar'), null); + $this->context->addViolationAt('bam.baz', 'Choice error', array('foo' => 'bar'), null, 2); + +- $this->assertEquals(new ConstraintViolationList(array( +- new ConstraintViolation( +- 'Translated error', +- 'Error', +- array('foo' => 'bar'), +- 'Root', +- 'foo.bar.bam.baz', +- null +- ), +- new ConstraintViolation( +- 'Translated choice error', +- 'Choice error', +- array('foo' => 'bar'), +- 'Root', +- 'foo.bar.bam.baz', +- null, +- 2 +- ), +- )), $this->context->getViolations()); ++ $this->assertEquals( ++ new ConstraintViolationList( ++ array( ++ new ConstraintViolation( ++ 'Translated error', ++ 'Error', ++ array('foo' => 'bar'), ++ 'Root', ++ 'foo.bar.bam.baz', ++ null ++ ), ++ new ConstraintViolation( ++ 'Translated choice error', ++ 'Choice error', ++ array('foo' => 'bar'), ++ 'Root', ++ 'foo.bar.bam.baz', ++ null, ++ 2 ++ ), ++ ) ++ ), $this->context->getViolations() ++ ); + } + + public function testAddViolationPluralTranslationError() +@@ -283,17 +307,25 @@ + + public function testGetPropertyPathWithNestedCollectionsAndAllMixed() + { +- $constraints = new Collection(array( +- 'shelves' => new All(array('constraints' => array( +- new Collection(array( ++ $constraints = new Collection( ++ array( ++ 'shelves' => new All( ++ array('constraints' => array( ++ new Collection( ++ array( + 'name' => new ConstraintA(), +- 'books' => new All(array('constraints' => array( ++ 'books' => new All( ++ array('constraints' => array( + new ConstraintA(), +- ))), +- )), +- ))), ++ )) ++ ), ++ ) ++ ), ++ )) ++ ), + 'name' => new ConstraintA(), +- )); ++ ) ++ ); + $data = array( + 'shelves' => array( + array( + +--- vendor/symfony/validator/Tests/Mapping/Loader/AnnotationLoaderTest.php ++++ PHP_CodeSniffer +@@ -59,14 +59,22 @@ + $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); + $expected->addPropertyConstraint('firstName', new All(array(new NotNull(), new Range(array('min' => 3))))); + $expected->addPropertyConstraint('firstName', new All(array('constraints' => array(new NotNull(), new Range(array('min' => 3)))))); +- $expected->addPropertyConstraint('firstName', new Collection(array('fields' => array( +- 'foo' => array(new NotNull(), new Range(array('min' => 3))), +- 'bar' => new Range(array('min' => 5)), +- )))); +- $expected->addPropertyConstraint('firstName', new Choice(array( +- 'message' => 'Must be one of %choices%', +- 'choices' => array('A', 'B'), +- ))); ++ $expected->addPropertyConstraint( ++ 'firstName', new Collection( ++ array('fields' => array( ++ 'foo' => array(new NotNull(), new Range(array('min' => 3))), ++ 'bar' => new Range(array('min' => 5)), ++ )) ++ ) ++ ); ++ $expected->addPropertyConstraint( ++ 'firstName', new Choice( ++ array( ++ 'message' => 'Must be one of %choices%', ++ 'choices' => array('A', 'B'), ++ ) ++ ) ++ ); + $expected->addGetterConstraint('lastName', new NotNull()); + $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterConstraint('permissions', new IsTrue()); +@@ -129,14 +137,22 @@ + $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); + $expected->addPropertyConstraint('firstName', new All(array(new NotNull(), new Range(array('min' => 3))))); + $expected->addPropertyConstraint('firstName', new All(array('constraints' => array(new NotNull(), new Range(array('min' => 3)))))); +- $expected->addPropertyConstraint('firstName', new Collection(array('fields' => array( +- 'foo' => array(new NotNull(), new Range(array('min' => 3))), +- 'bar' => new Range(array('min' => 5)), +- )))); +- $expected->addPropertyConstraint('firstName', new Choice(array( +- 'message' => 'Must be one of %choices%', +- 'choices' => array('A', 'B'), +- ))); ++ $expected->addPropertyConstraint( ++ 'firstName', new Collection( ++ array('fields' => array( ++ 'foo' => array(new NotNull(), new Range(array('min' => 3))), ++ 'bar' => new Range(array('min' => 5)), ++ )) ++ ) ++ ); ++ $expected->addPropertyConstraint( ++ 'firstName', new Choice( ++ array( ++ 'message' => 'Must be one of %choices%', ++ 'choices' => array('A', 'B'), ++ ) ++ ) ++ ); + $expected->addGetterConstraint('lastName', new NotNull()); + $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterConstraint('permissions', new IsTrue()); + +--- vendor/symfony/validator/Tests/Mapping/Loader/YamlFileLoaderTest.php ++++ PHP_CodeSniffer +@@ -104,14 +104,22 @@ + $expected->addPropertyConstraint('firstName', new Choice(array('A', 'B'))); + $expected->addPropertyConstraint('firstName', new All(array(new NotNull(), new Range(array('min' => 3))))); + $expected->addPropertyConstraint('firstName', new All(array('constraints' => array(new NotNull(), new Range(array('min' => 3)))))); +- $expected->addPropertyConstraint('firstName', new Collection(array('fields' => array( +- 'foo' => array(new NotNull(), new Range(array('min' => 3))), +- 'bar' => array(new Range(array('min' => 5))), +- )))); +- $expected->addPropertyConstraint('firstName', new Choice(array( +- 'message' => 'Must be one of %choices%', +- 'choices' => array('A', 'B'), +- ))); ++ $expected->addPropertyConstraint( ++ 'firstName', new Collection( ++ array('fields' => array( ++ 'foo' => array(new NotNull(), new Range(array('min' => 3))), ++ 'bar' => array(new Range(array('min' => 5))), ++ )) ++ ) ++ ); ++ $expected->addPropertyConstraint( ++ 'firstName', new Choice( ++ array( ++ 'message' => 'Must be one of %choices%', ++ 'choices' => array('A', 'B'), ++ ) ++ ) ++ ); + $expected->addGetterConstraint('lastName', new NotNull()); + $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterConstraint('permissions', new IsTrue()); + +--- vendor/symfony/validator/Tests/Mapping/Loader/FilesLoaderTest.php ++++ PHP_CodeSniffer +@@ -33,12 +33,14 @@ + + public function getFilesLoader(LoaderInterface $loader) + { +- return $this->getMockForAbstractClass('Symfony\Component\Validator\Tests\Fixtures\FilesLoader', array(array( ++ return $this->getMockForAbstractClass( ++ 'Symfony\Component\Validator\Tests\Fixtures\FilesLoader', array(array( + __DIR__.'/constraint-mapping.xml', + __DIR__.'/constraint-mapping.yaml', + __DIR__.'/constraint-mapping.test', + __DIR__.'/constraint-mapping.txt', +- ), $loader)); ++ ), $loader) ++ ); + } + + public function getFileLoader() + +--- vendor/symfony/validator/Tests/Mapping/Loader/XmlFileLoaderTest.php ++++ PHP_CodeSniffer +@@ -62,14 +62,22 @@ + $expected->addPropertyConstraint('firstName', new Choice(array('A', 'B'))); + $expected->addPropertyConstraint('firstName', new All(array(new NotNull(), new Range(array('min' => 3))))); + $expected->addPropertyConstraint('firstName', new All(array('constraints' => array(new NotNull(), new Range(array('min' => 3)))))); +- $expected->addPropertyConstraint('firstName', new Collection(array('fields' => array( +- 'foo' => array(new NotNull(), new Range(array('min' => 3))), +- 'bar' => array(new Range(array('min' => 5))), +- )))); +- $expected->addPropertyConstraint('firstName', new Choice(array( +- 'message' => 'Must be one of %choices%', +- 'choices' => array('A', 'B'), +- ))); ++ $expected->addPropertyConstraint( ++ 'firstName', new Collection( ++ array('fields' => array( ++ 'foo' => array(new NotNull(), new Range(array('min' => 3))), ++ 'bar' => array(new Range(array('min' => 5))), ++ )) ++ ) ++ ); ++ $expected->addPropertyConstraint( ++ 'firstName', new Choice( ++ array( ++ 'message' => 'Must be one of %choices%', ++ 'choices' => array('A', 'B'), ++ ) ++ ) ++ ); + $expected->addGetterConstraint('lastName', new NotNull()); + $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterConstraint('permissions', new IsTrue()); + +--- vendor/symfony/validator/Tests/Mapping/Loader/LoaderChainTest.php ++++ PHP_CodeSniffer +@@ -22,18 +22,20 @@ + + $loader1 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader1->expects($this->once()) +- ->method('loadClassMetadata') +- ->with($this->equalTo($metadata)); ++ ->method('loadClassMetadata') ++ ->with($this->equalTo($metadata)); + + $loader2 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader2->expects($this->once()) +- ->method('loadClassMetadata') +- ->with($this->equalTo($metadata)); ++ ->method('loadClassMetadata') ++ ->with($this->equalTo($metadata)); + +- $chain = new LoaderChain(array( ++ $chain = new LoaderChain( ++ array( + $loader1, + $loader2, +- )); ++ ) ++ ); + + $chain->loadClassMetadata($metadata); + } +@@ -44,18 +46,20 @@ + + $loader1 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader1->expects($this->any()) +- ->method('loadClassMetadata') +- ->will($this->returnValue(true)); ++ ->method('loadClassMetadata') ++ ->will($this->returnValue(true)); + + $loader2 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader2->expects($this->any()) +- ->method('loadClassMetadata') +- ->will($this->returnValue(false)); ++ ->method('loadClassMetadata') ++ ->will($this->returnValue(false)); + +- $chain = new LoaderChain(array( ++ $chain = new LoaderChain( ++ array( + $loader1, + $loader2, +- )); ++ ) ++ ); + + $this->assertTrue($chain->loadClassMetadata($metadata)); + } +@@ -66,18 +70,20 @@ + + $loader1 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader1->expects($this->any()) +- ->method('loadClassMetadata') +- ->will($this->returnValue(false)); ++ ->method('loadClassMetadata') ++ ->will($this->returnValue(false)); + + $loader2 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader2->expects($this->any()) +- ->method('loadClassMetadata') +- ->will($this->returnValue(false)); ++ ->method('loadClassMetadata') ++ ->will($this->returnValue(false)); + +- $chain = new LoaderChain(array( ++ $chain = new LoaderChain( ++ array( + $loader1, + $loader2, +- )); ++ ) ++ ); + + $this->assertFalse($chain->loadClassMetadata($metadata)); + } + +--- vendor/symfony/validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php ++++ PHP_CodeSniffer +@@ -39,20 +39,26 @@ + $metadata = $factory->getMetadataFor(self::CLASSNAME); + + $constraints = array( +- new ConstraintA(array('groups' => array( ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'EntityParent', + 'Entity', +- ))), +- new ConstraintA(array('groups' => array( ++ )) ++ ), ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'EntityInterface', + 'Entity', +- ))), +- new ConstraintA(array('groups' => array( ++ )) ++ ), ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'Entity', +- ))), ++ )) ++ ), + ); + + $this->assertEquals($constraints, $metadata->getConstraints()); +@@ -69,16 +75,20 @@ + ); + + $cache->expects($this->never()) +- ->method('has'); ++ ->method('has'); + $cache->expects($this->once()) +- ->method('read') +- ->with($this->equalTo(self::PARENTCLASS)) +- ->will($this->returnValue(false)); ++ ->method('read') ++ ->with($this->equalTo(self::PARENTCLASS)) ++ ->will($this->returnValue(false)); + $cache->expects($this->once()) +- ->method('write') +- ->will($this->returnCallback(function ($metadata) use ($tester, $constraints) { +- $tester->assertEquals($constraints, $metadata->getConstraints()); +- })); ++ ->method('write') ++ ->will( ++ $this->returnCallback( ++ function ($metadata) use ($tester, $constraints) { ++ $tester->assertEquals($constraints, $metadata->getConstraints()); ++ } ++ ) ++ ); + + $metadata = $factory->getMetadataFor(self::PARENTCLASS); + +@@ -97,13 +107,13 @@ + $metadata->addConstraint(new ConstraintA()); + + $loader->expects($this->never()) +- ->method('loadClassMetadata'); ++ ->method('loadClassMetadata'); + + $cache->expects($this->never()) +- ->method('has'); ++ ->method('has'); + $cache->expects($this->once()) +- ->method('read') +- ->will($this->returnValue($metadata)); ++ ->method('read') ++ ->will($this->returnValue($metadata)); + + $this->assertEquals($metadata, $factory->getMetadataFor(self::PARENTCLASS)); + } + +--- vendor/symfony/validator/Tests/Mapping/ClassMetadataTest.php ++++ PHP_CodeSniffer +@@ -116,15 +116,19 @@ + $this->metadata->addConstraint(new ConstraintA()); + + $constraints = array( +- new ConstraintA(array('groups' => array( ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'EntityParent', + 'Entity', +- ))), +- new ConstraintA(array('groups' => array( ++ )) ++ ), ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'Entity', +- ))), ++ )) ++ ), + ); + + $this->assertEquals($constraints, $this->metadata->getConstraints()); +@@ -139,15 +143,19 @@ + $this->metadata->addPropertyConstraint('firstName', new ConstraintA()); + + $constraints = array( +- new ConstraintA(array('groups' => array( ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'EntityParent', + 'Entity', +- ))), +- new ConstraintA(array('groups' => array( ++ )) ++ ), ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'Entity', +- ))), ++ )) ++ ), + ); + + $members = $this->metadata->getPropertyMetadata('firstName'); +@@ -174,17 +182,21 @@ + $this->metadata->addPropertyConstraint('internal', new ConstraintA()); + + $parentConstraints = array( +- new ConstraintA(array('groups' => array( ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'EntityParent', + 'Entity', +- ))), ++ )) ++ ), + ); + $constraints = array( +- new ConstraintA(array('groups' => array( ++ new ConstraintA( ++ array('groups' => array( + 'Default', + 'Entity', +- ))), ++ )) ++ ), + ); + + $members = $this->metadata->getPropertyMetadata('internal'); + +--- vendor/symfony/validator/Tests/Resources/TranslationFilesTest.php ++++ PHP_CodeSniffer +@@ -24,7 +24,9 @@ + public function provideTranslationFiles() + { + return array_map( +- function ($filePath) { return (array) $filePath; }, ++ function ($filePath) { ++ return (array) $filePath; ++ }, + glob(dirname(dirname(__DIR__)).'/Resources/translations/*.xlf') + ); + } + +--- vendor/symfony/validator/Tests/Fixtures/ConstraintA.php ++++ PHP_CodeSniffer +@@ -13,7 +13,9 @@ + + use Symfony\Component\Validator\Constraint; + +-/** @Annotation */ ++/** ++ * @Annotation ++*/ + class ConstraintA extends Constraint + { + public $property1; + +--- vendor/symfony/validator/Tests/Fixtures/ConstraintB.php ++++ PHP_CodeSniffer +@@ -13,7 +13,9 @@ + + use Symfony\Component\Validator\Constraint; + +-/** @Annotation */ ++/** ++ * @Annotation ++*/ + class ConstraintB extends Constraint + { + public function getTargets() + +--- vendor/symfony/validator/Tests/Fixtures/ConstraintWithValue.php ++++ PHP_CodeSniffer +@@ -13,7 +13,9 @@ + + use Symfony\Component\Validator\Constraint; + +-/** @Annotation */ ++/** ++ * @Annotation ++*/ + class ConstraintWithValue extends Constraint + { + public $property; + +--- vendor/symfony/validator/Tests/Fixtures/ConstraintWithValueAsDefault.php ++++ PHP_CodeSniffer +@@ -13,7 +13,9 @@ + + use Symfony\Component\Validator\Constraint; + +-/** @Annotation */ ++/** ++ * @Annotation ++*/ + class ConstraintWithValueAsDefault extends Constraint + { + public $property; + +--- vendor/symfony/validator/Tests/Fixtures/ConstraintC.php ++++ PHP_CodeSniffer +@@ -13,7 +13,9 @@ + + use Symfony\Component\Validator\Constraint; + +-/** @Annotation */ ++/** ++ * @Annotation ++*/ + class ConstraintC extends Constraint + { + public $option1; + +--- vendor/symfony/validator/Tests/Validator/Abstract2Dot5ApiTest.php ++++ PHP_CodeSniffer +@@ -29,7 +29,7 @@ + /** + * Verifies that a validator satisfies the API of Symfony 2.5+. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ +@@ -93,18 +93,31 @@ + $context->addViolation('Message 2'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => function () {}, +- 'groups' => 'Group 1', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group 2', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 3', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => function () { ++ }, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 3', ++ ) ++ ) ++ ); + + $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3')); + $violations = $this->validator->validate($entity, new Valid(), $sequence); +@@ -127,14 +140,22 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group 1', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 2', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); + + $sequence = new GroupSequence(array('Group 1', 'Entity')); + $violations = $this->validator->validate($entity, new Valid(), $sequence); +@@ -155,8 +176,7 @@ + ->getValidator() + // Since the validator is not context aware, the group must + // be passed explicitly +- ->validate($value->reference, new Valid(), 'Group') +- ; ++ ->validate($value->reference, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $test->assertCount(1, $violations); +@@ -187,14 +207,22 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, new Valid(), 'Group'); + +@@ -220,8 +248,7 @@ + ->getValidator() + ->inContext($context) + ->atPath('subpath') +- ->validate($value->reference) +- ; ++ ->validate($value->reference); + + // context changes shouldn't leak out of the validate() call + $test->assertSame($previousValue, $context->getValue()); +@@ -244,14 +271,22 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, new Valid(), 'Group'); + +@@ -284,8 +319,7 @@ + ->getValidator() + ->inContext($context) + ->atPath('subpath') +- ->validate(array('key' => $value->reference)) +- ; ++ ->validate(array('key' => $value->reference)); + + // context changes shouldn't leak out of the validate() call + $test->assertSame($previousValue, $context->getValue()); +@@ -308,14 +342,22 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, new Valid(), 'Group'); + +@@ -351,10 +393,14 @@ + }; + + $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator')); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($traversable, new Valid(), 'Group'); + +@@ -383,10 +429,14 @@ + $traversableMetadata->addConstraint(new Traverse(true)); + + $this->metadataFactory->addMetadata($traversableMetadata); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($traversable, new Valid(), 'Group'); + +@@ -408,10 +458,14 @@ + $traversableMetadata->addConstraint(new Traverse(false)); + + $this->metadataFactory->addMetadata($traversableMetadata); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($traversable, new Valid(), 'Group'); + +@@ -445,10 +499,14 @@ + $traversableMetadata->addConstraint(new Traverse(false)); + + $this->metadataFactory->addMetadata($traversableMetadata); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + $this->metadata->addPropertyConstraint('reference', new Valid()); + + $violations = $this->validate($entity, new Valid(), 'Group'); +@@ -471,13 +529,21 @@ + $traversableMetadata->addConstraint(new Traverse(false)); + + $this->metadataFactory->addMetadata($traversableMetadata); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => true, +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => true, ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, new Valid(), 'Group'); + +@@ -499,13 +565,21 @@ + $traversableMetadata->addConstraint(new Traverse(true)); + + $this->metadataFactory->addMetadata($traversableMetadata); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => false, +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => false, ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, new Valid(), 'Group'); + +@@ -607,10 +681,14 @@ + $context->addViolation('Message'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => array('Group 1', 'Group 2'), +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => array('Group 1', 'Group 2'), ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2')); + +@@ -626,10 +704,14 @@ + $context->addViolation('Message'); + }; + +- $this->metadata->addPropertyConstraint('firstName', new Callback(array( +- 'callback' => $callback, +- 'groups' => array('Group 1', 'Group 2'), +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'firstName', new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => array('Group 1', 'Group 2'), ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2')); + +@@ -678,18 +760,24 @@ + $initializer1->expects($this->once()) + ->method('initialize') + ->with($entity) +- ->will($this->returnCallback(function ($object) { +- $object->initialized = true; +- })); ++ ->will( ++ $this->returnCallback( ++ function ($object) { ++ $object->initialized = true; ++ } ++ ) ++ ); + + $initializer2->expects($this->once()) + ->method('initialize') + ->with($entity); + +- $this->validator = $this->createValidator($this->metadataFactory, array( ++ $this->validator = $this->createValidator( ++ $this->metadataFactory, array( + $initializer1, + $initializer2, +- )); ++ ) ++ ); + + // prepare constraint which + // * checks that "initialized" is set to true + +--- vendor/symfony/validator/Tests/Validator/AbstractLegacyApiTest.php ++++ PHP_CodeSniffer +@@ -23,10 +23,10 @@ + /** + * Verifies that a validator satisfies the API of Symfony < 2.5. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek +- * @group legacy ++ * @group legacy + */ + abstract class AbstractLegacyApiTest extends AbstractValidatorTest + { +@@ -85,10 +85,14 @@ + $test->fail('Should not be called'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $this->validator->validate($traversable, 'Group'); + } +@@ -100,18 +104,24 @@ + { + $test = $this; + $entity = new Entity(); +- $traversable = new \ArrayIterator(array( ++ $traversable = new \ArrayIterator( ++ array( + 2 => new \ArrayIterator(array('key' => $entity)), +- )); ++ ) ++ ); + + $callback = function () use ($test) { + $test->fail('Should not be called'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $this->validator->validate($traversable, 'Group'); + } +@@ -151,14 +161,22 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, 'Group'); + +@@ -209,14 +227,22 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validator->validate($entity, 'Group'); + +@@ -275,18 +301,24 @@ + $initializer1->expects($this->once()) + ->method('initialize') + ->with($entity) +- ->will($this->returnCallback(function ($object) { +- $object->initialized = true; +- })); ++ ->will( ++ $this->returnCallback( ++ function ($object) { ++ $object->initialized = true; ++ } ++ ) ++ ); + + $initializer2->expects($this->once()) + ->method('initialize') + ->with($entity); + +- $this->validator = $this->createValidator($this->metadataFactory, array( ++ $this->validator = $this->createValidator( ++ $this->metadataFactory, array( + $initializer1, + $initializer2, +- )); ++ ) ++ ); + + // prepare constraint which + // * checks that "initialized" is set to true + +--- vendor/symfony/validator/Tests/Validator/AbstractValidatorTest.php ++++ PHP_CodeSniffer +@@ -86,10 +86,12 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $constraint = new Callback(array( ++ $constraint = new Callback( ++ array( + 'callback' => $callback, + 'groups' => 'Group', +- )); ++ ) ++ ); + + $violations = $this->validate('Bernhard', $constraint, 'Group'); + +@@ -123,10 +125,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -163,10 +169,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addPropertyConstraint('firstName', new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'firstName', new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -203,10 +213,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addGetterConstraint('lastName', new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addGetterConstraint( ++ 'lastName', new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -241,10 +255,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($array, null, 'Group'); + +@@ -279,10 +297,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($array, null, 'Group'); + +@@ -317,10 +339,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($traversable, null, 'Group'); + +@@ -340,9 +366,11 @@ + { + $test = $this; + $entity = new Entity(); +- $traversable = new \ArrayIterator(array( ++ $traversable = new \ArrayIterator( ++ array( + 2 => new \ArrayIterator(array('key' => $entity)), +- )); ++ ) ++ ); + + $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $traversable) { + $test->assertSame($test::ENTITY_CLASS, $context->getClassName()); +@@ -357,10 +385,14 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($traversable, null, 'Group'); + +@@ -396,10 +428,14 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -438,10 +474,14 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addPropertyConstraint('value', new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addPropertyConstraint( ++ 'value', new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -480,10 +520,14 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addPropertyConstraint('privateValue', new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addPropertyConstraint( ++ 'privateValue', new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -545,10 +589,14 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -585,10 +633,14 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -613,9 +665,13 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => false, +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => false, ++ ) ++ ) ++ ); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); +@@ -633,9 +689,13 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => false, +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => false, ++ ) ++ ) ++ ); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); +@@ -690,10 +750,14 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -719,9 +783,13 @@ + }; + + $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator')); +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => false, +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => false, ++ ) ++ ) ++ ); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); +@@ -738,9 +806,13 @@ + $entity = new Entity(); + $entity->reference = new \ArrayIterator(); + +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => false, +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => false, ++ ) ++ ) ++ ); + + $this->validate($entity); + } +@@ -749,9 +821,11 @@ + { + $test = $this; + $entity = new Entity(); +- $entity->reference = new \ArrayIterator(array( ++ $entity->reference = new \ArrayIterator( ++ array( + 2 => new \ArrayIterator(array('key' => new Reference())), +- )); ++ ) ++ ); + + $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) { + $test->assertSame($test::REFERENCE_CLASS, $context->getClassName()); +@@ -766,13 +840,21 @@ + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + +- $this->metadata->addPropertyConstraint('reference', new Valid(array( +- 'traverse' => true, +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'reference', new Valid( ++ array( ++ 'traverse' => true, ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group'); + +@@ -814,14 +896,22 @@ + $context->addViolation('Other violation'); + }; + +- $this->metadata->addPropertyConstraint('firstName', new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->metadata->addPropertyConstraint('lastName', new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'firstName', new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->metadata->addPropertyConstraint( ++ 'lastName', new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validateProperty($entity, 'firstName', 'Group'); + +@@ -841,7 +931,7 @@ + * Cannot be UnsupportedMetadataException for BC with Symfony < 2.5. + * + * @expectedException \Symfony\Component\Validator\Exception\ValidatorException +- * @group legacy ++ * @group legacy + */ + public function testLegacyValidatePropertyFailsIfPropertiesNotSupported() + { +@@ -889,14 +979,22 @@ + $context->addViolation('Other violation'); + }; + +- $this->metadata->addPropertyConstraint('firstName', new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->metadata->addPropertyConstraint('lastName', new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'firstName', new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->metadata->addPropertyConstraint( ++ 'lastName', new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validatePropertyValue( + $entity, +@@ -940,14 +1038,22 @@ + $context->addViolation('Other violation'); + }; + +- $this->metadata->addPropertyConstraint('firstName', new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group', +- ))); +- $this->metadata->addPropertyConstraint('lastName', new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group', +- ))); ++ $this->metadata->addPropertyConstraint( ++ 'firstName', new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); ++ $this->metadata->addPropertyConstraint( ++ 'lastName', new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group', ++ ) ++ ) ++ ); + + $violations = $this->validatePropertyValue( + self::ENTITY_CLASS, +@@ -972,7 +1078,7 @@ + * Cannot be UnsupportedMetadataException for BC with Symfony < 2.5. + * + * @expectedException \Symfony\Component\Validator\Exception\ValidatorException +- * @group legacy ++ * @group legacy + */ + public function testLegacyValidatePropertyValueFailsIfPropertiesNotSupported() + { +@@ -1043,14 +1149,22 @@ + $context->addViolation('Message'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group 1', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group 2', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, 'Group 2'); + +@@ -1066,14 +1180,22 @@ + $context->addViolation('Message'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group 1', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback, +- 'groups' => 'Group 2', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); + + $violations = $this->validate($entity, null, array('Group 1', 'Group 2')); + +@@ -1092,18 +1214,31 @@ + $context->addViolation('Violation in Group 3'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => function () {}, +- 'groups' => 'Group 1', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group 2', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 3', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => function () { ++ }, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 3', ++ ) ++ ) ++ ); + + $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3', 'Entity')); + $this->metadata->setGroupSequence($sequence); +@@ -1126,18 +1261,31 @@ + $context->addViolation('Violation in Group 3'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => function () {}, +- 'groups' => 'Group 1', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group 2', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 3', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => function () { ++ }, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 3', ++ ) ++ ) ++ ); + + $sequence = array('Group 1', 'Group 2', 'Group 3', 'Entity'); + $this->metadata->setGroupSequence($sequence); +@@ -1162,14 +1310,22 @@ + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Default', +- ))); +- $this->referenceMetadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 1', +- ))); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Default', ++ ) ++ ) ++ ); ++ $this->referenceMetadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); + + $sequence = new GroupSequence(array('Group 1', 'Entity')); + $this->metadata->setGroupSequence($sequence); +@@ -1192,14 +1348,22 @@ + $context->addViolation('Violation in group sequence'); + }; + +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Other Group', +- ))); +- $this->metadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 1', +- ))); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Other Group', ++ ) ++ ) ++ ); ++ $this->metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); + + $sequence = new GroupSequence(array('Group 1', 'Entity')); + $this->metadata->setGroupSequence($sequence); +@@ -1224,18 +1388,31 @@ + }; + + $metadata = new ClassMetadata(get_class($entity)); +- $metadata->addConstraint(new Callback(array( +- 'callback' => function () {}, +- 'groups' => 'Group 1', +- ))); +- $metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group 2', +- ))); +- $metadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 3', +- ))); ++ $metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => function () { ++ }, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); ++ $metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 3', ++ ) ++ ) ++ ); + $metadata->setGroupSequenceProvider(true); + + $this->metadataFactory->addMetadata($metadata); +@@ -1260,18 +1437,31 @@ + }; + + $metadata = new ClassMetadata(get_class($entity)); +- $metadata->addConstraint(new Callback(array( +- 'callback' => function () {}, +- 'groups' => 'Group 1', +- ))); +- $metadata->addConstraint(new Callback(array( +- 'callback' => $callback1, +- 'groups' => 'Group 2', +- ))); +- $metadata->addConstraint(new Callback(array( +- 'callback' => $callback2, +- 'groups' => 'Group 3', +- ))); ++ $metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => function () { ++ }, ++ 'groups' => 'Group 1', ++ ) ++ ) ++ ); ++ $metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback1, ++ 'groups' => 'Group 2', ++ ) ++ ) ++ ); ++ $metadata->addConstraint( ++ new Callback( ++ array( ++ 'callback' => $callback2, ++ 'groups' => 'Group 3', ++ ) ++ ) ++ ); + $metadata->setGroupSequenceProvider(true); + + $this->metadataFactory->addMetadata($metadata); + +--- vendor/symfony/validator/Constraints/Iban.php ++++ PHP_CodeSniffer +@@ -23,11 +23,15 @@ + */ + class Iban extends Constraint + { +- /** @deprecated, to be removed in 3.0. */ ++ /** ++ * @deprecated, to be removed in 3.0. ++*/ + const TOO_SHORT_ERROR = '88e5e319-0aeb-4979-a27e-3d9ce0c16166'; + const INVALID_COUNTRY_CODE_ERROR = 'de78ee2c-bd50-44e2-aec8-3d8228aeadb9'; + const INVALID_CHARACTERS_ERROR = '8d3d85e4-784f-4719-a5bc-d9e40d45a3a5'; +- /** @deprecated, to be removed in 3.0. */ ++ /** ++ * @deprecated, to be removed in 3.0. ++*/ + const INVALID_CASE_ERROR = 'f4bf62fe-03ec-42af-a53b-68e21b1e7274'; + const CHECKSUM_FAILED_ERROR = 'b9401321-f9bf-4dcb-83c1-f31094440795'; + const INVALID_FORMAT_ERROR = 'c8d318f1-2ecc-41ba-b983-df70d225cf5a'; + +--- vendor/symfony/validator/Constraints/IpValidator.php ++++ PHP_CodeSniffer +@@ -44,53 +44,53 @@ + $value = (string) $value; + + switch ($constraint->version) { +- case Ip::V4: +- $flag = FILTER_FLAG_IPV4; +- break; +- +- case Ip::V6: +- $flag = FILTER_FLAG_IPV6; +- break; +- +- case Ip::V4_NO_PRIV: +- $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE; +- break; +- +- case Ip::V6_NO_PRIV: +- $flag = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE; +- break; +- +- case Ip::ALL_NO_PRIV: +- $flag = FILTER_FLAG_NO_PRIV_RANGE; +- break; +- +- case Ip::V4_NO_RES: +- $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE; +- break; +- +- case Ip::V6_NO_RES: +- $flag = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE; +- break; +- +- case Ip::ALL_NO_RES: +- $flag = FILTER_FLAG_NO_RES_RANGE; +- break; +- +- case Ip::V4_ONLY_PUBLIC: +- $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; +- break; +- +- case Ip::V6_ONLY_PUBLIC: +- $flag = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; +- break; +- +- case Ip::ALL_ONLY_PUBLIC: +- $flag = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; +- break; +- +- default: +- $flag = null; +- break; ++ case Ip::V4: ++ $flag = FILTER_FLAG_IPV4; ++ break; ++ ++ case Ip::V6: ++ $flag = FILTER_FLAG_IPV6; ++ break; ++ ++ case Ip::V4_NO_PRIV: ++ $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE; ++ break; ++ ++ case Ip::V6_NO_PRIV: ++ $flag = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE; ++ break; ++ ++ case Ip::ALL_NO_PRIV: ++ $flag = FILTER_FLAG_NO_PRIV_RANGE; ++ break; ++ ++ case Ip::V4_NO_RES: ++ $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE; ++ break; ++ ++ case Ip::V6_NO_RES: ++ $flag = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE; ++ break; ++ ++ case Ip::ALL_NO_RES: ++ $flag = FILTER_FLAG_NO_RES_RANGE; ++ break; ++ ++ case Ip::V4_ONLY_PUBLIC: ++ $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; ++ break; ++ ++ case Ip::V6_ONLY_PUBLIC: ++ $flag = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; ++ break; ++ ++ case Ip::ALL_ONLY_PUBLIC: ++ $flag = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; ++ break; ++ ++ default: ++ $flag = null; ++ break; + } + + if (!filter_var($value, FILTER_VALIDATE_IP, $flag)) { + +--- vendor/symfony/validator/Constraints/Valid.php ++++ PHP_CodeSniffer +@@ -32,10 +32,12 @@ + public function __construct($options = null) + { + if (is_array($options) && array_key_exists('groups', $options)) { +- throw new ConstraintDefinitionException(sprintf( +- 'The option "groups" is not supported by the constraint %s', +- __CLASS__ +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The option "groups" is not supported by the constraint %s', ++ __CLASS__ ++ ) ++ ); + } + + if (is_array($options) && array_key_exists('deep', $options)) { + +--- vendor/symfony/validator/Constraints/FileValidator.php ++++ PHP_CodeSniffer +@@ -51,116 +51,116 @@ + + if ($value instanceof UploadedFile && !$value->isValid()) { + switch ($value->getError()) { +- case UPLOAD_ERR_INI_SIZE: +- $iniLimitSize = UploadedFile::getMaxFilesize(); +- if ($constraint->maxSize && $constraint->maxSize < $iniLimitSize) { +- $limitInBytes = $constraint->maxSize; +- $binaryFormat = $constraint->binaryFormat; +- } else { +- $limitInBytes = $iniLimitSize; +- $binaryFormat = true; +- } ++ case UPLOAD_ERR_INI_SIZE: ++ $iniLimitSize = UploadedFile::getMaxFilesize(); ++ if ($constraint->maxSize && $constraint->maxSize < $iniLimitSize) { ++ $limitInBytes = $constraint->maxSize; ++ $binaryFormat = $constraint->binaryFormat; ++ } else { ++ $limitInBytes = $iniLimitSize; ++ $binaryFormat = true; ++ } + +- list($sizeAsString, $limitAsString, $suffix) = $this->factorizeSizes(0, $limitInBytes, $binaryFormat); +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadIniSizeErrorMessage) +- ->setParameter('{{ limit }}', $limitAsString) +- ->setParameter('{{ suffix }}', $suffix) +- ->setCode(UPLOAD_ERR_INI_SIZE) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadIniSizeErrorMessage) +- ->setParameter('{{ limit }}', $limitAsString) +- ->setParameter('{{ suffix }}', $suffix) +- ->setCode(UPLOAD_ERR_INI_SIZE) +- ->addViolation(); +- } ++ list($sizeAsString, $limitAsString, $suffix) = $this->factorizeSizes(0, $limitInBytes, $binaryFormat); ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadIniSizeErrorMessage) ++ ->setParameter('{{ limit }}', $limitAsString) ++ ->setParameter('{{ suffix }}', $suffix) ++ ->setCode(UPLOAD_ERR_INI_SIZE) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadIniSizeErrorMessage) ++ ->setParameter('{{ limit }}', $limitAsString) ++ ->setParameter('{{ suffix }}', $suffix) ++ ->setCode(UPLOAD_ERR_INI_SIZE) ++ ->addViolation(); ++ } + +- return; +- case UPLOAD_ERR_FORM_SIZE: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadFormSizeErrorMessage) +- ->setCode(UPLOAD_ERR_FORM_SIZE) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadFormSizeErrorMessage) +- ->setCode(UPLOAD_ERR_FORM_SIZE) +- ->addViolation(); +- } ++ return; ++ case UPLOAD_ERR_FORM_SIZE: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadFormSizeErrorMessage) ++ ->setCode(UPLOAD_ERR_FORM_SIZE) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadFormSizeErrorMessage) ++ ->setCode(UPLOAD_ERR_FORM_SIZE) ++ ->addViolation(); ++ } + +- return; +- case UPLOAD_ERR_PARTIAL: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadPartialErrorMessage) +- ->setCode(UPLOAD_ERR_PARTIAL) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadPartialErrorMessage) +- ->setCode(UPLOAD_ERR_PARTIAL) +- ->addViolation(); +- } ++ return; ++ case UPLOAD_ERR_PARTIAL: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadPartialErrorMessage) ++ ->setCode(UPLOAD_ERR_PARTIAL) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadPartialErrorMessage) ++ ->setCode(UPLOAD_ERR_PARTIAL) ++ ->addViolation(); ++ } + +- return; +- case UPLOAD_ERR_NO_FILE: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadNoFileErrorMessage) +- ->setCode(UPLOAD_ERR_NO_FILE) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadNoFileErrorMessage) +- ->setCode(UPLOAD_ERR_NO_FILE) +- ->addViolation(); +- } ++ return; ++ case UPLOAD_ERR_NO_FILE: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadNoFileErrorMessage) ++ ->setCode(UPLOAD_ERR_NO_FILE) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadNoFileErrorMessage) ++ ->setCode(UPLOAD_ERR_NO_FILE) ++ ->addViolation(); ++ } + +- return; +- case UPLOAD_ERR_NO_TMP_DIR: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage) +- ->setCode(UPLOAD_ERR_NO_TMP_DIR) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadNoTmpDirErrorMessage) +- ->setCode(UPLOAD_ERR_NO_TMP_DIR) +- ->addViolation(); +- } ++ return; ++ case UPLOAD_ERR_NO_TMP_DIR: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage) ++ ->setCode(UPLOAD_ERR_NO_TMP_DIR) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadNoTmpDirErrorMessage) ++ ->setCode(UPLOAD_ERR_NO_TMP_DIR) ++ ->addViolation(); ++ } + +- return; +- case UPLOAD_ERR_CANT_WRITE: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadCantWriteErrorMessage) +- ->setCode(UPLOAD_ERR_CANT_WRITE) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadCantWriteErrorMessage) +- ->setCode(UPLOAD_ERR_CANT_WRITE) +- ->addViolation(); +- } ++ return; ++ case UPLOAD_ERR_CANT_WRITE: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadCantWriteErrorMessage) ++ ->setCode(UPLOAD_ERR_CANT_WRITE) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadCantWriteErrorMessage) ++ ->setCode(UPLOAD_ERR_CANT_WRITE) ++ ->addViolation(); ++ } + +- return; +- case UPLOAD_ERR_EXTENSION: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadExtensionErrorMessage) +- ->setCode(UPLOAD_ERR_EXTENSION) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadExtensionErrorMessage) +- ->setCode(UPLOAD_ERR_EXTENSION) +- ->addViolation(); +- } ++ return; ++ case UPLOAD_ERR_EXTENSION: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadExtensionErrorMessage) ++ ->setCode(UPLOAD_ERR_EXTENSION) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadExtensionErrorMessage) ++ ->setCode(UPLOAD_ERR_EXTENSION) ++ ->addViolation(); ++ } + +- return; +- default: +- if ($this->context instanceof ExecutionContextInterface) { +- $this->context->buildViolation($constraint->uploadErrorMessage) +- ->setCode($value->getError()) +- ->addViolation(); +- } else { +- $this->buildViolation($constraint->uploadErrorMessage) +- ->setCode($value->getError()) +- ->addViolation(); +- } ++ return; ++ default: ++ if ($this->context instanceof ExecutionContextInterface) { ++ $this->context->buildViolation($constraint->uploadErrorMessage) ++ ->setCode($value->getError()) ++ ->addViolation(); ++ } else { ++ $this->buildViolation($constraint->uploadErrorMessage) ++ ->setCode($value->getError()) ++ ->addViolation(); ++ } + +- return; ++ return; + } + } + + +--- vendor/symfony/validator/Constraints/ImageValidator.php ++++ PHP_CodeSniffer +@@ -47,7 +47,8 @@ + if (null === $constraint->minWidth && null === $constraint->maxWidth + && null === $constraint->minHeight && null === $constraint->maxHeight + && null === $constraint->minRatio && null === $constraint->maxRatio +- && $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait) { ++ && $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait ++ ) { + return; + } + + +--- vendor/symfony/validator/Constraints/LuhnValidator.php ++++ PHP_CodeSniffer +@@ -22,7 +22,7 @@ + * For a list of example card numbers that are used to test this + * class, please see the LuhnValidatorTest class. + * +- * @see http://en.wikipedia.org/wiki/Luhn_algorithm ++ * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * + * @author Tim Nagel + * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/ + +--- vendor/symfony/validator/Constraints/Collection.php ++++ PHP_CodeSniffer +@@ -42,7 +42,8 @@ + { + // no known options set? $options is the fields array + if (is_array($options) +- && !array_intersect(array_keys($options), array('groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage'))) { ++ && !array_intersect(array_keys($options), array('groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage')) ++ ) { + $options = array('fields' => $options); + } + + +--- vendor/symfony/validator/Constraints/AbstractComparison.php ++++ PHP_CodeSniffer +@@ -31,10 +31,12 @@ + public function __construct($options = null) + { + if (is_array($options) && !isset($options['value'])) { +- throw new ConstraintDefinitionException(sprintf( +- 'The %s constraint requires the "value" option to be set.', +- get_class($this) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The %s constraint requires the "value" option to be set.', ++ get_class($this) ++ ) ++ ); + } + + parent::__construct($options); + +--- vendor/symfony/validator/Constraints/GroupSequence.php ++++ PHP_CodeSniffer +@@ -99,7 +99,7 @@ + * + * @return \Traversable The iterator + * +- * @see \IteratorAggregate::getIterator() ++ * @see \IteratorAggregate::getIterator() + * @deprecated since version 2.5, to be removed in 3.0. + */ + public function getIterator() +@@ -145,10 +145,12 @@ + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + + if (!isset($this->groups[$offset])) { +- throw new OutOfBoundsException(sprintf( +- 'The offset "%s" does not exist.', +- $offset +- )); ++ throw new OutOfBoundsException( ++ sprintf( ++ 'The offset "%s" does not exist.', ++ $offset ++ ) ++ ); + } + + return $this->groups[$offset]; + +--- vendor/symfony/validator/Constraints/Traverse.php ++++ PHP_CodeSniffer +@@ -28,10 +28,12 @@ + public function __construct($options = null) + { + if (is_array($options) && array_key_exists('groups', $options)) { +- throw new ConstraintDefinitionException(sprintf( +- 'The option "groups" is not supported by the constraint %s', +- __CLASS__ +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The option "groups" is not supported by the constraint %s', ++ __CLASS__ ++ ) ++ ); + } + + parent::__construct($options); + +--- vendor/symfony/validator/Constraints/UuidValidator.php ++++ PHP_CodeSniffer +@@ -232,20 +232,20 @@ + if ($i !== $h) { + if ($this->context instanceof ExecutionContextInterface) { + $this->context->buildViolation($constraint->message) +- ->setParameter( +- '{{ value }}', +- $this->formatValue($value) +- ) +- ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) +- ->addViolation(); ++ ->setParameter( ++ '{{ value }}', ++ $this->formatValue($value) ++ ) ++ ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) ++ ->addViolation(); + } else { + $this->buildViolation($constraint->message) +- ->setParameter( +- '{{ value }}', +- $this->formatValue($value) +- ) +- ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) +- ->addViolation(); ++ ->setParameter( ++ '{{ value }}', ++ $this->formatValue($value) ++ ) ++ ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) ++ ->addViolation(); + } + + return; + +--- vendor/symfony/validator/Constraints/Composite.php ++++ PHP_CodeSniffer +@@ -24,7 +24,7 @@ + * let {@link getCompositeOption()} return the name of the property which + * contains the nested constraints. + * +- * @since 2.6 ++ * @since 2.6 + * + * @author Bernhard Schussek + */ +@@ -97,13 +97,15 @@ + $excessGroups = array_diff($constraint->groups, $this->groups); + + if (count($excessGroups) > 0) { +- throw new ConstraintDefinitionException(sprintf( +- 'The group(s) "%s" passed to the constraint %s '. +- 'should also be passed to its containing constraint %s', +- implode('", "', $excessGroups), +- get_class($constraint), +- get_class($this) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The group(s) "%s" passed to the constraint %s '. ++ 'should also be passed to its containing constraint %s', ++ implode('", "', $excessGroups), ++ get_class($constraint), ++ get_class($this) ++ ) ++ ); + } + } else { + $constraint->groups = $this->groups; +@@ -124,7 +126,9 @@ + { + parent::addImplicitGroupName($group); + +- /** @var Constraint[] $nestedConstraints */ ++ /** ++ * @var Constraint[] $nestedConstraints ++*/ + $nestedConstraints = $this->{$this->getCompositeOption()}; + + foreach ($nestedConstraints as $constraint) { + +--- vendor/symfony/validator/Constraint.php ++++ PHP_CodeSniffer +@@ -78,11 +78,13 @@ + public static function getErrorName($errorCode) + { + if (!isset(static::$errorNames[$errorCode])) { +- throw new InvalidArgumentException(sprintf( +- 'The error code "%s" does not exist for constraint of type "%s".', +- $errorCode, +- get_called_class() +- )); ++ throw new InvalidArgumentException( ++ sprintf( ++ 'The error code "%s" does not exist for constraint of type "%s".', ++ $errorCode, ++ get_called_class() ++ ) ++ ); + } + + return static::$errorNames[$errorCode]; + +--- vendor/symfony/validator/Mapping/ClassMetadataInterface.php ++++ PHP_CodeSniffer +@@ -24,7 +24,7 @@ + * by a group sequence for that class and whether instances of that class + * should be traversed or not. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Mapping/TraversalStrategy.php ++++ PHP_CodeSniffer +@@ -23,7 +23,7 @@ + * + * The traversal strategy is ignored for arrays. Arrays are always iterated. + * +- * @since 2.1 ++ * @since 2.1 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Mapping/PropertyMetadataInterface.php ++++ PHP_CodeSniffer +@@ -24,7 +24,7 @@ + * should be validated against their class' metadata and whether traversable + * objects should be traversed or not. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Mapping/GenericMetadata.php ++++ PHP_CodeSniffer +@@ -23,7 +23,7 @@ + * + * This class supports serialization and cloning. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ +@@ -131,11 +131,13 @@ + public function addConstraint(Constraint $constraint) + { + if ($constraint instanceof Traverse) { +- throw new ConstraintDefinitionException(sprintf( +- 'The constraint "%s" can only be put on classes. Please use '. +- '"Symfony\Component\Validator\Constraints\Valid" instead.', +- get_class($constraint) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The constraint "%s" can only be put on classes. Please use '. ++ '"Symfony\Component\Validator\Constraints\Valid" instead.', ++ get_class($constraint) ++ ) ++ ); + } + + if ($constraint instanceof Valid) { + +--- vendor/symfony/validator/Mapping/MemberMetadata.php ++++ PHP_CodeSniffer +@@ -97,10 +97,12 @@ + public function addConstraint(Constraint $constraint) + { + if (!in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets())) { +- throw new ConstraintDefinitionException(sprintf( +- 'The constraint %s cannot be put on properties or getters', +- get_class($constraint) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The constraint %s cannot be put on properties or getters', ++ get_class($constraint) ++ ) ++ ); + } + + parent::addConstraint($constraint); +@@ -113,11 +115,13 @@ + */ + public function __sleep() + { +- return array_merge(parent::__sleep(), array( ++ return array_merge( ++ parent::__sleep(), array( + 'class', + 'name', + 'property', +- )); ++ ) ++ ); + } + + /** + +--- vendor/symfony/validator/Mapping/Loader/StaticMethodLoader.php ++++ PHP_CodeSniffer +@@ -43,7 +43,9 @@ + */ + public function loadClassMetadata(ClassMetadata $metadata) + { +- /** @var \ReflectionClass $reflClass */ ++ /** ++ * @var \ReflectionClass $reflClass ++*/ + $reflClass = $metadata->getReflectionClass(); + + if (!$reflClass->isInterface() && $reflClass->hasMethod($this->methodName)) { + +--- vendor/symfony/validator/Mapping/MetadataInterface.php ++++ PHP_CodeSniffer +@@ -24,7 +24,7 @@ + * against their class' metadata and whether traversable objects should be + * traversed or not. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Mapping/ClassMetadata.php ++++ PHP_CodeSniffer +@@ -136,7 +136,8 @@ + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + + if (null === $propagatedGroup && Constraint::DEFAULT_GROUP === $group +- && ($this->hasGroupSequence() || $this->isGroupSequenceProvider())) { ++ && ($this->hasGroupSequence() || $this->isGroupSequenceProvider()) ++ ) { + if ($this->hasGroupSequence()) { + $groups = $this->getGroupSequence()->groups; + } else { +@@ -177,7 +178,8 @@ + // Don't store the cascading strategy. Classes never cascade. + unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]); + +- return array_merge($parentProperties, array( ++ return array_merge( ++ $parentProperties, array( + 'getters', + 'groupSequence', + 'groupSequenceProvider', +@@ -185,7 +187,8 @@ + 'name', + 'properties', + 'defaultGroup', +- )); ++ ) ++ ); + } + + /** +@@ -222,17 +225,21 @@ + public function addConstraint(Constraint $constraint) + { + if (!in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) { +- throw new ConstraintDefinitionException(sprintf( +- 'The constraint "%s" cannot be put on classes.', +- get_class($constraint) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The constraint "%s" cannot be put on classes.', ++ get_class($constraint) ++ ) ++ ); + } + + if ($constraint instanceof Valid) { +- throw new ConstraintDefinitionException(sprintf( +- 'The constraint "%s" cannot be put on classes.', +- get_class($constraint) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'The constraint "%s" cannot be put on classes.', ++ get_class($constraint) ++ ) ++ ); + } + + if ($constraint instanceof Traverse) { + +--- vendor/symfony/validator/Mapping/CascadingStrategy.php ++++ PHP_CodeSniffer +@@ -27,7 +27,7 @@ + * Although the constants currently represent a boolean switch, they are + * implemented as bit mask in order to allow future extensions. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * + +--- vendor/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php ++++ PHP_CodeSniffer +@@ -16,7 +16,7 @@ + /** + * Returns {@link \Symfony\Component\Validator\Mapping\MetadataInterface} instances for values. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/ValidatorBuilderInterface.php ++++ PHP_CodeSniffer +@@ -178,8 +178,8 @@ + * + * @return ValidatorBuilderInterface The builder object + * +- * @see Validation::API_VERSION_2_5 +- * @see Validation::API_VERSION_2_5_BC ++ * @see Validation::API_VERSION_2_5 ++ * @see Validation::API_VERSION_2_5_BC + * @deprecated since version 2.7, to be removed in 3.0. + */ + public function setApiVersion($apiVersion); + +--- vendor/symfony/validator/Util/PropertyPath.php ++++ PHP_CodeSniffer +@@ -16,7 +16,7 @@ + * + * For more extensive functionality, use Symfony's PropertyAccess component. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/ExecutionContext.php ++++ PHP_CodeSniffer +@@ -104,17 +104,19 @@ + } + } + +- $this->globalContext->getViolations()->add(new ConstraintViolation( +- $translatedMessage, +- $message, +- $params, +- $this->globalContext->getRoot(), +- $this->propertyPath, +- // check using func_num_args() to allow passing null values +- func_num_args() >= 3 ? $invalidValue : $this->value, +- $plural, +- $code +- )); ++ $this->globalContext->getViolations()->add( ++ new ConstraintViolation( ++ $translatedMessage, ++ $message, ++ $params, ++ $this->globalContext->getRoot(), ++ $this->propertyPath, ++ // check using func_num_args() to allow passing null values ++ func_num_args() >= 3 ? $invalidValue : $this->value, ++ $plural, ++ $code ++ ) ++ ); + } + + /** +@@ -122,19 +124,21 @@ + */ + public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null) + { +- $this->globalContext->getViolations()->add(new ConstraintViolation( +- null === $plural ++ $this->globalContext->getViolations()->add( ++ new ConstraintViolation( ++ null === $plural + ? $this->translator->trans($message, $parameters, $this->translationDomain) + : $this->translator->transChoice($message, $plural, $parameters, $this->translationDomain), +- $message, +- $parameters, +- $this->globalContext->getRoot(), +- $this->getPropertyPath($subPath), +- // check using func_num_args() to allow passing null values +- func_num_args() >= 4 ? $invalidValue : $this->value, +- $plural, +- $code +- )); ++ $message, ++ $parameters, ++ $this->globalContext->getRoot(), ++ $this->getPropertyPath($subPath), ++ // check using func_num_args() to allow passing null values ++ func_num_args() >= 4 ? $invalidValue : $this->value, ++ $plural, ++ $code ++ ) ++ ); + } + + /** + +--- vendor/symfony/validator/ConstraintViolationInterface.php ++++ PHP_CodeSniffer +@@ -59,7 +59,7 @@ + * @return array A possibly empty list of parameters indexed by the names + * that appear in the message template. + * +- * @see getMessageTemplate() ++ * @see getMessageTemplate() + * @deprecated since version 2.7, to be replaced by getParameters() in 3.0. + */ + public function getMessageParameters(); + +--- vendor/symfony/validator/Validator/LegacyValidator.php ++++ PHP_CodeSniffer +@@ -16,12 +16,12 @@ + /** + * A validator that supports both the API of Symfony < 2.5 and Symfony 2.5+. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + * +- * @see \Symfony\Component\Validator\ValidatorInterface +- * @see \Symfony\Component\Validator\Validator\ValidatorInterface ++ * @see \Symfony\Component\Validator\ValidatorInterface ++ * @see \Symfony\Component\Validator\Validator\ValidatorInterface + * @deprecated since version 2.5, to be removed in 3.0. + */ + class LegacyValidator extends RecursiveValidator + +--- vendor/symfony/validator/Validator/ContextualValidatorInterface.php ++++ PHP_CodeSniffer +@@ -17,7 +17,7 @@ + /** + * A validator in a specific execution context. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/Validator/RecursiveContextualValidator.php ++++ PHP_CodeSniffer +@@ -33,7 +33,7 @@ + /** + * Recursive implementation of {@link ContextualValidatorInterface}. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ +@@ -177,11 +177,13 @@ + return $this; + } + +- throw new RuntimeException(sprintf( +- 'Cannot validate values of type "%s" automatically. Please '. +- 'provide a constraint.', +- gettype($value) +- )); ++ throw new RuntimeException( ++ sprintf( ++ 'Cannot validate values of type "%s" automatically. Please '. ++ 'provide a constraint.', ++ gettype($value) ++ ) ++ ); + } + + /** +@@ -194,12 +196,14 @@ + if (!$classMetadata instanceof ClassMetadataInterface) { + // Cannot be UnsupportedMetadataException because of BC with + // Symfony < 2.5 +- throw new ValidatorException(sprintf( +- 'The metadata factory should return instances of '. +- '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. +- 'got: "%s".', +- is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) +- )); ++ throw new ValidatorException( ++ sprintf( ++ 'The metadata factory should return instances of '. ++ '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. ++ 'got: "%s".', ++ is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) ++ ) ++ ); + } + + $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); +@@ -245,12 +249,14 @@ + if (!$classMetadata instanceof ClassMetadataInterface) { + // Cannot be UnsupportedMetadataException because of BC with + // Symfony < 2.5 +- throw new ValidatorException(sprintf( +- 'The metadata factory should return instances of '. +- '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. +- 'got: "%s".', +- is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) +- )); ++ throw new ValidatorException( ++ sprintf( ++ 'The metadata factory should return instances of '. ++ '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. ++ 'got: "%s".', ++ is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) ++ ) ++ ); + } + + $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); +@@ -345,12 +351,14 @@ + $classMetadata = $this->metadataFactory->getMetadataFor($object); + + if (!$classMetadata instanceof ClassMetadataInterface) { +- throw new UnsupportedMetadataException(sprintf( +- 'The metadata factory should return instances of '. +- '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. +- 'got: "%s".', +- is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) +- )); ++ throw new UnsupportedMetadataException( ++ sprintf( ++ 'The metadata factory should return instances of '. ++ '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. ++ 'got: "%s".', ++ is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) ++ ) ++ ); + } + + $this->validateClassNode( +@@ -552,15 +560,15 @@ + // group sequence and abort if necessary (G1, G2) + if ($group instanceof GroupSequence) { + $this->stepThroughGroupSequence( +- $object, +- $object, +- $cacheKey, +- $metadata, +- $propertyPath, +- $traversalStrategy, +- $group, +- $defaultOverridden ? Constraint::DEFAULT_GROUP : null, +- $context ++ $object, ++ $object, ++ $cacheKey, ++ $metadata, ++ $propertyPath, ++ $traversalStrategy, ++ $group, ++ $defaultOverridden ? Constraint::DEFAULT_GROUP : null, ++ $context + ); + + // Skip the group sequence when validating properties, because +@@ -586,12 +594,14 @@ + // returns two metadata objects, not just one + foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { + if (!$propertyMetadata instanceof PropertyMetadataInterface) { +- throw new UnsupportedMetadataException(sprintf( +- 'The property metadata instances should implement '. +- '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '. +- 'got: "%s".', +- is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata) +- )); ++ throw new UnsupportedMetadataException( ++ sprintf( ++ 'The property metadata instances should implement '. ++ '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '. ++ 'got: "%s".', ++ is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata) ++ ) ++ ); + } + + $propertyValue = $propertyMetadata->getPropertyValue($object); +@@ -632,11 +642,13 @@ + if (!$object instanceof \Traversable) { + // Must throw a ConstraintDefinitionException for backwards + // compatibility reasons with Symfony < 2.5 +- throw new ConstraintDefinitionException(sprintf( +- 'Traversal was enabled for "%s", but this class '. +- 'does not implement "\Traversable".', +- get_class($object) +- )); ++ throw new ConstraintDefinitionException( ++ sprintf( ++ 'Traversal was enabled for "%s", but this class '. ++ 'does not implement "\Traversable".', ++ get_class($object) ++ ) ++ ); + } + + $this->validateEachObjectIn( +@@ -690,15 +702,15 @@ + foreach ($groups as $key => $group) { + if ($group instanceof GroupSequence) { + $this->stepThroughGroupSequence( +- $value, +- $object, +- $cacheKey, +- $metadata, +- $propertyPath, +- $traversalStrategy, +- $group, +- null, +- $context ++ $value, ++ $object, ++ $cacheKey, ++ $metadata, ++ $propertyPath, ++ $traversalStrategy, ++ $group, ++ null, ++ $context + ); + + // Skip the group sequence when cascading, as the cascading +@@ -808,26 +820,26 @@ + + if ($metadata instanceof ClassMetadataInterface) { + $this->validateClassNode( +- $value, +- $cacheKey, +- $metadata, +- $propertyPath, +- $groups, +- $cascadedGroups, +- $traversalStrategy, +- $context ++ $value, ++ $cacheKey, ++ $metadata, ++ $propertyPath, ++ $groups, ++ $cascadedGroups, ++ $traversalStrategy, ++ $context + ); + } else { + $this->validateGenericNode( +- $value, +- $object, +- $cacheKey, +- $metadata, +- $propertyPath, +- $groups, +- $cascadedGroups, +- $traversalStrategy, +- $context ++ $value, ++ $object, ++ $cacheKey, ++ $metadata, ++ $propertyPath, ++ $groups, ++ $cascadedGroups, ++ $traversalStrategy, ++ $context + ); + } + + +--- vendor/symfony/validator/Validator/ValidatorInterface.php ++++ PHP_CodeSniffer +@@ -19,7 +19,7 @@ + /** + * Validates PHP values against constraints. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/validator/Validator/RecursiveValidator.php ++++ PHP_CodeSniffer +@@ -24,7 +24,7 @@ + /** + * Recursive implementation of {@link ValidatorInterface}. + * +- * @since 2.5 ++ * @since 2.5 + * + * @author Bernhard Schussek + */ + +--- vendor/symfony/stopwatch/Tests/StopwatchTest.php ++++ PHP_CodeSniffer +@@ -62,8 +62,7 @@ + + $stopwatchMockEvent = $this->getMockBuilder('Symfony\Component\Stopwatch\StopwatchEvent') + ->setConstructorArgs(array(microtime(true) * 1000)) +- ->getMock() +- ; ++ ->getMock(); + + $events->setValue(end($section), array('foo' => $stopwatchMockEvent)); + + +--- vendor/symfony/yaml/Dumper.php ++++ PHP_CodeSniffer +@@ -100,7 +100,8 @@ + + $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); + +- $output .= sprintf('%s%s%s%s', ++ $output .= sprintf( ++ '%s%s%s%s', + $prefix, + $isAHash ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + +--- vendor/symfony/yaml/Tests/ParserTest.php ++++ PHP_CodeSniffer +@@ -637,7 +637,8 @@ + */ + public function testMultipleDocumentsNotSupportedException() + { +- Yaml::parse(<<<'EOL' ++ Yaml::parse( ++ <<<'EOL' + # Ranking of 1998 home runs + --- + - Mark McGwire +@@ -657,7 +658,8 @@ + */ + public function testSequenceInAMapping() + { +- Yaml::parse(<<<'EOF' ++ Yaml::parse( ++ <<<'EOF' + yaml: + hash: me + - array stuff +@@ -717,7 +719,8 @@ + */ + public function testMappingInASequence() + { +- Yaml::parse(<<<'EOF' ++ Yaml::parse( ++ <<<'EOF' + yaml: + - array stuff + hash: me +@@ -731,7 +734,8 @@ + */ + public function testScalarInSequence() + { +- Yaml::parse(<<assertEquals(array( ++ $this->assertEquals( ++ array( + 'services' => array( + 'app.foo_service' => array( + 'class' => 'Foo', +@@ -802,7 +807,8 @@ + 'class' => 'Bar', + ), + ), +- ), Yaml::parse(<<<'EOF' ++ ), Yaml::parse( ++ <<<'EOF' + # comment 1 + services: + # comment 2 +@@ -814,12 +820,14 @@ + app/bar_service: + class: Bar + EOF +- )); ++ ) ++ ); + } + + public function testStringBlockWithComments() + { +- $this->assertEquals(array('content' => <<<'EOT' ++ $this->assertEquals( ++ array('content' => <<<'EOT' + # comment 1 + header + +@@ -830,7 +838,8 @@ + + footer # comment3 + EOT +- ), Yaml::parse(<<<'EOF' ++ ), Yaml::parse( ++ <<<'EOF' + content: | + # comment 1 + header +@@ -842,12 +851,14 @@ + + footer # comment3 + EOF +- )); ++ ) ++ ); + } + + public function testFoldedStringBlockWithComments() + { +- $this->assertEquals(array(array('content' => <<<'EOT' ++ $this->assertEquals( ++ array(array('content' => <<<'EOT' + # comment 1 + header + +@@ -858,7 +869,8 @@ + + footer # comment3 + EOT +- )), Yaml::parse(<<<'EOF' ++ )), Yaml::parse( ++ <<<'EOF' + - + content: | + # comment 1 +@@ -871,12 +883,14 @@ + + footer # comment3 + EOF +- )); ++ ) ++ ); + } + + public function testNestedFoldedStringBlockWithComments() + { +- $this->assertEquals(array(array( ++ $this->assertEquals( ++ array(array( + 'title' => 'some title', + 'content' => <<<'EOT' + # comment 1 +@@ -889,7 +903,8 @@ + + footer # comment3 + EOT +- )), Yaml::parse(<<<'EOF' ++ )), Yaml::parse( ++ <<<'EOF' + - + title: some title + content: | +@@ -903,12 +918,14 @@ + + footer # comment3 + EOF +- )); ++ ) ++ ); + } + + public function testReferenceResolvingInInlineStrings() + { +- $this->assertEquals(array( ++ $this->assertEquals( ++ array( + 'var' => 'var-value', + 'scalar' => 'var-value', + 'list' => array('var-value'), +@@ -918,7 +935,8 @@ + 'map' => array('key' => 'var-value'), + 'list_in_map' => array('key' => array('var-value')), + 'map_in_map' => array('foo' => array('bar' => 'var-value')), +- ), Yaml::parse(<<<'EOF' ++ ), Yaml::parse( ++ <<<'EOF' + var: &var var-value + scalar: *var + list: [ *var ] +@@ -929,7 +947,8 @@ + list_in_map: { key: [*var] } + map_in_map: { foo: { bar: *var } } + EOF +- )); ++ ) ++ ); + } + + public function testYamlDirective() +@@ -1188,13 +1207,13 @@ + data: !!binary | + SGVsbG8gd29ybGQ= + EOT +- ), ++ ), + 'containing spaces in block scalar' => array( + <<run($uri, 'before'); - $this->startController(); + $returned = $this->startController(); // Closure controller has run in startController(). if ( ! is_callable($this->controller)) @@ -196,16 +203,25 @@ public function run(RouteCollectionInterface $routes = null) //-------------------------------------------------------------------- Hooks::trigger('post_controller_constructor'); - $this->runController($controller); + $returned = $this->runController($controller); } + // If $returned is a string, then the controller output something, + // probably a view, instead of echoing it directly. Send it along + // so it can be used with the output. + $this->gatherOutput($cacheConfig, $returned); + //-------------------------------------------------------------------- // Run "after" filters //-------------------------------------------------------------------- - $filters->run($uri, 'after'); - unset($uri); + $response = $filters->run($uri, 'after'); + + if ($response instanceof Response) + { + $this->response = $response; + } - $this->gatherOutput($cacheConfig); + unset($uri); $this->sendResponse(); @@ -292,6 +308,17 @@ public function cachePage($config) //-------------------------------------------------------------------- + public function getPerfomanceStats() + { + return [ + 'startTime' => $this->startTime, + 'totalTime' => $this->totalTime, + 'startMemory' => $this->startMemory + ]; + } + + //-------------------------------------------------------------------- + /** * Generates the cache name to use for our full-page caching. * @@ -445,26 +472,7 @@ protected function tryToRouteIt(RouteCollectionInterface $routes = null) // $routes is defined in Config/Routes.php $this->router = Services::router($routes); - if (is_cli()) - { - $path = $this->request->getPath(); - } - else - { - $path = $this->request->uri->getPath(); - - // For web requests, we need to remove the path - // portion of the baseURL, if set, otherwise - // route portions won't be discovered correctly. - if (! empty($this->config->baseURL)) - { - $basePath = parse_url($this->config->baseURL, PHP_URL_PATH); - $path = strpos($path, $basePath) === 0 - ? substr($path, strlen($basePath) -1) - : $path; - } - } - + $path = is_cli() ? $this->request->getPath() : $this->request->uri->getPath(); $this->benchmark->stop('bootstrap'); $this->benchmark->start('routing'); @@ -493,7 +501,7 @@ protected function startController() if (is_object($this->controller) && (get_class($this->controller) == 'Closure')) { $controller = $this->controller; - echo $controller(...$this->router->params()); + return $controller(...$this->router->params()); } else { @@ -541,19 +549,23 @@ protected function createController() * Runs the controller, allowing for _remap methods to function. * * @param mixed $class + * + * @return mixed */ protected function runController($class) { if (method_exists($class, '_remap')) { - $class->_remap($this->method, ...$this->router->params()); + $output = $class->_remap($this->method, ...$this->router->params()); } else { - $class->{$this->method}(...$this->router->params()); + $output = $class->{$this->method}(...$this->router->params()); } $this->benchmark->stop('controller'); + + return $output; } //-------------------------------------------------------------------- @@ -639,7 +651,7 @@ protected function display404errors(PageNotFoundException $e) * Gathers the script output from the buffer, replaces some execution * time tag in the output and displays the debug toolbar, if required. */ - protected function gatherOutput($cacheConfig = null) + protected function gatherOutput($cacheConfig = null, $returned = null) { $this->output = ob_get_contents(); ob_end_clean(); @@ -651,18 +663,14 @@ protected function gatherOutput($cacheConfig = null) $this->cachePage($cacheConfig); } - $this->output = $this->displayPerformanceMetrics($this->output); - - //-------------------------------------------------------------------- - // Display the Debug Toolbar? - //-------------------------------------------------------------------- - if ( ! is_cli() && ENVIRONMENT != 'production' && $this->config->toolbarEnabled) + if (is_string($returned)) { - $toolbar = Services::toolbar($this->config); - $this->output .= $toolbar->run($this->startTime, $this->totalTime, - $this->startMemory, $this->request, - $this->response); + $this->output .= $returned; } + + $this->output = $this->displayPerformanceMetrics($this->output); + + $this->response->setBody($this->output); } //-------------------------------------------------------------------- @@ -673,8 +681,6 @@ protected function gatherOutput($cacheConfig = null) */ protected function sendResponse() { - $this->response->setBody($this->output); - $this->response->send(); } diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 966b5e9611f5..7bc0b0aa8f6e 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -812,12 +812,13 @@ protected function _whereIn($key = null, $values = null, $not = false, $type = ' * @param string $match * @param string $side * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search * * @return BaseBuilder */ - public function like($field, $match = '', $side = 'both', $escape = null) + public function like($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) { - return $this->_like($field, $match, 'AND ', $side, '', $escape); + return $this->_like($field, $match, 'AND ', $side, '', $escape, $insensitiveSearch); } //-------------------------------------------------------------------- @@ -832,12 +833,13 @@ public function like($field, $match = '', $side = 'both', $escape = null) * @param string $match * @param string $side * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search * * @return BaseBuilder */ - public function notLike($field, $match = '', $side = 'both', $escape = null) + public function notLike($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) { - return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape); + return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape, $insensitiveSearch); } //-------------------------------------------------------------------- @@ -852,12 +854,13 @@ public function notLike($field, $match = '', $side = 'both', $escape = null) * @param string $match * @param string $side * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search * * @return BaseBuilder */ - public function orLike($field, $match = '', $side = 'both', $escape = null) + public function orLike($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) { - return $this->_like($field, $match, 'OR ', $side, '', $escape); + return $this->_like($field, $match, 'OR ', $side, '', $escape, $insensitiveSearch); } //-------------------------------------------------------------------- @@ -872,12 +875,13 @@ public function orLike($field, $match = '', $side = 'both', $escape = null) * @param string $match * @param string $side * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search * * @return BaseBuilder */ - public function orNotLike($field, $match = '', $side = 'both', $escape = null) + public function orNotLike($field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false) { - return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape); + return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape, $insensitiveSearch); } //-------------------------------------------------------------------- @@ -896,10 +900,11 @@ public function orNotLike($field, $match = '', $side = 'both', $escape = null) * @param string $side * @param string $not * @param bool $escape + * @param bool $insensitiveSearch IF true, will force a case-insensitive search * * @return BaseBuilder */ - protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = null) + protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = null, $insensitiveSearch = false) { if ( ! is_array($field)) { @@ -916,6 +921,11 @@ protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $n $prefix = (count($this->QBWhere) === 0) ? $this->groupGetType('') : $this->groupGetType($type); + if ($insensitiveSearch === true) + { + $v = strtolower($v); + } + if ($side === 'none') { $bind = $this->setBind($k, $v); @@ -933,7 +943,7 @@ protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $n $bind = $this->setBind($k, "%$v%"); } - $like_statement = "{$prefix} {$k} {$not} LIKE :{$bind}"; + $like_statement = $this->_like_statement($prefix, $k, $not, $bind, $insensitiveSearch); // some platforms require an escape sequence definition for LIKE wildcards if ($escape === true && $this->db->likeEscapeStr !== '') @@ -949,6 +959,31 @@ protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $n //-------------------------------------------------------------------- + /** + * Platform independent LIKE statement builder. + * + * @param string|null $prefix + * @param string $column + * @param string|null $not + * @param string $bind + * @param bool $insensitiveSearch + * + * @return string $like_statement + */ + public function _like_statement(string $prefix=null, string $column, string $not = null, string $bind, bool $insensitiveSearch=false): string + { + $like_statement = "{$prefix} {$column} {$not} LIKE :{$bind}"; + + if ($insensitiveSearch === true) + { + $like_statement = "{$prefix} LOWER({$column}) {$not} LIKE :{$bind}"; + } + + return $like_statement; + } + + //-------------------------------------------------------------------- + /** * Starts a query group. * diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index a88cc1327b38..19ba0389c967 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -333,4 +333,30 @@ protected function _truncate($table) } //-------------------------------------------------------------------- + + /** + * Platform independent LIKE statement builder. + * + * In PostgreSQL, the ILIKE operator will perform case insensitive + * searches according to the current locale. + * + * @see https://www.postgresql.org/docs/9.2/static/functions-matching.html + * + * @param string|null $prefix + * @param string $column + * @param string|null $not + * @param string $bind + * @param bool $insensitiveSearch + * + * @return string $like_statement + */ + public function _like_statement(string $prefix=null, string $column, string $not = null, string $bind, bool $insensitiveSearch=false): string + { + $op = $insensitiveSearch === true ? 'ILIKE' : 'LIKE'; + + return "{$prefix} {$column} {$not} {$op} :{$bind}"; + } + + //-------------------------------------------------------------------- + } diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index f9875f4d1a98..53156082883d 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -29,7 +29,7 @@ class Toolbar /** * Constructor - * + * * @param BaseConfig $config */ public function __construct(BaseConfig $config) @@ -50,7 +50,7 @@ public function __construct(BaseConfig $config) /** * Run - * + * * @param type $startTime * @param type $totalTime * @param type $startMemory diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php index e33680ebc463..b05a7b2014cd 100644 --- a/system/HTTP/ResponseInterface.php +++ b/system/HTTP/ResponseInterface.php @@ -1,4 +1,6 @@ - 80, - 'https' => 443, - 'ftp' => 21, - 'sftp' => 22 + 'http' => 80, + 'https' => 443, + 'ftp' => 21, + 'sftp' => 22 ]; /** @@ -156,14 +158,7 @@ public function __construct(string $uri = null) { if ( ! is_null($uri)) { - $parts = parse_url($uri); - - if ($parts === false) - { - throw new \InvalidArgumentException("Unable to parse URI: {$uri}"); - } - - $this->applyParts($parts); + $this->setURI($uri); } } @@ -193,7 +188,6 @@ public function setURI(string $uri = null) //-------------------------------------------------------------------- - /** * Retrieve the scheme component of the URI. * @@ -462,11 +456,8 @@ public function getTotalSegments(): int public function __toString() { return self::createURIString( - $this->getScheme(), - $this->getAuthority(), - $this->getPath(), // Absolute URIs should use a "/" for an empty path - $this->getQuery(), - $this->getFragment() + $this->getScheme(), $this->getAuthority(), $this->getPath(), // Absolute URIs should use a "/" for an empty path + $this->getQuery(), $this->getFragment() ); } @@ -484,7 +475,7 @@ public function __toString() * * @return string */ - public static function createURIString($scheme=null, $authority=null, $path=null, $query=null, $fragment=null) + public static function createURIString($scheme = null, $authority = null, $path = null, $query = null, $fragment = null) { $uri = ''; if ( ! empty($scheme)) @@ -499,7 +490,7 @@ public static function createURIString($scheme=null, $authority=null, $path=null if ($path) { - if (empty($path) || '/' !== substr($path, 0, 1)) + if ('/' !== substr($path, 0, 1)) { $path = '/'.$path; } @@ -528,7 +519,7 @@ public static function createURIString($scheme=null, $authority=null, $path=null */ public function setAuthority(string $str) { - $parts = parse_url($str); + $parts = parse_url($str); if (empty($parts['host']) && ! empty($parts['path'])) { @@ -543,7 +534,6 @@ public function setAuthority(string $str) //-------------------------------------------------------------------- - /** * Sets the scheme for this URI. * @@ -578,7 +568,7 @@ public function setScheme(string $str) */ public function setUserInfo(string $user, string $pass) { - $this->user = trim($user); + $this->user = trim($user); $this->password = trim($pass); return $this; @@ -724,12 +714,10 @@ protected function splitQueryPart(string $part) protected function filterQuery($str) { return preg_replace_callback( - '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', - function (array $matches) - { - return rawurlencode($matches[0]); - }, - $str + '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', function (array $matches) + { + return rawurlencode($matches[0]); + }, $str ); } @@ -775,7 +763,7 @@ public function setFragment(string $string) * * @param $path */ - protected function filterPath(string $path=null) + protected function filterPath(string $path = null) { $orig = $path; @@ -787,17 +775,15 @@ protected function filterPath(string $path=null) $path = $this->removeDotSegments($path); // Fix up some leading slash edge cases... - if (strpos($orig, './') === 0) $path = '/'. $path; - if (strpos($orig, '../') === 0) $path = '/'. $path; + if (strpos($orig, './') === 0) $path = '/'.$path; + if (strpos($orig, '../') === 0) $path = '/'.$path; // Encode characters $path = preg_replace_callback( - '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', - function (array $matches) - { - return rawurlencode($matches[0]); - }, - $path + '/(?:[^'.self::CHAR_UNRESERVED.':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', function (array $matches) + { + return rawurlencode($matches[0]); + }, $path ); return $path; @@ -805,8 +791,6 @@ function (array $matches) //-------------------------------------------------------------------- - - /** * Saves our parts from a parse_url call. * @@ -814,24 +798,29 @@ function (array $matches) */ protected function applyParts($parts) { - if (! empty($parts['host'])) $this->host = $parts['host']; - if (! empty($parts['user'])) $this->user = $parts['user']; - if (! empty($parts['path'])) $this->path = $this->filterPath($parts['path']); - if (! empty($parts['query'])) $this->query = $this->filterQuery($parts['query']); - if (! empty($parts['fragment'])) $this->fragment = $this->filterQuery($parts['fragment']); + if ( ! empty($parts['host'])) $this->host = $parts['host']; + if ( ! empty($parts['user'])) $this->user = $parts['user']; + if ( ! empty($parts['path'])) $this->path = $this->filterPath($parts['path']); + if ( ! empty($parts['query'])) $this->query = $this->filterQuery($parts['query']); + if ( ! empty($parts['fragment'])) $this->fragment = $this->filterQuery($parts['fragment']); // Scheme if (isset($parts['scheme'])) { $this->scheme = rtrim(strtolower($parts['scheme']), ':/'); } + else + { + $this->parts['scheme'] = 'http://'; + $this->setScheme('http'); + } // Port if (isset($parts['port'])) { if ( ! is_null($parts['port'])) { - $port = (int)$parts['port']; + $port = (int) $parts['port']; if (1 > $port || 0xffff < $port) { @@ -870,7 +859,7 @@ public function resolveRelativeURI(string $uri) * NOTE: We don't use removeDotSegments in this * algorithm since it's already done by this line! */ - $relative = new URI(); + $relative = new URI(); $relative->setURI($uri); if ($relative->getScheme() == $this->getScheme()) @@ -881,20 +870,20 @@ public function resolveRelativeURI(string $uri) $transformed = clone $relative; // 5.2.2 Transform References - if (! empty($relative->getScheme())) + if ( ! empty($relative->getScheme())) { $transformed->setScheme($relative->getScheme()) - ->setAuthority($relative->getAuthority()) - ->setPath($relative->getPath()) - ->setQuery($relative->getQuery()); + ->setAuthority($relative->getAuthority()) + ->setPath($relative->getPath()) + ->setQuery($relative->getQuery()); } else { - if (! empty($relative->getAuthority())) + if ( ! empty($relative->getAuthority())) { $transformed->setAuthority($relative->getAuthority()) - ->setPath($relative->getPath()) - ->setQuery($relative->getQuery()); + ->setPath($relative->getPath()) + ->setQuery($relative->getQuery()); } else { @@ -902,7 +891,7 @@ public function resolveRelativeURI(string $uri) { $transformed->setPath($this->getPath()); - if (! is_null($relative->getQuery())) + if ( ! is_null($relative->getQuery())) { $transformed->setQuery($relative->getQuery()); } @@ -949,9 +938,9 @@ public function resolveRelativeURI(string $uri) */ protected function mergePaths(URI $base, URI $reference) { - if (! empty($base->getAuthority()) && empty($base->getPath())) + if ( ! empty($base->getAuthority()) && empty($base->getPath())) { - return '/'. ltrim($base->getPath(), '/ '); + return '/'.ltrim($base->getPath(), '/ '); } $path = explode('/', $base->getPath()); @@ -1012,7 +1001,7 @@ public function removeDotSegments(string $path): string if ($output != '/') { // Add leading slash if necessary - if (substr($path, 0, 1) == '/') $output = '/'. $output; + if (substr($path, 0, 1) == '/') $output = '/'.$output; // Add trailing slash if necessary if (substr($path, -1, 1) == '/') $output .= '/'; @@ -1022,5 +1011,4 @@ public function removeDotSegments(string $path): string } //-------------------------------------------------------------------- - } diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index e7564dd2b7cb..9248b72a8143 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -27,131 +27,559 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com - * @since Version 3.0.0 + * @package CodeIgniter + * @author CodeIgniter Dev Team + * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link http://codeigniter.com + * @since Version 3.0.0 * @filesource */ - - -if (! function_exists('site_url')) +if ( ! function_exists('site_url')) { + /** * Return a site URL to use in views * - * @param string $path - * @param string|null $scheme + * @param string|array $path + * @param string|null $scheme + * @param \Config\App|null $altConfig Alternate configuration to use * * @return string */ - function site_url(string $path = '', string $scheme = null): string + function site_url($path = '', string $scheme = null, \Config\App $altConfig = null): string { - $config = new \Config\App(); + // convert segment array to string + if (is_array($path)) + { + $path = implode('/', $path); + } + + // use alternate config if provided, else default one + $config = empty($altConfig) ? new \Config\App() : $altConfig; $base = base_url(); - // Add index page - if (! empty($config->indexPage)) + // Add index page, if so configured + if ( ! empty($config->indexPage)) { $path = rtrim($base, '/').'/'.rtrim($config->indexPage, '/').'/'.$path; } + else + { + $path = rtrim($base, '/').'/'.$path; + } $url = new \CodeIgniter\HTTP\URI($path); - if (! empty($scheme)) + // allow the scheme to be over-ridden; else, use default + if ( ! empty($scheme)) { $url->setScheme($scheme); } - return (string)$url; + return (string) $url; } + } //-------------------------------------------------------------------- -if (! function_exists('base_url')) +if ( ! function_exists('base_url')) { + /** * Return the base URL to use in views * - * @param string $path - * @param string $scheme + * @param string|array $path + * @param string $scheme * @return string */ - function base_url(string $path = '', string $scheme = null): string + function base_url($path = '', string $scheme = null): string { + // convert segment array to string + if (is_array($path)) + { + $path = implode('/', $path); + } + $url = \CodeIgniter\Services::request()->uri; - if (! empty($path)) + if ( ! empty($path)) { $url = $url->resolveRelativeURI($path); } - if (! empty($scheme)) + if ( ! empty($scheme)) { $url->setScheme($scheme); } - return (string)$url; + return (string) $url; } + } //-------------------------------------------------------------------- -if (! function_exists('current_url')) +if ( ! function_exists('current_url')) { + /** * Current URL * * Returns the full URL (including segments) of the page where this * function is placed * - * @return string + * @param boolean $returnObject True to return an object instead of a strong + * + * @return string|URI */ function current_url(bool $returnObject = false) { - return $returnObject === true - ? \CodeIgniter\Services::request()->uri - : (string)\CodeIgniter\Services::request()->uri; + return $returnObject === true ? \CodeIgniter\Services::request()->uri : (string) \CodeIgniter\Services::request()->uri; } + } //-------------------------------------------------------------------- -if (! function_exists('uri_string')) +if ( ! function_exists('uri_string')) { + /** * URL String * - * Returns the URI segments. + * Returns the path part of the current URL * - * @return string + * @return string */ function uri_string(): string { return \CodeIgniter\Services::request()->uri->getPath(); } + } //-------------------------------------------------------------------- -if (! function_exists('index_page')) +if ( ! function_exists('index_page')) { + /** * Index page * * Returns the "index_page" from your config file * - * @return string + * @param \Config\App|null $altConfig Alternate configuration to use + * @return string */ - function index_page() + function index_page(\Config\App $altConfig = null): string { - $config = new \Config\App(); + // use alternate config if provided, else default one + $config = empty($altConfig) ? new \Config\App() : $altConfig; return $config->indexPage; } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('anchor')) +{ + + /** + * Anchor Link + * + * Creates an anchor based on the local URL. + * + * @param string the URL + * @param string the link title + * @param mixed any attributes + * @param \Config\App|null $altConfig Alternate configuration to use + * @return string + */ + function anchor($uri = '', $title = '', $attributes = '', \Config\App $altConfig = null): string + { + // use alternate config if provided, else default one + $config = empty($altConfig) ? new \Config\App() : $altConfig; + + $title = (string) $title; + + $site_url = is_array($uri) ? site_url($uri, null, $config) : (preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri, null, $config)); + // eliminate trailing slash + $site_url = rtrim($site_url, '/'); + + if ($title === '') + { + $title = $site_url; + } + + if ($attributes !== '') + { + $attributes = stringify_attributes($attributes); + } + + return ''.$title.''; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('anchor_popup')) +{ + + /** + * Anchor Link - Pop-up version + * + * Creates an anchor based on the local URL. The link + * opens a new window based on the attributes specified. + * + * @param string the URL + * @param string the link title + * @param mixed any attributes + * @param \Config\App|null $altConfig Alternate configuration to use + * @return string + */ + function anchor_popup($uri = '', $title = '', $attributes = false, \Config\App $altConfig = null): string + { + // use alternate config if provided, else default one + $config = empty($altConfig) ? new \Config\App() : $altConfig; + + $title = (string) $title; + $site_url = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri, '', $config); + $site_url = rtrim($site_url, '/'); + + if ($title === '') + { + $title = $site_url; + } + + if ($attributes === false) + { + return '".$title.''; + } + + if ( ! is_array($attributes)) + { + $attributes = array ($attributes); + + // Ref: http://www.w3schools.com/jsref/met_win_open.asp + $window_name = '_blank'; + } + elseif ( ! empty($attributes['window_name'])) + { + $window_name = $attributes['window_name']; + unset($attributes['window_name']); + } + else + { + $window_name = '_blank'; + } + + foreach (array ('width' => '800', 'height' => '600', 'scrollbars' => 'yes', 'menubar' => 'no', 'status' => 'yes', 'resizable' => 'yes', 'screenx' => '0', 'screeny' => '0') as + $key => $val) + { + $atts[$key] = isset($attributes[$key]) ? $attributes[$key] : $val; + unset($attributes[$key]); + } + + $attributes = stringify_attributes($attributes); + + return ''.$title.''; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('mailto')) +{ + + /** + * Mailto Link + * + * @param string the email address + * @param string the link title + * @param mixed any attributes + * @return string + */ + function mailto($email, $title = '', $attributes = ''): string + { + $title = (string) $title; + + if ($title === '') + { + $title = $email; + } + + return ''.$title.''; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('safe_mailto')) +{ + + /** + * Encoded Mailto Link + * + * Create a spam-protected mailto link written in Javascript + * + * @param string the email address + * @param string the link title + * @param mixed any attributes + * @return string + */ + function safe_mailto($email, $title = '', $attributes = ''): string + { + $title = (string) $title; + + if ($title === '') + { + $title = $email; + } + + $x = str_split(' $val) + { + $x[] = ' '.$key.'="'; + for ($i = 0, $l = strlen($val); $i < $l; $i ++ ) + { + $x[] = '|'.ord($val[$i]); + } + $x[] = '"'; + } + } + else + { + for ($i = 0, $l = strlen($attributes); $i < $l; $i ++ ) + { + $x[] = $attributes[$i]; + } + } + } + + $x[] = '>'; + + $temp = array (); + for ($i = 0, $l = strlen($title); $i < $l; $i ++ ) + { + $ordinal = ord($title[$i]); + + if ($ordinal < 128) + { + $x[] = '|'.$ordinal; + } + else + { + if (count($temp) === 0) + { + $count = ($ordinal < 224) ? 2 : 3; + } + + $temp[] = $ordinal; + if (count($temp) === $count) + { + $number = ($count === 3) ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) : (($temp[0] % 32) * 64) + ($temp[1] % 64); + $x[] = '|'.$number; + $count = 1; + $temp = array (); + } + } + } + + $x[] = '<'; + $x[] = '/'; + $x[] = 'a'; + $x[] = '>'; + + $x = array_reverse($x); + + // improve obfuscation by eliminating newlines & whitespace + $output = "'; + + return $output; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('auto_link')) +{ + + /** + * Auto-linker + * + * Automatically links URL and Email addresses. + * Note: There's a bit of extra code here to deal with + * URLs or emails that end in a period. We'll strip these + * off and add them after the link. + * + * @param string the string + * @param string the type: email, url, or both + * @param bool whether to create pop-up links + * @return string + */ + function auto_link($str, $type = 'both', $popup = false): string + { + // Find and replace any URLs. + if ($type !== 'email' && preg_match_all('#(\w*://|www\.)[^\s()<>;]+\w#i', $str, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) + { + // Set our target HTML if using popup links. + $target = ($popup) ? ' target="_blank"' : ''; + + // We process the links in reverse order (last -> first) so that + // the returned string offsets from preg_match_all() are not + // moved as we add more HTML. + foreach (array_reverse($matches) as $match) + { + // $match[0] is the matched string/link + // $match[1] is either a protocol prefix or 'www.' + // + // With PREG_OFFSET_CAPTURE, both of the above is an array, + // where the actual value is held in [0] and its offset at the [1] index. + $a = ''.$match[0][0].''; + $str = substr_replace($str, $a, $match[0][1], strlen($match[0][0])); + } + } + + // Find and replace any emails. + if ($type !== 'url' && preg_match_all('#([\w\.\-\+]+@[a-z0-9\-]+\.[a-z0-9\-\.]+[^[:punct:]\s])#i', $str, $matches, PREG_OFFSET_CAPTURE)) + { + foreach (array_reverse($matches[0]) as $match) + { + if (filter_var($match[0], FILTER_VALIDATE_EMAIL) !== false) + { + $str = substr_replace($str, safe_mailto($match[0]), $match[1], strlen($match[0])); + } + } + } + + return $str; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('prep_url')) +{ + + /** + * Prep URL - Simply adds the http:// part if no scheme is included. + * + * Formerly used URI, but that does not play nicely with URIs missing + * the scheme. + * + * @param string the URL + * @return string + */ + function prep_url($str = ''): string + { + if ($str === 'http://' OR $str === '') + { + return ''; + } + + $url = parse_url($str); + + if ( ! $url OR ! isset($url['scheme'])) + { + return 'http://'.$str; + } + + return $str; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('url_title')) +{ + + /** + * Create URL Title + * + * Takes a "title" string as input and creates a + * human-friendly URL string with a "separator" string + * as the word separator. + * + * @todo Remove old 'dash' and 'underscore' usage in 3.1+. + * @param string $str Input string + * @param string $separator Word separator (usually '-' or '_') (usually '-' or '_') + * (usually '-' or '_') + * @param bool $lowercase Whether to transform the output string to lowercase + * @return string + */ + function url_title($str, $separator = '-', $lowercase = false): string + { + if ($separator === 'dash') + { + $separator = '-'; + } + elseif ($separator === 'underscore') + { + $separator = '_'; + } + + $q_separator = preg_quote($separator, '#'); + + $trans = array ( + '&.+?;' => '', + '[^\w\d _-]' => '', + '\s+' => $separator, + '('.$q_separator.')+' => $separator + ); + + $str = strip_tags($str); + foreach ($trans as $key => $val) + { + // $str = preg_replace('#'.$key.'#i'.( UTF8_ENABLED ? 'u' : ''), $val, $str); + $str = preg_replace('#'.$key.'#iu', $val, $str); + } + + if ($lowercase === true) + { + $str = strtolower($str); + } + + return trim(trim($str, $separator)); + } + } //-------------------------------------------------------------------- diff --git a/system/ThirdParty/ZendEscaper/Escaper.php b/system/ThirdParty/ZendEscaper/Escaper.php index 072d543f711d..e68e32b05d5c 100644 --- a/system/ThirdParty/ZendEscaper/Escaper.php +++ b/system/ThirdParty/ZendEscaper/Escaper.php @@ -24,12 +24,12 @@ class Escaper * * @var array */ - protected static $htmlNamedEntityMap = array( + protected static $htmlNamedEntityMap = [ 34 => 'quot', // quotation mark 38 => 'amp', // ampersand 60 => 'lt', // less-than sign 62 => 'gt', // greater-than sign - ); + ]; /** * Current encoding for escaping. If not UTF-8, we convert strings from this encoding @@ -41,13 +41,11 @@ class Escaper /** * Holds the value of the special flags passed as second parameter to - * htmlspecialchars(). We modify these for PHP 5.4 to take advantage - * of the new ENT_SUBSTITUTE flag for correctly dealing with invalid - * UTF-8 sequences. + * htmlspecialchars(). * - * @var string + * @var int */ - protected $htmlSpecialCharsFlags = ENT_QUOTES; + protected $htmlSpecialCharsFlags; /** * Static Matcher which escapes characters for HTML Attribute contexts @@ -75,7 +73,7 @@ class Escaper * * @var array */ - protected $supportedEncodings = array( + protected $supportedEncodings = [ 'iso-8859-1', 'iso8859-1', 'iso-8859-5', 'iso8859-5', 'iso-8859-15', 'iso8859-15', 'utf-8', 'cp866', 'ibm866', '866', 'cp1251', 'windows-1251', @@ -85,12 +83,11 @@ class Escaper 'big5-hkscs', 'shift_jis', 'sjis', 'sjis-win', 'cp932', '932', 'euc-jp', 'eucjp', 'eucjp-win', 'macroman' - ); + ]; /** * Constructor: Single parameter allows setting of global encoding for use by - * the current object. If PHP 5.4 is detected, additional ENT_SUBSTITUTE flag - * is set for htmlspecialchars() calls. + * the current object. * * @param string $encoding * @throws Exception\InvalidArgumentException @@ -116,14 +113,13 @@ public function __construct($encoding = null) $this->encoding = $encoding; } - if (defined('ENT_SUBSTITUTE')) { - $this->htmlSpecialCharsFlags|= ENT_SUBSTITUTE; - } + // We take advantage of ENT_SUBSTITUTE flag to correctly deal with invalid UTF-8 sequences. + $this->htmlSpecialCharsFlags = ENT_QUOTES | ENT_SUBSTITUTE; // set matcher callbacks - $this->htmlAttrMatcher = array($this, 'htmlAttrMatcher'); - $this->jsMatcher = array($this, 'jsMatcher'); - $this->cssMatcher = array($this, 'cssMatcher'); + $this->htmlAttrMatcher = [$this, 'htmlAttrMatcher']; + $this->jsMatcher = [$this, 'jsMatcher']; + $this->cssMatcher = [$this, 'cssMatcher']; } /** @@ -248,7 +244,7 @@ protected function htmlAttrMatcher($matches) * replace it with while grabbing the integer value of the character. */ if (strlen($chr) > 1) { - $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8'); + $chr = $this->convertEncoding($chr, 'UTF-32BE', 'UTF-8'); } $hex = bin2hex($chr); @@ -281,7 +277,13 @@ protected function jsMatcher($matches) return sprintf('\\x%02X', ord($chr)); } $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8'); - return sprintf('\\u%04s', strtoupper(bin2hex($chr))); + $hex = strtoupper(bin2hex($chr)); + if (strlen($hex) <= 4) { + return sprintf('\\u%04s', $hex); + } + $highSurrogate = substr($hex, 0, 4); + $lowSurrogate = substr($hex, 4, 4); + return sprintf('\\u%04s\\u%04s', $highSurrogate, $lowSurrogate); } /** @@ -297,7 +299,7 @@ protected function cssMatcher($matches) if (strlen($chr) == 1) { $ord = ord($chr); } else { - $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8'); + $chr = $this->convertEncoding($chr, 'UTF-32BE', 'UTF-8'); $ord = hexdec(bin2hex($chr)); } return sprintf('\\%X ', $ord); diff --git a/tests/system/Database/Builder/LikeTest.php b/tests/system/Database/Builder/LikeTest.php index 46648259e02b..16fb737accaf 100644 --- a/tests/system/Database/Builder/LikeTest.php +++ b/tests/system/Database/Builder/LikeTest.php @@ -120,4 +120,22 @@ public function testOrNotLike() } //-------------------------------------------------------------------- + + /** + * @group single + */ + public function testCaseInsensitiveLike() + { + $builder = new BaseBuilder('job', $this->db); + + $builder->like('name', 'VELOPER', 'both', null, true); + + $expectedSQL = "SELECT * FROM \"job\" WHERE LOWER(name) LIKE :name ESCAPE '!'"; + $expectedBinds = ['name' => '%veloper%']; + + $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + $this->assertSame($expectedBinds, $builder->getBinds()); + } + + //-------------------------------------------------------------------- } diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php index 5c6ca5c18121..ee23a2f56b06 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -53,6 +53,17 @@ public function testLikeBoth() //-------------------------------------------------------------------- + public function testLikeCaseInsensitive() + { + $job = $this->db->table('job')->like('name', 'VELOPER', 'both', null, true)->get(); + $job = $job->getRow(); + + $this->assertEquals(1, $job->id); + $this->assertEquals('Developer', $job->name); + } + + //-------------------------------------------------------------------- + public function testOrLike() { $jobs = $this->db->table('job')->like('name', 'ian') diff --git a/tests/system/HTTP/URITest.php b/tests/system/HTTP/URITest.php index 11d4fa02094e..a11b7d70479f 100644 --- a/tests/system/HTTP/URITest.php +++ b/tests/system/HTTP/URITest.php @@ -83,6 +83,21 @@ public function testEmptyUri() $url = ''; $uri = new URI($url); $this->assertEquals('http://'.$url, (string) $uri); + $url = '/'; + $uri = new URI($url); + $this->assertEquals('http://'.$url, (string) $uri); + } + + //-------------------------------------------------------------------- + + public function testMissingScheme() + { + $url = 'http://foo.bar/baz'; + $uri = new URI($url); + $this->assertEquals('http', $uri->getScheme()); + $this->assertEquals('foo.bar', $uri->getAuthority()); + $this->assertEquals('/baz', $uri->getPath()); + $this->assertEquals($url, (string) $uri); } //-------------------------------------------------------------------- @@ -364,23 +379,23 @@ public function testSetAuthorityReconstitutes() public function defaultDots() { - return array( - array('/foo/..', '/'), - array('//foo//..', '/'), - array('/foo/../..', '/'), - array('/foo/../.', '/'), - array('/./foo/..', '/'), - array('/./foo', '/foo'), - array('/./foo/', '/foo/'), - array('/./foo/bar/baz/pho/../..', '/foo/bar'), - array('*', '*'), - array('/foo', '/foo'), - array('/abc/123/../foo/', '/abc/foo/'), - array('/a/b/c/./../../g', '/a/g'), - array('/b/c/./../../g', '/g'), - array('/b/c/./../../g', '/g'), - array('/c/./../../g', '/g'), - array('/./../../g', '/g'), + return array ( + array ('/foo/..', '/'), + array ('//foo//..', '/'), + array ('/foo/../..', '/'), + array ('/foo/../.', '/'), + array ('/./foo/..', '/'), + array ('/./foo', '/foo'), + array ('/./foo/', '/foo/'), + array ('/./foo/bar/baz/pho/../..', '/foo/bar'), + array ('*', '*'), + array ('/foo', '/foo'), + array ('/abc/123/../foo/', '/abc/foo/'), + array ('/a/b/c/./../../g', '/a/g'), + array ('/b/c/./../../g', '/g'), + array ('/b/c/./../../g', '/g'), + array ('/c/./../../g', '/g'), + array ('/./../../g', '/g'), ); } diff --git a/tests/system/Helpers/URLHelperTest.php b/tests/system/Helpers/URLHelperTest.php index 7c2f5e609311..3d148022615e 100644 --- a/tests/system/Helpers/URLHelperTest.php +++ b/tests/system/Helpers/URLHelperTest.php @@ -1,4 +1,6 @@ -assertEquals('http://example.com/', current_url()); + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/index.php/', site_url('', null, $config)); } - //-------------------------------------------------------------------- + public function testSiteURLNoIndex() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; - public function testCurrentURLReturnsObject() + $config = new App(); + $config->baseURL = ''; + $config->indexPage = ''; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/', site_url('', null, $config)); + } + + public function testSiteURLDifferentIndex() { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; - $url = current_url(true); + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'banana.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); - $this->assertTrue($url instanceof URI); - $this->assertEquals('http://example.com/', (string)$url); + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/banana.php/', site_url('', null, $config)); + } + + public function testSiteURLNoIndexButPath() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = ''; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/abc', site_url('abc', null, $config)); + } + + public function testSiteURLAttachesPath() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/index.php/foo', site_url('foo', null, $config)); + } + + public function testSiteURLAttachesScheme() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('ftp://example.com/index.php/foo', site_url('foo', 'ftp', $config)); + } + + public function testSiteURLExample() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/index.php/news/local/123', site_url('news/local/123', null, $config)); + } + + public function testSiteURLSegments() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/index.php/news/local/123', site_url(['news', 'local', '123'], null, $config)); } //-------------------------------------------------------------------- + // Test base_url public function testBaseURLBasics() { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; $this->assertEquals('http://example.com/', base_url()); } - //-------------------------------------------------------------------- - public function testBaseURLAttachesPath() { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; $this->assertEquals('http://example.com/foo', base_url('foo')); } - //-------------------------------------------------------------------- - public function testBaseURLAttachesScheme() { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; $this->assertEquals('https://example.com/foo', base_url('foo', 'https')); } - //-------------------------------------------------------------------- - public function testBaseURLHeedsBaseURL() { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; // Since we're on a CLI, we must provide our own URI @@ -84,11 +188,74 @@ public function testBaseURLHeedsBaseURL() $this->assertEquals('http://example.com/public', base_url()); } + public function testBaseURLExample() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $this->assertEquals('http://example.com/blog/post/123', base_url('blog/post/123')); + } + //-------------------------------------------------------------------- + // Test current_url - public function testSiteURLBasics() + public function testCurrentURLReturnsBasicURL() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = 'http://example.com/public'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/public'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/public', current_url()); + } + + public function testCurrentURLReturnsObject() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = 'http://example.com/public'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/public'); + + Services::injectMock('request', $request); + + $url = current_url(true); + + $this->assertTrue($url instanceof URI); + $this->assertEquals('http://example.com/public', (string) $url); + } + + public function testCurrentURLEquivalence() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = 'http://example.com/'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/public'); + + Services::injectMock('request', $request); + + $this->assertEquals(base_url(uri_string()), current_url()); + } + + //-------------------------------------------------------------------- + // Test uri_string + + public function testUriString() { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; $config = new App(); @@ -99,14 +266,79 @@ public function testSiteURLBasics() Services::injectMock('request', $request); - $this->assertEquals('http://example.com/index.php/', site_url()); + $url = current_url(); + $this->assertEquals('/', uri_string()); + } + + public function testUriStringExample() + { + + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/assets/image.jpg'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/assets/image.jpg'); + + Services::injectMock('request', $request); + + $url = current_url(); + $this->assertEquals('/assets/image.jpg', uri_string()); } //-------------------------------------------------------------------- + // Test index_page - public function testSiteURLAttachesPath() + public function testIndexPage() + { + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('index.php', index_page()); + } + + public function testIndexPageAlt() + { + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'banana.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals('banana.php', index_page($config)); + } + + //-------------------------------------------------------------------- + // Test anchor + + public function anchorNormalPatterns() { - $_SERVER['HTTP_HOST'] = 'example.com'; + return [ + 'normal01' => ['http://example.com/index.php', ''], + 'normal02' => ['Bananas', '/', 'Bananas'], + 'normal03' => ['http://example.com/index.php', '/', '', 'fruit="peach"'], + 'normal04' => ['Bananas', '/', 'Bananas', 'fruit=peach'], + 'normal05' => ['http://example.com/index.php', '/', '', ['fruit' => 'peach']], + 'normal06' => ['Bananas', '/', 'Bananas', ['fruit' => 'peach']], + 'normal07' => ['http://example.com/index.php', '/'], + ]; + } + + /** + * @dataProvider anchorNormalPatterns + */ + public function testAnchor($expected = '', $uri = '', $title = '', $attributes = '') + { + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; $config = new App(); @@ -116,15 +348,196 @@ public function testSiteURLAttachesPath() $request->uri = new URI('http://example.com/'); Services::injectMock('request', $request); + $this->assertEquals($expected, anchor($uri, $title, $attributes, $config)); + } - $this->assertEquals('http://example.com/index.php/foo', site_url('foo')); + public function anchorNoindexPatterns() + { + return [ + 'noindex01' => ['http://example.com', ''], + 'noindex02' => ['Bananas', '', 'Bananas'], + 'noindex03' => ['http://example.com', '', '', 'fruit="peach"'], + 'noindex04' => ['Bananas', '', 'Bananas', 'fruit=peach'], + 'noindex05' => ['http://example.com', '', '', ['fruit' => 'peach']], + 'noindex06' => ['Bananas', '', 'Bananas', ['fruit' => 'peach']], + 'noindex07' => ['http://example.com', '/'], + ]; + } + + /** + * @dataProvider anchorNoindexPatterns + */ + public function testAnchorNoindex($expected = '', $uri = '', $title = '', $attributes = '') + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = ''; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + $this->assertEquals($expected, anchor($uri, $title, $attributes, $config)); + } + + public function anchorSubpagePatterns() + { + return [ + 'subpage01' => ['http://example.com/mush', '/mush'], + 'subpage02' => ['Bananas', '/mush', 'Bananas'], + 'subpage03' => ['http://example.com/mush', '/mush', '', 'fruit="peach"'], + 'subpage04' => ['Bananas', '/mush', 'Bananas', 'fruit=peach'], + 'subpage05' => ['http://example.com/mush', '/mush', '', ['fruit' => 'peach']], + 'subpage06' => ['Bananas', '/mush', 'Bananas', ['fruit' => 'peach']], + ]; + } + + /** + * @dataProvider anchorSubpagePatterns + */ + public function testAnchorTargetted($expected = '', $uri = '', $title = '', $attributes = '') + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = ''; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + $this->assertEquals($expected, anchor($uri, $title, $attributes, $config)); + } + + public function anchorExamplePatterns() + { + return [ + 'egpage01' => ['My News', 'news/local/123', 'My News', 'title="News title"'], + 'egpage02' => ['My News', 'news/local/123', 'My News', array ('title' => 'The best news!')], + 'egpage03' => ['Click here', '', 'Click here'], + 'egpage04' => ['Click here', '/', 'Click here'], + ]; + } + + /** + * @dataProvider anchorExamplePatterns + */ + public function testAnchorExamples($expected = '', $uri = '', $title = '', $attributes = '') + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + $this->assertEquals($expected, anchor($uri, $title, $attributes, $config)); } //-------------------------------------------------------------------- + // Test anchor_popup - public function testSiteURLAttachesScheme() + public function anchorPopupPatterns() + { + return [ + 'normal01' => ['http://example.com/index.php', ''], + 'normal02' => ['Bananas', '/', 'Bananas'], + 'normal07' => ['http://example.com/index.php', '/'], + 'normal08' => ['Click Me!', + 'news/local/123', 'Click Me!', array ( + 'width' => 800, + 'height' => 600, + 'scrollbars' => 'yes', + 'status' => 'yes', + 'resizable' => 'yes', + 'screenx' => 0, + 'screeny' => 0, + 'window_name' => '_blank' + )], + 'normal09' => [ + 'Click Me!', + 'news/local/123', + 'Click Me!', + array ()], + ]; + } + + /** + * @dataProvider anchorPopupPatterns + */ + public function testAnchorPopup($expected = '', $uri = '', $title = '', $attributes = false) + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + $this->assertEquals($expected, anchor_popup($uri, $title, $attributes, $config)); + } + + //-------------------------------------------------------------------- + // Test mailto + + public function mailtoPatterns() + { + return [ + 'page01' => ['Click Here to Contact Me', 'me@my-site.com', 'Click Here to Contact Me'], + 'page02' => ['Contact Me', 'me@my-site.com', 'Contact Me', array ('title' => 'Mail me')], + 'page03' => ['me@my-site.com', 'me@my-site.com'], + ]; + } + + /** + * @dataProvider mailtoPatterns + */ + public function testMailto($expected = '', $email, $title = '', $attributes = '') + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/'; + + $config = new App(); + $config->baseURL = ''; + $config->indexPage = 'index.php'; + $request = Services::request($config); + $request->uri = new URI('http://example.com/'); + + Services::injectMock('request', $request); + + $this->assertEquals($expected, mailto($email, $title, $attributes)); + } + + //-------------------------------------------------------------------- + // Test safe_mailto + + public function safeMailtoPatterns() + { + return [ + 'page01' => ["", + 'me@my-site.com', 'Click Here to Contact Me'], + 'page02' => ["", + 'me@my-site.com', 'Contact Me', array ('title' => 'Mail me')], + 'page03' => ["", + 'me@my-site.com'], + ]; + } + + /** + * @dataProvider safeMailtoPatterns + */ + public function testSafeMailto($expected = '', $email, $title = '', $attributes = '') { - $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; $config = new App(); @@ -135,9 +548,168 @@ public function testSiteURLAttachesScheme() Services::injectMock('request', $request); - $this->assertEquals('ftp://example.com/index.php/foo', site_url('foo', 'ftp')); + $this->assertEquals($expected, safe_mailto($email, $title, $attributes)); } //-------------------------------------------------------------------- + // Test auto_link + + public function autolinkUrls() + { + return [ + 'test01' => ['www.codeigniter.com test', + 'www.codeigniter.com test'], + 'test02' => ['This is my noreply@codeigniter.com test', + 'This is my noreply@codeigniter.com test'], + 'test03' => ['
www.google.com', + '
www.google.com'], + 'test04' => ['Download CodeIgniter at www.codeigniter.com. Period test.', + 'Download CodeIgniter at www.codeigniter.com. Period test.'], + 'test05' => ['Download CodeIgniter at www.codeigniter.com, comma test', + 'Download CodeIgniter at www.codeigniter.com, comma test'], + 'test06' => ['This one: ://codeigniter.com must not break this one: http://codeigniter.com', + 'This one: ://codeigniter.com must not break this one: http://codeigniter.com'], + 'test07' => ['Visit example.com or email foo@bar.com', + 'Visit example.com or email foo@bar.com'], + 'test08' => ['Visit www.example.com or email foo@bar.com', + 'Visit www.example.com or email foo@bar.com'], + ]; + } + + /** + * @dataProvider autolinkUrls + */ + public function testAutoLinkUrl($in, $out) + { + $this->assertEquals($out, auto_link($in, 'url')); + } + + public function autolinkEmails() + { + return [ + 'test01' => ['www.codeigniter.com test', + 'www.codeigniter.com test'], + 'test02' => ['This is my noreply@codeigniter.com test', + "This is my test"], + 'test03' => ['
www.google.com', + '
www.google.com'], + 'test04' => ['Download CodeIgniter at www.codeigniter.com. Period test.', + 'Download CodeIgniter at www.codeigniter.com. Period test.'], + 'test05' => ['Download CodeIgniter at www.codeigniter.com, comma test', + 'Download CodeIgniter at www.codeigniter.com, comma test'], + 'test06' => ['This one: ://codeigniter.com must not break this one: http://codeigniter.com', + 'This one: ://codeigniter.com must not break this one: http://codeigniter.com'], + 'test07' => ['Visit example.com or email foo@bar.com', + "Visit example.com or email "], + 'test08' => ['Visit www.example.com or email foo@bar.com', + "Visit www.example.com or email "], + ]; + } + + /** + * @dataProvider autolinkEmails + */ + public function testAutoLinkEmail($in, $out) + { + $this->assertEquals($out, auto_link($in, 'email')); + } + + public function autolinkBoth() + { + return [ + 'test01' => ['www.codeigniter.com test', + 'www.codeigniter.com test'], + 'test02' => ['This is my noreply@codeigniter.com test', + "This is my test"], + 'test03' => ['
www.google.com', + '
www.google.com'], + 'test04' => ['Download CodeIgniter at www.codeigniter.com. Period test.', + 'Download CodeIgniter at www.codeigniter.com. Period test.'], + 'test05' => ['Download CodeIgniter at www.codeigniter.com, comma test', + 'Download CodeIgniter at www.codeigniter.com, comma test'], + 'test06' => ['This one: ://codeigniter.com must not break this one: http://codeigniter.com', + 'This one: ://codeigniter.com must not break this one: http://codeigniter.com'], + 'test07' => ['Visit example.com or email foo@bar.com', + "Visit example.com or email "], + 'test08' => ['Visit www.example.com or email foo@bar.com', + "Visit www.example.com or email "], + ]; + } + + /** + * @dataProvider autolinkBoth + */ + public function testAutolinkBoth($in, $out) + { + $this->assertEquals($out, auto_link($in)); + } + + public function autolinkPopup() + { + return [ + 'test01' => ['www.codeigniter.com test', + 'www.codeigniter.com test'], + 'test02' => ['This is my noreply@codeigniter.com test', + 'This is my noreply@codeigniter.com test'], + 'test03' => ['
www.google.com', + '
www.google.com'], + 'test04' => ['Download CodeIgniter at www.codeigniter.com. Period test.', + 'Download CodeIgniter at www.codeigniter.com. Period test.'], + 'test05' => ['Download CodeIgniter at www.codeigniter.com, comma test', + 'Download CodeIgniter at www.codeigniter.com, comma test'], + 'test06' => ['This one: ://codeigniter.com must not break this one: http://codeigniter.com', + 'This one: ://codeigniter.com must not break this one: http://codeigniter.com'], + 'test07' => ['Visit example.com or email foo@bar.com', + 'Visit example.com or email foo@bar.com'], + 'test08' => ['Visit www.example.com or email foo@bar.com', + 'Visit www.example.com or email foo@bar.com'], + ]; + } + + /** + * @dataProvider autolinkPopup + */ + public function testAutoLinkPopup($in, $out) + { + $this->assertEquals($out, auto_link($in, 'url', true)); + } + + //-------------------------------------------------------------------- + // Test prep_url + + public function testPrepUrl() + { + $this->assertEquals('http://codeigniter.com', prep_url('codeigniter.com')); + $this->assertEquals('http://www.codeigniter.com', prep_url('www.codeigniter.com')); + } + + //-------------------------------------------------------------------- + // Test url_title + + public function testUrlTitle() + { + $words = array ( + 'foo bar /' => 'foo-bar', + '\ testing 12' => 'testing-12' + ); + + foreach ($words as $in => $out) + { + $this->assertEquals($out, url_title($in, 'dash', TRUE)); + } + } + + public function testUrlTitleExtraDashes() + { + $words = array ( + '_foo bar_' => 'foo_bar', + '_What\'s wrong with CSS?_' => 'Whats_wrong_with_CSS' + ); + + foreach ($words as $in => $out) + { + $this->assertEquals($out, url_title($in, 'underscore')); + } + } -} \ No newline at end of file +} diff --git a/user_guide_src/source/contributing/styleguide.rst b/user_guide_src/source/contributing/styleguide.rst index b88f023a1d8a..7dcfcc7a4148 100644 --- a/user_guide_src/source/contributing/styleguide.rst +++ b/user_guide_src/source/contributing/styleguide.rst @@ -69,18 +69,23 @@ Naming - File names MUST end with a ".php" name extension and MUST NOT have multiple name extensions. -- Files declaring classes MUST have names exactly matching the classes - that they declare (obviously excluding the ".php" name extension). +- Files declaring classes, interfaces or traits MUST have names exactly matching + the classes that they declare (obviously excluding the ".php" name extension). - Files declaring functions SHOULD be named in *snake_case.php*. ************************************* Whitespace, indentation and alignment ************************************* -- Indentation MUST use only tabs. -- Alignment MUST use only spaces. +- Best practice: indentation SHOULD use only tabs. +- Best practice: alignment SHOULD use only spaces. +- If using tabs for anything, you MUST set the tab spacing to 4. -The following code block would have a single tab at the beginning of +This will accommodate the widest range of developer environment options, +while maintaining consistency of code appearance. + +Following the "best practice" above, +the following code block would have a single tab at the beginning of each line containing braces, and two tabs at the beginning of the nested statements. No alignment is implied.:: @@ -90,7 +95,8 @@ nested statements. No alignment is implied.:: $third = 3; } -The following code block would use spaces to have the assignment +Following the "best practice" above, +the following code block would use spaces to have the assignment operators line up with each other:: { @@ -101,7 +107,7 @@ operators line up with each other:: .. note:: Our indenting and alignment convention differs from PSR-2, which - uses spaces for indenting and alignment. + **only** uses spaces for both indenting and alignment. - Unnecessary whitespace characters MUST NOT be present anywhere within a script. @@ -111,6 +117,11 @@ operators line up with each other:: well as any other whitespace usage that is not functionally required or explicitly described in this document. +.. note:: With conforming tab settings, alignment spacing should + be preserved in all development environments. + A pull request that deals only with tabs or spaces for alignment + will not be favorably considered. + **** Code **** @@ -218,6 +229,31 @@ Logical Operators - If there is potential confusion with a logical expression, then use parentheses for clarity, as shown above. +Control Structures +================== + +- Control structures, such as **if/else** statements, **for/foreach** statements, or + **while/do** statements, MUST use a brace-surrounded block for their body + segments. + + Good control structure examples:: + + if ( $foo ) + { + $bar += $baz; + } + else + { + $baz = 'bar'; + } + + Not-acceptable control structures:: + + if ( $foo ) $bar = $oneThing + $anotherThing + $yetAnotherThing + $evenMore; + + if ( $foo ) $bar += $baz; + else $baz = 'bar'; + Other ===== diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index ba4d5fa4bb3b..d8ca0a15921e 100644 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -350,6 +350,11 @@ searches. .. note:: All values passed to this method are escaped automatically. +.. note:: All ``like*`` method variations can be forced to be perform case-insensitive searches by passing + a fifth parameter of ``true`` to the method. This will use platform-specific features where available + otherwise, will force the values to be lowercase, i.e. ``WHERE LOWER(column) LIKE '%search%'``. This + may require indexes to be made for ``LOWER(column)`` instead of ``column`` to be effective. + #. **Simple key/value method:** :: diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst index 9217f2bd1b25..7c5c620a84c0 100644 --- a/user_guide_src/source/general/controllers.rst +++ b/user_guide_src/source/general/controllers.rst @@ -318,4 +318,5 @@ inside the controller:: That's it! ========== -That, in a nutshell, is all there is to know about controllers. \ No newline at end of file +That, in a nutshell, is all there is to know about controllers. + diff --git a/user_guide_src/source/general/debugging.rst b/user_guide_src/source/general/debugging.rst index 759081452410..89de0f33addf 100644 --- a/user_guide_src/source/general/debugging.rst +++ b/user_guide_src/source/general/debugging.rst @@ -60,10 +60,13 @@ to help you debug and optimize. Enabling the Toolbar ==================== -The toolbar is enabled by default in any environment _except_ production. You can turn change the environments -that the toolbar is active on by editing **application/Config/App.php**:: +The toolbar is enabled by default in any environment _except_ production. It will be shown whenever the +constant CI_DEBUG is defined and it's value is positive. This is defined in the boot files (i.e. +application/Config/Boot/development.php) and can be modified there to determine what environments it shows +itself in. - public $toolbarEnabled = (ENVIRONMENT != 'production' && CI_DEBUG); +The toolbar itself is displayed as an :doc:`After Filter `. You can stop it from ever +running by removing it from the ``$globals`` property of **application/Config/Filters.php**. Choosing What to Show --------------------- diff --git a/user_guide_src/source/general/index.rst b/user_guide_src/source/general/index.rst index 85b87c69a2e5..15e6bfe451c3 100644 --- a/user_guide_src/source/general/index.rst +++ b/user_guide_src/source/general/index.rst @@ -9,7 +9,10 @@ General Topics urls controllers views - Helpers + view_cells + view_renderer + view_parser + helpers core_classes hooks common_functions diff --git a/user_guide_src/source/general/view_cells.rst b/user_guide_src/source/general/view_cells.rst new file mode 100644 index 000000000000..802ee4a46d33 --- /dev/null +++ b/user_guide_src/source/general/view_cells.rst @@ -0,0 +1,68 @@ +########## +View Cells +########## + +View Cells allow you to insert HTML that is generated outside of your controller. It simply calls the specified +class and method, which must return valid HTML. This method could be in an callable method, found in any class +that the autoloader can locate. The only restriction is that the class can not have any constructor parameters. +This is intended to be used within views, and is a great aid to modularizing your code. +:: + + + +In this example, the class ``App\Libraries\Blog`` is loaded, and the method ``recentPosts()`` is ran. That method +must return a string with the generated HTML. The method used can be either a static method or not. Either way works. + +Cell Parameters +--------------- + +You can further refine the call by passing a string with a list of parameters in the second parameter that are passed +to the method as an array of key/value pairs, or a comma-seperated string of key/value pairs:: + + // Passing Parameter Array + 'codeigniter', 'limit' => 5]) ?> + + // Passing Parameter String + + + public function recentPosts(array $params=[]) + { + $posts = $this->blogModel->where('category', $params['category']) + ->orderBy('published_on', 'desc') + ->limit($params['limit']) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } + +Additionally, you can use parameter names that match the parameter variables in the method for better readability. +When you use it this way, all of the parameters must always be specified in the view cell call:: + + + + public function recentPosts(int $limit, string $category) + { + $posts = $this->blogModel->where('category', $category) + ->orderBy('published_on', 'desc') + ->limit($limit) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } + +Cell Caching +------------ + +You can cache the results of the view cell call by passing the number of seconds to cache the data for as the +third parameter. This will use the currently configured cache engine. +:: + + // Cache the view for 5 minutes + + +You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name +as the fourth parameter.:: + + // Cache the view for 5 minutes + + diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst new file mode 100644 index 000000000000..79be751a6ae8 --- /dev/null +++ b/user_guide_src/source/general/view_parser.rst @@ -0,0 +1,6 @@ +########### +View Parser +########### + +Coming soon :) + diff --git a/user_guide_src/source/general/view_renderer.rst b/user_guide_src/source/general/view_renderer.rst new file mode 100644 index 000000000000..f2afa255509a --- /dev/null +++ b/user_guide_src/source/general/view_renderer.rst @@ -0,0 +1,116 @@ +############# +View Renderer +############# + +The ``view()`` function is a convenience method that grabs an instance of the ``renderer`` service, +sets the data, and renders the view. While this is often exactly what you want, you may find times where you +want to work with it more directly. In that case you can access the View service directly:: + + $renderer = \Config\Services::renderer(); + +.. important:: You should create services only within controllers. If you need access to the View class + from a library, you should set that as a dependency in the constructor. + +Then you can use any of the three standard methods that it provides: +**render(viewpath, options, save)**, **setVar(name, value, context)** and **setData(data, context)**. + +Method Chaining +=============== + +The `setVar()` and `setData()` methods are chainable, allowing you to combine a number of different calls together in a chain:: + + service('renderer')->setVar('one', $one) + ->setVar('two', $two) + ->render('myView'); + +Escaping Data +============= + +When you pass data to the ``setVar()`` and ``setData()`` functions you have the option to escape the data to protect +against cross-site scripting attacks. As the last parameter in either method, you can pass the desired context to +escape the data for. See below for context descriptions. + +If you don't want the data to be escaped, you can pass `null` or `raw` as the final parameter to each function:: + + $renderer->setVar('one', $one, 'raw'); + +If you choose not to escape data, or you are passing in an object instance, you can manually escape the data within +the view with the ``esc()`` function. The first parameter is the string to escape. The second parameter is the +context to escape the data for (see below):: + + getStat()) ?> + +Escaping Contexts +----------------- + +By default, the ``esc()`` and, in turn, the ``setVar()`` and ``setData()`` functions assume that the data you want to +escape is intended to be used within standard HTML. However, if the data is intended for use in Javascript, CSS, +or in an href attribute, you would need different escaping rules to be effective. You can pass in the name of the +context as the second parameter. Valid contexts are 'html', 'js', 'css', 'url', and 'attr':: + + Some Link + + + + + +*************** +Class Reference +*************** + +.. php:interface:: CodeIgniter\\View\\RendererableInterface + + .. php:method:: render($view[, $options[, $saveData=false]]]) + + :param string $view: File name of the view source + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string + + Builds the output based upon a file name and any data that has already been set:: + + echo $renderer->render('myview'); + + Options supported: + + - ``cache`` - the time in seconds, to save a view's results + - ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath + - ``saveData`` - true if the view data parameter should be retained for subsequent calls + + + .. php:method:: setData([$data[, $context=null]]) + + :param array $data: Array of view data strings, as key/value pairs + :param string $context: The context to use for data escaping. + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RenderableInterface. + + Sets several pieces of view data at once:: + + $renderer->setData(['name'=>'George', 'position'=>'Boss']); + + Supported escape contexts: html, css, js, url, or attr or raw. + If 'raw', no escaping will happen. + + .. php:method:: setVar($name[, $value=null[, $context=null]]) + + :param string $name: Name of the view data variable + :param mixed $value: The value of this view data + :param string $context: The context to use for data escaping. + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RenderableInterface. + + Sets a single piece of view data:: + + $renderer->setVar('name','Joe','html'); + + Supported escape contexts: html, css, js, url, attr or raw. + If 'raw', no escaping will happen. + diff --git a/user_guide_src/source/general/views.rst b/user_guide_src/source/general/views.rst index f56a7137962a..43f8b82109bd 100644 --- a/user_guide_src/source/general/views.rst +++ b/user_guide_src/source/general/views.rst @@ -18,18 +18,18 @@ Creating a View Using your text editor, create a file called ``BlogView.php`` and put this in it:: - - My Blog - - -

Welcome to my Blog!

- + + My Blog + + +

Welcome to my Blog!

+ Then save the file in your **application/Views** directory. -Loading a View -============== +Displaying a View +================= To load a particular view file you will use the following function:: @@ -53,6 +53,9 @@ If you visit your site using the URL you did earlier you should see your new vie example.com/index.php/blog/ +.. note:: While all of the examples show echo the view directly, you can also return the output from the view, instead, + and it will be appended to any captured output. + Loading Multiple Views ====================== @@ -65,7 +68,7 @@ content view, and a footer view. That might look something like this:: public function index() { $data = [ - 'page_title' = 'Your title' + 'page_title' => 'Your title' ]; echo view('header'); @@ -143,12 +146,12 @@ Let's try it with your controller file. Open it and add this code:: Now open your view file and change the text to variables that correspond to the array keys in your data:: - - <?= $title ?> - - -

- + + <?= $title ?> + + +

+ Then load the page at the URL you've been using and you should see the variables replaced. @@ -167,70 +170,6 @@ into the `$option` array in the third parameter. echo view('blogview', $data, ['saveData' => true]); -Direct Access To View Class -=========================== - -The ``view()`` function is a convenience method that grabs an instance of the ``renderer`` service, -sets the data, and renders the view. While this is often exactly what you want, you may find times where you -want to work with it more directly. In that case you can access the View service directly:: - - $renderer = \Config\Services::renderer(); - -.. important:: You should create services only within controllers. If you need access to the View class - from a library, you should set that as a dependency in the constructor. - -Then you can use any of the three standard methods that it provides. - -* **render('view_name', array $options)** Performs the rendering of the view and its data. The $options array is - unused by default, but provided for third-party libraries to use when integrating with different template engines. -* **setVar('name', 'value', $context=null)** Sets a single piece of dynamic data. $context specifies the context - to escape for. Defaults to no escaping. Set to empty value to skip escaping. -* **setData($array, $context=null)** Takes an array of key/value pairs for dynamic data and optionally escapes it. - $context specifies the context to escape for. Defaults to no escaping. Set to empty value to skip escaping. - -The `setVar()` and `setData()` methods are chainable, allowing you to combine a number of different calls together in a chain:: - - service('renderer')->setVar('one', $one) - ->setVar('two', $two) - ->render('myView'); - -Escaping Data -============= - -When you pass data to the ``setVar()`` and ``setData()`` functions you have the option to escape the data to protect -against cross-site scripting attacks. As the last parameter in either method, you can pass the desired context to -escape the data for. See below for context descriptions. - -If you don't want the data to be escaped, you can pass `null` or `raw` as the final parameter to each function:: - - $renderer->setVar('one', $one, 'raw'); - -If you choose not to escape data, or you are passing in an object instance, you can manually escape the data within -the view with the ``esc()`` function. The first parameter is the string to escape. The second parameter is the -context to escape the data for (see below):: - - getStat()) ?> - -Escaping Contexts ------------------ - -By default, the ``esc()`` and, in turn, the ``setVar()`` and ``setData()`` functions assume that the data you want to -escape is intended to be used within standard HTML. However, if the data is intended for use in Javascript, CSS, -or in an href attribute, you would need different escaping rules to be effective. You can pass in the name of the -context as the second parameter. Valid contexts are 'html', 'js', 'css', 'url', and 'attr':: - - Some Link - - - - - Creating Loops ============== @@ -276,70 +215,3 @@ Now open your view file and create a loop:: -View Cells -========== - -View Cells allow you to insert HTML that is generated outside of your controller. It simply calls the specified -class and method, which must return valid HTML. This method could be in an callable method, found in any class -that the autoloader can locate. The only restriction is that the class can not have any constructor parameters. -This is intended to be used within views, and is a great aid to modularizing your code. -:: - - - -In this example, the class ``App\Libraries\Blog`` is loaded, and the method ``recentPosts()`` is ran. That method -must return a string with the generated HTML. The method used can be either a static method or not. Either way works. - -Cell Parameters ---------------- - -You can further refine the call by passing a string with a list of parameters in the second parameter that are passed -to the method as an array of key/value pairs, or a comma-seperated string of key/value pairs:: - - // Passing Parameter Array - 'codeigniter', 'limit' => 5]) ?> - - // Passing Parameter String - - - public function recentPosts(array $params=[]) - { - $posts = $this->blogModel->where('category', $params['category']) - ->orderBy('published_on', 'desc') - ->limit($params['limit']) - ->get(); - - return view('recentPosts', ['posts' => $posts]); - } - -Additionally, you can use parameter names that match the parameter variables in the method for better readability. -When you use it this way, all of the parameters must always be specified in the view cell call:: - - - - public function recentPosts(int $limit, string $category) - { - $posts = $this->blogModel->where('category', $category) - ->orderBy('published_on', 'desc') - ->limit($limit) - ->get(); - - return view('recentPosts', ['posts' => $posts]); - } - -Cell Caching ------------- - -You can cache the results of the view cell call by passing the number of seconds to cache the data for as the -third parameter. This will use the currently configured cache engine. -:: - - // Cache the view for 5 minutes - - -You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name -as the fourth parameter.:: - - // Cache the view for 5 minutes - - diff --git a/user_guide_src/source/helpers/index.rst b/user_guide_src/source/helpers/index.rst index 46ae3912a180..c083d35d4de9 100644 --- a/user_guide_src/source/helpers/index.rst +++ b/user_guide_src/source/helpers/index.rst @@ -2,4 +2,10 @@ Helpers ####### -TODO: Do we need this anymore? \ No newline at end of file +Helpers are collections of useful procedureal functions. + +.. toctree:: + :glob: + :titlesonly: + + * diff --git a/user_guide_src/source/helpers/url_helper.rst b/user_guide_src/source/helpers/url_helper.rst new file mode 100644 index 000000000000..a91873b926b6 --- /dev/null +++ b/user_guide_src/source/helpers/url_helper.rst @@ -0,0 +1,339 @@ +########## +URL Helper +########## + +The URL Helper file contains functions that assist in working with URLs. + +.. contents:: + :local: + +.. raw:: html + +
+ +Loading this Helper +=================== + +This helper is loaded using the following code:: + + helper('url'); + +Available Functions +=================== + +The following functions are available: + +.. php:function:: site_url([$uri = ''[, $protocol = NULL[, $altConfig = NULL]]]) + + :param string $uri: URI string + :param string $protocol: Protocol, e.g. 'http' or 'https' + :param \\Config\\App $altConfig: Alternate configuration to use + :returns: Site URL + :rtype: string + + Returns your site URL, as specified in your config file. The index.php + file (or whatever you have set as your site **index_page** in your config + file) will be added to the URL, as will any URI segments you pass to the + function, plus the **url_suffix** as set in your config file. + + You are encouraged to use this function any time you need to generate a + local URL so that your pages become more portable in the event your URL + changes. + + Segments can be optionally passed to the function as a string or an + array. Here is a string example:: + + echo site_url('news/local/123'); + + The above example would return something like: + *http://example.com/index.php/news/local/123* + + Here is an example of segments passed as an array:: + + $segments = array('news', 'local', '123'); + echo site_url($segments); + + You may find the alternate configuration useful if generating URLs for a + different site than yours, which contains different configuration preferences. + We use this for unit testing the framework itself. + +.. php:function:: base_url([$uri = ''[, $protocol = NULL]]) + + :param string $uri: URI string + :param string $protocol: Protocol, e.g. 'http' or 'https' + :returns: Base URL + :rtype: string + + Returns your site base URL, as specified in your config file. Example:: + + echo base_url(); + + This function returns the same thing as :php:func:`site_url()`, without + the *index_page* or *url_suffix* being appended. + + Also like :php:func:`site_url()`, you can supply segments as a string or + an array. Here is a string example:: + + echo base_url("blog/post/123"); + + The above example would return something like: + *http://example.com/blog/post/123* + + This is useful because unlike :php:func:`site_url()`, you can supply a + string to a file, such as an image or stylesheet. For example:: + + echo base_url("images/icons/edit.png"); + + This would give you something like: + *http://example.com/images/icons/edit.png* + +.. php:function:: current_url([$returnObject = false]) + + :param boolean $returnObject: True if you would like an object returned, instead of a string. + :returns: The current URL + :rtype: string|URI + + Returns the full URL (including segments) of the page being currently + viewed. + + .. note:: Calling this function is the same as doing this:: + + base_url(uri_string()); + + +.. php:function:: uri_string() + + :returns: An URI string + :rtype: string + + Returns the path part of your current URL. + For example, if your URL was this:: + + http://some-site.com/blog/comments/123 + + The function would return:: + + blog/comments/123 + + +.. php:function:: index_page([$altConfig = NULL]) + + :param \Config\App $altConfig: Alternate configuration to use + :returns: 'index_page' value + :rtype: mixed + + Returns your site **index_page**, as specified in your config file. + Example:: + + echo index_page(); + + As with :php:func:`site_url()`, you may specify an alternate configuration. + You may find the alternate configuration useful if generating URLs for a + different site than yours, which contains different configuration preferences. + We use this for unit testing the framework itself. + +.. php:function:: anchor([$uri = ''[, $title = ''[, $attributes = ''[, $altConfig = NULL]]]]) + + :param mixed $uri: URI string or array of URI segments + :param string $title: Anchor title + :param mixed $attributes: HTML attributes + :param \Config\App $altConfig: Alternate configuration to use + :returns: HTML hyperlink (anchor tag) + :rtype: string + + Creates a standard HTML anchor link based on your local site URL. + + The first parameter can contain any segments you wish appended to the + URL. As with the :php:func:`site_url()` function above, segments can + be a string or an array. + + .. note:: If you are building links that are internal to your application + do not include the base URL (http://...). This will be added + automatically from the information specified in your config file. + Include only the URI segments you wish appended to the URL. + + The second segment is the text you would like the link to say. If you + leave it blank, the URL will be used. + + The third parameter can contain a list of attributes you would like + added to the link. The attributes can be a simple string or an + associative array. + + Here are some examples:: + + echo anchor('news/local/123', 'My News', 'title="News title"'); + // Prints: My News + + echo anchor('news/local/123', 'My News', array('title' => 'The best news!')); + // Prints: My News + + echo anchor('', 'Click here'); + // Prints: Click here + + As above, you may specify an alternate configuration. + You may find the alternate configuration useful if generating links for a + different site than yours, which contains different configuration preferences. + We use this for unit testing the framework itself. + +.. php:function:: anchor_popup([$uri = ''[, $title = ''[, $attributes = FALSE[, $altConfig = NULL]]]]) + + :param string $uri: URI string + :param string $title: Anchor title + :param mixed $attributes: HTML attributes + :param \Config\App $altConfig: Alternate configuration to use + :returns: Pop-up hyperlink + :rtype: string + + Nearly identical to the :php:func:`anchor()` function except that it + opens the URL in a new window. You can specify JavaScript window + attributes in the third parameter to control how the window is opened. + If the third parameter is not set it will simply open a new window with + your own browser settings. + + Here is an example with attributes:: + + $atts = array( + 'width' => 800, + 'height' => 600, + 'scrollbars' => 'yes', + 'status'      => 'yes', + 'resizable'   => 'yes', + 'screenx' => 0, + 'screeny' => 0, + 'window_name' => '_blank' + ); + + echo anchor_popup('news/local/123', 'Click Me!', $atts); + + .. note:: The above attributes are the function defaults so you only need to + set the ones that are different from what you need. If you want the + function to use all of its defaults simply pass an empty array in the + third parameter:: + + echo anchor_popup('news/local/123', 'Click Me!', array()); + + .. note:: The **window_name** is not really an attribute, but an argument to + the JavaScript `window.open() ` + method, which accepts either a window name or a window target. + + .. note:: Any other attribute than the listed above will be parsed as an + HTML attribute to the anchor tag. + + As above, you may specify an alternate configuration. + You may find the alternate configuration useful if generating links for a + different site than yours, which contains different configuration preferences. + We use this for unit testing the framework itself. + +.. php:function:: mailto($email[, $title = ''[, $attributes = '']]) + + :param string $email: E-mail address + :param string $title: Anchor title + :param mixed $attributes: HTML attributes + :returns: A "mail to" hyperlink + :rtype: string + + Creates a standard HTML e-mail link. Usage example:: + + echo mailto('me@my-site.com', 'Click Here to Contact Me'); + + As with the :php:func:`anchor()` tab above, you can set attributes using the + third parameter:: + + $attributes = array('title' => 'Mail me'); + echo mailto('me@my-site.com', 'Contact Me', $attributes); + +.. php:function:: safe_mailto($email[, $title = ''[, $attributes = '']]) + + :param string $email: E-mail address + :param string $title: Anchor title + :param mixed $attributes: HTML attributes + :returns: A spam-safe "mail to" hyperlink + :rtype: string + + Identical to the :php:func:`mailto()` function except it writes an obfuscated + version of the *mailto* tag using ordinal numbers written with JavaScript to + help prevent the e-mail address from being harvested by spam bots. + +.. php:function:: auto_link($str[, $type = 'both'[, $popup = FALSE]]) + + :param string $str: Input string + :param string $type: Link type ('email', 'url' or 'both') + :param bool $popup: Whether to create popup links + :returns: Linkified string + :rtype: string + + Automatically turns URLs and e-mail addresses contained in a string into + links. Example:: + + $string = auto_link($string); + + The second parameter determines whether URLs and e-mails are converted or + just one or the other. Default behavior is both if the parameter is not + specified. E-mail links are encoded as :php:func:`safe_mailto()` as shown + above. + + Converts only URLs:: + + $string = auto_link($string, 'url'); + + Converts only e-mail addresses:: + + $string = auto_link($string, 'email'); + + The third parameter determines whether links are shown in a new window. + The value can be TRUE or FALSE (boolean):: + + $string = auto_link($string, 'both', TRUE); + + .. note:: The only URLs recognized are those that start with "www." or with "://". + +.. php:function:: url_title($str[, $separator = '-'[, $lowercase = FALSE]]) + + :param string $str: Input string + :param string $separator: Word separator + :param bool $lowercase: Whether to transform the output string to lower-case + :returns: URL-formatted string + :rtype: string + + Takes a string as input and creates a human-friendly URL string. This is + useful if, for example, you have a blog in which you'd like to use the + title of your entries in the URL. Example:: + + $title = "What's wrong with CSS?"; + $url_title = url_title($title); + // Produces: Whats-wrong-with-CSS + + The second parameter determines the word delimiter. By default dashes + are used. Preferred options are: **-** (dash) or **_** (underscore) + + Example:: + + $title = "What's wrong with CSS?"; + $url_title = url_title($title, 'underscore'); + // Produces: Whats_wrong_with_CSS + + .. note:: Old usage of 'dash' and 'underscore' as the second parameter + is DEPRECATED. + + The third parameter determines whether or not lowercase characters are + forced. By default they are not. Options are boolean TRUE/FALSE. + + Example:: + + $title = "What's wrong with CSS?"; + $url_title = url_title($title, 'underscore', TRUE); + // Produces: whats_wrong_with_css + + +.. php:function:: prep_url($str = '') + + :param string $str: URL string + :returns: Protocol-prefixed URL string + :rtype: string + + This function will add http:// in the event that a protocol prefix + is missing from a URL. + + Pass the URL string to the function like this:: + + $url = prep_url('example.com'); diff --git a/user_guide_src/source/libraries/response.rst b/user_guide_src/source/libraries/response.rst index ea6e8b924667..727377894b92 100644 --- a/user_guide_src/source/libraries/response.rst +++ b/user_guide_src/source/libraries/response.rst @@ -1,6 +1,6 @@ -************** +============== HTTP Responses -************** +============== The Response class extends the :doc:`HTTP Message Class ` with methods only appropriate for a server responding to the client that called it. From db15be191f7f9dcc1a418104b66743fd0421a647 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Sat, 23 Jul 2016 01:39:36 -0700 Subject: [PATCH 0121/1807] Fix phpunit code coverage --- phpunit.xml.dist | 6 ++---- system/HTTP/ContentSecurityPolicy.php | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 34aba4712b04..6c3d38bb6ed5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,10 +20,8 @@ ./system ./system/ComposerScripts.php - ./system/View/Escaper.php - ./system/View/Exception - ./system/Debug/Toolbar/View - ./system/Debug/Kint + ./system/ThirdParty + ./system/Debug/Toolbar/View diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index f70581490ead..5149494bb3a2 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -36,7 +36,7 @@ * @filesource */ -use Config\ContentSecurityPolicy; +//use Config\ContentSecurityPolicy; /** * Class ContentSecurityPolicy @@ -175,7 +175,7 @@ class ContentSecurityPolicy * * @param ContentSecurityPolicy $config */ - public function __construct(ContentSecurityPolicy $config) + public function __construct(Config\ContentSecurityPolicy $config) { foreach ($config as $setting => $value) { From a985d10f4fd17fd71f5c057f6b15d3c34dbea6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mi=C4=8Dulis?= Date: Mon, 25 Jul 2016 01:11:46 +0300 Subject: [PATCH 0122/1807] Migration validation fix --- system/Database/MigrationRunner.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 00e2ba68afae..de4a4bc30491 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -198,6 +198,8 @@ public function version(string $targetVersion, $group='default') { return true; } + + $previous = false; // Validate all available migrations, and run the ones within our target range foreach ($migrations as $number => $file) From 0c2e8ea4eaca0bb63ee897f964301ffb219452d3 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Mon, 25 Jul 2016 00:38:13 -0700 Subject: [PATCH 0123/1807] Single substitutions work --- system/Debug/Toolbar/Collectors/Views.php | 2 +- .../ThirdParty/Kint/inc/kintParser.class.php | 2 +- system/View/Parser.php | 134 +++++++++++------ ...bleInterface.php => RendererInterface.php} | 6 +- system/View/View.php | 110 +++++++------- tests/system/View/ParserTest.php | 135 +++++++++++++----- tests/system/View/Views/template1.php | 1 + .../source/general/view_renderer.rst | 6 +- 8 files changed, 251 insertions(+), 145 deletions(-) rename system/View/{RenderableInterface.php => RendererInterface.php} (97%) create mode 100644 tests/system/View/Views/template1.php diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php index 6f4734b8df8a..297161d5971b 100644 --- a/system/Debug/Toolbar/Collectors/Views.php +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -37,7 +37,7 @@ */ use CodeIgniter\Services; -use CodeIgniter\View\RenderableInterface; +use CodeIgniter\View\RendererInterface; /** * Views collector diff --git a/system/ThirdParty/Kint/inc/kintParser.class.php b/system/ThirdParty/Kint/inc/kintParser.class.php index 48153dc1a2a3..b205711f98d9 100755 --- a/system/ThirdParty/Kint/inc/kintParser.class.php +++ b/system/ThirdParty/Kint/inc/kintParser.class.php @@ -129,7 +129,7 @@ public final static function factory( & $variable, $name = null ) $parser = new $className; $parser->name = $name; # the parser may overwrite the name value, so set it first - if ( $parser->_parse( $variable ) !== false ) { + if ( $parser->parse( $variable ) !== false ) { $varData->_alternatives[] = $parser; } } diff --git a/system/View/Parser.php b/system/View/Parser.php index 3adf56304a81..1c03a9fb1336 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -1,4 +1,6 @@ -_parse($template, $data, $options); + $start = microtime(true); + + $view = str_replace('.php', '', $view).'.php'; + + // Was it cached? + if (isset($options['cache'])) + { + $cacheName = $options['cache_name'] ?: str_replace('.php', '', $view); + + if ($output = cache($cacheName)) + { + $this->logPerformance($start, microtime(true), $view); + return $output; + } + } + + $file = $this->viewPath.$view; + + if ( ! file_exists($file)) + { + $file = $this->loader->locateFile($view, 'Views'); + } + + // locateFile will return an empty string if the file cannot be found. + if (empty($file)) + { + throw new \InvalidArgumentException('View file not found: '.$file); + } + + $template = file_get_contents($file); + $output = $this->parse($template, $this->data, $options); + $this->logPerformance($start, microtime(true), $view); + + if ( ! $saveData) + { + $this->data = []; + } + // Should we cache? + if (isset($options['cache'])) + { + cache()->save($cacheName, $output, (int) $options['cache']); + } + + return $output; } // -------------------------------------------------------------------- @@ -109,7 +152,7 @@ public function render(string $view, array $options=null, bool $saveData=false) * Parse a String * * Parses pseudo-variables contained in the specified string, - * replacing them with the data in the second param + * replacing them with any data that has already been set. * * @param string $template * @param array $options @@ -117,9 +160,19 @@ public function render(string $view, array $options=null, bool $saveData=false) * * @return string */ - public function renderString(string $template, array $options=null, bool $saveData=false) : string + public function renderString(string $template, array $options = null, bool $saveData = false): string { - return $this->_parse($template, $options, $saveData); + $start = microtime(true); + + $output = $this->parse($template, $this->data, $options); + + $this->logPerformance($start, microtime(true), $this->excerpt($template)); + + if ( ! $saveData) + { + $this->data = []; + } + return $output; } // -------------------------------------------------------------------- @@ -132,35 +185,31 @@ public function renderString(string $template, array $options=null, bool $saveDa * * @param string $template * @param array $data - * @param array $options + * @param array $options Future options * @return string */ - protected function _parse($template, $data, $return = FALSE) + protected function parse(string $template, array $data = [], array $options = null) : string { if ($template === '') { - return FALSE; + return ''; } - $replace = array(); + // TODO processing of control structures goes here + + // build the variable substitution list + $replace = array (); foreach ($data as $key => $val) { $replace = array_merge( - $replace, - is_array($val) - ? $this->_parse_pair($key, $val, $template) - : $this->_parse_single($key, (string) $val, $template) + $replace, is_array($val) ? $this->parsePair($key, $val, $template) : $this->parseSingle($key, (string) $val, $template) ); } unset($data); + // do the substitutions $template = strtr($template, $replace); - if ($return === FALSE) - { - $this->CI->output->append_output($template); - } - return $template; } @@ -184,14 +233,14 @@ public function setDelimiters($l = '{', $r = '}') /** * Parse a single key/value * - * @param string - * @param string - * @param string - * @return string + * @param string $key + * @param string $val + * @param string $template + * @return array */ - protected function _parseSingle($key, $val, $string) + protected function parseSingle(string $key, string $val, string $template) : array { - return array($this->leftDelimiter.$key.$this->rightDelimiter => (string) $val); + return array ($this->leftDelimiter.$key.$this->rightDelimiter => (string) $val); } // -------------------------------------------------------------------- @@ -201,19 +250,16 @@ protected function _parseSingle($key, $val, $string) * * Parses tag pairs: {some_tag} string... {/some_tag} * - * @param string - * @param array - * @param string - * @return string + * @param string $variable + * @param array $data + * @param string $template + * @return array */ - protected function _parsePair($variable, $data, $string) + protected function parsePair(string $variable, string $data, string $template) : array { - $replace = array(); + $replace = array (); preg_match_all( - '#'.preg_quote($this->leftDelimiter.$variable.$this->rightDelimiter).'(.+?)'.preg_quote($this->leftDelimiter.'/'.$variable.$this->rightDelimiter).'#s', - $string, - $matches, - PREG_SET_ORDER + '#'.preg_quote($this->leftDelimiter.$variable.$this->rightDelimiter).'(.+?)'.preg_quote($this->leftDelimiter.'/'.$variable.$this->rightDelimiter).'#s', $string, $matches, PREG_SET_ORDER ); foreach ($matches as $match) @@ -221,12 +267,12 @@ protected function _parsePair($variable, $data, $string) $str = ''; foreach ($data as $row) { - $temp = array(); + $temp = array (); foreach ($row as $key => $val) { if (is_array($val)) { - $pair = $this->_parse_pair($key, $val, $match[1]); + $pair = $this->parsePair($key, $val, $match[1]); if ( ! empty($pair)) { $temp = array_merge($temp, $pair); diff --git a/system/View/RenderableInterface.php b/system/View/RendererInterface.php similarity index 97% rename from system/View/RenderableInterface.php rename to system/View/RendererInterface.php index 4cb79439f9d7..f6add5b681f8 100644 --- a/system/View/RenderableInterface.php +++ b/system/View/RendererInterface.php @@ -37,13 +37,13 @@ */ /** - * Interface RenderableInterface + * Interface RendererInterface * * The interface used for displaying Views and/or theme files. * * @package CodeIgniter\View */ -interface RenderableInterface { +interface RendererInterface { /** * Builds the output based upon a file name and any @@ -63,7 +63,7 @@ public function render(string $view, array $options=null, bool $saveData=false): //-------------------------------------------------------------------- /** - * Builds the output based upon a stringand any + * Builds the output based upon a string and any * data that has already been set. * * @param string $view The view contents diff --git a/system/View/View.php b/system/View/View.php index 7b1573a61d99..1d1b7409f153 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -1,4 +1,6 @@ -viewPath = rtrim($viewPath, '/ ').'/'; @@ -125,7 +128,7 @@ public function __construct(string $viewPath=null, $loader=null, bool $debug = n * * @return string */ - public function render(string $view, array $options=null, bool $saveData=false): string + public function render(string $view, array $options = null, bool $saveData = false): string { $start = microtime(true); @@ -145,7 +148,7 @@ public function render(string $view, array $options=null, bool $saveData=false): $file = $this->viewPath.$view; - if (! file_exists($file)) + if ( ! file_exists($file)) { $file = $this->loader->locateFile($view, 'Views'); } @@ -153,21 +156,19 @@ public function render(string $view, array $options=null, bool $saveData=false): // locateFile will return an empty string if the file cannot be found. if (empty($file)) { - throw new \InvalidArgumentException('View file not found: '. $file); + throw new \InvalidArgumentException('View file not found: '.$file); } // Make our view data available to the view. extract($this->data); - if (! $saveData) + if ( ! $saveData) { $this->data = []; } ob_start(); - include($file); - $output = ob_get_contents(); @ob_end_clean(); @@ -176,7 +177,7 @@ public function render(string $view, array $options=null, bool $saveData=false): // Should we cache? if (isset($options['cache'])) { - cache()->save($cacheName, $output, (int)$options['cache']); + cache()->save($cacheName, $output, (int) $options['cache']); } return $output; @@ -185,67 +186,55 @@ public function render(string $view, array $options=null, bool $saveData=false): //-------------------------------------------------------------------- /** - * Builds the output based upon a file name and any + * Builds the output based upon a string and any * data that has already been set. + * Cache does not apply, because there is no "key". * - * Valid $options: - * - cache number of seconds to cache for - * - cache_name Name to use for cache - * - * @param string $view - * @param array $options - * @param bool $saveData + * @param string $view The view contents + * @param array $options Reserved for 3rd-party uses since + * it might be needed to pass additional info + * to other template engines. + * @param bool $saveData If true, will save data for use with any other calls, + * if false, will clean the data after displaying the view. * * @return string */ - public function renderString(string $view, array $options=null, bool $saveData=false): string + public function renderString(string $view, array $options = null, bool $saveData = false): string { $start = microtime(true); - - $view = str_replace('.php', '', $view).'.php'; - - // Was it cached? - if (isset($options['cache'])) - { - $cacheName = $options['cache_name'] ?: str_replace('.php', '', $view); - - if ($output = cache($cacheName)) - { - $this->logPerformance($start, microtime(true), $view); - return $output; - } - } - - $file = $this->viewPath.$view; - - // Make our view data available to the view. extract($this->data); - if (! $saveData) + if ( ! $saveData) { $this->data = []; } ob_start(); - - include($file); - + include("php://memory:".$view); $output = ob_get_contents(); @ob_end_clean(); - $this->logPerformance($start, microtime(true), $view); - - // Should we cache? - if (isset($options['cache'])) - { - cache()->save($cacheName, $output, (int)$options['cache']); - } + $this->logPerformance($start, microtime(true), $this->excerpt($view)); return $output; } //-------------------------------------------------------------------- + /** + * Extract first bit of a long string and add ellipsis + * + * @param string $string + * @parm int $length + * @return string + */ + public function excerpt(string $string, int $length = 20): string + { + return (strlen($string) > $length) ? substr($string, 0, $length - 3).'...' : $string; + } + + //-------------------------------------------------------------------- + /** * Sets several pieces of view data at once. * @@ -255,9 +244,9 @@ public function renderString(string $view, array $options=null, bool $saveData=f * * @return RenderableInterface */ - public function setData(array $data=[], string $context=null): RenderableInterface + public function setData(array $data = null, string $context = null): RendererInterface { - if (! empty($context)) + if ( ! empty($context)) { $data = \esc($data, $context); } @@ -273,15 +262,15 @@ public function setData(array $data=[], string $context=null): RenderableInterfa * Sets a single piece of view data. * * @param string $name - * @param null $value + * @param mixed $value * @param string $context The context to escape it for: html, css, js, url * If null, no escaping will happen * * @return RenderableInterface */ - public function setVar(string $name, $value=null, string $context=null): RenderableInterface + public function setVar(string $name, $value = null, string $context = null): RendererInterface { - if (! empty($context)) + if ( ! empty($context)) { $value = \esc($value, $context); } @@ -314,7 +303,7 @@ public function resetData() */ public function getData() { - return $this->data; + return $this->data; } //-------------------------------------------------------------------- @@ -327,7 +316,7 @@ public function getData() */ public function getPerformanceData(): array { - return $this->performanceData; + return $this->performanceData; } //-------------------------------------------------------------------- @@ -341,15 +330,14 @@ public function getPerformanceData(): array */ protected function logPerformance(float $start, float $end, string $view) { - if (! $this->debug) return; + if ( ! $this->debug) return; $this->performanceData[] = [ - 'start' => $start, - 'end' => $end, - 'view' => $view + 'start' => $start, + 'end' => $end, + 'view' => $view ]; } //-------------------------------------------------------------------- - } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 1fed20a5a549..36d03cc7d56c 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -2,98 +2,169 @@ use CodeIgniter\View\Parser; -class ParserTest extends \CIUnitTestCase { +class ParserTest extends \CIUnitTestCase +{ public function setUp() { $this->loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); $this->viewsDir = __DIR__.'/Views'; - $this->parser = new Parser($this->viewsDir, $this->loader); } // -------------------------------------------------------------------- public function testSetDelimiters() { - + $parser = new Parser($this->viewsDir, $this->loader); + // Make sure default delimiters are there - $this->assertEquals('{', $this->parser->leftDelimiter); - $this->assertEquals('}', $this->parser->rightDelimiter); + $this->assertEquals('{', $parser->leftDelimiter); + $this->assertEquals('}', $parser->rightDelimiter); // Change them to square brackets - $this->parser->setDelimiters('[', ']'); + $parser->setDelimiters('[', ']'); // Make sure they changed - $this->assertEquals('[', $this->parser->leftDelimiter); - $this->assertEquals(']', $this->parser->rightDelimiter); + $this->assertEquals('[', $parser->leftDelimiter); + $this->assertEquals(']', $parser->rightDelimiter); // Reset them - $this->parser->setDelimiters(); + $parser->setDelimiters(); // Make sure default delimiters are there - $this->assertEquals('{', $this->parser->leftDelimiter); - $this->assertEquals('}', $this->parser->rightDelimiter); + $this->assertEquals('{', $parser->leftDelimiter); + $this->assertEquals('}', $parser->rightDelimiter); + } + + // -------------------------------------------------------------------- + + public function testParseSimple() + { + $parser = new Parser($this->viewsDir, $this->loader); + $parser->setVar('teststring', 'Hello World'); + + $expected = '

Hello World

'; + $this->assertEquals($expected, $parser->render('template1')); } // -------------------------------------------------------------------- public function testParseString() { - $data = array( - 'title' => 'Page Title', - 'body' => 'Lorem ipsum dolor sit amet.' + $parser = new Parser($this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.' ); $template = "{title}\n{body}"; $result = implode("\n", $data); - $this->assertEquals($result, $this->parser->renderString($template, $data, TRUE)); + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); } // -------------------------------------------------------------------- - public function testParse() + public function testParseStringMissingData() { - $this->_parseNoTemplate(); - $this->_parseVarPair(); - $this->_mismatchedVarPair(); + $parser = new Parser($this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.' + ); + + $template = "{title}\n{body}\n{name}"; + + $result = implode("\n", $data)."\n{name}"; + + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); } // -------------------------------------------------------------------- - private function _parseNoTemplate() + public function testParseStringUnusedData() { - $this->assertFalse($this->parser->renderString('', '', TRUE)); + $parser = new Parser($this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.', + 'name' => 'Someone' + ); + + $template = "{title}\n{body}"; + + $result = "Page Title\nLorem ipsum dolor sit amet."; + + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); } // -------------------------------------------------------------------- - private function _parseVarPair() + public function testParseNoTemplate() { - $data = array( - 'title' => 'Super Heroes', - 'powers' => array(array('invisibility' => 'yes', 'flying' => 'no')) + $parser = new Parser($this->viewsDir, $this->loader); + $this->assertEquals('', $parser->renderString('')); + } + + // -------------------------------------------------------------------- + + public function testParseVarPair() + { + $parser = new Parser($this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Super Heroes', + 'powers' => array ( + array ('invisibility' => 'yes', 'flying' => 'no') + ) ); $template = "{title}\n{powers}{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{/powers}"; - $this->assertEquals("Super Heroes\nyes\nno\nsecond: yes no", $this->parser->renderString($template, $data, TRUE)); + $parser->setData($data); + $this->assertEquals("Super Heroes\nyes\nno\nsecond: yes no", $parser->renderString($template)); + } + + // -------------------------------------------------------------------- + + public function testParseVarList() + { + $parser = new Parser($this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Super Heroes', + 'powers' => array ( + array ('name' => 'Tom'), + array ('name' => 'Dick'), + array ('name' => 'Henry') + ) + ); + + $template = "{title}\n{powers}{name} {/powers}"; + + $parser->setData($data); + $this->assertEquals("Super Heroes\nTom Dick Henry", $parser->renderString($template)); } // -------------------------------------------------------------------- - private function _mismatchedVarPair() + public function testMismatchedVarPair() { - $data = array( - 'title' => 'Super Heroes', - 'powers' => array(array('invisibility' => 'yes', 'flying' => 'no')) + $parser = new Parser($this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Super Heroes', + 'powers' => array ( + array ('invisibility' => 'yes', 'flying' => 'no') + ) ); $template = "{title}\n{powers}{invisibility}\n{flying}"; $result = "Super Heroes\n{powers}{invisibility}\n{flying}"; - $this->assertEquals($result, $this->parser->renderString($template, $data, TRUE)); + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); } -} \ No newline at end of file +} diff --git a/tests/system/View/Views/template1.php b/tests/system/View/Views/template1.php new file mode 100644 index 000000000000..5eeb73112667 --- /dev/null +++ b/tests/system/View/Views/template1.php @@ -0,0 +1 @@ +

{teststring}

\ No newline at end of file diff --git a/user_guide_src/source/general/view_renderer.rst b/user_guide_src/source/general/view_renderer.rst index f2afa255509a..c93893ebd000 100644 --- a/user_guide_src/source/general/view_renderer.rst +++ b/user_guide_src/source/general/view_renderer.rst @@ -64,7 +64,7 @@ context as the second parameter. Valid contexts are 'html', 'js', 'css', 'url', Class Reference *************** -.. php:interface:: CodeIgniter\\View\\RendererableInterface +.. php:interface:: CodeIgniter\\View\\RendererInterface .. php:method:: render($view[, $options[, $saveData=false]]]) @@ -90,7 +90,7 @@ Class Reference :param array $data: Array of view data strings, as key/value pairs :param string $context: The context to use for data escaping. :returns: The Renderer, for method chaining - :rtype: CodeIgniter\\View\\RenderableInterface. + :rtype: CodeIgniter\\View\\RendererInterface. Sets several pieces of view data at once:: @@ -105,7 +105,7 @@ Class Reference :param mixed $value: The value of this view data :param string $context: The context to use for data escaping. :returns: The Renderer, for method chaining - :rtype: CodeIgniter\\View\\RenderableInterface. + :rtype: CodeIgniter\\View\\RendererInterface. Sets a single piece of view data:: From a5d576359ffbb1432548e89e2fd02dc258546158 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Mon, 25 Jul 2016 08:13:30 -0700 Subject: [PATCH 0124/1807] Remove unneeded use --- system/HTTP/ContentSecurityPolicy.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 5149494bb3a2..8c15f83b74ea 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -36,8 +36,6 @@ * @filesource */ -//use Config\ContentSecurityPolicy; - /** * Class ContentSecurityPolicy * From e71ea2ed1aceabacdeface279ae1cc8208dbf3b3 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Mon, 25 Jul 2016 10:38:28 -0700 Subject: [PATCH 0125/1807] Revert inadvertent change inside Kint --- system/ThirdParty/Kint/inc/kintParser.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ThirdParty/Kint/inc/kintParser.class.php b/system/ThirdParty/Kint/inc/kintParser.class.php index b205711f98d9..48153dc1a2a3 100755 --- a/system/ThirdParty/Kint/inc/kintParser.class.php +++ b/system/ThirdParty/Kint/inc/kintParser.class.php @@ -129,7 +129,7 @@ public final static function factory( & $variable, $name = null ) $parser = new $className; $parser->name = $name; # the parser may overwrite the name value, so set it first - if ( $parser->parse( $variable ) !== false ) { + if ( $parser->_parse( $variable ) !== false ) { $varData->_alternatives[] = $parser; } } From 5fd3105a67bf781bd432958780dd7d212a8f4b58 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 27 Jul 2016 00:17:18 -0500 Subject: [PATCH 0126/1807] Update URI to include query var manipulation. --- system/HTTP/URI.php | 252 ++++++++++++++++++------ tests/system/HTTP/URITest.php | 79 +++++++- user_guide_src/source/libraries/uri.rst | 34 ++++ 3 files changed, 305 insertions(+), 60 deletions(-) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 1729799453b9..c48744cd71f2 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -29,12 +29,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com - * @since Version 3.0.0 + * @package CodeIgniter + * @author CodeIgniter Dev Team + * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link http://codeigniter.com + * @since Version 3.0.0 * @filesource */ class URI @@ -124,7 +124,7 @@ class URI * * @var array */ - protected $query = ''; + protected $query = []; /** * Default schemes/ports. @@ -132,10 +132,10 @@ class URI * @var array */ protected $defaultPorts = [ - 'http' => 80, - 'https' => 443, - 'ftp' => 21, - 'sftp' => 22 + 'http' => 80, + 'https' => 443, + 'ftp' => 21, + 'sftp' => 22, ]; /** @@ -150,13 +150,14 @@ class URI /** * Constructor. - * + * * @param string $uri + * * @throws \InvalidArgumentException */ public function __construct(string $uri = null) { - if ( ! is_null($uri)) + if (! is_null($uri)) { $this->setURI($uri); } @@ -171,7 +172,7 @@ public function __construct(string $uri = null) */ public function setURI(string $uri = null) { - if ( ! is_null($uri)) + if (! is_null($uri)) { $parts = parse_url($uri); @@ -225,7 +226,9 @@ public function getScheme(): string * scheme, it SHOULD NOT be included. * * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * * @param bool $ignorePort + * * @return string The URI authority, in "[user-info@]host[:port]" format. */ public function getAuthority(bool $ignorePort = false): string @@ -237,12 +240,12 @@ public function getAuthority(bool $ignorePort = false): string $authority = $this->host; - if ( ! empty($this->getUserInfo())) + if (! empty($this->getUserInfo())) { $authority = $this->getUserInfo().'@'.$authority; } - if ( ! empty($this->port) && ! $ignorePort) + if (! empty($this->port) && ! $ignorePort) { // Don't add port if it's a standard port for // this scheme @@ -380,18 +383,47 @@ public function getPath(): string /** * Retrieve the query string - * @return type + * + * @param array $options + * + * @return string */ - public function getQuery(): string + public function getQuery(array $options = []): string { - return is_null($this->query) ? '' : $this->query; + $vars = $this->query; + + if (array_key_exists('except', $options)) + { + foreach ($options['except'] as $var) + { + unset($vars[$var]); + } + } + elseif (array_key_exists('only', $options)) + { + $temp = []; + + foreach ($options['only'] as $var) + { + if (array_key_exists($var, $vars)) + { + $temp[$var] = $vars[$var]; + } + } + + $vars = $temp; + } + + return empty($vars) + ? '' + : http_build_query($vars); } //-------------------------------------------------------------------- /** * Retrieve a URI fragment - * + * * @return type */ public function getFragment(): string @@ -456,8 +488,8 @@ public function getTotalSegments(): int public function __toString() { return self::createURIString( - $this->getScheme(), $this->getAuthority(), $this->getPath(), // Absolute URIs should use a "/" for an empty path - $this->getQuery(), $this->getFragment() + $this->getScheme(), $this->getAuthority(), $this->getPath(), // Absolute URIs should use a "/" for an empty path + $this->getQuery(), $this->getFragment() ); } @@ -478,12 +510,12 @@ public function __toString() public static function createURIString($scheme = null, $authority = null, $path = null, $query = null, $fragment = null) { $uri = ''; - if ( ! empty($scheme)) + if (! empty($scheme)) { $uri .= $scheme.'://'; } - if ( ! empty($authority)) + if (! empty($authority)) { $uri .= $authority; } @@ -568,7 +600,7 @@ public function setScheme(string $str) */ public function setUserInfo(string $user, string $pass) { - $this->user = trim($user); + $this->user = trim($user); $this->password = trim($pass); return $this; @@ -601,7 +633,10 @@ public function setHost(string $str) */ public function setPort($port) { - if (is_null($port)) return $this; + if (is_null($port)) + { + return $this; + } if ($port <= 0 || $port > 65535) { @@ -649,28 +684,29 @@ public function setQuery(string $query) } // Can't have leading ? - if ( ! empty($query) && strpos($query, '?') === 0) + if (! empty($query) && strpos($query, '?') === 0) { $query = substr($query, 1); } - $parts = explode('&', $query); + $temp = explode('&', $query); + $parts = []; - foreach ($parts as $index => $part) + foreach ($temp as $index => $part) { list($key, $value) = $this->splitQueryPart($part); // Only 1 part? if (is_null($value)) { - $parts[$index] = $this->filterQuery($key); + $parts[$this->filterQuery($key)] = null; continue; } - $parts[$index] = $this->filterQuery($key).'='.$this->filterQuery($value); + $parts[$this->filterQuery($key)] = $this->filterQuery($value); } - $this->query = implode('&', $parts); + $this->query = $parts; return $this; } @@ -714,7 +750,7 @@ protected function splitQueryPart(string $part) protected function filterQuery($str) { return preg_replace_callback( - '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', function (array $matches) + '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', function(array $matches) { return rawurlencode($matches[0]); }, $str @@ -728,6 +764,8 @@ protected function filterQuery($str) * portion of the URI. * * @param array $query + * + * @return \CodeIgniter\HTTP\URI */ public function setQueryArray(array $query) { @@ -738,6 +776,71 @@ public function setQueryArray(array $query) //-------------------------------------------------------------------- + /** + * Adds a single new element to the query vars. + * + * @param string $key + * @param null $value + * + * @return $this + */ + public function addQuery(string $key, $value = null) + { + $this->query[$key] = $value; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Removes one or more query vars from the URI. + * + * @param array ...$params + * + * @return $this + */ + public function stripQuery(...$params) + { + foreach ($params as $param) + { + unset($this->query[$param]); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Filters the query variables so that only the keys passed in + * are kept. The rest are removed from the object. + * + * @param array ...$params + * + * @return $this + */ + public function keepQuery(...$params) + { + $temp = []; + + foreach ($this->query as $key => $value) + { + if (! in_array($key, $params)) + { + continue; + } + + $temp[$key] = $value; + } + + $this->query = $temp; + + return $this; + } + + //-------------------------------------------------------------------- + /** * Sets the fragment portion of the URI. * @@ -775,12 +878,18 @@ protected function filterPath(string $path = null) $path = $this->removeDotSegments($path); // Fix up some leading slash edge cases... - if (strpos($orig, './') === 0) $path = '/'.$path; - if (strpos($orig, '../') === 0) $path = '/'.$path; + if (strpos($orig, './') === 0) + { + $path = '/'.$path; + } + if (strpos($orig, '../') === 0) + { + $path = '/'.$path; + } // Encode characters $path = preg_replace_callback( - '/(?:[^'.self::CHAR_UNRESERVED.':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', function (array $matches) + '/(?:[^'.self::CHAR_UNRESERVED.':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', function(array $matches) { return rawurlencode($matches[0]); }, $path @@ -798,11 +907,26 @@ protected function filterPath(string $path = null) */ protected function applyParts($parts) { - if ( ! empty($parts['host'])) $this->host = $parts['host']; - if ( ! empty($parts['user'])) $this->user = $parts['user']; - if ( ! empty($parts['path'])) $this->path = $this->filterPath($parts['path']); - if ( ! empty($parts['query'])) $this->query = $this->filterQuery($parts['query']); - if ( ! empty($parts['fragment'])) $this->fragment = $this->filterQuery($parts['fragment']); + if (! empty($parts['host'])) + { + $this->host = $parts['host']; + } + if (! empty($parts['user'])) + { + $this->user = $parts['user']; + } + if (! empty($parts['path'])) + { + $this->path = $this->filterPath($parts['path']); + } + if (! empty($parts['query'])) + { + $this->setQuery($parts['query']); + } + if (! empty($parts['fragment'])) + { + $this->fragment = $this->filterQuery($parts['fragment']); + } // Scheme if (isset($parts['scheme'])) @@ -818,9 +942,9 @@ protected function applyParts($parts) // Port if (isset($parts['port'])) { - if ( ! is_null($parts['port'])) + if (! is_null($parts['port'])) { - $port = (int) $parts['port']; + $port = (int)$parts['port']; if (1 > $port || 0xffff < $port) { @@ -837,7 +961,7 @@ protected function applyParts($parts) } // Populate our segments array - if ( ! empty($parts['path'])) + if (! empty($parts['path'])) { $this->segments = explode('/', trim($parts['path'], '/')); } @@ -870,20 +994,20 @@ public function resolveRelativeURI(string $uri) $transformed = clone $relative; // 5.2.2 Transform References - if ( ! empty($relative->getScheme())) + if (! empty($relative->getScheme())) { $transformed->setScheme($relative->getScheme()) - ->setAuthority($relative->getAuthority()) - ->setPath($relative->getPath()) - ->setQuery($relative->getQuery()); + ->setAuthority($relative->getAuthority()) + ->setPath($relative->getPath()) + ->setQuery($relative->getQuery()); } else { - if ( ! empty($relative->getAuthority())) + if (! empty($relative->getAuthority())) { $transformed->setAuthority($relative->getAuthority()) - ->setPath($relative->getPath()) - ->setQuery($relative->getQuery()); + ->setPath($relative->getPath()) + ->setQuery($relative->getQuery()); } else { @@ -891,7 +1015,7 @@ public function resolveRelativeURI(string $uri) { $transformed->setPath($this->getPath()); - if ( ! is_null($relative->getQuery())) + if (! is_null($relative->getQuery())) { $transformed->setQuery($relative->getQuery()); } @@ -938,14 +1062,17 @@ public function resolveRelativeURI(string $uri) */ protected function mergePaths(URI $base, URI $reference) { - if ( ! empty($base->getAuthority()) && empty($base->getPath())) + if (! empty($base->getAuthority()) && empty($base->getPath())) { return '/'.ltrim($base->getPath(), '/ '); } $path = explode('/', $base->getPath()); - if (empty($path[0])) unset($path[0]); + if (empty($path[0])) + { + unset($path[0]); + } array_pop($path); array_push($path, $reference->getPath()); @@ -963,11 +1090,14 @@ protected function mergePaths(URI $base, URI $reference) * @see http://tools.ietf.org/html/rfc3986#section-5.2.4 * * @param string $path - * @param URI $uri + * @param URI $uri */ public function removeDotSegments(string $path): string { - if (empty($path) || $path == '/') return $path; + if (empty($path) || $path == '/') + { + return $path; + } $output = []; @@ -1001,10 +1131,16 @@ public function removeDotSegments(string $path): string if ($output != '/') { // Add leading slash if necessary - if (substr($path, 0, 1) == '/') $output = '/'.$output; + if (substr($path, 0, 1) == '/') + { + $output = '/'.$output; + } // Add trailing slash if necessary - if (substr($path, -1, 1) == '/') $output .= '/'; + if (substr($path, -1, 1) == '/') + { + $output .= '/'; + } } return $output; diff --git a/tests/system/HTTP/URITest.php b/tests/system/HTTP/URITest.php index a11b7d70479f..d682a42a6d28 100644 --- a/tests/system/HTTP/URITest.php +++ b/tests/system/HTTP/URITest.php @@ -7,14 +7,14 @@ class URITest extends \CIUnitTestCase public function setUp() { - + } //-------------------------------------------------------------------- public function tearDown() { - + } //-------------------------------------------------------------------- @@ -439,4 +439,79 @@ public function testResolveRelativeURI($rel, $expected) } //-------------------------------------------------------------------- + + public function testAddQueryVar() + { + $base = 'http://example.com/foo'; + + $uri = new URI($base); + + $uri->addQuery('bar', 'baz'); + + $this->assertEquals('http://example.com/foo?bar=baz', (string)$uri); + } + + //-------------------------------------------------------------------- + + public function testAddQueryVarRespectsExistingQueryVars() + { + $base = 'http://example.com/foo?bar=baz'; + + $uri = new URI($base); + + $uri->addQuery('baz', 'foz'); + + $this->assertEquals('http://example.com/foo?bar=baz&baz=foz', (string)$uri); + } + + //-------------------------------------------------------------------- + + public function testStripQueryVars() + { + $base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz'; + + $uri = new URI($base); + + $uri->stripQuery('bar', 'baz'); + + $this->assertEquals('http://example.com/foo?foo=bar', (string)$uri); + } + + //-------------------------------------------------------------------- + + public function testKeepQueryVars() + { + $base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz'; + + $uri = new URI($base); + + $uri->keepQuery('bar', 'baz'); + + $this->assertEquals('http://example.com/foo?bar=baz&baz=foz', (string)$uri); + } + + //-------------------------------------------------------------------- + + public function testGetQueryExcept() + { + $base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz'; + + $uri = new URI($base); + + $this->assertEquals('foo=bar&baz=foz', $uri->getQuery(['except' => ['bar']])); + } + + //-------------------------------------------------------------------- + + public function testGetQueryOnly() + { + $base = 'http://example.com/foo?foo=bar&bar=baz&baz=foz'; + + $uri = new URI($base); + + $this->assertEquals('bar=baz', $uri->getQuery(['only' => ['bar']])); + } + + //-------------------------------------------------------------------- + } diff --git a/user_guide_src/source/libraries/uri.rst b/user_guide_src/source/libraries/uri.rst index 7a49a120820c..516f8182df1e 100644 --- a/user_guide_src/source/libraries/uri.rst +++ b/user_guide_src/source/libraries/uri.rst @@ -172,6 +172,40 @@ be set as a string currently. .. note:: Query values cannot contain fragments. An InvalidArgumentException will be thrown if it does. +You can set query values using an array:: + + $uri->setQueryArray(['foo' => 'bar', 'bar' => 'baz']); + +The ``setQuery()`` and ``setQueryArray()`` methods overwrite any existing query variables. You can add a value to the +query variables collection without destroying the existing query variables with the ``addQuery()`` method. The first +parameter is the name of the variable, and the second parameter is the value:: + + $uri->addQuery('foo', 'bar'); + +**Filtering Query Values** + +You can filter the query values returned by passing an options array to the ``getQuery()`` method, with either an +*only* or an *except* key:: + + $uri = new \CodeIgniter\HTTP\URI('http://www.example.com?foo=bar&bar=baz&baz=foz'); + + // Returns 'foo=bar' + echo $uri->getQuery(['only' => ['foo']); + + // Returns 'foo=bar&baz=foz' + echo $uri->getQuery(['except' => ['bar']]); + +This only changes the values returned during this one call. If you need to modify the URI's query values more permenantly, +you can use the ``stripQuery()`` and ``keepQuery()`` methods to change the actual object's query variable collection:: + + $uri = new \CodeIgniter\HTTP\URI('http://www.example.com?foo=bar&bar=baz&baz=foz'); + + // Leaves just the 'baz' variable + $uri->stripQuery('foo', 'bar'); + + // Leaves just the 'foo' variable + $uri->keepQuery('foo'); + Fragment -------- From a7d98e049cc016fb217f9f56ee623699ed7212d7 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 27 Jul 2016 00:17:41 -0500 Subject: [PATCH 0127/1807] Fix bug in View class --- system/View/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/View/View.php b/system/View/View.php index f2ab5557c0a9..6e76d35ca3e7 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -153,7 +153,7 @@ public function render(string $view, array $options=null, $saveData=false): stri // locateFile will return an empty string if the file cannot be found. if (empty($file)) { - throw new \InvalidArgumentException('View file not found: '. $file); + throw new \InvalidArgumentException('View file not found: '. $view); } // Make our view data available to the view. From 3a5a904d5d427b7eb7b04f82b31fad20ef86f55d Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 27 Jul 2016 00:25:17 -0500 Subject: [PATCH 0128/1807] Fix small bug with FileLocater --- system/Autoloader/FileLocator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 53ab08612342..b624fd975ae6 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -61,7 +61,7 @@ class FileLocator { /** * Constructor - * + * * @param Autoload $autoload */ public function __construct(Autoload $autoload) @@ -137,7 +137,7 @@ public function locateFile(string $file, string $folder=null, string $ext = 'php // expects this file to be within that folder, like 'Views', // or 'libraries'. // @todo Allow it to check with and without the nested folder. - if (! empty($folder)) + if (! empty($folder) && strpos($filename, $folder) === false) { $filename = $folder.'/'.$filename; } From ad718df19feb2d327760879bd956482033f90249 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 27 Jul 2016 00:33:56 -0500 Subject: [PATCH 0129/1807] Initial work on pagination library. --- application/Config/Pager.php | 34 ++ application/Config/Services.php | 23 ++ system/Common.php | 2 +- system/Model.php | 12 +- system/Pager/Pager.php | 387 +++++++++++++++++++++++ system/Pager/PagerInterface.php | 161 ++++++++++ system/Pager/PagerRenderer.php | 152 +++++++++ system/Pager/Views/default_full.php | 29 ++ system/Pager/Views/default_simple.php | 6 + tests/system/Pager/PagerRendererTest.php | 131 ++++++++ tests/system/Pager/PagerTest.php | 254 +++++++++++++++ 11 files changed, 1185 insertions(+), 6 deletions(-) create mode 100644 application/Config/Pager.php create mode 100644 system/Pager/Pager.php create mode 100644 system/Pager/PagerInterface.php create mode 100644 system/Pager/PagerRenderer.php create mode 100644 system/Pager/Views/default_full.php create mode 100644 system/Pager/Views/default_simple.php create mode 100644 tests/system/Pager/PagerRendererTest.php create mode 100644 tests/system/Pager/PagerTest.php diff --git a/application/Config/Pager.php b/application/Config/Pager.php new file mode 100644 index 000000000000..eafc7697e848 --- /dev/null +++ b/application/Config/Pager.php @@ -0,0 +1,34 @@ + 'CodeIgniter\Pager\Views\default_full', + 'default_simple' => 'CodeIgntier\Pager\default_simple' + ]; + + /* + |-------------------------------------------------------------------------- + | Items Per Page + |-------------------------------------------------------------------------- + | + | The default number of results shown in a single page. + | + */ + public $perPage = 20; +} diff --git a/application/Config/Services.php b/application/Config/Services.php index 574aecab9024..1231c25c48b7 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -3,6 +3,7 @@ use CodeIgniter\Config\BaseConfig; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\MigrationRunner; +use CodeIgniter\View\RenderableInterface; /** * Services Configuration file. @@ -275,6 +276,28 @@ public static function negotiator(\CodeIgniter\HTTP\RequestInterface $request=nu //-------------------------------------------------------------------- + public static function pager($config = null, RenderableInterface $view = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('pager', $config, $view); + } + + if (empty($config)) + { + $config = new Pager(); + } + + if (! $view instanceof RenderableInterface) + { + $view = self::renderer(); + } + + return new \CodeIgniter\Pager\Pager($config, $view); + } + + //-------------------------------------------------------------------- + /** * The Renderer class is the class that actually displays a file to the user. * The default View class within CodeIgniter is intentionally simple, but this diff --git a/system/Common.php b/system/Common.php index 55ae55cf04a5..a54a7f820f15 100644 --- a/system/Common.php +++ b/system/Common.php @@ -127,7 +127,7 @@ function view(string $name, array $data = [], array $options = []) { /** * View cells are used within views to insert HTML chunks that are managed - * by other classes. + * by other classes. * * @param string $library * @param null $params diff --git a/system/Model.php b/system/Model.php index 462892fdc4d7..73a78f22a1b7 100644 --- a/system/Model.php +++ b/system/Model.php @@ -260,7 +260,7 @@ public function find($id) /** * Extract a subset of data - * + * * @param $key * @param null $value * @@ -340,7 +340,7 @@ public function first() { $builder->orderBy($this->primaryKey, 'asc'); } - + $row = $builder->limit(1, 0) ->get(); @@ -787,11 +787,13 @@ public function chunk($size = 100, \Closure $userFunc) * Expects a GET variable (?page=2) that specifies the page of results * to display. * - * @param int $perPage + * @param int $perPage + * @param string $alias Will be used by the pagination library + * to identify a unique pagination set. * * @return array|null */ - public function paginate($perPage = 20) + public function paginate(int $perPage = 20, string $alias = null) { $page = $_GET['page'] ?? 1; @@ -920,7 +922,7 @@ protected function setDate($userData = null) /** * Specify the table associated with a model - * + * * @param string $table * * @return $this diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php new file mode 100644 index 000000000000..a936a8b69626 --- /dev/null +++ b/system/Pager/Pager.php @@ -0,0 +1,387 @@ +config = $config; + $this->view = $view; + } + + //-------------------------------------------------------------------- + + /** + * Handles creating and displaying the + * + * @param string|null $group + * @param string $template The output template alias to render. + * + * @return string + */ + public function links(string $template = 'default_full', string $group = 'default'): string + { + $this->ensureGroup($group); + } + + //-------------------------------------------------------------------- + + /** + * Creates simple Next/Previous links, instead of full pagination. + * + * @param string $template + * @param string $group + * + * @return string + */ + public function simpleLinks(string $template = 'default_simple', string $group = 'default'): string + { + $this->ensureGroup($group); + } + + //-------------------------------------------------------------------- + + /** + * Allows for a simple, manual, form of pagination where all of the data + * is provided by the user. The URL is the current URI. + * + * @param int $page + * @param int $perPage + * @param int $total + * @param string $template The output template alias to render. + * + * @return string + */ + public function makeLinks(int $page, int $perPage, int $total, string $template = 'default_full'): string + { + $name = time(); + + $this->store($name, $page, $perPage, $total); + + $pager = new PagerRenderer($this->getDetails($name)); + + if (! array_key_exists($template, $this->config->templates)) + { + throw new \InvalidArgumentException($template.' is not a valid Pager template.'); + } + + return $this->view->setVar('pager', $pager) + ->render($this->config->templates[$template]); + } + + //-------------------------------------------------------------------- + + /** + * Stores a set of pagination data for later display. Most commonly used + * by the model to automate the process. + * + * @param string $group + * @param int $page + * @param int $perPage + * @param int $total + * + * @return mixed + */ + public function store(string $group, int $page, int $perPage, int $total) + { + $this->ensureGroup($group); + + $this->groups[$group]['currentPage'] = $page; + $this->groups[$group]['perPage'] = $perPage; + $this->groups[$group]['total'] = $total; + $this->groups[$group]['pageCount'] = ceil($total/$perPage); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Sets the path that an aliased group of links will use. + * + * @param string $group + * @param string $path + * + * @return mixed + */ + public function setPath(string $path, string $group = 'default') + { + $this->ensureGroup($group); + + $this->groups[$group]['uri']->setPath($path); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the total number of pages. + * + * @param string|null $group + * + * @return int + */ + public function getPageCount(string $group = 'default'): int + { + $this->ensureGroup($group); + + return $this->groups[$group]['pageCount']; + } + + //-------------------------------------------------------------------- + + /** + * Returns the number of the current page of results. + * + * @param string|null $group + * + * @return int + */ + public function getCurrentPage(string $group = 'default'): int + { + $this->ensureGroup($group); + + return $this->groups[$group]['currentPage']; + } + + //-------------------------------------------------------------------- + + /** + * Tells whether this group of results has any more pages of results. + * + * @param string|null $group + * + * @return bool + */ + public function hasMore(string $group = 'default'): bool + { + $this->ensureGroup($group); + + return ($this->groups[$group]['currentPage']*$this->groups[$group]['perPage']) + < $this->groups[$group]['total']; + } + + //-------------------------------------------------------------------- + + /** + * Returns the last page, if we have a total that we can calculate with. + * + * @param string $group + * + * @return int|null + */ + public function getLastPage(string $group = 'default') + { + $this->ensureGroup($group); + + if (! is_numeric($this->groups[$group]['total']) || ! is_numeric($this->groups[$group]['perPage'])) + { + return null; + } + + return ceil($this->groups[$group]['total']/$this->groups[$group]['perPage']); + } + + //-------------------------------------------------------------------- + + /** + * Determines the first page # that should be shown. + * + * @param string $group + * + * @return int + */ + public function getFirstPage(string $group = 'default') + { + $this->ensureGroup($group); + + // @todo determine based on a 'surroundCount' value + return 1; + } + + //-------------------------------------------------------------------- + + + /** + * Returns the URI for a specific page for the specified group. + * + * @param int $page + * @param string $group + * @param bool $returnObject + * + * @return string + */ + public function getPageURI(int $page = null, string $group = 'default', $returnObject = false) + { + $this->ensureGroup($group); + + $uri = $this->groups[$group]['uri']; + + $uri->setQuery('page='.$page); + + return $returnObject === true + ? $uri + : (string)$uri; + } + + //-------------------------------------------------------------------- + + /** + * Returns the full URI to the next page of results, or null. + * + * @param string $group + * @param bool $returnObject + * + * @return string|null + */ + public function getNextPageURI(string $group = 'default', $returnObject = false) + { + $this->ensureGroup($group); + + $last = $this->getLastPage($group); + $curr = $this->getCurrentPage($group); + $page = null; + + if (! empty($last) && ! empty($curr) && $last == $curr) + { + return null; + } + + if ($last > $curr) + { + $page = $curr+1; + } + + return $this->getPageURI($page, $group, $returnObject); + } + + //-------------------------------------------------------------------- + + /** + * Returns the full URL to the previous page of results, or null. + * + * @param string $group + * @param bool $returnObject + * + * @return string|null + */ + public function getPreviousPageURI(string $group = 'default', $returnObject = false) + { + $this->ensureGroup($group); + + $first = $this->getFirstPage($group); + $curr = $this->getCurrentPage($group); + $page = null; + + if (! empty($first) && ! empty($curr) && $first == $curr) + { + return null; + } + + if ($first < $curr) + { + $page = $curr-1; + } + + return $this->getPageURI($page, $group, $returnObject); + } + + //-------------------------------------------------------------------- + + /** + * Returns the number of results per page that should be shown. + * + * @param string $group + * + * @return int + */ + public function getPerPage(string $group = 'default'): int + { + $this->ensureGroup($group); + + return (int)$this->groups[$group]['perPage']; + } + + //-------------------------------------------------------------------- + + /** + * Returns an array with details about the results, including + * total, per_page, current_page, last_page, next_url, prev_url, from, to. + * Does not include the actual data. This data is suitable for adding + * a 'data' object to with the result set and converting to JSON. + * + * @param string $group + * + * @return array + */ + public function getDetails(string $group = 'default'): array + { + if (! array_key_exists($group, $this->groups)) + { + throw new \InvalidArgumentException($group.' is not a valid Pagination group.'); + } + + $newGroup = $this->groups[$group]; + + $newGroup['uri'] = $newGroup['uri']; + $newGroup['next'] = $this->getNextPageURI($group); + $newGroup['previous'] = $this->getPreviousPageURI($group); + + return $newGroup; + } + + //-------------------------------------------------------------------- + + /** + * Ensures that an array exists for the group specified. + * + * @param string $group + */ + protected function ensureGroup(string $group) + { + if (array_key_exists($group, $this->groups)) + { + return; + } + + $this->groups[$group] = [ + 'uri' => clone Services::request()->uri, + 'hasMore' => false, + 'total' => null, + 'currentPage' => $_GET['page_'.$group] ?? $_GET['page'] ?? 1, + 'perPage' => $this->config->perPage, + 'pageCount' => 1, + ]; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Pager/PagerInterface.php b/system/Pager/PagerInterface.php new file mode 100644 index 000000000000..ea82b3c94f12 --- /dev/null +++ b/system/Pager/PagerInterface.php @@ -0,0 +1,161 @@ +first = 1; + $this->last = $details['pageCount']; + $this->current = $details['currentPage']; + $this->total = $details['total']; + $this->uri = $details['uri']; + $this->pageCount = $details['pageCount']; + } + + //-------------------------------------------------------------------- + + /** + * Sets the total number of links that should appear on either + * side of the current page. Adjusts the first and last counts + * to reflect it. + * + * @param int $count + * + * @return $this + */ + public function setSurroundCount(int $count) + { + $this->updatePages($count); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Checks to see if there is a "previous" page before our "first" page. + * + * @return bool + */ + public function hasPrevious(): bool + { + return $this->first > 1; + } + + //-------------------------------------------------------------------- + + /** + * Returns a URL to the "previous" page. The previous page is NOT the + * page before the current page, but is the page just before the + * "first" page. + * + * You MUST call hasPrevious() first, or this value may be invalid. + * + * @return string + */ + public function getPrevious(): string + { + $uri = clone $this->uri; + + $uri->addQuery('page', $this->first-1); + + return (string)$uri; + } + + //-------------------------------------------------------------------- + + /** + * Checks to see if there is a "next" page after our "last" page. + * + * @return bool + */ + public function hasNext(): bool + { + return $this->pageCount > $this->last; + } + + //-------------------------------------------------------------------- + + /** + * Returns a URL to the "next" page. The next page is NOT, the + * page after the current page, but is the page that follows the + * "last" page. + * + * You MUST call hasNext() first, or this value may be invalid. + * + * @return string + */ + public function getNext(): string + { + $uri = clone $this->uri; + + $uri->addQuery('page', $this->last+1); + + return (string)$uri; + } + + //-------------------------------------------------------------------- + + public function links(): array + { + $links = []; + + $uri = clone $this->uri; + + for ($i=$this->first; $i <= $this->last; $i++) + { + $links[] = [ + 'uri' => (string)$uri->addQuery('page', $i), + 'title' => (int)$i, + 'active' => ($i == $this->current) + ]; + } + + return $links; + } + + //-------------------------------------------------------------------- + + /** + * Updates the first and last pages based on $surroundCount, + * which is the number of links surrounding the active page + * to show. + * + * @param int|null $count + */ + protected function updatePages(int $count = null) + { + if (empty($count)) + { + return; + } + + $this->first = $this->current-$count > 0 + ? (int)($this->current-$count) + : 1; + $this->last = $this->current+$count <= $this->pageCount + ? (int)($this->current+$count) + : (int)$this->pageCount; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Pager/Views/default_full.php b/system/Pager/Views/default_full.php new file mode 100644 index 000000000000..5966b966d6ce --- /dev/null +++ b/system/Pager/Views/default_full.php @@ -0,0 +1,29 @@ +setSurroundCount(2) ?> + + \ No newline at end of file diff --git a/system/Pager/Views/default_simple.php b/system/Pager/Views/default_simple.php new file mode 100644 index 000000000000..c0154c5f4ce0 --- /dev/null +++ b/system/Pager/Views/default_simple.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/tests/system/Pager/PagerRendererTest.php b/tests/system/Pager/PagerRendererTest.php new file mode 100644 index 000000000000..064848026981 --- /dev/null +++ b/tests/system/Pager/PagerRendererTest.php @@ -0,0 +1,131 @@ +uri = new URI('http://example.com/foo'); + } + + //-------------------------------------------------------------------- + + public function testHasPreviousReturnsFalseWhenFirstIsOne() + { + $details = [ + 'uri' => $this->uri, + 'pageCount' => 5, + 'currentPage' => 1, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + + $this->assertFalse($pager->hasPrevious()); + } + + //-------------------------------------------------------------------- + + public function testHasPreviousReturnsTrueWhenFirstIsMoreThanOne() + { + $uri = $this->uri; + $uri->addQuery('foo', 'bar'); + + $details = [ + 'uri' => $uri, + 'pageCount' => 10, + 'currentPage' => 5, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + $pager->setSurroundCount(2); + + $this->assertTrue($pager->hasPrevious()); + $this->assertEquals('http://example.com/foo?foo=bar&page=2', $pager->getPrevious()); + } + + //-------------------------------------------------------------------- + + public function testHasNextReturnsFalseWhenLastIsTotal() + { + $uri = $this->uri; + $uri->addQuery('foo', 'bar'); + + $details = [ + 'uri' => $uri, + 'pageCount' => 5, + 'currentPage' => 4, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + $pager->setSurroundCount(2); + + $this->assertFalse($pager->hasNext()); + } + + //-------------------------------------------------------------------- + + public function testHasNextReturnsTrueWhenLastIsSmallerThanTotal() + { + $uri = $this->uri; + $uri->addQuery('foo', 'bar'); + + $details = [ + 'uri' => $uri, + 'pageCount' => 50, + 'currentPage' => 4, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + $pager->setSurroundCount(2); + + $this->assertTrue($pager->hasNext()); + $this->assertEquals('http://example.com/foo?foo=bar&page=7', $pager->getNext()); + } + + //-------------------------------------------------------------------- + + public function testLinksBasics() + { + $details = [ + 'uri' => $this->uri, + 'pageCount' => 50, + 'currentPage' => 4, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + $pager->setSurroundCount(1); + + $expected = [ + [ + 'uri' => 'http://example.com/foo?page=3', + 'title' => 3, + 'active' => false + ], + [ + 'uri' => 'http://example.com/foo?page=4', + 'title' => 4, + 'active' => true + ], + [ + 'uri' => 'http://example.com/foo?page=5', + 'title' => 5, + 'active' => false + ], + ]; + + $this->assertEquals($expected, $pager->links()); + } + + //-------------------------------------------------------------------- + +} diff --git a/tests/system/Pager/PagerTest.php b/tests/system/Pager/PagerTest.php new file mode 100644 index 000000000000..42b7b1a94c77 --- /dev/null +++ b/tests/system/Pager/PagerTest.php @@ -0,0 +1,254 @@ +config = new Pager(); + $this->pager = new \CodeIgniter\Pager\Pager($this->config, Services::renderer()); + } + + //-------------------------------------------------------------------- + + public function testSetPathRemembersPath() + { + $this->pager->setPath('foo/bar'); + + $expected = current_url(true); + $expected = $expected->resolveRelativeURI('/foo/bar'); + + $details = $this->pager->getDetails(); + + $this->assertEquals($expected, $details['uri']); + } + + //-------------------------------------------------------------------- + + public function testGetDetailsRecognizesPageQueryVar() + { + $_GET['page'] = 2; + + // Need this to create the group. + $this->pager->setPath('foo/bar'); + + $details = $this->pager->getDetails(); + + $this->assertEquals(2, $details['currentPage']); + } + + //-------------------------------------------------------------------- + + public function testGetDetailsRecognizesGroupedPageQueryVar() + { + $_GET['page_foo'] = 2; + + // Need this to create the group. + $this->pager->setPath('foo/bar', 'foo'); + + $details = $this->pager->getDetails('foo'); + + $this->assertEquals(2, $details['currentPage']); + } + + //-------------------------------------------------------------------- + + public function testGetDetailsThrowExceptionIfGroupNotFound() + { + $this->setExpectedException('InvalidArgumentException'); + + $this->pager->getDetails('foo'); + } + + //-------------------------------------------------------------------- + + public function testDetailsHasConfiguredPerPageValue() + { + // Need this to create the group. + $this->pager->setPath('foo/bar', 'foo'); + + $details = $this->pager->getDetails('foo'); + + $this->assertEquals($this->config->perPage, $details['perPage']); + } + + //-------------------------------------------------------------------- + + public function testStoreDoesBasicCalcs() + { + $this->pager->store('foo', 3, 25, 100); + + $details = $this->pager->getDetails('foo'); + + $this->assertEquals($details['total'], 100); + $this->assertEquals($details['perPage'], 25); + $this->assertEquals($details['currentPage'], 3); + } + + //-------------------------------------------------------------------- + + public function testStoreAndHasMore() + { + $this->pager->store('foo', 3, 25, 100); + + $this->assertTrue($this->pager->hasMore('foo')); + } + + //-------------------------------------------------------------------- + + public function testStoreAndHasMoreCanBeFalse() + { + $this->pager->store('foo', 3, 25, 70); + + $this->assertFalse($this->pager->hasMore('foo')); + } + + //-------------------------------------------------------------------- + + public function testHasMoreDefaultsToFalse() + { + $this->assertFalse($this->pager->hasMore('foo')); + } + + //-------------------------------------------------------------------- + + public function testPerPageHasDefaultValue() + { + $this->assertEquals($this->config->perPage, $this->pager->getPerPage()); + } + + //-------------------------------------------------------------------- + + public function testPerPageKeepsStoredValue() + { + $this->pager->store('foo', 3, 13, 70); + + $this->assertEquals(13, $this->pager->getPerPage('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetCurrentPageDefaultsToOne() + { + $this->assertEquals(1, $this->pager->getCurrentPage()); + } + + //-------------------------------------------------------------------- + + public function testGetCurrentPageRemembersStoredPage() + { + $this->pager->store('foo', 3, 13, 70); + + $this->assertEquals(3, $this->pager->getCurrentPage('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetCurrentPageDetectsURI() + { + $_GET['page'] = 2; + + $this->assertEquals(2, $this->pager->getCurrentPage()); + } + + //-------------------------------------------------------------------- + + public function testGetCurrentPageDetectsGroupedURI() + { + $_GET['page_foo'] = 2; + + $this->assertEquals(2, $this->pager->getCurrentPage('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetTotalPagesDefaultsToOne() + { + $this->assertEquals(1, $this->pager->getPageCount()); + } + + //-------------------------------------------------------------------- + + public function testGetTotalPagesCalcsCorrectValue() + { + $this->pager->store('foo', 3, 12, 70); + + $this->assertEquals(6, $this->pager->getPageCount('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetNextURIUsesCurrentURI() + { + $_GET['page'] = 2; + + $this->pager->store('foo', 2, 12, 70); + + $expected = current_url(true); + $expected = (string)$expected->setQuery('page=3'); + + $this->assertEquals((string)$expected, $this->pager->getNextPageURI('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetNextURIReturnsNullOnLastPage() + { + $this->pager->store('foo', 6, 12, 70); + + $this->assertNull($this->pager->getNextPageURI('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetNextURICorrectOnFirstPage() + { + $this->pager->store('foo', 1, 12, 70); + + $expected = current_url(true); + $expected = (string)$expected->setQuery('page=2'); + + $this->assertEquals($expected, $this->pager->getNextPageURI('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetPreviousURIUsesCurrentURI() + { + $_GET['page'] = 2; + + $this->pager->store('foo', 2, 12, 70); + + $expected = current_url(true); + $expected = (string)$expected->setQuery('page=1'); + + $this->assertEquals((string)$expected, $this->pager->getPreviousPageURI('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetNextURIReturnsNullOnFirstPage() + { + $this->pager->store('foo', 1, 12, 70); + + $this->assertNull($this->pager->getPreviousPageURI('foo')); + } + + //-------------------------------------------------------------------- +} From 4827744e7b32e802d314dfd3942494b7d13cfba5 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Wed, 27 Jul 2016 00:15:59 -0700 Subject: [PATCH 0130/1807] Working on Parser writeup --- system/View/View.php | 2 +- tests/system/View/ParserTest.php | 4 +- user_guide_src/source/general/view_parser.rst | 87 +++++++++++++++++-- .../source/general/view_renderer.rst | 64 +++++++++++--- user_guide_src/source/general/views.rst | 2 +- 5 files changed, 137 insertions(+), 22 deletions(-) diff --git a/system/View/View.php b/system/View/View.php index 1d1b7409f153..607163c87565 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -168,7 +168,7 @@ public function render(string $view, array $options = null, bool $saveData = fal } ob_start(); - include($file); + include($file); // PHP will be processed $output = ob_get_contents(); @ob_end_clean(); diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 36d03cc7d56c..b8eee7ab876c 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -112,7 +112,7 @@ public function testParseNoTemplate() // -------------------------------------------------------------------- - public function testParseVarPair() + public function testParseNested() { $parser = new Parser($this->viewsDir, $this->loader); $data = array ( @@ -130,7 +130,7 @@ public function testParseVarPair() // -------------------------------------------------------------------- - public function testParseVarList() + public function testParseLoop() { $parser = new Parser($this->viewsDir, $this->loader); $data = array ( diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index 301e7c05ce9e..a18179e451bc 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -6,7 +6,7 @@ The View Parser can perform simple text substitution for pseudo-variables contained within your view files. It can parse simple variables or variable tag pairs. -Pseudo-variable names are enclosed in braces, like this:: +Pseudo-variable names or control constructs are enclosed in braces, like this:: @@ -61,6 +61,26 @@ is ignored by the parser, and only substitutions are performed. This is purposeful: view files with no PHP. +What It Does +============ + +The ``Parser`` class processes "PHP/HTML scripts" stored in the application's view path. +These scripts have a ``.php`` extension, but should not contain any PHP. + +Each view parameter (which we refer to as a pseudo-variable) triggers a substitution, +based on the type of value you provided for it. Pseudo-variables are not +extracted into PHP variables; instead their value is accessed through the pseudo-variable +syntax, where its name is referenced inside braces. +This means that your view parameter names need not be legal PHP variable names. + +The Parser class uses an associative array internally, to accumulate pseudo-variable +settings until you call its ``render()``. This means that your pseudo-variable names +need to be unique, or a later parameter setting will over-ride an earlier one. + +This also impacts escaping parameter values for different contexts inside your +script. You will have to give each escaped value a unique parameter name. + + Parser templates ================ @@ -82,27 +102,41 @@ The first parameter to ``render()`` contains the name of the :doc:`view file <../general/views>` (in this example the file would be called blog_template.php), -As with the other renderers, There is no need to "echo" the parser output. -You can accumulate it and "echo" it before exiting the request -handling method in your controller, or that method -could "return" it. Parser Configuration Options ============================ -TODO +Several options can be passed to the ``render()`` or ``renderString()`` methods. + + +- ``cache`` - the time in seconds, to save a view's results; ignored for renderString() +- ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath; + ignored for renderString() +- ``saveData`` - true if the view data parameters should be retained for subsequent calls; + default is **false** +- ``cascadeData`` - true if pseudo-variable settings should be passed on to nested + substitutions; default is **true** +- ``beamMeUp`` - just here to see if anyone is reading the user guide page + *********************** Substitution Variations *********************** -The simplest substitution performed by the parser is a one-to-one +There are four types of substitution supported: simple, looping, nested +and conditional. Any conditional substitutions are performed first, before any of +the others. The other substitutions are performed in the same sequence that +pseudo-variables were added. + +The **simple substitution** performed by the parser is a one-to-one replacement of pseudo-variables where the corresponding data parameter has either a scalar or string value, as in this example:: $template = '{blog_title}'; $data = ['blog_title' => 'My ramblings']; + echo $parser->setData($data)->renderString($template); + // Result: My ramblings The ``Parser`` takes substitution a lot further with "variable pairs", @@ -118,6 +152,9 @@ When the parser executes, it will generally Variable Pairs for Looping ========================== +A loop substitution happens when the value for a pseudo-variable is +a sequential array of arrays, like an array of row settings. + The above example code allows simple variables to be replaced. What if you would like an entire block of variables to be repeated, with each iteration containing new values? Consider the template example we showed @@ -162,6 +199,10 @@ corresponding to your variable pair data. Consider this example:: echo $parser->setData($data) ->render('blog_template'); +The value for the pseudo-variable ``blog_entries`` is a sequential +array of associative arrays. The outer level does not have keys associated +with each of the nested "rows". + If your "pair" data is coming from a database result, which is already a multi-dimensional array, you can simply use the database ``getResultArray()`` method:: @@ -180,7 +221,37 @@ method:: Variable Pairs for Nested Substitutions ======================================= -TODO +A nested substitution happens when the value for a pseudo-variable is +an associative array of values, like a record from a database:: + + $data = array( + 'blog_title' => 'My Blog Title', + 'blog_heading' => 'My Blog Heading', + 'blog_entry' => array( + 'title' => 'Title 1', 'body' => 'Body 1' + ) + ); + + echo $parser->setData($data) + ->render('blog_template'); + +The value for the pseudo-variable ``blog_entry`` is an associative +array. The key/value pairs defined inside it will be exposed inside +the variable pair loop for that variable. + +A ``blog_template`` that might work for the above:: + +

{blog_title} - {blog_heading}

+ {blog_entry} +
+

{title}

+

{body}{/p} +

+ {/blog_entry} + +If you would like the other pseudo-variables accessible inside the "blog_entry" +scope, then make sure that the "cascadeData" option is set to true. + Conditional Substitutions ========================= diff --git a/user_guide_src/source/general/view_renderer.rst b/user_guide_src/source/general/view_renderer.rst index 522d717db6f2..41051da0db1e 100644 --- a/user_guide_src/source/general/view_renderer.rst +++ b/user_guide_src/source/general/view_renderer.rst @@ -2,9 +2,10 @@ View Renderer ############# -The ``view()`` function is a convenience method that grabs an instance of the ``renderer`` service, -sets the data, and renders the view. While this is often exactly what you want, you may find times where you -want to work with it more directly. In that case you can access the View service directly:: +The ``view()`` function is a convenience function that grabs an instance of the +``renderer`` service, sets the data, and renders the view. While this is often +exactly what you want, you may find times where you want to work with it more directly. +In that case you can access the View service directly:: $view = \Config\Services::renderer(); @@ -14,16 +15,35 @@ can instantiate it directly:: $view = new \CodeIgniter\View\View(); -.. important:: You should create services only within controllers. If you need access to the View class - from a library, you should set that as a dependency in the constructor. +.. important:: You should create services only within controllers. If you need + access to the View class from a library, you should set that as a dependency + in your library's constructor. Then you can use any of the three standard methods that it provides: **render(viewpath, options, save)**, **setVar(name, value, context)** and **setData(data, context)**. +What It Does +============ + +The ``View`` class processes conventional HTML/PHP scripts stored in the application's view path, +after extracting view parameters into PHP variables, accessible inside the scripts. +This means that your view parameter names need to be legal PHP variable names. + +The View class uses an associative array internally, to accumulate view parameters +until you call its ``render()``. This means that your parameter (or variable) names +need to be unique, or a later variable setting will over-ride an earlier one. + +This also impacts escaping parameter values for different contexts inside your +script. You will have to give each escaped value a unique parameter name. + +No special meaning is attached to parameters whose value is an array. It is up +to you to process the array appropriately in your PHP code. + Method Chaining =============== -The `setVar()` and `setData()` methods are chainable, allowing you to combine a number of different calls together in a chain:: +The `setVar()` and `setData()` methods are chainable, allowing you to combine a +number of different calls together in a chain:: $view->setVar('one', $one) ->setVar('two', $two) @@ -66,6 +86,18 @@ context as the second parameter. Valid contexts are 'html', 'js', 'css', 'url', } +View Renderer Options +===================== + +Several options can be passed to the ``render()`` or ``renderString()`` methods: + + +- ``cache`` - the time in seconds, to save a view's results; ignored for renderString() +- ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath; + ignored for renderString() +- ``saveData`` - true if the view data parameters should be retained for subsequent calls + + *************** Class Reference *************** @@ -84,11 +116,17 @@ Class Reference echo $view->render('myview'); - Options supported: + .. php:method:: renderString($view[, $options[, $saveData=false]]]) - - ``cache`` - the time in seconds, to save a view's results - - ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath - - ``saveData`` - true if the view data parameter should be retained for subsequent calls + :param string $view: Contents of the view to render + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string + + Builds the output based upon a file name and any data that has already been set:: + + echo $view->render('myview'); .. php:method:: setData([$data[, $context=null]]) @@ -105,6 +143,9 @@ Class Reference Supported escape contexts: html, css, js, url, or attr or raw. If 'raw', no escaping will happen. + Each call adds to the array of data that the object is accumulating, + until the view is rendered. + .. php:method:: setVar($name[, $value=null[, $context=null]]) :param string $name: Name of the view data variable @@ -120,3 +161,6 @@ Class Reference Supported escape contexts: html, css, js, url, attr or raw. If 'raw', no escaping will happen. + If you use the a view data variable that you have previously used + for this object, the new value will replace the existing one. + diff --git a/user_guide_src/source/general/views.rst b/user_guide_src/source/general/views.rst index 43f8b82109bd..8660b687b7cc 100644 --- a/user_guide_src/source/general/views.rst +++ b/user_guide_src/source/general/views.rst @@ -33,7 +33,7 @@ Displaying a View To load a particular view file you will use the following function:: - view('name'); + echo view('name'); Where _name_ is the name of your view file. From 29c30734fa8d5b2c9c610d9506db04e394918c9a Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 27 Jul 2016 23:23:33 -0500 Subject: [PATCH 0131/1807] Functional pagination in place. --- application/Config/Pager.php | 2 +- system/Model.php | 16 +++-- system/Pager/Pager.php | 77 +++++++++++++++++++++--- system/Pager/PagerRenderer.php | 55 ++++++++++++++++- system/Pager/Views/default_simple.php | 13 +++- tests/system/Pager/PagerRendererTest.php | 45 ++++++++++++++ tests/system/Pager/PagerTest.php | 9 +-- 7 files changed, 198 insertions(+), 19 deletions(-) diff --git a/application/Config/Pager.php b/application/Config/Pager.php index eafc7697e848..bfb8ec554e4b 100644 --- a/application/Config/Pager.php +++ b/application/Config/Pager.php @@ -19,7 +19,7 @@ class Pager extends BaseConfig */ public $templates = [ 'default_full' => 'CodeIgniter\Pager\Views\default_full', - 'default_simple' => 'CodeIgntier\Pager\default_simple' + 'default_simple' => 'CodeIgniter\Pager\Views\default_simple' ]; /* diff --git a/system/Model.php b/system/Model.php index 73a78f22a1b7..4dfa88e0b143 100644 --- a/system/Model.php +++ b/system/Model.php @@ -36,13 +36,13 @@ * @filesource */ -use CodeIgniter\Config\BaseConfig; use Config\App; use Config\Database; +//use Config\Services; +use CodeIgniter\Config\BaseConfig; use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; -use phpDocumentor\Reflection\DocBlock\Tag\VarTag; /** * Class Model @@ -788,15 +788,23 @@ public function chunk($size = 100, \Closure $userFunc) * to display. * * @param int $perPage - * @param string $alias Will be used by the pagination library + * @param string $group Will be used by the pagination library * to identify a unique pagination set. * * @return array|null */ - public function paginate(int $perPage = 20, string $alias = null) + public function paginate(int $perPage = 20, string $group = 'default') { + // Get the necessary parts. $page = $_GET['page'] ?? 1; + $total = $this->countAllResults(false); + + // Store it in the Pager library so it can be + // paginated in the views. + $pager = \Config\Services::pager(); + $pager->store($group, $page, $perPage, $total); + $offset = ($page - 1) * $perPage; return $this->findAll($perPage, $offset); diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php index a936a8b69626..6eacfa0017df 100644 --- a/system/Pager/Pager.php +++ b/system/Pager/Pager.php @@ -1,10 +1,54 @@ ensureGroup($group); + + return $this->displayLinks($group, $template); } //-------------------------------------------------------------------- @@ -61,9 +107,11 @@ public function links(string $template = 'default_full', string $group = 'defaul * * @return string */ - public function simpleLinks(string $template = 'default_simple', string $group = 'default'): string + public function simpleLinks(string $group = 'default', string $template = 'default_simple'): string { $this->ensureGroup($group); + + return $this->displayLinks($group, $template); } //-------------------------------------------------------------------- @@ -85,7 +133,23 @@ public function makeLinks(int $page, int $perPage, int $total, string $template $this->store($name, $page, $perPage, $total); - $pager = new PagerRenderer($this->getDetails($name)); + return $this->displayLinks($name, $template); + } + + //-------------------------------------------------------------------- + + /** + * Does the actual work of displaying the view file. Used internally + * by links(), simpleLinks(), and makeLinks(). + * + * @param string $group + * @param string $template + * + * @return string + */ + protected function displayLinks(string $group, string $template) + { + $pager = new PagerRenderer($this->getDetails($group)); if (! array_key_exists($template, $this->config->templates)) { @@ -351,7 +415,6 @@ public function getDetails(string $group = 'default'): array $newGroup = $this->groups[$group]; - $newGroup['uri'] = $newGroup['uri']; $newGroup['next'] = $this->getNextPageURI($group); $newGroup['previous'] = $this->getPreviousPageURI($group); diff --git a/system/Pager/PagerRenderer.php b/system/Pager/PagerRenderer.php index e881a6e5458c..a2b33104519d 100644 --- a/system/Pager/PagerRenderer.php +++ b/system/Pager/PagerRenderer.php @@ -1,5 +1,50 @@ setSurroundCount(0) ?> \ No newline at end of file diff --git a/tests/system/Pager/PagerRendererTest.php b/tests/system/Pager/PagerRendererTest.php index 064848026981..ca596bc9570f 100644 --- a/tests/system/Pager/PagerRendererTest.php +++ b/tests/system/Pager/PagerRendererTest.php @@ -52,6 +52,30 @@ public function testHasPreviousReturnsTrueWhenFirstIsMoreThanOne() //-------------------------------------------------------------------- + /** + * @group single + */ + public function testGetPreviousWhenSurroundCountIsZero() + { + $uri = $this->uri; + $uri->addQuery('foo', 'bar'); + + $details = [ + 'uri' => $uri, + 'pageCount' => 50, + 'currentPage' => 4, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + $pager->setSurroundCount(0); + + $this->assertTrue($pager->hasPrevious()); + $this->assertEquals('http://example.com/foo?foo=bar&page=3', $pager->getPrevious()); + } + + //-------------------------------------------------------------------- + public function testHasNextReturnsFalseWhenLastIsTotal() { $uri = $this->uri; @@ -93,6 +117,27 @@ public function testHasNextReturnsTrueWhenLastIsSmallerThanTotal() //-------------------------------------------------------------------- + public function testGetNextWhenSurroundCountIsZero() + { + $uri = $this->uri; + $uri->addQuery('foo', 'bar'); + + $details = [ + 'uri' => $uri, + 'pageCount' => 50, + 'currentPage' => 4, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + $pager->setSurroundCount(0); + + $this->assertTrue($pager->hasNext()); + $this->assertEquals('http://example.com/foo?foo=bar&page=5', $pager->getNext()); + } + + //-------------------------------------------------------------------- + public function testLinksBasics() { $details = [ diff --git a/tests/system/Pager/PagerTest.php b/tests/system/Pager/PagerTest.php index 42b7b1a94c77..8e0de83ef09a 100644 --- a/tests/system/Pager/PagerTest.php +++ b/tests/system/Pager/PagerTest.php @@ -20,6 +20,7 @@ public function __construct() public function setUp() { + $_SERVER['HTTP_HOST'] = 'example.com'; $_GET = []; $this->config = new Pager(); $this->pager = new \CodeIgniter\Pager\Pager($this->config, Services::renderer()); @@ -27,16 +28,16 @@ public function setUp() //-------------------------------------------------------------------- + /** + * @group single + */ public function testSetPathRemembersPath() { $this->pager->setPath('foo/bar'); - $expected = current_url(true); - $expected = $expected->resolveRelativeURI('/foo/bar'); - $details = $this->pager->getDetails(); - $this->assertEquals($expected, $details['uri']); + $this->assertEquals('foo/bar', $details['uri']->getPath()); } //-------------------------------------------------------------------- From f7688cdde9a281a41705037206ef66a0d610a7d9 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 28 Jul 2016 00:12:04 -0500 Subject: [PATCH 0132/1807] Getting started on docs --- system/Model.php | 11 +- user_guide_src/source/libraries/index.rst | 2 +- .../source/libraries/pagination.rst | 102 ++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 user_guide_src/source/libraries/pagination.rst diff --git a/system/Model.php b/system/Model.php index 4dfa88e0b143..9924f98777b7 100644 --- a/system/Model.php +++ b/system/Model.php @@ -36,6 +36,7 @@ * @filesource */ +use CodeIgniter\Pager\Pager; use Config\App; use Config\Database; //use Config\Services; @@ -63,6 +64,14 @@ */ class Model { + /** + * Pager instance. + * Populated after calling $this->paginate() + * + * @var Pager + */ + public $pager; + /** * Name of database table * @@ -803,7 +812,7 @@ public function paginate(int $perPage = 20, string $group = 'default') // Store it in the Pager library so it can be // paginated in the views. $pager = \Config\Services::pager(); - $pager->store($group, $page, $perPage, $total); + $this->pager = $pager->store($group, $page, $perPage, $total); $offset = ($page - 1) * $perPage; diff --git a/user_guide_src/source/libraries/index.rst b/user_guide_src/source/libraries/index.rst index dc4b698fd5f5..78af41551b5d 100644 --- a/user_guide_src/source/libraries/index.rst +++ b/user_guide_src/source/libraries/index.rst @@ -12,10 +12,10 @@ Library Reference curlrequest incomingrequest message + pagination request response security sessions uploaded_files uri - \ No newline at end of file diff --git a/user_guide_src/source/libraries/pagination.rst b/user_guide_src/source/libraries/pagination.rst new file mode 100644 index 000000000000..957fc9a951a0 --- /dev/null +++ b/user_guide_src/source/libraries/pagination.rst @@ -0,0 +1,102 @@ +########## +Pagination +########## + +CodeIgniter provides a very simple, but flexible pagination library that is simple to theme, works with the model, +and capable of supporting multiple paginators on a single page. + +******************* +Loading the Library +******************* + +Like all services in CodeIgniter, it can be loaded via ``Config\Services``, though you usually will not need +to load it manually:: + + $pager = \Config\Services::pager(); + +*************************** +Paginating Database Results +*************************** + +In most cases, you will be using the Pager library in order to paginate results that you retrieve from the database. +When using the :doc:`Model ` class, you can use its built-in ``paginate()`` method to automatically +retrieve the current batch of results, as well as setup the Pager library so it's ready to use in your controllers. +It even reads the current page it should display from the current URL via a ``page=X`` query variable. + +To provide a paginated list of users in your application, your controller's method would look something like:: + + class UserController extends Controller + { + public function index() + { + $model = new \App\Models\UserModel(); + + $data = [ + 'users' => $model->paginate(10), + 'pager' => $model->pager + ]; + + echo view('users/index', $data); + } + } + +In this example, we first create a new instance of our UserModel. Then we populate the data to sent to the view. +The first element is the results from the database, **users**, which is retrieved for the correct page, returning +10 users per page. The second item that must be sent to the view is the Pager instance itself. As a convenience, +the Model will hold on to the instance it used and store it in the public class variable, **$pager**. So, we grab +that and assign it to the $pager variable in the view. + +Within the view, we then need to tell it where to display the resulting links:: + + links() ?> + +And that's all it takes. The Pager class will render a series of links that are compatible with the Boostrap CSS +framework by default. It will have First and Last page links, as well as Next and Previous links for any pages more +than two pages on either side of the current page. + +If you prefer a simpler output, you can use the ``simpleLinks()`` method, which only uses "Older" and "Newer" links, +instead of the details pagination links:: + + simpleLinks() ?> + + +Behind the scenes, the library loads a view file that determines how the links are formatted, making it simple to +modify to your needs. See below for details on how to completely customize the output. + +Paginating Multiple Results +=========================== + +If you need to provide links from two different result sets, you can pass group names to most of the pagination +methods to keep the data separate:: + + // In the Controller + public function index() + { + $userModel = new \App\Models\UserModel(); + $pageModel = new \App\Models\PageModel(); + + $data = [ + 'users' => $userModel->paginate(10, 'group1'), + 'pages' => $pageModel->paginate(15, 'group2'), + 'pager' => $model->pager + ]; + + echo view('users/index', $data); + } + + // In the views: + links('group1') ?> + simpleLinks('group2') ?> + +Manual Pagination +================= + +You may find times where you just need to create pagination based on known data. You can create links manually +with the ``makeLinks()`` method, which takes the current page, the amount of results per page, and +the total number of items as the first, second, and third parameters, respectively:: + + makeLinks($page, $perPage, $total) ?> + +********************* +Customizing the Links +********************* From 87cd7d713c9c3b36bf75a3e1cb460719d56f25e0 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Thu, 28 Jul 2016 01:38:05 -0700 Subject: [PATCH 0133/1807] Flesh out user guide for views --- system/View/Parser.php | 13 --- user_guide_src/source/general/view_cells.rst | 68 ------------ user_guide_src/source/general/view_parser.rst | 102 ++++++++++++------ user_guide_src/source/general/views.rst | 67 ++++++++++++ 4 files changed, 137 insertions(+), 113 deletions(-) delete mode 100644 user_guide_src/source/general/view_cells.rst diff --git a/system/View/Parser.php b/system/View/Parser.php index 1c03a9fb1336..1b5c42b5aace 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -215,19 +215,6 @@ protected function parse(string $template, array $data = [], array $options = nu // -------------------------------------------------------------------- - /** - * Set the left/right variable delimiters - * - * @param string - * @param string - * @return void - */ - public function setDelimiters($l = '{', $r = '}') - { - $this->leftDelimiter = $l; - $this->rightDelimiter = $r; - } - // -------------------------------------------------------------------- /** diff --git a/user_guide_src/source/general/view_cells.rst b/user_guide_src/source/general/view_cells.rst deleted file mode 100644 index 802ee4a46d33..000000000000 --- a/user_guide_src/source/general/view_cells.rst +++ /dev/null @@ -1,68 +0,0 @@ -########## -View Cells -########## - -View Cells allow you to insert HTML that is generated outside of your controller. It simply calls the specified -class and method, which must return valid HTML. This method could be in an callable method, found in any class -that the autoloader can locate. The only restriction is that the class can not have any constructor parameters. -This is intended to be used within views, and is a great aid to modularizing your code. -:: - - - -In this example, the class ``App\Libraries\Blog`` is loaded, and the method ``recentPosts()`` is ran. That method -must return a string with the generated HTML. The method used can be either a static method or not. Either way works. - -Cell Parameters ---------------- - -You can further refine the call by passing a string with a list of parameters in the second parameter that are passed -to the method as an array of key/value pairs, or a comma-seperated string of key/value pairs:: - - // Passing Parameter Array - 'codeigniter', 'limit' => 5]) ?> - - // Passing Parameter String - - - public function recentPosts(array $params=[]) - { - $posts = $this->blogModel->where('category', $params['category']) - ->orderBy('published_on', 'desc') - ->limit($params['limit']) - ->get(); - - return view('recentPosts', ['posts' => $posts]); - } - -Additionally, you can use parameter names that match the parameter variables in the method for better readability. -When you use it this way, all of the parameters must always be specified in the view cell call:: - - - - public function recentPosts(int $limit, string $category) - { - $posts = $this->blogModel->where('category', $category) - ->orderBy('published_on', 'desc') - ->limit($limit) - ->get(); - - return view('recentPosts', ['posts' => $posts]); - } - -Cell Caching ------------- - -You can cache the results of the view cell call by passing the number of seconds to cache the data for as the -third parameter. This will use the currently configured cache engine. -:: - - // Cache the view for 5 minutes - - -You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name -as the fourth parameter.:: - - // Cache the view for 5 minutes - - diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index a18179e451bc..d8e9bba5aaf8 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -149,8 +149,8 @@ When the parser executes, it will generally - handle any nested/looping substutions - handle the remaining single substitutions -Variable Pairs for Looping -========================== +Loop Substitutions +================== A loop substitution happens when the value for a pseudo-variable is a sequential array of arrays, like an array of row settings. @@ -218,8 +218,8 @@ method:: echo $parser->setData($data) ->render('blog_template'); -Variable Pairs for Nested Substitutions -======================================= +Nested Substitutions +==================== A nested substitution happens when the value for a pseudo-variable is an associative array of values, like a record from a database:: @@ -252,11 +252,59 @@ A ``blog_template`` that might work for the above:: If you would like the other pseudo-variables accessible inside the "blog_entry" scope, then make sure that the "cascadeData" option is set to true. +Cascading Data +============== + +With both a nested and a loop substitution, you have the option of cascading +data pairs into the inner substitution. + +The following example is not impacted by cascading:: + + $template = '{name} lives in {location}{city} on {planet}{/location}.'; + + $data = ['name' => 'George', + 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; + + echo $parser->setData($data)->renderString($template); + // Result: George lives in Red City on Mars. + +This example gives different results, depending on cascading:: + + $template = '{location}{name} lives in {city} on {planet}{/location}.'; + + $data = ['name' => 'George', + 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; + + echo $parser->setData($data)->renderString($template, ['cascadeData'=>false]); + // Result: {name} lives in Red City on Mars. + + echo $parser->setData($data)->renderString($template, ['cascadeData'=>true]); + // Result: George lives in Red City on Mars. Conditional Substitutions ========================= -TODO +A conditional substitution provides a simple if/else capabitlity for including +a block inside your template. It uses a special syntax for the pseudo-variable: +prefixing the pseudo-variable name with ``if:``, providing for an option +``{else)``, and then having a final ``{/if}`` tag. + +If the pseudo-variable referenced is``true``, then the first block is kept in +the template; otherwise the ``else`` block is. + +An example:: + + $data = [ 'first' => true]; + + $template = '{if:first}Yabba dabba{/if} doo'; + + echo $parser->setData($data)->renderString($template); + // Result: Yabba dabba doo + +.. note:: This is a very simple conditional substitution. + Future enhancements could provide for a logical expression instead of just + a pseudo-variable name, or for more elaborate tags! + *********** Usage Notes @@ -309,23 +357,6 @@ pair tag, but the closing variable pair tag is not rendered properly:: // Result: Hello, John Doe (Mr{degree} {/degrees}) -If you name one of your individual substitution parameters the same as one -used inside a variable pair, the results may not be as expected:: - - $template = 'Hello, {firstname} {lastname} ({degrees}{degree} {/degrees})'; - $data = array( - 'degree' => 'Mr', - 'firstname' => 'John', - 'lastname' => 'Doe', - 'degrees' => array( - array('degree' => 'BSc'), - array('degree' => 'PhD') - ) - ); - echo $parser->setData($data) - ->renderString($template); - - // Result: Hello, John Doe (Mr Mr ) View Fragments ============== @@ -408,10 +439,27 @@ Class Reference - ``cache`` - the time in seconds, to save a view's results - ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath + - ``cascadeData`` - true if the data pairs in effect when a nested or loop substitution occurs should be propagated - ``saveData`` - true if the view data parameter should be retained for subsequent calls - ``leftDelimiter`` - the left delimiter to use in pseudo-variable syntax - ``rightDelimiter`` - the right delimiter to use in pseudo-variable syntax + Any conditional substitutions are performed first, then remaining + substitutions are performed for each data pair. + + .. php:method:: renderString($template[, $options[, $saveData=false]]]) + + :param string $template: View source provided as a string + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string + + Builds the output based upon a provided template source and any data that has already been set:: + + echo $parser->render('myview'); + + Options supported, and behavior, as above. .. php:method:: setData([$data[, $context=null]]) @@ -442,13 +490,3 @@ Class Reference Supported escape contexts: html, css, js, url, attr or raw. If 'raw', no escaping will happen. - - - .. php:method:: setDelimiters([$l = '{'[, $r = '}']]) - - :param string $l: Left delimiter - :param string $r: Right delimiter - :rtype: void - - Sets the delimiters (opening and closing) for a - pseudo-variable "tag" in a template. diff --git a/user_guide_src/source/general/views.rst b/user_guide_src/source/general/views.rst index 8660b687b7cc..f7c35953c005 100644 --- a/user_guide_src/source/general/views.rst +++ b/user_guide_src/source/general/views.rst @@ -215,3 +215,70 @@ Now open your view file and create a loop:: +********** +View Cells +********** + +View Cells allow you to insert HTML that is generated outside of your controller. It simply calls the specified +class and method, which must return valid HTML. This method could be in an callable method, found in any class +that the autoloader can locate. The only restriction is that the class can not have any constructor parameters. +This is intended to be used within views, and is a great aid to modularizing your code. +:: + + + +In this example, the class ``App\Libraries\Blog`` is loaded, and the method ``recentPosts()`` is ran. That method +must return a string with the generated HTML. The method used can be either a static method or not. Either way works. + +Cell Parameters +--------------- + +You can further refine the call by passing a string with a list of parameters in the second parameter that are passed +to the method as an array of key/value pairs, or a comma-seperated string of key/value pairs:: + + // Passing Parameter Array + 'codeigniter', 'limit' => 5]) ?> + + // Passing Parameter String + + + public function recentPosts(array $params=[]) + { + $posts = $this->blogModel->where('category', $params['category']) + ->orderBy('published_on', 'desc') + ->limit($params['limit']) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } + +Additionally, you can use parameter names that match the parameter variables in the method for better readability. +When you use it this way, all of the parameters must always be specified in the view cell call:: + + + + public function recentPosts(int $limit, string $category) + { + $posts = $this->blogModel->where('category', $category) + ->orderBy('published_on', 'desc') + ->limit($limit) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } + +Cell Caching +------------ + +You can cache the results of the view cell call by passing the number of seconds to cache the data for as the +third parameter. This will use the currently configured cache engine. +:: + + // Cache the view for 5 minutes + + +You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name +as the fourth parameter.:: + + // Cache the view for 5 minutes + From 72cc5ddea14b94bc58faf29e13a7924fc4ef7160 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Thu, 28 Jul 2016 01:58:17 -0700 Subject: [PATCH 0134/1807] User guide rebuilding --- user_guide_src/ghpages.rst | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 user_guide_src/ghpages.rst diff --git a/user_guide_src/ghpages.rst b/user_guide_src/ghpages.rst new file mode 100644 index 000000000000..299026365a9e --- /dev/null +++ b/user_guide_src/ghpages.rst @@ -0,0 +1,52 @@ +######################### +Generating the User Guide +######################### + +The intent is, eventually, for the in-progress user guide to be automatically +generated as part of a PR merge. This writeup explains how it can be done manually +in the meantime. + +The user guide takes advantage of Github pages, where the "gh-pages" branch of +a repo, containing HTML only, is accessible through `github.io +`_. + +Setup for Repo Maintainers +========================== + +You already have the repo cloned into ``CodeIgniter4`` in a projects folder. +Create another folder at the same level as this, ``CodeIgniter4-guide``. +Clone the CodeIgniter4 repo again, into ``CodeIgniter4-guide/html``. + +Inside the ``html`` folder, ``git checkout gh-pages``. +All you should see is the generated HTML for the user guide. + +Re-generating the User Guide +============================ + +In the ``user_guide_src`` folder, you generate a conventional user guide, +for testing, using the command:: + + make html + +An additional target has been configured, which will generate the same +HTML but inside the ``html`` folder of the second repo clone:: + + make ghpages + +After making this target, update the online user guide by switching to +the ``CodeIgniter4-guide/html`` folder, and then:: + + git add . + git commit -S -m "Suitable comment" + git push origin gh-pages + +Process +======= + +There should be only one maintainer doing this, to avoid collisions. +The user guide would get regenerated whenever there is a PR merge +that affects it. + +.. note:: You might have to delete the ``user_guide_src/doctree`` folder before +making the ``gh-pages`` version of the guide, to make sure that the TOC +is properly rebuilt, especially if you are rebuilding the ``html`` target a number of times. From 5552f0397b7b47c818a32046ace732a9f2e1a987 Mon Sep 17 00:00:00 2001 From: "Instructor, Computer Systems Technology" Date: Thu, 28 Jul 2016 02:04:19 -0700 Subject: [PATCH 0135/1807] Update ghpages.rst --- user_guide_src/ghpages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/ghpages.rst b/user_guide_src/ghpages.rst index 299026365a9e..500b7eb4fd51 100644 --- a/user_guide_src/ghpages.rst +++ b/user_guide_src/ghpages.rst @@ -47,6 +47,6 @@ There should be only one maintainer doing this, to avoid collisions. The user guide would get regenerated whenever there is a PR merge that affects it. -.. note:: You might have to delete the ``user_guide_src/doctree`` folder before +Note: You might have to delete the ``user_guide_src/doctree`` folder before making the ``gh-pages`` version of the guide, to make sure that the TOC is properly rebuilt, especially if you are rebuilding the ``html`` target a number of times. From be09a4ef587f16701926ad8bb7d5efcac1763c5f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 28 Jul 2016 22:17:52 -0500 Subject: [PATCH 0136/1807] more docs --- .../source/libraries/pagination.rst | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/user_guide_src/source/libraries/pagination.rst b/user_guide_src/source/libraries/pagination.rst index 957fc9a951a0..a7071261f57a 100644 --- a/user_guide_src/source/libraries/pagination.rst +++ b/user_guide_src/source/libraries/pagination.rst @@ -97,6 +97,119 @@ the total number of items as the first, second, and third parameters, respective makeLinks($page, $perPage, $total) ?> +This will, by default, display the links in the normal manner, as a series of links, but you can change the display +template used by passing in the name of the template as the fourth parameter. More details can be found in the following +sections. +:: + + makeLinks($page, $perPage, $total, 'template_name') ?> + ********************* Customizing the Links ********************* + +View Configuration +================== + +When the links are rendered out to the page, they use a view file to describe the HTML. You can easily change the view +that is used by editing **application/Config/Pager.php**:: + + public $templates = [ + 'default_full' => 'CodeIgniter\Pager\Views\default_full', + 'default_simple' => 'CodeIgniter\Pager\Views\default_simple' + ]; + +This setting stores the alias and :doc:`namespaced view paths ` for the view that +should be used. The *default_full* and *default_simple* views are used for the ``links()`` and ``simpleLinks()`` +methods, respectively. To change the way those are displayed application-wide, you could assign a new view here. + +For example, say you create a new view file that works with the Foundation CSS framework, instead of Bootstrap, and +you place that file at *application/Views/Pagers/foundation_full.php**. Since the **application** directory is +namespaced as ``App``, and all directories underneath it map directly to segments of the namespace, you can locate +the view file through it's namespace:: + + 'default_full' => 'App\Views\Pagers\foundation_full', + +Since it is under the standard **application/Views** directory, though, you do not need to namespace it since the +``view()`` method will locate. In that case, you can simple give the sub-directory and file name:: + + 'default_full' => 'Pagers/foundation_full', + +Once you have created the view and set it in the configuration, it will automatically be used. You don't have to +just replace the existing templates. You can create as many additional templates as you need in the configuration +file. A common situation would be needing different styles for the frontend and the backend of your application. +:: + + public $templates = [ + 'default_full' => 'CodeIgniter\Pager\Views\default_full', + 'default_simple' => 'CodeIgniter\Pager\Views\default_simple', + 'front_full' => 'App\Views\Pagers\foundation_full', + ]; + +Once configured, you can specify it as a the last parameter in the ``links()``, ``simpleLinks()``, and ``makeLinks()`` +methods:: + + links('group1', 'front_full') ?> + simpleLinks('group2', 'front_full') ?> + makeLinks($page, $perPage, $total, 'front_full') ?> + +Creating the View +================= + +When you create a new view, you only need to create the code that is needed for creating the pagination links themselves. +You should never create unneccessary wrapping divs since it might be used in multiple places and you only limit their +usefullness. It is easiest to demonstrate creating a new view by showing you the existing default_full template:: + + setSurroundCount(2) ?> + + + +**setSurroundCount()** + +In the first line, the ``setSurroundCount()`` method specifies that we want to show two links to either side of +the current page link. The only parameter that it accepts is the number of links to show. + +**hasPrevious()** +**hasNext()** + +These methods return a boolean true if it has more links than can be displayed on either side of the current page, +based on the value passed to ``setSurroundCount``. For example, let's say we have 20 pages of data. The current +page is page 3. If the surround count is 2, then the following links would show up in the list: 1, 2, 3, 4, and 5. +Since the first link displayed is page one, ``hasPrevious()`` would return **false** since there is no page zero. However, +``hasNext()`` would return **true** since there are 15 additional pages of results after page five. + +**getPrevious()** +**getNext()** + +These methods return the URL for the previous or next pages of results on either side of the numbered links. See the +previous paragraph for a full explanation. + +**links()** + +Returns an array of data about all of the numbered links. \ No newline at end of file From 10d6a22056dd9c5dfef9122adac93f6857181906 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 28 Jul 2016 22:27:11 -0500 Subject: [PATCH 0137/1807] Actually checking rules for after filters. Should probably refactor it a bit in the future, though. Fixes #195 --- system/Filters/Filters.php | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index cddb0934ca54..9f5ed6973303 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -59,12 +59,14 @@ public function __construct($config, RequestInterface $request, ResponseInterfac * * @param string $uri * @param string $position + * + * @return \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\ResponseInterface|mixed */ public function run(string $uri, $position = 'before') { $this->initialize($uri); - foreach ($this->filters[$position] as $alias) + foreach ($this->filters[$position] as $alias => $rules) { if (! array_key_exists($alias, $this->config->aliases)) { @@ -205,6 +207,38 @@ protected function processGlobals(string $uri = null) // After if (isset($this->config->globals['after'])) { + // Take any 'except' routes into consideration + foreach ($this->config->globals['after'] as $alias => $rules) + { + if (! is_array($rules) || ! array_key_exists('except', $rules)) + { + continue; + } + + $rules = $rules['except']; + + if (is_string($rules)) + { + $rules = [$rules]; + } + + foreach ($rules as $path) + { + // Prep it for regex + $path = str_replace('/*', '*', $path); + $path = trim(str_replace('*', '.+', $path), '/ '); + + // Path doesn't match the URI? continue on... + if (preg_match('/'.$path.'/', $uri, $match) !== 1) + { + continue; + } + + unset($this->config->globals['after'][$alias]); + break; + } + } + $this->filters['after'] = array_merge($this->filters['after'], $this->config->globals['after']); } } From ac20037507779b2df6755119cbb48bd11e88aeae Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 28 Jul 2016 22:33:44 -0500 Subject: [PATCH 0138/1807] Fixing bug I introduced. --- system/Filters/Filters.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php index 9f5ed6973303..032e4b9a3fb8 100644 --- a/system/Filters/Filters.php +++ b/system/Filters/Filters.php @@ -68,6 +68,11 @@ public function run(string $uri, $position = 'before') foreach ($this->filters[$position] as $alias => $rules) { + if (is_numeric($alias) && is_string($rules)) + { + $alias = $rules; + } + if (! array_key_exists($alias, $this->config->aliases)) { throw new \InvalidArgumentException("'{$alias}' filter must have a matching alias defined."); From 92a1e230bfc7001e75cf8348f402783f4606a85c Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 28 Jul 2016 22:46:32 -0500 Subject: [PATCH 0139/1807] Final pagination docs. Added getFirst and getLast methods. --- system/Pager/PagerRenderer.php | 32 +++++++++++++++++++ system/Pager/Views/default_full.php | 10 ++++++ tests/system/Pager/PagerRendererTest.php | 22 +++++++++++-- .../source/libraries/pagination.rst | 25 ++++++++++++++- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/system/Pager/PagerRenderer.php b/system/Pager/PagerRenderer.php index a2b33104519d..4fe1dd75bb6b 100644 --- a/system/Pager/PagerRenderer.php +++ b/system/Pager/PagerRenderer.php @@ -150,6 +150,38 @@ public function getNext(): string //-------------------------------------------------------------------- + /** + * Returns the URI of the first page. + * + * @return string + */ + public function getFirst(): string + { + $uri = clone $this->uri; + + $uri->addQuery('page', 1); + + return (string)$uri; + } + + //-------------------------------------------------------------------- + + /** + * Returns the URI of the last page. + * + * @return string + */ + public function getLast(): string + { + $uri = clone $this->uri; + + $uri->addQuery('page', $this->pageCount); + + return (string)$uri; + } + + //-------------------------------------------------------------------- + /** * Returns an array of links that should be displayed. Each link * is represented by another array containing of the URI the link diff --git a/system/Pager/Views/default_full.php b/system/Pager/Views/default_full.php index 5966b966d6ce..8ead28a7eb0f 100644 --- a/system/Pager/Views/default_full.php +++ b/system/Pager/Views/default_full.php @@ -3,6 +3,11 @@ \ No newline at end of file diff --git a/tests/system/Pager/PagerRendererTest.php b/tests/system/Pager/PagerRendererTest.php index ca596bc9570f..17571a0e450a 100644 --- a/tests/system/Pager/PagerRendererTest.php +++ b/tests/system/Pager/PagerRendererTest.php @@ -52,9 +52,6 @@ public function testHasPreviousReturnsTrueWhenFirstIsMoreThanOne() //-------------------------------------------------------------------- - /** - * @group single - */ public function testGetPreviousWhenSurroundCountIsZero() { $uri = $this->uri; @@ -173,4 +170,23 @@ public function testLinksBasics() //-------------------------------------------------------------------- + public function testGetFirstAndGetLast() + { + $uri = $this->uri; + $uri->addQuery('foo', 'bar'); + + $details = [ + 'uri' => $uri, + 'pageCount' => 50, + 'currentPage' => 4, + 'total' => 100 + ]; + + $pager = new PagerRenderer($details); + + $this->assertEquals('http://example.com/foo?foo=bar&page=1', $pager->getFirst()); + $this->assertEquals('http://example.com/foo?foo=bar&page=50', $pager->getLast()); + } + + //-------------------------------------------------------------------- } diff --git a/user_guide_src/source/libraries/pagination.rst b/user_guide_src/source/libraries/pagination.rst index a7071261f57a..395d16625659 100644 --- a/user_guide_src/source/libraries/pagination.rst +++ b/user_guide_src/source/libraries/pagination.rst @@ -165,6 +165,11 @@ usefullness. It is easiest to demonstrate creating a new view by showing you the @@ -210,6 +220,19 @@ Since the first link displayed is page one, ``hasPrevious()`` would return **fal These methods return the URL for the previous or next pages of results on either side of the numbered links. See the previous paragraph for a full explanation. +**getFirst()** +**getLast()** + +Much like ``getPrevious()`` and ``getNext()``, these methods return links to the first and last pages in the +result set. + **links()** -Returns an array of data about all of the numbered links. \ No newline at end of file +Returns an array of data about all of the numbered links. Each link's array contains the uri for the link, the +title, which is just the number, and a boolean that tells whether the link is the current/active link or not:: + + $link = [ + 'active' => false, + 'uri' => 'http://example.com/foo?page=2', + 'title' => 1 + ]; From 1d9a7dae2b17d035f0369ae853a7001c7b21cd12 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 29 Jul 2016 00:20:01 -0500 Subject: [PATCH 0140/1807] Database connection can now pretend to do a query. First step toward doing a prepare. --- system/Database/BaseConnection.php | 41 ++++++++++++++++++++-- tests/system/Database/Live/PretendTest.php | 27 ++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/system/Database/Live/PretendTest.php diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 9e4ca66e7cb4..74819de05277 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -278,6 +278,14 @@ abstract class BaseConnection implements ConnectionInterface */ protected $connectDuration; + /** + * If true, no queries will actually be + * ran against the database. + * + * @var bool + */ + protected $pretend = false; + //-------------------------------------------------------------------- /** @@ -523,8 +531,10 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da $startTime = microtime(true); - // Run the query - if (false === ($this->resultID = $this->simpleQuery($query->getQuery()))) + + + // Run the query for real + if (! $this->pretend && false === ($this->resultID = $this->simpleQuery($query->getQuery()))) { $query->setDuration($startTime, $startTime); @@ -545,7 +555,12 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da $this->queries[] = $query; } - return new $resultClass($this->connID, $this->resultID); + // If $pretend is true, then we just want to return + // the actual query object here. There won't be + // any results to return. + return $this->pretend + ? $query + : new $resultClass($this->connID, $this->resultID); } //-------------------------------------------------------------------- @@ -1289,6 +1304,26 @@ public function getFieldData(string $table) //-------------------------------------------------------------------- + /** + * Allows the engine to be set into a mode where queries are not + * actually executed, but they are still generated, timed, etc. + * + * This is primarily used by the prepared query functionality. + * + * @param bool $pretend + * + * @return $this + */ + public function pretend(bool $pretend = true) + { + $this->pretend = $pretend; + + return $this; + } + + //-------------------------------------------------------------------- + + /** * Returns the last error code and message. * diff --git a/tests/system/Database/Live/PretendTest.php b/tests/system/Database/Live/PretendTest.php new file mode 100644 index 000000000000..f872b2fef563 --- /dev/null +++ b/tests/system/Database/Live/PretendTest.php @@ -0,0 +1,27 @@ +db->pretend(false) + ->table('user') + ->get(); + + $this->assertFalse($result instanceof Query); + + $result = $this->db->pretend(true) + ->table('user') + ->get(); + + $this->assertTrue($result instanceof Query); + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file From a1114e1e3cbe5aeb705b6977c715cf79ca4c32b0 Mon Sep 17 00:00:00 2001 From: Geoffrey Hughes Date: Sat, 30 Jul 2016 11:42:34 +1200 Subject: [PATCH 0141/1807] Add cache handler for predis Signed-off-by: Geoffrey Hughes --- application/Config/Cache.php | 16 ++ system/Cache/Handlers/PredisHandler.php | 263 ++++++++++++++++++++++++ system/Config/AutoloadConfig.php | 1 + 3 files changed, 280 insertions(+) create mode 100644 system/Cache/Handlers/PredisHandler.php diff --git a/application/Config/Cache.php b/application/Config/Cache.php index e297929b42a4..c671082f6c1a 100644 --- a/application/Config/Cache.php +++ b/application/Config/Cache.php @@ -100,6 +100,21 @@ class Cache extends BaseConfig 'timeout' => 0, ]; + /* + | ------------------------------------------------------------------------- + | Predis settings + | ------------------------------------------------------------------------- + | Your Redis server can be specified below, if you are using + | the Predis drivers. + | + */ + public $predis = [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + ]; + /* |-------------------------------------------------------------------------- | Available Cache Handlers @@ -113,6 +128,7 @@ class Cache extends BaseConfig 'dummy' => \CodeIgniter\Cache\Handlers\DummyHandler::class, 'file' => \CodeIgniter\Cache\Handlers\FileHandler::class, 'memcached' => \CodeIgniter\Cache\Handlers\MemcachedHandler::class, + 'predis' => \CodeIgniter\Cache\Handlers\PredisHandler::class, 'redis' => \CodeIgniter\Cache\Handlers\RedisHandler::class, 'wincache' => \CodeIgniter\Cache\Handlers\WincacheHandler::class, ]; diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php new file mode 100644 index 000000000000..07de72426d80 --- /dev/null +++ b/system/Cache/Handlers/PredisHandler.php @@ -0,0 +1,263 @@ + 'tcp', + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + ]; + + /** + * Predis connection + * + * @var Predis + */ + protected $redis; + + //-------------------------------------------------------------------- + + public function __construct($config) + { + $this->prefix = $config->prefix ?: ''; + + if (isset($config->predis)) + { + $this->config = array_merge($this->config, $config->predis); + } + } + + //-------------------------------------------------------------------- + + /** + * Takes care of any handler-specific setup that must be done. + */ + public function initialize() + { + try + { + // Create a new instance of Predis\Client + $this->redis = new \Predis\Client($this->config, ['prefix' => $this->prefix]); + + // Check if the connection is valid by trying to get the time. + $this->redis->time(); + } + catch (Exception $e) + { + // thrown if can't connect to redis server. + throw new CriticalError('Cache: Predis connection refused ('.$e->getMessage().')'); + } + } + + //-------------------------------------------------------------------- + + /** + * Attempts to fetch an item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function get(string $key) + { + $data = array_combine( + ['__ci_type', '__ci_value'], + $this->redis->hmget($key, ['__ci_type', '__ci_value']) + ); + + if (! isset($data['__ci_type'], $data['__ci_value']) OR $data['__ci_value'] === false) + { + return false; + } + + switch ($data['__ci_type']) + { + case 'array': + case 'object': + return unserialize($data['__ci_value']); + case 'boolean': + case 'integer': + case 'double': // Yes, 'double' is returned and NOT 'float' + case 'string': + case 'NULL': + return settype($data['__ci_value'], $data['__ci_type']) + ? $data['__ci_value'] + : false; + case 'resource': + default: + return false; + } + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by predis in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param bool $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, int $ttl = 60, bool $raw = false) + { + switch ($data_type = gettype($value)) + { + case 'array': + case 'object': + $value = serialize($value); + break; + case 'boolean': + case 'integer': + case 'double': // Yes, 'double' is returned and NOT 'float' + case 'string': + case 'NULL': + break; + case 'resource': + default: + return false; + } + + if (! $this->redis->hmset($key, ['__ci_type' => $data_type, '__ci_value' => $value])) + { + return false; + } + + $this->redis->expireat($key, time()+$ttl); + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + return ($this->redis->del($key) === 1); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, int $offset = 1) + { + return $this->redis->hincrby($key, 'data', $offset); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, int $offset = 1) + { + return $this->redis->hincrby($key, 'data', -$offset); + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + return $this->redis->flushdb(); + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return $this->redis->info(); + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + $data = array_combine(['__ci_value'], $this->redis->hmget($key, ['__ci_value'])); + + if (isset($data['__ci_value']) AND $data['__ci_value'] !== false) + { + return array( + 'expire' => time() + $this->redis->ttl($key), + 'data' => $data['__ci_value'] + ); + } + + return FALSE; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return class_exists('\Predis\Client'); + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index b50af3eb9790..b17472a6815a 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -119,6 +119,7 @@ public function __construct() 'CodeIgniter\Cache\Handlers\DummyHandler' => BASEPATH.'Cache/Handlers/DummyHandler.php', 'CodeIgniter\Cache\Handlers\FileHandler' => BASEPATH.'Cache/Handlers/FileHandler.php', 'CodeIgniter\Cache\Handlers\MemcachedHandler' => BASEPATH.'Cache/Handlers/MemcachedHandler.php', + 'CodeIgniter\Cache\Handlers\PredisHandler' => BASEPATH.'Cache/Handlers/PredisHandler.php', 'CodeIgniter\Cache\Handlers\RedisHandler' => BASEPATH.'Cache/Handlers/RedisHandler.php', 'CodeIgniter\Cache\Handlers\WincacheHandler' => BASEPATH.'Cache/Handlers/WincacheHandler.php', 'CodeIgniter\Controller' => BASEPATH.'Controller.php', From 0a452edc47531759d30a9e91f031f005edc59578 Mon Sep 17 00:00:00 2001 From: Geoffrey Hughes Date: Sat, 30 Jul 2016 12:01:32 +1200 Subject: [PATCH 0142/1807] Fix 'CodeIgniter\Cache\Handlers\Redis' not found Signed-off-by: Geoffrey Hughes --- system/Cache/Handlers/RedisHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index 725b14e2ed02..e10e9d9bc250 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -53,7 +53,7 @@ public function initialize() { $config = $this->config; - $this->redis = new Redis(); + $this->redis = new \Redis(); try { From 925cddb0068a0203c5b5c520c480a6faf8724e23 Mon Sep 17 00:00:00 2001 From: Geoffrey Hughes Date: Sat, 30 Jul 2016 16:04:21 +1200 Subject: [PATCH 0143/1807] Get rid of the predis config and use redis config. Signed-off-by: Geoffrey Hughes --- application/Config/Cache.php | 17 +---------------- system/Cache/Handlers/PredisHandler.php | 4 ++-- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/application/Config/Cache.php b/application/Config/Cache.php index c671082f6c1a..eda7a08631ae 100644 --- a/application/Config/Cache.php +++ b/application/Config/Cache.php @@ -90,7 +90,7 @@ class Cache extends BaseConfig | Redis settings | ------------------------------------------------------------------------- | Your Redis server can be specified below, if you are using - | the Redis drivers. + | the Redis or Predis drivers. | */ public $redis = [ @@ -100,21 +100,6 @@ class Cache extends BaseConfig 'timeout' => 0, ]; - /* - | ------------------------------------------------------------------------- - | Predis settings - | ------------------------------------------------------------------------- - | Your Redis server can be specified below, if you are using - | the Predis drivers. - | - */ - public $predis = [ - 'host' => '127.0.0.1', - 'password' => null, - 'port' => 6379, - 'timeout' => 0, - ]; - /* |-------------------------------------------------------------------------- | Available Cache Handlers diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php index 07de72426d80..b5bd1db34ada 100644 --- a/system/Cache/Handlers/PredisHandler.php +++ b/system/Cache/Handlers/PredisHandler.php @@ -39,9 +39,9 @@ public function __construct($config) { $this->prefix = $config->prefix ?: ''; - if (isset($config->predis)) + if (isset($config->redis)) { - $this->config = array_merge($this->config, $config->predis); + $this->config = array_merge($this->config, $config->redis); } } From d518d16992b6c1d134f2428ae7d1bdc6b508bcd9 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sat, 30 Jul 2016 00:06:51 -0500 Subject: [PATCH 0144/1807] Initial start on prepared queries --- system/Database/BaseConnection.php | 50 ++++++++ system/Database/BasePreparedQuery.php | 140 +++++++++++++++++++++ system/Database/MySQLi/Connection.php | 3 +- system/Database/MySQLi/PreparedQuery.php | 56 +++++++++ system/Database/PreparedQueryInterface.php | 68 ++++++++++ 5 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 system/Database/BasePreparedQuery.php create mode 100644 system/Database/MySQLi/PreparedQuery.php create mode 100644 system/Database/PreparedQueryInterface.php diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 74819de05277..329a0c4a4e90 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -608,6 +608,46 @@ public function table($tableName) //-------------------------------------------------------------------- + /** + * Creates a prepared statement with the database that can then + * be used to execute multiple statements against. Within the + * closure, you would build the query in any normal way, though + * the Query Builder is the expected manner. + * + * Example: + * $stmt = $db->prepare(function($db) + * { + * return $db->table('users') + * ->where('id', 1) + * ->get(); + * }) + * + * @param \Closure $func + * @param array $options Passed to the prepare() method + * + * @return PreparedQueryInterface|null + */ + public function prepare(\Closure $func, array $options = []) + { + $this->pretend(true); + + $sql = $func($this); + + $this->pretend(false); + + if ($sql instanceof QueryInterface) + { + $sql = $sql->getOriginalQuery(); + } + + $class = str_ireplace('Connection', 'PreparedQuery', get_class($this)); + $class = new $class($this); + + return $class->prepare($sql, $options); + } + + //-------------------------------------------------------------------- + /** * Returns an array containing all of the * @@ -1323,6 +1363,16 @@ public function pretend(bool $pretend = true) //-------------------------------------------------------------------- + public function __get(string $name) + { + if (isset($this->$name)) + { + return $this->$name; + } + } + + //-------------------------------------------------------------------- + /** * Returns the last error code and message. diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php new file mode 100644 index 000000000000..5d60d8d67599 --- /dev/null +++ b/system/Database/BasePreparedQuery.php @@ -0,0 +1,140 @@ +db =& $db; + } + + //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return ResultInterface + */ + abstract public function execute(array $data); + + //-------------------------------------------------------------------- + + /** + * Takes an array containing multiple rows of data that should + * be inserted, one after the other, using the prepared statement. + * + * @param array $data + * + * @return \CodeIgniter\Database\ResultInterface + */ + public function executeBatch(array $data): ResultInterface + { + + } + + //-------------------------------------------------------------------- + + /** + * Prepares the query against the database, and saves the connection + * info necessary to execute the query later. + * + * NOTE: This version is based on SQL code. Child classes should + * override this method. + * + * @param string $sql + * @param array $options Passed to the connection's prepare statement. + * + * @return mixed + */ + abstract public function prepare(string $sql, array $options = []); + + //-------------------------------------------------------------------- + + /** + * Returns the SQL that has been prepared. + * + * @return string + */ + public function getQueryString(): string + { + return $this->sql; + } + + //-------------------------------------------------------------------- + + /** + * A helper to determine if any error exists. + * + * @return bool + */ + public function hasError() + { + return ! empty($this->errorString); + } + + //-------------------------------------------------------------------- + + + /** + * Returns the error code created while executing this statement. + * + * @return string + */ + public function getErrorCode(): int + { + return $this->errorCode; + } + + //-------------------------------------------------------------------- + + /** + * Returns the error message created while executing this statement. + * + * @return string + */ + public function getErrorMessage(): string + { + return $this->errorString; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 8f20826f1315..c2d272406331 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -89,7 +89,9 @@ class Connection extends BaseConnection implements ConnectionInterface * Connect to the database. * * @param bool $persistent + * * @return mixed + * @throws \CodeIgniter\DatabaseException */ public function connect($persistent = false) { @@ -447,5 +449,4 @@ public function insertID() //-------------------------------------------------------------------- - } diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php new file mode 100644 index 000000000000..21ec595eaa65 --- /dev/null +++ b/system/Database/MySQLi/PreparedQuery.php @@ -0,0 +1,56 @@ +sql = rtrim($sql, ';'); + + // MySQLi also only supports positional placeholders (?) + // so we need to replace our named placeholders (:name) + $this->sql = preg_replace('/:[\S]+/', '?', $this->sql); + + if (! $this->statement = $this->db->mysqli->prepare($this->sql)) + { + $this->errorCode = $this->db->mysqli->errno; + $this->errorString = $this->db->mysqli->error; + } + + return $this; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/PreparedQueryInterface.php b/system/Database/PreparedQueryInterface.php new file mode 100644 index 000000000000..be4f454fd199 --- /dev/null +++ b/system/Database/PreparedQueryInterface.php @@ -0,0 +1,68 @@ + Date: Sat, 30 Jul 2016 00:09:36 -0500 Subject: [PATCH 0145/1807] Removing magic getter since it was causing problems --- system/Database/BaseConnection.php | 11 ----------- system/Database/MySQLi/Connection.php | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 329a0c4a4e90..2ab38b7fffa8 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1363,17 +1363,6 @@ public function pretend(bool $pretend = true) //-------------------------------------------------------------------- - public function __get(string $name) - { - if (isset($this->$name)) - { - return $this->$name; - } - } - - //-------------------------------------------------------------------- - - /** * Returns the last error code and message. * diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index c2d272406331..94d098f53773 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -81,7 +81,7 @@ class Connection extends BaseConnection implements ConnectionInterface * * @var MySQLi */ - protected $mysqli; + public $mysqli; //-------------------------------------------------------------------- From 665aeac5e78be14f2cce16867a7d7430d14c2914 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sat, 30 Jul 2016 22:15:52 -0500 Subject: [PATCH 0146/1807] Fix overzealous MySQLi placeholder conversion --- system/Database/MySQLi/PreparedQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 21ec595eaa65..17b15be4e0d0 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -41,7 +41,7 @@ public function prepare(string $sql, array $options = []) // MySQLi also only supports positional placeholders (?) // so we need to replace our named placeholders (:name) - $this->sql = preg_replace('/:[\S]+/', '?', $this->sql); + $this->sql = preg_replace('/:[^\s,)]+/', '?', $this->sql); if (! $this->statement = $this->db->mysqli->prepare($this->sql)) { From 5689bf04051aea1bb09d6dbb890de95a2e40a75e Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Sun, 31 Jul 2016 21:30:37 +0800 Subject: [PATCH 0147/1807] Fixed User Guide spelling, grammar and punctuation issues. --- user_guide_src/source/general/debugging.rst | 2 +- .../source/libraries/content_negotiation.rst | 8 +++--- .../source/libraries/curlrequest.rst | 6 ++--- user_guide_src/source/libraries/message.rst | 4 +-- .../source/libraries/uploaded_files.rst | 25 +++++++------------ user_guide_src/source/libraries/uri.rst | 8 +++--- .../source/tutorial/news_section.rst | 2 +- 7 files changed, 24 insertions(+), 31 deletions(-) diff --git a/user_guide_src/source/general/debugging.rst b/user_guide_src/source/general/debugging.rst index 89de0f33addf..00a3dc1da7c6 100644 --- a/user_guide_src/source/general/debugging.rst +++ b/user_guide_src/source/general/debugging.rst @@ -19,7 +19,7 @@ Enabling Kint ============= By default, Kint is enabled in **development** and **testing** environments only. This can be altered by modifying -the ``$useKing`` value in the environment configuration section of the main **index.php** file:: +the ``$useKint`` value in the environment configuration section of the main **index.php** file:: $useKint = true; diff --git a/user_guide_src/source/libraries/content_negotiation.rst b/user_guide_src/source/libraries/content_negotiation.rst index 37daed019d44..27eaec2cf415 100644 --- a/user_guide_src/source/libraries/content_negotiation.rst +++ b/user_guide_src/source/libraries/content_negotiation.rst @@ -39,7 +39,7 @@ Media ===== The first aspect to look at is handling 'media' negotiations. These are provided by the ``Accept`` header and -is one of the most complex headers available. A common example is the client telling the server what format it +are one of the most complex headers available. A common example is the client telling the server what format it wants the data in. This is especially common in API's. For example, a client might request JSON formatted data from an API endpoint:: @@ -47,7 +47,7 @@ from an API endpoint:: Accept: application/json The server now needs to provide a list of what type of content it can provide. In this example, the API might -be able to return data as raw HTML, JSON, or XML. This list should be provided in order of preference.:: +be able to return data as raw HTML, JSON, or XML. This list should be provided in order of preference:: $supported = [ 'application/json', @@ -79,8 +79,8 @@ header:: GET /foo HTTP/1.1 Accept-Language: fr; q=1.0, en; q=0.5 -In this example, the browser would prefer french, with a second choice of english. If your website supports english -and german you would do something like:: +In this example, the browser would prefer French, with a second choice of English. If your website supports English +and German you would do something like:: $supported = [ 'en', diff --git a/user_guide_src/source/libraries/curlrequest.rst b/user_guide_src/source/libraries/curlrequest.rst index 31d2fff18448..ff0270ca631f 100644 --- a/user_guide_src/source/libraries/curlrequest.rst +++ b/user_guide_src/source/libraries/curlrequest.rst @@ -6,7 +6,7 @@ The ``CURLRequest`` class is a lightweight HTTP client based on CURL that allows web sites and servers. It can be used to get the contents of a Google search, retrieve a web page or image, or communicate with an API, among many other things. -This class is modeled after the `Guzzle HTTP Client `_ library since +This class is modelled after the `Guzzle HTTP Client `_ library since it is one of the more widely used libraries. Where possible, the syntax has been kept the same so that if your application needs something a little more powerful than what this library provides, you will have to change very little to move over to use Guzzle. @@ -26,7 +26,7 @@ To load with the Services class call the ``curlrequest()`` method:: $client = CodeIgniter\HTTP\Services::curlrequest(); You can pass in an array of default options as the first parameter to modify how cURL will handle the request. -The options are described later in this document.:: +The options are described later in this document:: $options = [ 'base_uri' => 'http://example.com/api/v1/', @@ -36,7 +36,7 @@ The options are described later in this document.:: When creating the class manually, you need to pass a few dependencies in. The first parameter is an instance of the ``Config\App`` class. The second parameter is a URI instance. The third -parameter is a Response object. The fourth parameter is the optional ``$options`` array.:: +parameter is a Response object. The fourth parameter is the optional ``$options`` array:: $client = new CodeIgniter\HTTP\CURLRequest( new Config\App(), diff --git a/user_guide_src/source/libraries/message.rst b/user_guide_src/source/libraries/message.rst index e2c1a8769207..37c0a4208f07 100644 --- a/user_guide_src/source/libraries/message.rst +++ b/user_guide_src/source/libraries/message.rst @@ -195,7 +195,7 @@ Class Reference Per the `RFC `_ the match has the option of returning a default value, like this method does, or to return an empty string. If you need to have an exact match and - would like an empty string returned instead, pass ``true`` as the second parameter.:: + would like an empty string returned instead, pass ``true`` as the second parameter:: // Returns empty string if no match. $imageType = $message->negotiateMedia($supported, true); @@ -243,7 +243,7 @@ Class Reference :rtype: string Determines the best match between the application-supported languages and the ``Accept-Language`` header value. - If no match is found, will return teh first element of the ``$supported`` array.:: + If no match is found, will return the first element of the ``$supported`` array:: $supported = [ 'en', diff --git a/user_guide_src/source/libraries/uploaded_files.rst b/user_guide_src/source/libraries/uploaded_files.rst index 00c6d84ec490..b1eae323ffd2 100644 --- a/user_guide_src/source/libraries/uploaded_files.rst +++ b/user_guide_src/source/libraries/uploaded_files.rst @@ -21,8 +21,7 @@ are not aware of. CodeIgniter helps with both of these situations by standardizi common interface. Files are accessed through the current ``IncomingRequest`` instance. To retrieve all files that were uploaded with this -request, use ``getFiles()``. This will return an array of files represented by instances of ``CodeIgniter\HTTP\Files\UploadedFile``. -:: +request, use ``getFiles()``. This will return an array of files represented by instances of ``CodeIgniter\HTTP\Files\UploadedFile``:: $files = $this->request->getFiles(); @@ -110,21 +109,20 @@ File Names You can retrieve the original filename provided by the client with the ``getName()`` method. This will typically be the filename sent by the client, and should not be trusted. If the file has been moved, this will return the final name of -the moved file.:: +the moved file:: $name = $file->getName(); **getTempName()** -To get the name of the temp file that was created during the upload, you can use the ``getTempName()`` method.:: +To get the name of the temp file that was created during the upload, you can use the ``getTempName()`` method:: $tempfile = $file->getTempName(); **getRandomName()** You can generate a cryptographically secure random filename, with the current timestamp prepended, with the ``getRandomName()`` -method. This is especially useful to rename files when moving it so that the filename is unguessable. -:: +method. This is especially useful to rename files when moving it so that the filename is unguessable:: // Generates something like: 1465965676_385e33f741.jpg $newName = $file->getRandomName(); @@ -137,8 +135,7 @@ Other File Info Attempts to determine the file extension based on the trusted ``getType()`` method instead of the value listed in ``$_FILES``. If the mime type is unknown, will return null. Uses the values in **application/Config/Mimes.php** -to determine extension. -:: +to determine extension:: // Returns 'jpg' (WITHOUT the period) $ext = $file->getExtension(); @@ -146,16 +143,14 @@ to determine extension. **getClientExtension()** Returns the original file extension, based on the file name that was uploaded. This is NOT a trusted source. For a -trusted version, use ``getExtension()`` instead. -:: +trusted version, use ``getExtension()`` instead:: $ext = $file->getClientExtension(); **getType()** Retrieve the media type (mime type) of the file. Does not use information from the $_FILES array, but uses other methods to more -accurately determine the type of file, like ``finfo``, or ``mime_content_type``. -:: +accurately determine the type of file, like ``finfo``, or ``mime_content_type``:: $type = $file->getType(); @@ -164,8 +159,7 @@ accurately determine the type of file, like ``finfo``, or ``mime_content_type``. **getClientType()** Returns the mime type (mime type) of the file as provided by the client. This is NOT a trusted value. For a trusted -version, use ``getType()`` instead. -:: +version, use ``getType()`` instead:: $type = $file->getClientType(); @@ -174,8 +168,7 @@ version, use ``getType()`` instead. **getSize()** Returns the size of the uploaded file in bytes. You can pass in either 'kb' or 'mb' as the first parameter to get -the results in kilobytes or megabytes, respectively. -:: +the results in kilobytes or megabytes, respectively:: $bytes = $file->getSize(); // 256901 $kilobytes = $file->getSize('kb'); // 250.880 diff --git a/user_guide_src/source/libraries/uri.rst b/user_guide_src/source/libraries/uri.rst index 516f8182df1e..bdd9e93b98af 100644 --- a/user_guide_src/source/libraries/uri.rst +++ b/user_guide_src/source/libraries/uri.rst @@ -22,7 +22,7 @@ Alternatively, you can use the ``service()`` function to return an instance for $uri = service('uri'); When you create the new instance, you can pass a full or partial URL in the constructor and it will be parsed -into it's appropriate sections:: +into its appropriate sections:: $uri = new \CodeIgniter\HTTP\URI('http://www.example.com/some/path'); $uri = service('uri', 'http://www.example.com/some/path'); @@ -30,7 +30,7 @@ into it's appropriate sections:: The Current URI --------------- -Many times, all you really want is an object representing the current URL of this request. This can be accesssed +Many times, all you really want is an object representing the current URL of this request. This can be accessed in two different ways. The first, is to grab it directly from the current request object. Assuming that you're in a controller that extends ``CodeIgniter\Controller`` you can get it like:: @@ -209,7 +209,7 @@ you can use the ``stripQuery()`` and ``keepQuery()`` methods to change the actua Fragment -------- -Fragments are the portion at the end of the URL, preceeded by the pound-sign (#). In HTML URL's these are links +Fragments are the portion at the end of the URL, preceded by the pound-sign (#). In HTML URL's these are links to an on-page anchor. Media URI's can make use of them in various other ways. :: @@ -223,7 +223,7 @@ URI Segments ============ Each section of the path between the slashes are a single segment. The URI class provides a simple way to determine -what the value of the segments are. The segments start at 1 being the furthest left of the path. +what the values of the segments are. The segments start at 1 being the furthest left of the path. :: // URI = http://example.com/users/15/profile diff --git a/user_guide_src/source/tutorial/news_section.rst b/user_guide_src/source/tutorial/news_section.rst index 83f59e273682..546b34bd32b5 100644 --- a/user_guide_src/source/tutorial/news_section.rst +++ b/user_guide_src/source/tutorial/news_section.rst @@ -222,7 +222,7 @@ Routing Because of the wildcard routing rule created earlier, you need an extra route to view the controller that you just made. Modify your routing file (*application/config/routes.php*) so it looks as follows. -This makes sure the requests reaches the ``News`` controller instead of +This makes sure the requests reach the ``News`` controller instead of going directly to the ``Pages`` controller. The first line routes URI's with a slug to the ``view()`` method in the ``News`` controller. From 31ef2655916729c1a74ccb88e5a1068830761258 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 31 Jul 2016 22:54:34 -0500 Subject: [PATCH 0148/1807] Fixing edge case in matching named parameters. Fixes #201 --- system/Database/MySQLi/Connection.php | 5 +++++ system/Database/Query.php | 13 +++++++------ tests/system/Database/BaseQueryTest.php | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 8f20826f1315..0db88602cd27 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -337,6 +337,11 @@ public function affectedRows(): int */ protected function _escapeString(string $str): string { + if (is_bool($str)) + { + return $str; + } + return $this->connID->real_escape_string($str); } diff --git a/system/Database/Query.php b/system/Database/Query.php index a6f6982a470c..0b7187225479 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -122,10 +122,10 @@ public function __construct(&$db) { $this->db = $db; } - + //-------------------------------------------------------------------- - - + + /** * Sets the raw query string to use for this statement. * @@ -230,7 +230,7 @@ public function getDuration(int $decimals = 6) /** * Stores the error description that happened for this query. - * + * * @param int $code * @param string $error */ @@ -392,7 +392,8 @@ protected function matchNamedBinds(string $sql, array $binds) { $escapedValue = '('.implode(',', $escapedValue).')'; } - $sql = str_replace(':'.$placeholder, $escapedValue, $sql); + + $sql = preg_replace('/:'.$placeholder.'(?!\w)/', $escapedValue, $sql); } return $sql; @@ -451,7 +452,7 @@ protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, i /** * Return text representation of the query - * + * * @return type */ public function __toString() diff --git a/tests/system/Database/BaseQueryTest.php b/tests/system/Database/BaseQueryTest.php index 9ca7e51934c7..bc51751eec61 100644 --- a/tests/system/Database/BaseQueryTest.php +++ b/tests/system/Database/BaseQueryTest.php @@ -179,4 +179,22 @@ public function testNamedBinds() } //-------------------------------------------------------------------- + + /** + * @group single + * + * @see https://github.com/bcit-ci/CodeIgniter4/issues/201 + */ + public function testSimilarNamedBinds() + { + $query = new Query($this->db); + + $query->setQuery('SELECT * FROM users WHERE sitemap = :sitemap OR site = :site', ['sitemap' => 'sitemap', 'site' => 'site']); + + $expected = "SELECT * FROM users WHERE sitemap = 'sitemap' OR site = 'site'"; + + $this->assertEquals($expected, $query->getQuery()); + } + + //-------------------------------------------------------------------- } From 64a845385719206efdaf07372ca22129cf18b0b6 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 31 Jul 2016 22:56:07 -0500 Subject: [PATCH 0149/1807] Typo in pagination docs. Fixes #196 --- user_guide_src/source/libraries/pagination.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/pagination.rst b/user_guide_src/source/libraries/pagination.rst index 395d16625659..1da33564ee29 100644 --- a/user_guide_src/source/libraries/pagination.rst +++ b/user_guide_src/source/libraries/pagination.rst @@ -78,7 +78,7 @@ methods to keep the data separate:: $data = [ 'users' => $userModel->paginate(10, 'group1'), 'pages' => $pageModel->paginate(15, 'group2'), - 'pager' => $model->pager + 'pager' => $userModel->pager ]; echo view('users/index', $data); From 366a9f7b172d902438b9e477509f1ff5e81501a1 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 31 Jul 2016 23:38:35 -0500 Subject: [PATCH 0150/1807] More work on prepare queries... though I can't get it to work just yet. --- system/Database/BasePreparedQuery.php | 32 ++++++------ system/Database/MySQLi/PreparedQuery.php | 57 ++++++++++++++++------ system/Database/PreparedQueryInterface.php | 21 +++----- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php index 5d60d8d67599..9adb19e8b4a0 100644 --- a/system/Database/BasePreparedQuery.php +++ b/system/Database/BasePreparedQuery.php @@ -54,22 +54,7 @@ public function __construct(ConnectionInterface $db) * * @return ResultInterface */ - abstract public function execute(array $data); - - //-------------------------------------------------------------------- - - /** - * Takes an array containing multiple rows of data that should - * be inserted, one after the other, using the prepared statement. - * - * @param array $data - * - * @return \CodeIgniter\Database\ResultInterface - */ - public function executeBatch(array $data): ResultInterface - { - - } + abstract public function execute(...$data); //-------------------------------------------------------------------- @@ -89,6 +74,21 @@ abstract public function prepare(string $sql, array $options = []); //-------------------------------------------------------------------- + /** + * Explicity closes the statement. + */ + public function close() + { + if (! is_object($this->statement)) + { + return; + } + + $this->statement->close(); + } + + //-------------------------------------------------------------------- + /** * Returns the SQL that has been prepared. * diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 17b15be4e0d0..1b696c681159 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -5,21 +5,6 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface { - /** - * Takes a new set of data and runs it against the currently - * prepared query. Upon success, will return a Results object. - * - * @param array $data - * - * @return ResultInterface|null - */ - public function execute(array $data) - { - - } - - //-------------------------------------------------------------------- - /** * Prepares the query against the database, and saves the connection * info necessary to execute the query later. @@ -53,4 +38,46 @@ public function prepare(string $sql, array $options = []) } //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return ResultInterface + */ + public function execute(...$data) + { + if (is_null($this->statement)) + { + throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + } + + // First off -bind the parameters + $bindTypes = ''; + + foreach ($data as $item) + { + if (is_integer($item)) + { + $bindTypes .= 'i'; + } + elseif (is_numeric($item)) + { + $bindTypes .= 'd'; + } + else + { + $bindTypes .= 's'; + } + } +die(var_dump($data)); + $this->statement->bind_param($bindTypes, ...$data); + + return $this->statement->execute(); + } + + //-------------------------------------------------------------------- + } diff --git a/system/Database/PreparedQueryInterface.php b/system/Database/PreparedQueryInterface.php index be4f454fd199..0417ab1a1959 100644 --- a/system/Database/PreparedQueryInterface.php +++ b/system/Database/PreparedQueryInterface.php @@ -10,19 +10,7 @@ interface PreparedQueryInterface * * @return ResultInterface */ - public function execute(array $data); - - //-------------------------------------------------------------------- - - /** - * Takes an array containing multiple rows of data that should - * be inserted, one after the other, using the prepared statement. - * - * @param array $data - * - * @return \CodeIgniter\Database\ResultInterface - */ - public function executeBatch(array $data): ResultInterface; + public function execute(...$data); //-------------------------------------------------------------------- @@ -39,6 +27,13 @@ public function prepare(string $sql, array $options = []); //-------------------------------------------------------------------- + /** + * Explicity closes the statement. + */ + public function close(); + + //-------------------------------------------------------------------- + /** * Returns the SQL that has been prepared. * From 61620c157d9b6b629942eff72b70c8bb8f41569c Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Mon, 1 Aug 2016 21:27:50 +0800 Subject: [PATCH 0151/1807] Reverted "are" back to "is" as suggested by lonnieezell. --- user_guide_src/source/libraries/content_negotiation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/content_negotiation.rst b/user_guide_src/source/libraries/content_negotiation.rst index 27eaec2cf415..eab75d43ab12 100644 --- a/user_guide_src/source/libraries/content_negotiation.rst +++ b/user_guide_src/source/libraries/content_negotiation.rst @@ -39,7 +39,7 @@ Media ===== The first aspect to look at is handling 'media' negotiations. These are provided by the ``Accept`` header and -are one of the most complex headers available. A common example is the client telling the server what format it +is one of the most complex headers available. A common example is the client telling the server what format it wants the data in. This is especially common in API's. For example, a client might request JSON formatted data from an API endpoint:: From a30ea0a279283be3661677e8854e8f2f385e6b2b Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Tue, 2 Aug 2016 07:10:20 +0700 Subject: [PATCH 0152/1807] correcting literal code block on user guide Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/concepts/autoloader.rst | 2 +- user_guide_src/source/concepts/services.rst | 2 +- .../source/contributing/styleguide.rst | 2 +- .../source/contributing/workflow.rst | 4 +-- user_guide_src/source/database/migration.rst | 10 +++---- user_guide_src/source/database/model.rst | 8 +++--- .../source/database/query_builder.rst | 2 +- user_guide_src/source/general/cli.rst | 2 +- .../source/general/configuration.rst | 2 +- .../source/general/core_classes.rst | 2 +- user_guide_src/source/general/debugging.rst | 2 +- user_guide_src/source/general/errors.rst | 10 +++---- user_guide_src/source/general/hooks.rst | 8 +++--- user_guide_src/source/general/logging.rst | 2 +- user_guide_src/source/general/view_cells.rst | 2 +- user_guide_src/source/libraries/cli.rst | 4 +-- .../source/libraries/curlrequest.rst | 26 +++++++++---------- .../source/libraries/incomingrequest.rst | 10 +++---- user_guide_src/source/libraries/message.rst | 12 ++++----- user_guide_src/source/libraries/response.rst | 8 +++--- user_guide_src/source/libraries/sessions.rst | 2 +- 21 files changed, 61 insertions(+), 61 deletions(-) diff --git a/user_guide_src/source/concepts/autoloader.rst b/user_guide_src/source/concepts/autoloader.rst index 86a28fe1c195..1e277dbaab84 100644 --- a/user_guide_src/source/concepts/autoloader.rst +++ b/user_guide_src/source/concepts/autoloader.rst @@ -53,7 +53,7 @@ have a trailing slash. By default, the application folder is namespace to the ``App`` namespace. While you are not forced to namespace the controllers, libraries, or models in the application directory, if you do, they will be found under the ``App`` namespace. You may change this namespace by editing the **/application/Config/Constants.php** file and setting the -new namespace value under the ``APP_NAMESPACE`` setting.:: +new namespace value under the ``APP_NAMESPACE`` setting:: define('APP_NAMESPACE', 'App'); diff --git a/user_guide_src/source/concepts/services.rst b/user_guide_src/source/concepts/services.rst index 729fa583da02..8c87049ac13b 100644 --- a/user_guide_src/source/concepts/services.rst +++ b/user_guide_src/source/concepts/services.rst @@ -117,7 +117,7 @@ There are occasions where you need to require that only a single instance of a s is created. This is easily handled with the ``getSharedInstance()`` method that is called from within the factory method. This handles checking if an instance has been created and saved within the class, and, if not, creates a new one. All of the factory methods provide a -``$getShared = false`` value as the last parameter. You should stick to the method also.:: +``$getShared = false`` value as the last parameter. You should stick to the method also:: class Services { diff --git a/user_guide_src/source/contributing/styleguide.rst b/user_guide_src/source/contributing/styleguide.rst index 7dcfcc7a4148..c84bb1b994b9 100644 --- a/user_guide_src/source/contributing/styleguide.rst +++ b/user_guide_src/source/contributing/styleguide.rst @@ -87,7 +87,7 @@ while maintaining consistency of code appearance. Following the "best practice" above, the following code block would have a single tab at the beginning of each line containing braces, and two tabs at the beginning of the -nested statements. No alignment is implied.:: +nested statements. No alignment is implied:: { $first = 1; diff --git a/user_guide_src/source/contributing/workflow.rst b/user_guide_src/source/contributing/workflow.rst index 4f04bae9be9a..82261bffb1c0 100644 --- a/user_guide_src/source/contributing/workflow.rst +++ b/user_guide_src/source/contributing/workflow.rst @@ -71,7 +71,7 @@ Synching Within your local repository, Git will have created an alias, **origin**, for the Github repository it is bound to. You want to create an alias for the shared repository, so that you can "synch" the two, making sure that your repository -includes any other contributions that have been merged by us into the shared repo.:: +includes any other contributions that have been merged by us into the shared repo:: git remote add upstream UPSTREAM_URL @@ -102,7 +102,7 @@ change log, etc). This local branch should be named appropriately, for instance "fix/problem123" or "new/mind-reader". For instance, make sure you are in the *develop* branch, and create a -new feature branch, based on *develop*, for a new feature you are creating.:: +new feature branch, based on *develop*, for a new feature you are creating:: git checkout develop git checkout -b new/mind-reader diff --git a/user_guide_src/source/database/migration.rst b/user_guide_src/source/database/migration.rst index 3120a3e74683..88ceacbd0272 100644 --- a/user_guide_src/source/database/migration.rst +++ b/user_guide_src/source/database/migration.rst @@ -126,7 +126,7 @@ Usage Example ************* In this example some simple code is placed in **application/controllers/Migrate.php** -to update the schema.:: +to update the schema:: php index.php migrations current @@ -194,13 +194,13 @@ Rolls back all migrations, taking all database groups to a blank slate, effectiv **refresh** -Refreshes the database state by first rolling back all migrations, and then migrating to the latest version.:: +Refreshes the database state by first rolling back all migrations, and then migrating to the latest version:: > php index.php migrations refresh **status** -Displays a list of all migrations and the date and time they were ran, or '--' if they have not be ran.:: +Displays a list of all migrations and the date and time they were ran, or '--' if they have not be ran:: > php index.php migrations status Filename Migrated On @@ -271,7 +271,7 @@ Class Reference :returns: The current MigrationRunner instance :rtype: CodeIgniter\Database\MigrationRunner - Sets the path the library should look for migration files.:: + Sets the path the library should look for migration files:: $migration->setPath($path) ->latest(); diff --git a/user_guide_src/source/database/model.rst b/user_guide_src/source/database/model.rst index ecda8c529802..29bb548e0080 100644 --- a/user_guide_src/source/database/model.rst +++ b/user_guide_src/source/database/model.rst @@ -76,7 +76,7 @@ Configuring Your Model The model class has a few configuration options that can be set to allow the class' methods to work seamlessly for you. The first two are used by all of the CRUD methods to determine -what table to use and how we can find the required records.:: +what table to use and how we can find the required records:: class UserModel extends \CodeIgniter\Model { @@ -159,7 +159,7 @@ Returns a single row where the primary key matches the value passed in as the fi The value is returned in the format specified in $returnType. You can specify more than one row to return by passing an array of primaryKey values instead -of just one.:: +of just one:: $users = $userModel->find([1,2,3]); @@ -183,7 +183,7 @@ Returns all results. $users = $userModel->findAll(); -This query may be modified by interjecting Query Builder commands as needed prior to calling this method.:: +This query may be modified by interjecting Query Builder commands as needed prior to calling this method:: $users = $userModel->where('active', 1) ->findAll(); @@ -218,7 +218,7 @@ temporarily override this, you can use the withDeleted() method prior to calling **onlyDeleted()** Whereas withDeleted() will return both deleted and not-deleted rows, this method modifies -the next find* methods to return only soft deleted rows.:: +the next find* methods to return only soft deleted rows:: $deletedUsers = $userModel->onlyDeleted() ->findAll(); diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index d8ca0a15921e..4d3094e8af59 100644 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -915,7 +915,7 @@ function, or empty_table(). **$builder->emptyTable()** Generates a delete SQL string and runs the -query.:: +query:: $builder->emptyTable('mytable'); // Produces: DELETE FROM mytable diff --git a/user_guide_src/source/general/cli.rst b/user_guide_src/source/general/cli.rst index 22b03ac44aac..030c79de650f 100644 --- a/user_guide_src/source/general/cli.rst +++ b/user_guide_src/source/general/cli.rst @@ -87,7 +87,7 @@ CLI-Only Routing In your **Routes.php** file you can create routes that are only accessible from the CLI as easily as you would create any other route. Instead of using the ``get()``, ``post()``, or similar method, you would use the ``cli()`` method. Everything else -works exactly like a normal route definition.:: +works exactly like a normal route definition:: $routes->cli('tools/message/(:segment)', 'Tools::message/$1'); diff --git a/user_guide_src/source/general/configuration.rst b/user_guide_src/source/general/configuration.rst index b44470e8574f..7c81d2429a89 100644 --- a/user_guide_src/source/general/configuration.rst +++ b/user_guide_src/source/general/configuration.rst @@ -101,7 +101,7 @@ There will be times when you will have several variables of the same name. When system has no way of knowing what the correct value should be. You can protect against this by "namespacing" the variables. This is done by including the name of the config file (all lower case), followed by a period (.), and then the variable name itself. This is especially common when you -have multiple database connections.:: +have multiple database connections:: // Not namespaced db_name = "my_db" diff --git a/user_guide_src/source/general/core_classes.rst b/user_guide_src/source/general/core_classes.rst index cd5f4d1dbb75..5565fd334dd9 100644 --- a/user_guide_src/source/general/core_classes.rst +++ b/user_guide_src/source/general/core_classes.rst @@ -97,7 +97,7 @@ If you need to use a constructor in your class make sure you extend the parent c instead of the native ones (this is known as “method overridingâ€). This allows you to substantially alter the CodeIgniter core. If you are extending the Controller core class, then be sure to extend your new class in your application controller’s -constructors.:: +constructors:: class Home extends App\BaseController { diff --git a/user_guide_src/source/general/debugging.rst b/user_guide_src/source/general/debugging.rst index 00a3dc1da7c6..5fc34775fecb 100644 --- a/user_guide_src/source/general/debugging.rst +++ b/user_guide_src/source/general/debugging.rst @@ -189,7 +189,7 @@ To add data to the Vars tab you must: 2. Implement ``getVarData()`` method. The ``getVarData()`` method should return an array containing arrays of key/value pairs to display. The name of the -outer array's key is the name of the section on the Vars tab.:: +outer array's key is the name of the section on the Vars tab:: $data = [ 'section 1' => [ diff --git a/user_guide_src/source/general/errors.rst b/user_guide_src/source/general/errors.rst index 99849019318a..16e587aaf21c 100644 --- a/user_guide_src/source/general/errors.rst +++ b/user_guide_src/source/general/errors.rst @@ -71,7 +71,7 @@ PageNotFoundException This is used to signal a 404, Page Not Found error. When thrown, the system will show the view found at ``/application/views/errors/html/error_404.php``. You should customize all of the error views for your site. If, in ``Config/Routes.php``, you have specified a 404 Override, that will be called instead of the standard -404 page.:: +404 page:: if (! $page = $pageModel->find($id)) { @@ -84,7 +84,7 @@ ConfigException --------------- This exception should be used when the values from the configuration class are invalid, or when the config class -is not the right type, etc.:: +is not the right type, etc:: throw new \CodeIgniter\ConfigException(); @@ -93,7 +93,7 @@ This provides an HTTP status code of 500, and an exit code of 3. UnknownFileException -------------------- -Use this exception when a file cannot be found.:: +Use this exception when a file cannot be found:: throw new \CodeIgniter\UnknownFileException(); @@ -102,7 +102,7 @@ This provides an HTTP status code of 500, and an exit code of 4. UnknownClassException --------------------- -Use this exception when a class cannot be found.:: +Use this exception when a class cannot be found:: throw new \CodeIgniter\UnknownClassException($className); @@ -120,7 +120,7 @@ This provides an HTTP status code of 500, and an exit code of 6. UserInputException ------------------ -Use this exception when the user's input is not valid.:: +Use this exception when the user's input is not valid:: throw new \CodeIgniter\UserInputException(); diff --git a/user_guide_src/source/general/hooks.rst b/user_guide_src/source/general/hooks.rst index a371dde815b1..fd86516d6413 100644 --- a/user_guide_src/source/general/hooks.rst +++ b/user_guide_src/source/general/hooks.rst @@ -22,7 +22,7 @@ Defining a Hook Most hooks are defined within the **application/Config/Hooks.php** file. You can subscribe an action to a hook with the Hooks class' ``on()`` method. The first parameter is the name of the hook to subscribe to. The second parameter is -a callable that will be run when that event is triggered.:: +a callable that will be run when that event is triggered:: use CodeIgniter\Hooks\Hooks; @@ -53,14 +53,14 @@ Setting Priorities Since multiple methods can be subscribed to a single event, you will need a way to define in what order those methods are called. You can do this by passing a priority value as the third parameter of the ``on()`` method. Lower values -are executed first, with a value of 1 having the highest priority, and there being no limit on the lower values.:: +are executed first, with a value of 1 having the highest priority, and there being no limit on the lower values:: Hooks::on('post_controller_constructor', 'some_function', 25); Any subscribers with the same priority will be executed in the order they were defined. Three constants are defined for your use, that set some helpful ranges on the values. You are not required to use these -but you might find they aid readability.:: +but you might find they aid readability:: define('HOOKS_PRIORITY_LOW', 200); define('HOOKS_PRIORITY_NORMAL', 100); @@ -78,7 +78,7 @@ need to call the ``trigger()`` method on the **Hooks** class with the name of th \CodeIgniter\Hooks\Hooks::trigger('some_hook'); You can pass any number of arguments to the subscribers by adding them as additional parameters. Subscribers will be -given the arguments in the same order as defined.:: +given the arguments in the same order as defined:: \CodeIgniter\Hooks\Hooks::trigger('some_hook', $foo, $bar, $baz); diff --git a/user_guide_src/source/general/logging.rst b/user_guide_src/source/general/logging.rst index a66c53848c0f..7a53418f8eca 100644 --- a/user_guide_src/source/general/logging.rst +++ b/user_guide_src/source/general/logging.rst @@ -37,7 +37,7 @@ are requested to be logged by the application, but the threshold doesn't allow t ignored. The simplest method to use is to set this value to the minimum level that you want to have logged. For example, if you want to log debug messages, and not information messages, you would set the threshold to ``5``. Any log requests with a level of 5 or less (which includes runtime errors, system errors, etc) would be logged and info, notices, and warnings -would be ignored.:: +would be ignored:: public $threshold = 5; diff --git a/user_guide_src/source/general/view_cells.rst b/user_guide_src/source/general/view_cells.rst index 802ee4a46d33..d47e3d6dc85c 100644 --- a/user_guide_src/source/general/view_cells.rst +++ b/user_guide_src/source/general/view_cells.rst @@ -61,7 +61,7 @@ third parameter. This will use the currently configured cache engine. You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name -as the fourth parameter.:: +as the fourth parameter:: // Cache the view for 5 minutes diff --git a/user_guide_src/source/libraries/cli.rst b/user_guide_src/source/libraries/cli.rst index f5f3191ea93b..7133f13df3e2 100644 --- a/user_guide_src/source/libraries/cli.rst +++ b/user_guide_src/source/libraries/cli.rst @@ -119,7 +119,7 @@ exactly as you would the ``write()`` method:: This command will take a string, start printing it on the current line, and wrap it to a set length on new lines. This might be useful when displaying a list of options with descriptions that you want to wrap in the current -window and not go off screen.:: +window and not go off screen:: CLI::color("task1\t", 'yellow'); CLI::wrap("Some long description goes here that might be longer than the current window."); @@ -172,7 +172,7 @@ The ``newLine()`` method displays a blank line to the user. It does not take any You can clear the current terminal window with the ``clearScreen()`` method. In most versions of Windows, this will simply insert 40 blank lines since Windows doesn't support this feature. Windows 10 bash integration should change -this.:: +this:: CLI::clearScreen(); diff --git a/user_guide_src/source/libraries/curlrequest.rst b/user_guide_src/source/libraries/curlrequest.rst index ff0270ca631f..53e97c5cff39 100644 --- a/user_guide_src/source/libraries/curlrequest.rst +++ b/user_guide_src/source/libraries/curlrequest.rst @@ -75,7 +75,7 @@ available to you:: $language = $response->negotiateLanguage(['en', 'fr']); While the ``request()`` method is the most flexible, you can also use the following shortcut methods. They -each take the URL as the first parameter and an array of options as the second.:: +each take the URL as the first parameter and an array of options as the second:: * $client->get('http://example.com'); * $client->delete('http://example.com'); @@ -90,7 +90,7 @@ Base URI A ``base_uri`` can be set as one of the options during the instantiation of the class. This allows you to set a base URI, and then make all requests with that client using relative URLs. This is especially handy -when working with APIs.:: +when working with APIs:: $client = Services::curlrequest([ 'base_uri' => 'https://example.com/api/v1/' @@ -194,7 +194,7 @@ Allows you to provide Authentication details for `HTTP Basic `_ and authentication. Your script may have to do extra to support Digest authentication - this simply passes the username and password along for you. The value must be an array where the first element is the username, and the second is the password. The third parameter should be -the type of authentication to use, either ``basic`` or ``digest``.:: +the type of authentication to use, either ``basic`` or ``digest``:: $client->request('GET', 'http://example.com', ['auth' => ['username', 'password', 'digest']]); @@ -208,7 +208,7 @@ The first way is to use the ``setBody()`` method:: ->request('put', 'http://example.com'); The second method is by passing a ``body`` option in. This is provided to maintain Guzzle API compatibility, -and functions the exact same way as the previous example. The value must be a string.:: +and functions the exact same way as the previous example. The value must be a string:: $client->request('put', 'http://example.com', ['body' => $body]); @@ -247,7 +247,7 @@ You can pass a filename as the value for debug to have the output written to a f delay ===== -Allows you to pause a number of milliseconds before sending the request.:: +Allows you to pause a number of milliseconds before sending the request:: // Delay for 2 seconds $response->request('GET', 'http://example.com', ['delay' => 2000]); @@ -257,7 +257,7 @@ form_params You can send form data in an application/x-www-form-urlencoded POST request by passing an associative array in the ``form_params`` option. This will set the ``Content-Type`` header to ``application/x-www-form-urlencoded`` -if it's not already set.:: +if it's not already set:: $client->request('POST', '/post', [ 'form_params' => [ @@ -275,7 +275,7 @@ headers While you can set any headers this request needs by using the ``setHeader()`` method, you can also pass an associative array of headers in as an option. Each key is the name of a header, and each value is a string or array of strings -representing the header field values.:: +representing the header field values:: $client->request('get', '/', [ 'headers' => [ @@ -292,7 +292,7 @@ http_errors =========== By default, CURLRequest will fail if the HTTP code returned is greater than or equal to 400. You can set -``http_errors`` to ``false`` to return the content instead.:: +``http_errors`` to ``false`` to return the content instead:: $client->request('GET', '/status/500'); // Will fail verbosely @@ -306,7 +306,7 @@ json The ``json`` option is used to easily upload JSON encoded data as the body of a request. A Content-Type header of ``application/json`` is added, overwriting any Content-Type that might be already set. The data provided to -this option can be any value that ``json_encode()`` accepts.:: +this option can be any value that ``json_encode()`` accepts:: $response = $client->request('PUT, '/put', ['json' => ['foo' => 'bar']]); @@ -320,7 +320,7 @@ multipart When you need to send files and other data via a POST request, you can use the ``multipart`` option, along with the `CURLFile Class `_. The values should be an associative array of POST data to send. For safer usage, the legacy method of uploading files by prefixing their name with an `@` -has been disabled. Any files that you want to send must be passed as instances of CURLFile.:: +has been disabled. Any files that you want to send must be passed as instances of CURLFile:: $post_data = [ 'foo' => 'bar', @@ -334,7 +334,7 @@ has been disabled. Any files that you want to send must be passed as instances o query ===== -You can pass along data to send as query string variables by passing an associative array as the ``query`` option.:: +You can pass along data to send as query string variables by passing an associative array as the ``query`` option:: // Send a GET request to /get?foo=bar $client->request('GET', '/get', ['query' => ['foo' => 'bar']]); @@ -355,7 +355,7 @@ This option describes the SSL certificate verification behavior. If the ``verify SSL certificate verification and uses the default CA bundle provided by the operating system. If set to ``false`` it will disable the certificate verification (this is insecure, and allows man-in-the-middle attacks!). You can set it to a string that contains the path to a CA bundle to enable verification with a custom certificate. The default value -is true.:: +is true:: // Use the system's CA bundle (this is the default setting) $client->request('GET', '/', ['verify' => true]); @@ -370,7 +370,7 @@ version ======= To set the HTTP protocol to use, you can pass a string or float with the version number (typically either 1.0 -or 1.1, 2.0 is currently unsupported.) +or 1.1, 2.0 is currently unsupported.):: // Force HTTP/1.0 $client->request('GET', '/', ['version' => 1.0]); diff --git a/user_guide_src/source/libraries/incomingrequest.rst b/user_guide_src/source/libraries/incomingrequest.rst index cb91187e1231..1760335fa6f7 100644 --- a/user_guide_src/source/libraries/incomingrequest.rst +++ b/user_guide_src/source/libraries/incomingrequest.rst @@ -325,16 +325,16 @@ The methods provided by the parent classes that are available are: To return all POST items and pass them through the filter, set the first parameter to null while setting the second parameter to the filter - you want to use.:: + you want to use:: $request->getVar(null, FILTER_SANITIZE_STRING); // returns all POST items with string sanitation - To return an array of multiple POST parameters, pass all the required keys as an array.:: + To return an array of multiple POST parameters, pass all the required keys as an array:: $request->getVar(['field1', 'field2']); Same rule applied here, to retrieve the parameters with filtering, set the second parameter to - the filter type to apply.:: + the filter type to apply:: $request->getVar(['field1', 'field2'], FILTER_SANITIZE_STRING); @@ -394,7 +394,7 @@ The methods provided by the parent classes that are available are: $request->getCookie('some_cookie'); $request->getCookie('some_cookie, FILTER_SANITIZE_STRING); // with filter - To return an array of multiple cookie values, pass all the required keys as an array.:: + To return an array of multiple cookie values, pass all the required keys as an array:: $request->getCookie(array('some_cookie', 'some_cookie2')); @@ -426,7 +426,7 @@ The methods provided by the parent classes that are available are: :returns: The User Agent string, as found in the SERVER data, or null if not found. :rtype: mixed - This method returns the User Agent string from the SERVER data.:: + This method returns the User Agent string from the SERVER data:: $request->getUserAgent(); diff --git a/user_guide_src/source/libraries/message.rst b/user_guide_src/source/libraries/message.rst index 37c0a4208f07..9a4e8541fe0b 100644 --- a/user_guide_src/source/libraries/message.rst +++ b/user_guide_src/source/libraries/message.rst @@ -41,7 +41,7 @@ Class Reference :returns: The current message body :rtype: string - Returns the current message body, if any has been set. If not body exists, returns null.:: + Returns the current message body, if any has been set. If not body exists, returns null:: echo $message->body(); @@ -81,7 +81,7 @@ Class Reference :rtype: string|array|null Allows you to retrieve the current value of a single message header. ``$name`` is the case-insensitive header name. - While the header is converted internally as described above, you can access the header with any type of case.:: + While the header is converted internally as described above, you can access the header with any type of case:: // These are all the same: $message->header('HOST'); @@ -89,7 +89,7 @@ Class Reference $message->header('host'); If the header has multiple values, the values will return as an array of values. You can use the ``headerLine()`` - method to retrieve the values as a string.:: + method to retrieve the values as a string:: echo $message->header('Accept-Language'); @@ -126,7 +126,7 @@ Class Reference Sets the value of a single header. ``$name`` is the case-insensitive name of the header. If the header doesn't already exist in the collection, it will be created. The ``$value`` can be either a string - or an array of strings.:: + or an array of strings:: $message->setHeader('Host', 'codeigniter.com'); @@ -167,7 +167,7 @@ Class Reference :returns: The current message instance :rtype: CodeIgniter\\HTTP\\Message - Sets the HTTP protocol version this Message uses. Valid values are ``1.0`` or ``1.1``.:: + Sets the HTTP protocol version this Message uses. Valid values are ``1.0`` or ``1.1``:: $message->setProtocolVersion('1.1'); @@ -228,7 +228,7 @@ Class Reference :rtype: string Determines the best match between the application-supported values and the ``Accept-Encoding`` header value. - If no match is found, will return the first element of the ``$supported`` array.:: + If no match is found, will return the first element of the ``$supported`` array:: $supported = [ 'gzip', diff --git a/user_guide_src/source/libraries/response.rst b/user_guide_src/source/libraries/response.rst index 727377894b92..061363bccd5d 100644 --- a/user_guide_src/source/libraries/response.rst +++ b/user_guide_src/source/libraries/response.rst @@ -202,7 +202,7 @@ The methods provided by the parent class that are available are: :rtype: int Returns the currently status code for this response. If no status code has been set, a BadMethodCallException - will be thrown.:: + will be thrown:: echo $response->statusCode(); @@ -237,7 +237,7 @@ The methods provided by the parent class that are available are: :returns: The current response instance. :rtype: CodeIgniter\HTTP\Response - Sets the date used for this response. The ``$date`` argument must be an instance of ``DateTime``.:: + Sets the date used for this response. The ``$date`` argument must be an instance of ``DateTime``:: $date = DateTime::createFromFormat('j-M-Y', '15-Feb-2016'); $response->setDate($date); @@ -249,7 +249,7 @@ The methods provided by the parent class that are available are: :returns: The current response instance. :rtype: CodeIgniter\HTTP\Response - Sets the content type this response represents.:: + Sets the content type this response represents:: $response->setContentType('text/plain'); $response->setContentType('text/html'); @@ -266,7 +266,7 @@ The methods provided by the parent class that are available are: :rtype: CodeIgniter\HTTP\Response Sets the ``Cache-Control`` header to turn off all HTTP caching. This is the default setting - of all response messages.:: + of all response messages:: $response->noCache(); diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst index 090c899c50ae..188c6bb6a7c9 100644 --- a/user_guide_src/source/libraries/sessions.rst +++ b/user_guide_src/source/libraries/sessions.rst @@ -398,7 +398,7 @@ same way:: You may also use the ``stop()`` method to completely kill the session by removing the old session_id, destroying all data, and destroying -the cookie that contained the session id.:: +the cookie that contained the session id:: $session->stop(); From 6a07698b8fdc2ac4e24ba3f525adc268a39ac03f Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Tue, 2 Aug 2016 08:54:15 +0700 Subject: [PATCH 0153/1807] impossible to make link nested inline markup Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/contributing/styleguide.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/contributing/styleguide.rst b/user_guide_src/source/contributing/styleguide.rst index c84bb1b994b9..958401dfa91e 100644 --- a/user_guide_src/source/contributing/styleguide.rst +++ b/user_guide_src/source/contributing/styleguide.rst @@ -23,11 +23,11 @@ below. .. note:: See the `CodeIgniter4-developer-setup `_ repository for tips on configuring your IDE or editor to help you conform - to the style guide.. + to the style guide. -*The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to -be interpreted as described in `RFC 2119 `_.* +be interpreted as described in `RFC 2119 `_. *Note: When used below, the term "class" refers to all kinds of classes, interfaces and traits.* From c453dae659f09737d47a6fca71ac740c4bcfff60 Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Tue, 2 Aug 2016 09:28:03 +0700 Subject: [PATCH 0154/1807] replace whitespace with tab Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/tutorial/news_section.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/tutorial/news_section.rst b/user_guide_src/source/tutorial/news_section.rst index 546b34bd32b5..fcca6d92356d 100644 --- a/user_guide_src/source/tutorial/news_section.rst +++ b/user_guide_src/source/tutorial/news_section.rst @@ -108,7 +108,7 @@ a new ``News`` controller is defined. Create the new controller at public function view($slug = null) { - $model = new NewsModel(); + $model = new NewsModel(); $data['news'] = $model->getNews($slug); } @@ -135,7 +135,7 @@ the views. Modify the ``index()`` method to look like this:: $data = [ 'news' => $model->getNews(), - 'title' => 'News archive', + 'title' => 'News archive', ]; echo view('Templates/Header', $data); From 535e034a6b9f2f028fdeb817c57218d5a5244017 Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Tue, 2 Aug 2016 09:29:41 +0700 Subject: [PATCH 0155/1807] correct hellow word Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/general/controllers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst index 7c5c620a84c0..341f5b9688b0 100644 --- a/user_guide_src/source/general/controllers.rst +++ b/user_guide_src/source/general/controllers.rst @@ -30,7 +30,7 @@ and put the following code in it:: { public function index() { - echo 'Hellow World!'; + echo 'Hello World!'; } } From 9ec496b6a78eb5a2beff61ddb453b6539db54f1b Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Tue, 2 Aug 2016 10:21:59 +0700 Subject: [PATCH 0156/1807] added missing backslash on instantiation Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/general/configuration.rst | 2 +- user_guide_src/source/libraries/benchmark.rst | 2 +- .../source/libraries/content_negotiation.rst | 2 +- user_guide_src/source/libraries/curlrequest.rst | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/user_guide_src/source/general/configuration.rst b/user_guide_src/source/general/configuration.rst index 7c81d2429a89..8f1a3e5fb583 100644 --- a/user_guide_src/source/general/configuration.rst +++ b/user_guide_src/source/general/configuration.rst @@ -14,7 +14,7 @@ Accessing Config Files You can access config files within your classes by creating a new instance. All of the properties are public, so you access the settings like any other property:: - $config = new Config\EmailConfig(); + $config = new \Config\EmailConfig(); // Access settings as class properties $protocol = $config->protocol; diff --git a/user_guide_src/source/libraries/benchmark.rst b/user_guide_src/source/libraries/benchmark.rst index 8c3fbdc5c482..00bfb111a442 100644 --- a/user_guide_src/source/libraries/benchmark.rst +++ b/user_guide_src/source/libraries/benchmark.rst @@ -93,7 +93,7 @@ Tasks are defined within Closures. Any output the task creates will be discarded added to the Iterator class through the `add()` method. The first parameter is a name you want to refer to this test by. The second parameter is the Closure, itself:: - $iterator = new CodeIgniter\Benchmark\Iterator(); + $iterator = new \CodeIgniter\Benchmark\Iterator(); // Add a new task $iterator->add('single_concat', function() diff --git a/user_guide_src/source/libraries/content_negotiation.rst b/user_guide_src/source/libraries/content_negotiation.rst index eab75d43ab12..7d5f55a08427 100644 --- a/user_guide_src/source/libraries/content_negotiation.rst +++ b/user_guide_src/source/libraries/content_negotiation.rst @@ -15,7 +15,7 @@ Loading the Class You can load an instance of the class manually through the Service class:: - $negotiator = Config\Services::negotiator(); + $negotiator = \Config\Services::negotiator(); This will grab the current request instance and automatically inject it into the Negotiator class. diff --git a/user_guide_src/source/libraries/curlrequest.rst b/user_guide_src/source/libraries/curlrequest.rst index 53e97c5cff39..dd44468cdc2f 100644 --- a/user_guide_src/source/libraries/curlrequest.rst +++ b/user_guide_src/source/libraries/curlrequest.rst @@ -23,7 +23,7 @@ The library can be loaded either manually or through the :doc:`Services class 'http://example.com/api/v1/', 'timeout' => 3 ]; - $client = Config\Services::curlrequest($options); + $client = \Config\Services::curlrequest($options); When creating the class manually, you need to pass a few dependencies in. The first parameter is an instance of the ``Config\App`` class. The second parameter is a URI instance. The third parameter is a Response object. The fourth parameter is the optional ``$options`` array:: - $client = new CodeIgniter\HTTP\CURLRequest( - new Config\App(), - new CodeIgniter\HTTP\URI(), - new CodeIgniter\HTTP\Response(), + $client = new \CodeIgniter\HTTP\CURLRequest( + new \Config\App(), + new \CodeIgniter\HTTP\URI(), + new \CodeIgniter\HTTP\Response(), $options ); From f793c7bd64af6fdc1c55df1e284221549330c39e Mon Sep 17 00:00:00 2001 From: Diego Sala Date: Wed, 3 Aug 2016 08:24:00 +0200 Subject: [PATCH 0157/1807] Count all records function --- user_guide_src/source/database/helpers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/database/helpers.rst b/user_guide_src/source/database/helpers.rst index 1e7967c47bf2..1f510eef9f9f 100644 --- a/user_guide_src/source/database/helpers.rst +++ b/user_guide_src/source/database/helpers.rst @@ -47,7 +47,7 @@ Permits you to determine the number of rows in a particular table. Submit the table name in the first parameter. This is part of Query Builder. Example:: - echo $db->table('my_table')->count_all(); + echo $db->table('my_table')->countAll(); // Produces an integer, like 25 From 2f5fa92111bc34d80d91ee225a3f1e32ebe0fedf Mon Sep 17 00:00:00 2001 From: Mitha Aprilia Date: Wed, 3 Aug 2016 00:14:49 -0700 Subject: [PATCH 0158/1807] added missing backslash Signed-off-by: Mitha Aprilia --- user_guide_src/source/concepts/services.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/concepts/services.rst b/user_guide_src/source/concepts/services.rst index 8c87049ac13b..a77665a3a7d2 100644 --- a/user_guide_src/source/concepts/services.rst +++ b/user_guide_src/source/concepts/services.rst @@ -12,7 +12,7 @@ configuration file. This file acts as a type of factory to create new instances A quick example will probably make things clearer, so imagine that you need to pull in an instance of the Timer class. The simplest method would simply be to create a new instance of that class:: - $timer = new CodeIgniter\Debug\Timer(); + $timer = new \CodeIgniter\Debug\Timer(); And this works great. Until you decide that you want to use a different timer class in its place. Maybe this one has some advanced reporting the default timer does not provide. In order to do this, @@ -26,7 +26,7 @@ class for us. This class is kept very simple. It only contains a method for each to use as a service. The method typically returns a new instance of that class, passing any dependencies it might have into it. Then, we would replace our timer creation code with code that calls this new class:: - $timer = Config\Services::timer(); + $timer = \Config\Services::timer(); When you need to change the implementation used, you can modify the services configuration file, and the change happens automatically throughout your application without you having to do anything. Now From b567393e2e615888bd9774d95dbd6b1ff1441baa Mon Sep 17 00:00:00 2001 From: Mitha Aprilia Date: Wed, 3 Aug 2016 00:23:05 -0700 Subject: [PATCH 0159/1807] added missing backslash on controllers section Signed-off-by: Mitha Aprilia --- user_guide_src/source/general/controllers.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst index 341f5b9688b0..177d1a14c958 100644 --- a/user_guide_src/source/general/controllers.rst +++ b/user_guide_src/source/general/controllers.rst @@ -51,14 +51,14 @@ If you did it right, you should see:: This is valid:: Date: Thu, 4 Aug 2016 00:09:26 -0500 Subject: [PATCH 0160/1807] I think I finally have working MySQLi queries in a flexible manner. Next stop - PostgreSQL --- system/Database/BaseConnection.php | 20 +++++ system/Database/BasePreparedQuery.php | 99 ++++++++++++++++++++---- system/Database/MySQLi/PreparedQuery.php | 16 ++-- system/Database/Query.php | 26 +++++-- 4 files changed, 131 insertions(+), 30 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 2ab38b7fffa8..a10c833de6ea 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -486,6 +486,26 @@ public function saveQueries($save = false) //-------------------------------------------------------------------- + /** + * Stores a new query with the object. This is primarily used by + * the prepared queries. + * + * @param \CodeIgniter\Database\Query $query + * + * @return $this + */ + public function addQuery(Query $query) + { + if ($this->saveQueries) + { + $this->queries[] = $query; + } + + return $this; + } + + //-------------------------------------------------------------------- + /** * Executes the query against the database. * diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php index 9adb19e8b4a0..d6fd21eca7ff 100644 --- a/system/Database/BasePreparedQuery.php +++ b/system/Database/BasePreparedQuery.php @@ -2,13 +2,6 @@ abstract class BasePreparedQuery implements PreparedQueryInterface { - /** - * The SQL that this statement uses. - * - * @var string - */ - protected $sql; - /** * The prepared statement itself. * @@ -30,6 +23,14 @@ abstract class BasePreparedQuery implements PreparedQueryInterface */ protected $errorString; + /** + * Holds the prepared query object + * that is cloned during execute. + * + * @var Query + */ + protected $query; + /** * A reference to the db connection to use. * @@ -46,6 +47,53 @@ public function __construct(ConnectionInterface $db) //-------------------------------------------------------------------- + /** + * Prepares the query against the database, and saves the connection + * info necessary to execute the query later. + * + * NOTE: This version is based on SQL code. Child classes should + * override this method. + * + * @param string $sql + * @param array $options Passed to the connection's prepare statement. + * + * @return mixed + */ + public function prepare(string $sql, array $options = [], $queryClass = 'CodeIgniter\\Database\\Query') + { + // We only supports positional placeholders (?) + // in order to work with the execute method below, so we + // need to replace our named placeholders (:name) + $sql = preg_replace('/:[^\s,)]+/', '?', $sql); + + $query = new $queryClass($this->db); + + $query->setQuery($sql); + + if (! empty($this->db->swapPre) && ! empty($this->db->DBPrefix)) + { + $query->swapPrefix($this->db->DBPrefix, $this->db->swapPre); + } + + $this->query = $query; + + return $this->_prepare($query->getOriginalQuery(), $options); + } + + //-------------------------------------------------------------------- + + /** + * The database-dependent portion of the prepare statement. + * + * @param string $sql + * @param array $options Passed to the connection's prepare statement. + * + * @return mixed + */ + abstract public function _prepare(string $sql, array $options = []); + + //-------------------------------------------------------------------- + /** * Takes a new set of data and runs it against the currently * prepared query. Upon success, will return a Results object. @@ -54,23 +102,40 @@ public function __construct(ConnectionInterface $db) * * @return ResultInterface */ - abstract public function execute(...$data); + public function execute(...$data) + { + // Execute the Query. + $startTime = microtime(true); + + $this->_execute($data); + + // Update our query object + $query = clone $this->query; + $query->setBinds($data); + + $query->setDuration($startTime); + + // Save it to the connection + $this->db->addQuery($query); + + // Return a result object + $resultClass = str_replace('PreparedQuery', 'Result', get_class($this)); + + $resultID = $this->statement->get_result(); + + return new $resultClass($this->db->connID, $resultID); + } //-------------------------------------------------------------------- /** - * Prepares the query against the database, and saves the connection - * info necessary to execute the query later. + * The database dependant version of the execute method. * - * NOTE: This version is based on SQL code. Child classes should - * override this method. - * - * @param string $sql - * @param array $options Passed to the connection's prepare statement. + * @param array $data * - * @return mixed + * @return ResultInterface */ - abstract public function prepare(string $sql, array $options = []); + abstract public function _execute($data); //-------------------------------------------------------------------- diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 1b696c681159..5051b6de6287 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -18,16 +18,12 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface * * @return mixed */ - public function prepare(string $sql, array $options = []) + public function _prepare(string $sql, array $options = []) { // Mysqli driver doesn't like statements // with terminating semicolons. $this->sql = rtrim($sql, ';'); - // MySQLi also only supports positional placeholders (?) - // so we need to replace our named placeholders (:name) - $this->sql = preg_replace('/:[^\s,)]+/', '?', $this->sql); - if (! $this->statement = $this->db->mysqli->prepare($this->sql)) { $this->errorCode = $this->db->mysqli->errno; @@ -47,7 +43,7 @@ public function prepare(string $sql, array $options = []) * * @return ResultInterface */ - public function execute(...$data) + public function _execute($data) { if (is_null($this->statement)) { @@ -57,6 +53,7 @@ public function execute(...$data) // First off -bind the parameters $bindTypes = ''; + // Determine the type string foreach ($data as $item) { if (is_integer($item)) @@ -72,10 +69,13 @@ public function execute(...$data) $bindTypes .= 's'; } } -die(var_dump($data)); + + // Bind it $this->statement->bind_param($bindTypes, ...$data); - return $this->statement->execute(); + $success = $this->statement->execute(); + + return $success; } //-------------------------------------------------------------------- diff --git a/system/Database/Query.php b/system/Database/Query.php index a6f6982a470c..2c1b1dc2b03c 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -122,10 +122,10 @@ public function __construct(&$db) { $this->db = $db; } - + //-------------------------------------------------------------------- - - + + /** * Sets the raw query string to use for this statement. * @@ -148,6 +148,22 @@ public function setQuery(string $sql, $binds=null) //-------------------------------------------------------------------- + /** + * Will store the variables to bind into the query later. + * + * @param array $binds + * + * @return $this + */ + public function setBinds(array $binds) + { + $this->binds = $binds; + + return $this; + } + + //-------------------------------------------------------------------- + /** * Returns the final, processed query string after binding, etal * has been performed. @@ -230,7 +246,7 @@ public function getDuration(int $decimals = 6) /** * Stores the error description that happened for this query. - * + * * @param int $code * @param string $error */ @@ -451,7 +467,7 @@ protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, i /** * Return text representation of the query - * + * * @return type */ public function __toString() From f8d7c24f33d2e49bda61bf38bba91185f23cbf36 Mon Sep 17 00:00:00 2001 From: Przemek Date: Thu, 4 Aug 2016 14:42:47 +0200 Subject: [PATCH 0161/1807] Fix class constant in view error_exception.php --- application/Views/errors/html/error_exception.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Views/errors/html/error_exception.php b/application/Views/errors/html/error_exception.php index 5bd77ba9f4e3..9da76cbbffac 100644 --- a/application/Views/errors/html/error_exception.php +++ b/application/Views/errors/html/error_exception.php @@ -373,7 +373,7 @@

Displayed at — PHP: — - CodeIgniter: + CodeIgniter:

From 87fb4f4bbe692251c481f0971151cbf81eb39370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mi=C4=8Dulis?= Date: Sun, 7 Aug 2016 23:56:05 +0300 Subject: [PATCH 0162/1807] session->remove fix if passing array Documentation says that you can pass array or string to session->remove method. Currently passing an array, will cause error because of the `string` typehint. --- system/Session/Session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index 8c92da5486ad..84aa1cf1d00a 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -481,7 +481,7 @@ public function has(string $key) * * @param $key Identifier of the session property or properties to remove. */ - public function remove(string $key) + public function remove($key) { if (is_array($key)) { From 45d6b5e6e0c0198614ee5a83c34f8ea7b1600540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mi=C4=8Dulis?= Date: Sun, 7 Aug 2016 23:57:40 +0300 Subject: [PATCH 0163/1807] session->remove fix if passing array (Interface file) --- system/Session/SessionInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Session/SessionInterface.php b/system/Session/SessionInterface.php index 1a5e9f8e6bfb..cabe4add0c18 100644 --- a/system/Session/SessionInterface.php +++ b/system/Session/SessionInterface.php @@ -111,7 +111,7 @@ public function has(string $key); * * @param $key Identifier of the session property or properties to remove. */ - public function remove(string $key); + public function remove($key); //-------------------------------------------------------------------- From 257db404929cd9a224fa12e6ef7c16499bbdc575 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 7 Aug 2016 23:10:21 -0500 Subject: [PATCH 0164/1807] Fixing small bug. --- system/Database/BaseConnection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index a10c833de6ea..c97c157db732 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -560,7 +560,7 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da // @todo deal with errors - if ($this->saveQueries) + if ($this->saveQueries && ! $this->pretend) { $this->queries[] = $query; } @@ -570,7 +570,7 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da $query->setDuration($startTime); - if ($this->saveQueries) + if ($this->saveQueries && ! $this->pretend) { $this->queries[] = $query; } From e9f3d9d5cff4be1be23df1c3b24ed0de425a6dbc Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 7 Aug 2016 23:24:28 -0500 Subject: [PATCH 0165/1807] Updating sphinx version in the user guide docs, since 1.2.3 is no longer available. --- user_guide_src/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/README.rst b/user_guide_src/README.rst index 7ab1b82b8f59..a92e5ea9ae0c 100644 --- a/user_guide_src/README.rst +++ b/user_guide_src/README.rst @@ -23,7 +23,7 @@ Installation ============ 1. Install `easy_install `_ -2. ``easy_install "sphinx==1.2.3"`` +2. ``easy_install "sphinx==1.4.5"`` 3. ``easy_install sphinxcontrib-phpdomain`` 4. Install the CI Lexer which allows PHP, HTML, CSS, and JavaScript syntax highlighting in code examples (see *cilexer/README*) 5. ``cd user_guide_src`` From b5d5859d8f24a53ddc6cd48c4eab7b52f522304d Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 7 Aug 2016 23:53:37 -0500 Subject: [PATCH 0166/1807] [ci skip] Added docs for prepared queries --- user_guide_src/source/database/queries.rst | 109 ++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/database/queries.rst b/user_guide_src/source/database/queries.rst index 8353da6e64b3..0e167c560e12 100644 --- a/user_guide_src/source/database/queries.rst +++ b/user_guide_src/source/database/queries.rst @@ -2,6 +2,8 @@ Queries ####### +.. contents:: Table of Contents + ************ Query Basics ************ @@ -121,7 +123,7 @@ this: :: - $search = '20% raise'; + $search = '20% raise'; $sql = "SELECT id FROM table WHERE column LIKE '%" . $db->escapeLikeString($search)."%' ESCAPE '!'"; @@ -187,6 +189,111 @@ example:: } +**************** +Prepared Queries +**************** + +Most database engines support some form of prepared statements, that allow you to prepare a query once, and then run +that query multiple times with new sets of data. This eliminates the possibility of SQL injection since the data is +passed to the database in a different format than the query itself. When you need to run the same query multiple times +it can be quite a bit faster, too. However, to use it for every query can have major performance hits, since you're calling +out to the database twice as often. Since the Query Builder and Database connections already handle escaping the data +for you, the safety aspect is already taken care of for you. There will be times, though, when you need to ability +to optimize the query by running a prepared statement, or prepared query. + +Preparing the Query +------------------- + +This can be easily done with the ``prepare()`` method. This takes a single parameter, which is a Closure that returns +a query object. Query objects are automatically generated by any of the "final" type queries, including **insert**, +**update**, **delete**, **replace**, and **get**. This is handled the easiest by using the Query Builder to +run a query. The query is not actually ran, and the values don't matter since they're never applied, instead acting +as placeholders. This returns a PreparedQuery object:: + + $pQuery = $db->prepare(function($db) + { + return $db->table('user') + ->insert([ + 'name' => 'x', + 'email' => 'y', + 'country' => 'US' + ]); + }); + +If you don't want to use the Query Builder, you can create the Query object manually, using question marks for +value placeholders:: + + $pQuery = $db->prepare(function($db) + { + $sql = "INSERT INTO user (name, email, country) VALUES (?, ?, ?)"; + + return new Query($db)->setQuery($sql); + }); + +If the database requires an array of options passed to it during the prepare statement phase, you can pass that +array through in the second parameter:: + + $pQuery = $db->prepare(function($db) + { + $sql = "INSERT INTO user (name, email, country) VALUES (?, ?, ?)"; + + return new Query($db)->setQuery($sql); + }, $options); + +Executing the Query +------------------- + +Once you have a prepared query, you can use the ``execute()`` method to actually run the query. You can pass in as +many variables as you need in the query parameters. The number of parameters you pass must match the number of +placeholders in the query. They must also be passed in the same order as the placeholders appear in the original +query:: + + // Prepare the Query + $pQuery = $db->prepare(function($db) + { + return $db->table('user') + ->insert([ + 'name' => 'x', + 'email' => 'y', + 'country' => 'US' + ]); + }); + + // Collect the Data + $name = 'John Doe'; + $email' = 'j.doe@example.com'; + $country = 'US'; + + // Run the Query + $results = $pQuery->execute($name, $email, $country); + +This returns a standard :doc:`result set `. + +Other Methods +------------- + +In addition, to these two primary methods, the prepared query object also has the following methods: + +**close()** + +While PHP does a pretty good job of closing all open statements with the database, it's always a good idea to +close out the prepared statement when you're done with it:: + + $pQuery->close(); + +**getQueryString()** + +This returns the prepared query as a string. + +**hasError()** + +Returns boolean true/false if the last execute() call created any errors. + +**getErrorCode()** +**getErrorMessage()** + +If any errors were encountered, these methods can be used to retrieve the error code and string. + ************************** Working with Query Objects ************************** From 62d330bb46a2c0bf28aa817af99524a6c4030494 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 7 Aug 2016 23:56:44 -0500 Subject: [PATCH 0167/1807] [ci skip] Fixing doc styles --- user_guide_src/source/database/queries.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/database/queries.rst b/user_guide_src/source/database/queries.rst index 0e167c560e12..75743f30371b 100644 --- a/user_guide_src/source/database/queries.rst +++ b/user_guide_src/source/database/queries.rst @@ -202,7 +202,7 @@ for you, the safety aspect is already taken care of for you. There will be times to optimize the query by running a prepared statement, or prepared query. Preparing the Query -------------------- +=================== This can be easily done with the ``prepare()`` method. This takes a single parameter, which is a Closure that returns a query object. Query objects are automatically generated by any of the "final" type queries, including **insert**, @@ -241,7 +241,7 @@ array through in the second parameter:: }, $options); Executing the Query -------------------- +=================== Once you have a prepared query, you can use the ``execute()`` method to actually run the query. You can pass in as many variables as you need in the query parameters. The number of parameters you pass must match the number of @@ -270,7 +270,7 @@ query:: This returns a standard :doc:`result set `. Other Methods -------------- +============= In addition, to these two primary methods, the prepared query object also has the following methods: From 0b8fb3182493132c70e989b488af5e9cbf15e3aa Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 8 Aug 2016 00:39:00 -0500 Subject: [PATCH 0168/1807] Basic tests for prepared queries, and fixing the select test issue. --- .../Database/Live/PreparedQueryTest.php | 55 +++++++++++++++++++ tests/system/Database/Live/SelectTest.php | 11 ++++ 2 files changed, 66 insertions(+) create mode 100644 tests/system/Database/Live/PreparedQueryTest.php diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php new file mode 100644 index 000000000000..fe040f63585e --- /dev/null +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -0,0 +1,55 @@ +db->prepare(function($db){ + return $db->table('user')->insert([ + 'name' => 'a', + 'email' => 'b@example.com' + ]); + }); + + $this->assertTrue($query instanceof BasePreparedQuery); + + $ec = $this->db->escapeChar; + $pre = $this->db->DBPrefix; + + $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES (?, ?)"; + $this->assertEquals($expected, $query->getQueryString()); + + $query->close(); + } + + //-------------------------------------------------------------------- + + public function testExecuteRunsQueryAndReturnsResultObject() + { + $query = $this->db->prepare(function($db){ + return $db->table('user')->insert([ + 'name' => 'a', + 'email' => 'b@example.com' + ]); + }); + + $query->execute('foo', 'foo@example.com'); + $query->execute('bar', 'bar@example.com'); + + $this->seeInDatabase($this->db->DBPrefix.'user', ['name' => 'foo', 'email' => 'foo@example.com']); + $this->seeInDatabase($this->db->DBPrefix.'user', ['name' => 'bar', 'email' => 'bar@example.com']); + + $query->close(); + } + + //-------------------------------------------------------------------- + +} diff --git a/tests/system/Database/Live/SelectTest.php b/tests/system/Database/Live/SelectTest.php index 997165cb140c..16a8a3dfb5e0 100644 --- a/tests/system/Database/Live/SelectTest.php +++ b/tests/system/Database/Live/SelectTest.php @@ -11,6 +11,17 @@ class SelectTest extends \CIDatabaseTestCase protected $seed = 'CITestSeeder'; + public function __construct() + { + parent::__construct(); + + $this->db = \Config\Database::connect($this->DBGroup); + $this->db->initialize(); + } + + //-------------------------------------------------------------------- + + public function testSelectAllByDefault() { $row = $this->db->table('job')->get()->getRowArray(); From 5faf91fa959c3d1ac412776f8e5ffb3ae1b9c6b4 Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Mon, 8 Aug 2016 23:14:56 +0800 Subject: [PATCH 0169/1807] Created inflector helper, updated related user guide and unit test. --- system/Helpers/inflector_helper.php | 310 ++++++++++++++++++ tests/system/Helpers/InflectorHelperTest.php | 163 +++++++++ .../source/helpers/inflector_helper.rst | 96 ++++++ 3 files changed, 569 insertions(+) create mode 100755 system/Helpers/inflector_helper.php create mode 100755 tests/system/Helpers/InflectorHelperTest.php create mode 100755 user_guide_src/source/helpers/inflector_helper.rst diff --git a/system/Helpers/inflector_helper.php b/system/Helpers/inflector_helper.php new file mode 100755 index 000000000000..1a87478d528d --- /dev/null +++ b/system/Helpers/inflector_helper.php @@ -0,0 +1,310 @@ + '\1ix', + '/(vert|ind)ices$/' => '\1ex', + '/^(ox)en/' => '\1', + '/(alias)es$/' => '\1', + '/([octop|vir])i$/' => '\1us', + '/(cris|ax|test)es$/' => '\1is', + '/(shoe)s$/' => '\1', + '/(o)es$/' => '\1', + '/(bus|campus)es$/' => '\1', + '/([m|l])ice$/' => '\1ouse', + '/(x|ch|ss|sh)es$/' => '\1', + '/(m)ovies$/' => '\1\2ovie', + '/(s)eries$/' => '\1\2eries', + '/([^aeiouy]|qu)ies$/' => '\1y', + '/([lr])ves$/' => '\1f', + '/(tive)s$/' => '\1', + '/(hive)s$/' => '\1', + '/([^f])ves$/' => '\1fe', + '/(^analy)ses$/' => '\1sis', + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/' => '\1\2sis', + '/([ti])a$/' => '\1um', + '/(p)eople$/' => '\1\2erson', + '/(m)en$/' => '\1an', + '/(s)tatuses$/' => '\1\2tatus', + '/(c)hildren$/' => '\1\2hild', + '/(n)ews$/' => '\1\2ews', + '/([^us])s$/' => '\1' + ]; + + foreach ($singularRules as $rule => $replacement) + { + if (preg_match($rule, $result)) + { + $result = preg_replace($rule, $replacement, $result); + break; + } + } + + return $result; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('plural')) +{ + + /** + * Plural + * + * Takes a singular word and makes it plural + * + * @param string $string Input string + * @return string + */ + function plural(string $string): string + { + $result = strval($string); + + if ( ! is_countable($result)) + { + return $result; + } + + $pluralRules = + [ + '/^(ox)$/' => '\1\2en', // ox + '/([m|l])ouse$/' => '\1ice', // mouse, louse + '/(matr|vert|ind)ix|ex$/' => '\1ices', // matrix, vertex, index + '/(x|ch|ss|sh)$/' => '\1es', // search, switch, fix, box, process, address + '/([^aeiouy]|qu)y$/' => '\1ies', // query, ability, agency + '/(hive)$/' => '\1s', // archive, hive + '/(?:([^f])fe|([lr])f)$/' => '\1\2ves', // half, safe, wife + '/sis$/' => 'ses', // basis, diagnosis + '/([ti])um$/' => '\1a', // datum, medium + '/(p)erson$/' => '\1eople', // person, salesperson + '/(m)an$/' => '\1en', // man, woman, spokesman + '/(c)hild$/' => '\1hildren', // child + '/(buffal|tomat)o$/' => '\1\2oes', // buffalo, tomato + '/(bu|campu)s$/' => '\1\2ses', // bus, campus + '/(alias|status|virus)$/' => '\1es', // alias + '/(octop)us$/' => '\1i', // octopus + '/(ax|cris|test)is$/' => '\1es', // axis, crisis + '/s$/' => 's', // no change (compatibility) + '/$/' => 's', + ]; + + foreach ($pluralRules as $rule => $replacement) + { + if (preg_match($rule, $result)) + { + $result = preg_replace($rule, $replacement, $result); + break; + } + } + + return $result; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('camelize')) +{ + + /** + * Camelize + * + * Takes multiple words separated by spaces or + * underscores and camelizes them + * + * @param string $string Input string + * @return string + */ + function camelize(string $string): string + { + return ucwords(preg_replace('/[\s_]+/', ' ', $string)); + } + +} +//-------------------------------------------------------------------- + +if ( ! function_exists('underscore')) +{ + + /** + * Underscore + * + * Takes multiple words separated by spaces and underscores them + * + * @param string $string Input string + * @return string + */ + function underscore(string $string): string + { + $replacement = trim($string); + + return preg_replace('/[\s]+/', '_', $replacement); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('humanize')) +{ + + /** + * Humanize + * + * Takes multiple words separated by the separator, + * camelizes and changes them to spaces + * + * @param string $string Input string + * @param string $separator Input separator + * @return string + */ + function humanize(string $string, string $separator = '_'): string + { + $replacement = trim($string); + $upperCased = ucwords + ( + preg_replace('/['.$separator.']+/', ' ', $replacement) + ); + + return $upperCased; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('is_countable')) +{ + + /** + * Checks if the given word has a plural version. + * + * @param string $word Word to check + * @return bool + */ + function is_countable($word): bool + { + $uncountables = in_array + ( + strtolower($word), + [ + 'advice', + 'bravery', + 'butter', + 'clarity', + 'coal', + 'courage', + 'cowardice', + 'curiosity', + 'education', + 'equipment', + 'evidence', + 'fish', + 'fun', + 'furniture', + 'help', + 'homework', + 'honesty', + 'information', + 'insurance', + 'jewelry', + 'knowledge', + 'livestock', + 'love', + 'luck', + 'marketing', + 'meta', + 'money', + 'mud', + 'news', + 'rice', + 'satisfaction', + 'scenery', + 'series', + 'silence', + 'species', + 'spelling', + 'sugar', + 'water', + 'weather', + 'wisdom', + 'work' + ] + ); + + return !$uncountables; + } + +} \ No newline at end of file diff --git a/tests/system/Helpers/InflectorHelperTest.php b/tests/system/Helpers/InflectorHelperTest.php new file mode 100755 index 000000000000..0f51a9028070 --- /dev/null +++ b/tests/system/Helpers/InflectorHelperTest.php @@ -0,0 +1,163 @@ + 'matrix', + 'oxen' => 'ox', + 'aliases' => 'alias', + 'octupus' => 'octupus', + 'shoes' => 'shoe', + 'buses' => 'bus', + 'campus' => 'campus', + 'campuses' => 'campus', + 'mice' => 'mouse', + 'movies' => 'movie', + 'series' => 'series', + 'hives' => 'hive', + 'lives' => 'life', + 'analyses' => 'analysis', + 'men' => 'man', + 'people' => 'person', + 'children' => 'child', + 'statuses' => 'status', + 'news' => 'news', + 'us' => 'us', + 'tests' => 'test', + 'queries' => 'query', + 'dogs' => 'dog', + 'cats' => 'cat', + 'families' => 'family', + 'countries' => 'country' + ]; + + foreach ($strings as $pluralizedString => $singularizedString) + { + $singular = singular($pluralizedString); + $this->assertEquals($singular, $singularizedString); + } + } + + //-------------------------------------------------------------------- + + public function testPlural() + { + $strings = + [ + 'searches' => 'search', + 'matrices' => 'matrix', + 'oxen' => 'ox', + 'aliases' => 'alias', + 'octupus' => 'octupus', + 'shoes' => 'shoe', + 'buses' => 'bus', + 'mice' => 'mouse', + 'movies' => 'movie', + 'series' => 'series', + 'hives' => 'hive', + 'lives' => 'life', + 'analyses' => 'analysis', + 'men' => 'man', + 'people' => 'person', + 'children' => 'child', + 'statuses' => 'status', + 'news' => 'news', + 'us' => 'us', + 'tests' => 'test', + 'queries' => 'query', + 'dogs' => 'dog', + 'cats' => 'cat', + 'families' => 'family', + 'countries' => 'country' + ]; + + foreach ($strings as $pluralizedString => $singularizedString) + { + $plural = plural($singularizedString); + $this->assertEquals($plural, $pluralizedString); + } + } + + //-------------------------------------------------------------------- + + public function testCamelize() + { + $strings = + [ + 'hello from codeIgniter 4' => 'Hello From CodeIgniter 4', + 'hello_world' => 'Hello World' + ]; + + foreach ($strings as $lowerCasedString => $camelizedString) + { + $camelized = camelize($lowerCasedString); + $this->assertEquals($camelized, $camelizedString); + } + } + + //-------------------------------------------------------------------- + + public function testUnderscore() + { + $strings = + [ + 'Hello From CodeIgniter 4' => 'Hello_From_CodeIgniter_4', + 'hello world' => 'hello_world' + ]; + + foreach ($strings as $lowerCasedString => $camelizedString) + { + $underscored = underscore($lowerCasedString); + $this->assertEquals($underscored, $camelizedString); + } + } + + //-------------------------------------------------------------------- + + public function testHumanize() + { + $underscored = ['Hello_From_CodeIgniter_4', 'Hello From CodeIgniter 4']; + $dashed = ['hello-world' , 'Hello World']; + + $humanizedUnderscore = humanize($underscored[0]); + $humanizedDash = humanize($dashed[0], '-'); + + $this->assertEquals($humanizedUnderscore, $underscored[1]); + $this->assertEquals($humanizedDash, $dashed[1]); + } + + //-------------------------------------------------------------------- + + public function testIsCountable() + { + $words = + [ + 'tip' => 'advice', + 'fight' => 'bravery', + 'thing' => 'equipment', + 'deocration' => 'jewelry', + 'line' => 'series', + 'letter' => 'spelling' + ]; + + foreach ($words as $countable => $unCountable) + { + $this->assertEquals(is_countable($countable), true); + $this->assertEquals(is_countable($unCountable), false); + } + } + +} \ No newline at end of file diff --git a/user_guide_src/source/helpers/inflector_helper.rst b/user_guide_src/source/helpers/inflector_helper.rst new file mode 100755 index 000000000000..c5241c347314 --- /dev/null +++ b/user_guide_src/source/helpers/inflector_helper.rst @@ -0,0 +1,96 @@ +################ +Inflector Helper +################ + +The Inflector Helper file contains functions that permits you to change +**English** words to plural, singular, camel case, etc. + +.. contents:: + :local: + +.. raw:: html + +
+ +Loading this Helper +=================== + +This helper is loaded using the following code:: + + helper('inflector'); + +Available Functions +=================== + +The following functions are available: + + +.. php:function:: singular($string) + + :param string $string: Input string + :returns: A singular word + :rtype: string + + Changes a plural word to singular. Example:: + + echo singular('dogs'); // Prints 'dog' + +.. php:function:: plural($string) + + :param string $string: Input string + :returns: A plural word + :rtype: string + + Changes a singular word to plural. Example:: + + echo plural('dog'); // Prints 'dogs' + +.. php:function:: camelize($string) + + :param string $string: Input string + :returns: Camelized string + :rtype: string + + Changes a string of words separated by spaces or underscores to camel + case. Example:: + + echo camelize('my_dog_spot'); // Prints 'myDogSpot' + +.. php:function:: underscore($string) + + :param string $string: Input string + :returns: String containing underscores instead of spaces + :rtype: string + + Takes multiple words separated by spaces and underscores them. + Example:: + + echo underscore('my dog spot'); // Prints 'my_dog_spot' + +.. php:function:: humanize($string[, $separator = '_']) + + :param string $string: Input string + :param string $separator: Input separator + :returns: Humanized string + :rtype: string + + Takes multiple words separated by underscores and adds spaces between + them. Each word is capitalized. + + Example:: + + echo humanize('my_dog_spot'); // Prints 'My Dog Spot' + + To use dashes instead of underscores:: + + echo humanize('my-dog-spot', '-'); // Prints 'My Dog Spot' + +.. php:function:: is_countable($word) + + :param string $word: Input string + :returns: TRUE if the word is countable or FALSE if not + :rtype: bool + + Checks if the given word has a plural version. Example:: + + is_countable('equipment'); // Returns FALSE \ No newline at end of file From dfc39cdde2ee38c1e04845ba0c6cb8b7e4962893 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 8 Aug 2016 23:41:25 -0500 Subject: [PATCH 0170/1807] Implementing Postgre prepared query. --- system/Database/BasePreparedQuery.php | 11 +- system/Database/MySQLi/PreparedQuery.php | 12 ++ system/Database/Postgre/PreparedQuery.php | 112 ++++++++++++++++++ .../Database/Live/PreparedQueryTest.php | 16 ++- 4 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 system/Database/Postgre/PreparedQuery.php diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php index d6fd21eca7ff..04427c72cd2d 100644 --- a/system/Database/BasePreparedQuery.php +++ b/system/Database/BasePreparedQuery.php @@ -121,7 +121,7 @@ public function execute(...$data) // Return a result object $resultClass = str_replace('PreparedQuery', 'Result', get_class($this)); - $resultID = $this->statement->get_result(); + $resultID = $this->_getResult(); return new $resultClass($this->db->connID, $resultID); } @@ -139,6 +139,15 @@ abstract public function _execute($data); //-------------------------------------------------------------------- + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + abstract public function _getResult(); + + //-------------------------------------------------------------------- + /** * Explicity closes the statement. */ diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 5051b6de6287..88a1e171d8b6 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -80,4 +80,16 @@ public function _execute($data) //-------------------------------------------------------------------- + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + public function _getResult() + { + return $this->statement->get_result(); + } + + //-------------------------------------------------------------------- + } diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php new file mode 100644 index 000000000000..ee2aa4374f0b --- /dev/null +++ b/system/Database/Postgre/PreparedQuery.php @@ -0,0 +1,112 @@ +name = mt_rand(1, 10000000000000000); + + $this->sql = $this->parameterize($sql); + + if (! $this->statement = pg_prepare($this->db->connID, $this->name, $this->sql)) + { + $this->errorCode = 0; + $this->errorString = pg_last_error($this->db->connID); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return ResultInterface + */ + public function _execute($data) + { + if (is_null($this->statement)) + { + throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + } + + $this->result = pg_execute($this->db->connID, $this->name, $data); + + return (bool)$this->result; + } + + //-------------------------------------------------------------------- + + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + public function _getResult() + { + return $this->result; + } + + //-------------------------------------------------------------------- + + /** + * Replaces the ? placeholders with $1, $2, etc parameters for use + * within the prepared query. + * + * @param string $sql + * + * @return string + */ + public function parameterize(string $sql): string + { + // Track our current value + $count = 0; + + $sql = preg_replace_callback('/\?/', function($matches) use (&$count){ + $count++; + return "\${$count}"; + }, $sql); + + return $sql; + } + + //-------------------------------------------------------------------- + +} diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index fe040f63585e..2d8ae3dcac88 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -24,7 +24,14 @@ public function testPrepareReturnsPreparedQuery() $ec = $this->db->escapeChar; $pre = $this->db->DBPrefix; - $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES (?, ?)"; + $placeholders = '?, ?'; + + if ($this->db->DBDriver == 'Postgre') + { + $placeholders = '$1, $2'; + } + + $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES ({$placeholders})"; $this->assertEquals($expected, $query->getQueryString()); $query->close(); @@ -37,12 +44,13 @@ public function testExecuteRunsQueryAndReturnsResultObject() $query = $this->db->prepare(function($db){ return $db->table('user')->insert([ 'name' => 'a', - 'email' => 'b@example.com' + 'email' => 'b@example.com', + 'country' => 'x' ]); }); - $query->execute('foo', 'foo@example.com'); - $query->execute('bar', 'bar@example.com'); + $query->execute('foo', 'foo@example.com', 'US'); + $query->execute('bar', 'bar@example.com', 'GB'); $this->seeInDatabase($this->db->DBPrefix.'user', ['name' => 'foo', 'email' => 'foo@example.com']); $this->seeInDatabase($this->db->DBPrefix.'user', ['name' => 'bar', 'email' => 'bar@example.com']); From be5bb41692b1be29d201d6a4d3acb977bf1cd29c Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 9 Aug 2016 00:26:27 -0500 Subject: [PATCH 0171/1807] Adding download method to the Response object. Fixes #208 --- system/HTTP/Response.php | 164 +++++++++++++++---- user_guide_src/source/libraries/response.rst | 28 ++++ 2 files changed, 162 insertions(+), 30 deletions(-) diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index ef021bef9a67..8c450cad17e4 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -27,23 +27,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com - * @since Version 3.0.0 + * @package CodeIgniter + * @author CodeIgniter Dev Team + * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link http://codeigniter.com + * @since Version 3.0.0 * @filesource */ use Config\App; use Config\ContentSecurityPolicy; +use Config\Mimes; /** * Redirect exception * */ -class RedirectException extends \Exception {} +class RedirectException extends \Exception +{ +} /** * Representation of an outgoing, getServer-side response. @@ -62,6 +65,7 @@ class Response extends Message implements ResponseInterface { /** * HTTP status codes + * * @var type */ protected static $statusCodes = [ @@ -122,7 +126,7 @@ class Response extends Message implements ResponseInterface 428 => 'Precondition Required', // 1.1; http://www.ietf.org/rfc/rfc6585.txt 429 => 'Too Many Requests', // 1.1; http://www.ietf.org/rfc/rfc6585.txt 431 => 'Request Header Fields Too Large', // 1.1; http://www.ietf.org/rfc/rfc6585.txt - 451 => 'Unavailable For Legal Reasons', // http://tools.ietf.org/html/rfc7725 + 451 => 'Unavailable For Legal Reasons', // http://tools.ietf.org/html/rfc7725 // 5xx: Server error 500 => 'Internal Server Error', @@ -155,6 +159,7 @@ class Response extends Message implements ResponseInterface /** * Whether Content Security Policy is being enforced. + * * @var bool */ protected $CSPEnabled = false; @@ -210,14 +215,14 @@ class Response extends Message implements ResponseInterface */ public function __construct(App $config) { - // Default to a non-caching page. + // Default to a non-caching page. // Also ensures that a Cache-control header exists. $this->noCache(); // Are we enforcing a Content Security Policy? if ($config->CSPEnabled === true) { - $this->CSP = new ContentSecurityPolicy(); + $this->CSP = new ContentSecurityPolicy(); $this->CSPEnabled = true; } @@ -350,12 +355,12 @@ public function setDate(\DateTime $date): self * * @return Response */ - public function setContentType(string $mime, string $charset='UTF-8'): self + public function setContentType(string $mime, string $charset = 'UTF-8'): self { - if (! empty($charset)) - { - $mime .= '; charset='. $charset; - } + if (! empty($charset)) + { + $mime .= '; charset='.$charset; + } $this->setHeader('Content-Type', $mime); @@ -377,7 +382,7 @@ public function setContentType(string $mime, string $charset='UTF-8'): self */ public function noCache(): self { - $this->removeHeader('Cache-control'); + $this->removeHeader('Cache-control'); $this->setHeader('Cache-control', ['no-store', 'max-age=0', 'no-cache']); @@ -414,7 +419,7 @@ public function noCache(): self * * @return $this */ - public function setCache(array $options=[]): self + public function setCache(array $options = []): self { if (empty($options)) { @@ -490,7 +495,7 @@ public function send(): self $this->CSP->finalize($this); } - $this->sendHeaders(); + $this->sendHeaders(); $this->sendBody(); return $this; @@ -505,7 +510,7 @@ public function send(): self */ public function sendHeaders(): self { - // Have the headers already been sent? + // Have the headers already been sent? if (headers_sent()) { return $this; @@ -539,7 +544,7 @@ public function sendHeaders(): self */ public function sendBody() { - echo $this->body; + echo $this->body; return $this; } @@ -549,14 +554,14 @@ public function sendBody() /** * Perform a redirect to a new URL, in two flavors: header or location. * - * @param string $uri The URI to redirect to + * @param string $uri The URI to redirect to * @param string $method - * @param int $code The type of redirection, defaults to 302 + * @param int $code The type of redirection, defaults to 302 */ public function redirect(string $uri, string $method = 'auto', int $code = null) { // IIS environment likely? Use 'refresh' for better compatibility - if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== FALSE) + if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false) { $method = 'refresh'; } @@ -565,7 +570,7 @@ public function redirect(string $uri, string $method = 'auto', int $code = null) if (isset($_SERVER['SERVER_PROTOCOL'], $_SERVER['REQUEST_METHOD']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1') { $code = ($_SERVER['REQUEST_METHOD'] !== 'GET') - ? 303 // reference: http://en.wikipedia.org/wiki/Post/Redirect/Get + ? 303 // reference: http://en.wikipedia.org/wiki/Post/Redirect/Get : 307; } else @@ -589,7 +594,7 @@ public function redirect(string $uri, string $method = 'auto', int $code = null) $this->sendHeaders(); // CodeIgniter will catch this exception and exit. - throw new RedirectException('Redirect to ' . $uri, $code); + throw new RedirectException('Redirect to '.$uri, $code); } //-------------------------------------------------------------------- @@ -618,8 +623,7 @@ public function setCookie( $prefix = '', $secure = false, $httponly = false - ) - { + ) { if (is_array($name)) { // always leave 'name' in last place, as the loop will break otherwise, due to $$item @@ -657,13 +661,13 @@ public function setCookie( $httponly = $this->cookieHTTPOnly; } - if ( ! is_numeric($expire)) + if (! is_numeric($expire)) { - $expire = time() - 86500; + $expire = time()-86500; } else { - $expire = ($expire > 0) ? time() + $expire : 0; + $expire = ($expire > 0) ? time()+$expire : 0; } setcookie($prefix.$name, $value, $expire, $path, $domain, $secure, $httponly); @@ -671,4 +675,104 @@ public function setCookie( //-------------------------------------------------------------------- + /** + * Force a download. + * + * Generates the headers that force a download to happen. And + * sends the file to the browser. + * + * @param string $filename The path to the file to send + * @param string $data The data to be downloaded + * @param bool $setMime Whether to try and send the actual MIME type + */ + public function download(string $filename = '', $data = '', bool $setMime = false) + { + if ($filename === '' || $data === '') + { + return; + } + elseif ($data === null) + { + if (! @is_file($filename) || ($filesize = @filesize($filename)) === false) + { + return; + } + + $filepath = $filename; + $filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename)); + $filename = end($filename); + } + else + { + $filesize = strlen($data); + } + + // Set the default MIME type to send + $mime = 'application/octet-stream'; + + $x = explode('.', $filename); + $extension = end($x); + + if ($setMime === true) + { + if (count($x) === 1 OR $extension === '') + { + /* If we're going to detect the MIME type, + * we'll need a file extension. + */ + return; + } + + $mime = Mimes::guessTypeFromExtension($extension); + } + + /* It was reported that browsers on Android 2.1 (and possibly older as well) + * need to have the filename extension upper-cased in order to be able to + * download it. + * + * Reference: http://digiblog.de/2011/04/19/android-and-the-download-file-headers/ + */ + if (count($x) !== 1 && isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/Android\s(1|2\.[01])/', $_SERVER['HTTP_USER_AGENT'])) + { + $x[count($x)-1] = strtoupper($extension); + $filename = implode('.', $x); + } + + if ($data === null && ($fp = @fopen($filepath, 'rb')) === false) + { + return; + } + + // Clean output buffer + if (ob_get_level() !== 0 && @ob_end_clean() === false) + { + @ob_clean(); + } + + // Generate the server headers + header('Content-Type: '.$mime); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + header('Expires: 0'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: '.$filesize); + header('Cache-Control: private, no-transform, no-store, must-revalidate'); + + // If we have raw data - just dump it + if ($data !== null) + { + exit($data); + } + + // Flush 1MB chunks of data + while (! feof($fp) && ($data = fread($fp, 1048576)) !== false) + { + echo $data; + } + + fclose($fp); + exit; + } + + //-------------------------------------------------------------------- + } diff --git a/user_guide_src/source/libraries/response.rst b/user_guide_src/source/libraries/response.rst index 061363bccd5d..776d3375a09f 100644 --- a/user_guide_src/source/libraries/response.rst +++ b/user_guide_src/source/libraries/response.rst @@ -58,6 +58,34 @@ parameter. This is not case-sensitive. $response->removeHeader('Location'); +Force File Download +=================== + +The Reponse class provides a simple way to send a file to the client, prompting the browser to download the data +to your computer. This sets the appropriate headers to make it happen. + +The first parameter is the **name you want the downloaded file to be named**, the second parameter is the +file data. + +If you set the second parameter to NULL and ``$filename`` is an existing, readable +file path, then its content will be read instead. + +If you set the third parameter to boolean TRUE, then the actual file MIME type +(based on the filename extension) will be sent, so that if your browser has a +handler for that type - it can use it. + +Example:: + + $data = 'Here is some text!'; + $name = 'mytext.txt'; + $response->download($name, $data); + +If you want to download an existing file from your server you'll need to +do the following:: + + // Contents of photo.jpg will be automatically read + $response->download('/path/to/photo.jpg', NULL); + HTTP Caching ============ From b6096a5a76303cdb49916ed78af9f47fcd321171 Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Tue, 9 Aug 2016 21:09:29 +0800 Subject: [PATCH 0172/1807] Added new functions, updated user documentation and related unit tests. --- system/Helpers/inflector_helper.php | 75 +++++++++++++++++++ tests/system/Helpers/InflectorHelperTest.php | 73 +++++++++++++++++- .../source/helpers/inflector_helper.rst | 36 ++++++++- 3 files changed, 180 insertions(+), 4 deletions(-) diff --git a/system/Helpers/inflector_helper.php b/system/Helpers/inflector_helper.php index 1a87478d528d..67c2091f1137 100755 --- a/system/Helpers/inflector_helper.php +++ b/system/Helpers/inflector_helper.php @@ -307,4 +307,79 @@ function is_countable($word): bool return !$uncountables; } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('dasherize')) +{ + + /** + * Replaces underscores with dashes in the string. + * + * @param string $string Input string + * @return string + */ + function dasherize(string $string): string + { + return str_replace('_', '-', $string); + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('ordinal')) +{ + + /** + * Returns the suffix that should be added to a + * number to denote the position in an ordered + * sequence such as 1st, 2nd, 3rd, 4th. + * + * @param int $integer The integer to determine + * the suffix + * @return string + */ + function ordinal(int $integer): string + { + $suffixes = + [ + 'th', + 'st', + 'nd', + 'rd', + 'th', + 'th', + 'th', + 'th', + 'th', + 'th' + ]; + + return $integer % 100 >= 11 && $integer % 100 <= 13 + ? 'th' + : $suffixes[$integer % 10]; + } + +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('ordinalize')) +{ + + /** + * Turns a number into an ordinal string used + * to denote the position in an ordered sequence + * such as 1st, 2nd, 3rd, 4th. + * + * @param int $integer The integer to ordinalize + * @return string + */ + function ordinalize(int $integer): string + { + return $integer . ordinal($integer); + } + } \ No newline at end of file diff --git a/tests/system/Helpers/InflectorHelperTest.php b/tests/system/Helpers/InflectorHelperTest.php index 0f51a9028070..169c3ce146d3 100755 --- a/tests/system/Helpers/InflectorHelperTest.php +++ b/tests/system/Helpers/InflectorHelperTest.php @@ -118,10 +118,10 @@ public function testUnderscore() 'hello world' => 'hello_world' ]; - foreach ($strings as $lowerCasedString => $camelizedString) + foreach ($strings as $spaced => $underscore) { - $underscored = underscore($lowerCasedString); - $this->assertEquals($underscored, $camelizedString); + $underscored = underscore($spaced); + $this->assertEquals($underscored, $underscore); } } @@ -160,4 +160,71 @@ public function testIsCountable() } } + //-------------------------------------------------------------------- + + public function testDasherize() + { + $strings = + [ + 'hello_world' => 'hello-world', + 'Hello_From_CodeIgniter_4' => 'Hello-From-CodeIgniter-4' + ]; + + foreach ($strings as $underscored => $dashed) + { + $dasherized = dasherize($underscored); + $this->assertEquals($dasherized, $dashed); + } + } + + //-------------------------------------------------------------------- + + public function testOrdinal() + { + $suffixes = + [ + 'st' => 1, + 'nd' => 2, + 'rd' => 3, + 'th' => 4, + 'th' => 11, + 'th' => 20, + 'st' => 21, + 'nd' => 22, + 'rd' => 23, + 'th' => 24 + ]; + + foreach ($suffixes as $suffix => $number) + { + $ordinal = ordinal($number); + $this->assertEquals($suffix, $ordinal); + } + } + + //-------------------------------------------------------------------- + + public function testOrdinalize() + { + $suffixedNumbers = + [ + '1st' => 1, + '2nd' => 2, + '3rd' => 3, + '4th' => 4, + '11th' => 11, + '20th' => 20, + '21st' => 21, + '22nd' => 22, + '23rd' => 23, + '24th' => 24 + ]; + + foreach ($suffixedNumbers as $suffixed => $number) + { + $ordinalized = ordinalize($number); + $this->assertEquals($suffixed, $ordinalized); + } + } + } \ No newline at end of file diff --git a/user_guide_src/source/helpers/inflector_helper.rst b/user_guide_src/source/helpers/inflector_helper.rst index c5241c347314..aaaf254af67e 100755 --- a/user_guide_src/source/helpers/inflector_helper.rst +++ b/user_guide_src/source/helpers/inflector_helper.rst @@ -93,4 +93,38 @@ The following functions are available: Checks if the given word has a plural version. Example:: - is_countable('equipment'); // Returns FALSE \ No newline at end of file + is_countable('equipment'); // Returns FALSE + +.. php:function:: dasherize($string) + + :param string $string: Input string + :returns: Dasherized string + :rtype: string + + Replaces underscores with dashes in the string. Example:: + + dasherize('hello_world'); // Returns 'hello-world' + +.. php:function:: ordinal($integer) + + :param int $integer: The integer to determine the suffix + :returns: Ordinal suffix + :rtype: string + + Returns the suffix that should be added to a + number to denote the position such as + 1st, 2nd, 3rd, 4th. Example:: + + ordinal(1); // Returns 'st' + +.. php:function:: ordinalize($integer) + + :param int $integer: The integer to ordinalize + :returns: Ordinalized integer + :rtype: string + + Turns a number into an ordinal string used + to denote the position such as 1st, 2nd, 3rd, 4th. + Example:: + + ordinal(1); // Returns '1st' \ No newline at end of file From 816f9599a80c6d69698e8ff3783225a9242e2768 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 10 Aug 2016 22:41:26 -0500 Subject: [PATCH 0173/1807] Requests can now store and retrieve locale information as setup for i18n efforts. --- application/Config/App.php | 13 +++++++ system/HTTP/Request.php | 65 +++++++++++++++++++++++++++++-- system/HTTP/RequestInterface.php | 29 ++++++++++++++ tests/system/HTTP/RequestTest.php | 20 ++++++++++ 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/application/Config/App.php b/application/Config/App.php index cd0a2034f870..fc34b8e2ac44 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -52,6 +52,19 @@ class App extends BaseConfig */ public $uriProtocol = 'REQUEST_URI'; + /* + |-------------------------------------------------------------------------- + | Default Locale + |-------------------------------------------------------------------------- + | + | The Locale roughly represents the language and location that your visitor + | is viewing the site from. It affects the language strings and other + | strings (like currency markers, numbers, etc), that your program + | should run under for this request. + | + */ + public $defaultLocale = 'en'; + /* |-------------------------------------------------------------------------- | URI PROTOCOL diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index 44c57ad9cdc5..147b123b76ad 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -52,22 +52,81 @@ class Request extends Message implements RequestInterface /** * Proxy IPs - * - * @var type + * + * @var type */ protected $proxyIPs; + /** + * The default Locale this request + * should operate under. + * + * @var string + */ + protected $defaultLocale; + + /** + * The current locale of the application. + * Default value is set in Config\App.php + * + * @var string + */ + protected $locale; + //-------------------------------------------------------------------- /** * Constructor. - * + * * @param type $config * @param type $uri */ public function __construct($config, $uri=null) { $this->proxyIPs = $config->proxyIPs; + + $this->locale = $this->defaultLocale = $config->defaultLocale; + } + + //-------------------------------------------------------------------- + + /** + * Returns the default locale as set in Config\App.php + * + * @return string + */ + public function getDefaultLocale(): string + { + return $this->defaultLocale; + } + + //-------------------------------------------------------------------- + + /** + * Gets the current locale, with a fallback to the default + * locale if none is set. + * + * @return string + */ + public function getLocale(): string + { + return $this->locale ?? $this->defaultLocale; + } + + //-------------------------------------------------------------------- + + /** + * Sets the locale string for this request. + * + * @param string $locale + * + * @return $this + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + + return $this; } //-------------------------------------------------------------------- diff --git a/system/HTTP/RequestInterface.php b/system/HTTP/RequestInterface.php index 573d905a2dfe..9b8ddfa14441 100644 --- a/system/HTTP/RequestInterface.php +++ b/system/HTTP/RequestInterface.php @@ -85,4 +85,33 @@ public function getServer($index = null, $filter = null); //-------------------------------------------------------------------- + /** + * Returns the default locale as set in Config\App.php + * + * @return string + */ + public function getDefaultLocale(): string; + + //-------------------------------------------------------------------- + + /** + * Gets the current locale, with a fallback to the default + * locale if none is set. + * + * @return string + */ + public function getLocale(): string; + + //-------------------------------------------------------------------- + + /** + * Sets the locale string for this request. + * + * @param string $locale + * + * @return $this + */ + public function setLocale(string $locale); + + //-------------------------------------------------------------------- } diff --git a/tests/system/HTTP/RequestTest.php b/tests/system/HTTP/RequestTest.php index 05a9a0e6e18a..bc5ee88187e1 100644 --- a/tests/system/HTTP/RequestTest.php +++ b/tests/system/HTTP/RequestTest.php @@ -55,4 +55,24 @@ public function testMethodReturnsRightStuff() //-------------------------------------------------------------------- + public function testStoresDefaultLocale() + { + $config = new App(); + + $this->assertEquals($config->defaultLocale, $this->request->getDefaultLocale()); + $this->assertEquals($config->defaultLocale, $this->request->getLocale()); + } + + //-------------------------------------------------------------------- + + public function testSetLocaleSaves() + { + $this->request->setLocale('en'); + + $this->assertEquals('en', $this->request->getLocale()); + } + + //-------------------------------------------------------------------- + + } From 314ef49a78ff38832955780e664a7dea27b34491 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 11 Aug 2016 23:32:39 -0500 Subject: [PATCH 0174/1807] Fix MockAppConfig with new settings so tests pass again. --- tests/_support/Config/MockAppConfig.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/_support/Config/MockAppConfig.php b/tests/_support/Config/MockAppConfig.php index 4280562559e0..96d4d81f6d0e 100644 --- a/tests/_support/Config/MockAppConfig.php +++ b/tests/_support/Config/MockAppConfig.php @@ -6,10 +6,10 @@ class MockAppConfig public $uriProtocol = 'REQUEST_URI'; - public $cookiePrefix = ''; - public $cookieDomain = ''; - public $cookiePath = '/'; - public $cookieSecure = false; + public $cookiePrefix = ''; + public $cookieDomain = ''; + public $cookiePath = '/'; + public $cookieSecure = false; public $cookieHTTPOnly = false; public $proxyIPs = ''; @@ -22,4 +22,8 @@ class MockAppConfig public $CSRFExcludeURIs = ['http://example.com']; public $CSPEnabled = false; + + public $defaultLocale = 'en'; + public $negotiateLocale = false; + public $supportedLocales = ['en', 'es']; } From 311607c6d6372aae3e1decc0d305c657c75bb2ca Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 11 Aug 2016 23:52:00 -0500 Subject: [PATCH 0175/1807] Getting a start on language functions, including auto-detecting current locale based on headers --- application/Config/App.php | 25 +++ application/Config/Services.php | 15 ++ system/Common.php | 17 ++ system/HTTP/IncomingRequest.php | 38 +++++ system/HTTP/Request.php | 18 -- system/Language/Loader.php | 157 ++++++++++++++++++ tests/system/HTTP/IncomingRequestTest.php | 35 ++++ tests/system/HTTP/RequestTest.php | 20 --- .../source/general/common_functions.rst | 11 +- user_guide_src/source/libraries/index.rst | 1 + .../source/libraries/localization.rst | 68 ++++++++ 11 files changed, 366 insertions(+), 39 deletions(-) create mode 100644 system/Language/Loader.php create mode 100644 user_guide_src/source/libraries/localization.rst diff --git a/application/Config/App.php b/application/Config/App.php index fc34b8e2ac44..a31d323b382f 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -65,6 +65,31 @@ class App extends BaseConfig */ public $defaultLocale = 'en'; + /* + |-------------------------------------------------------------------------- + | Negotiate Locale + |-------------------------------------------------------------------------- + | + | If true, the current Request object will automatically determine the + | language to use based on the value of the Accept-Language header. + | + | If false, no automatic detection will be performed. + | + */ + public $negotiateLocale = false; + + /* + |-------------------------------------------------------------------------- + | Supported Locales + |-------------------------------------------------------------------------- + | + | If $negotiateLocale is true, this array lists the locales supported + | by the application in descending order of priority. If no match is + | found, the first locale will be used. + | + */ + public $supportedLocales = ['en']; + /* |-------------------------------------------------------------------------- | URI PROTOCOL diff --git a/application/Config/Services.php b/application/Config/Services.php index 1231c25c48b7..26c8c4d4cde2 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -206,6 +206,21 @@ public static function iterator($getShared = true) //-------------------------------------------------------------------- + /** + * Responsible for loading the language string translations. + */ + public static function language($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('language'); + } + + return new \CodeIgniter\Language\Loader(); + } + + //-------------------------------------------------------------------- + /** * The file locator provides utility methods for looking for non-classes * within namespaced folders, as well as convenience methods for diff --git a/system/Common.php b/system/Common.php index a54a7f820f15..7c34f282ade7 100644 --- a/system/Common.php +++ b/system/Common.php @@ -310,7 +310,24 @@ function shared_service(string $name, ...$params) //-------------------------------------------------------------------- +if (! function_exists('lang')) +{ + /** + * A convenience method to translate a string and format it + * with the intl extension's MessageFormatter object. + * + * @param string $line + * @param array $args + * + * @return string + */ + function lang(string $line, array $args=[]) + { + return Services::language()->getLine($line, $args); + } +} +//-------------------------------------------------------------------- diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index b627a1b2bffc..84c7d01db136 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -97,6 +97,22 @@ class IncomingRequest extends Request */ protected $negotiate; + /** + * The default Locale this request + * should operate under. + * + * @var string + */ + protected $defaultLocale; + + /** + * The current locale of the application. + * Default value is set in Config\App.php + * + * @var string + */ + protected $locale; + //-------------------------------------------------------------------- /** @@ -123,6 +139,28 @@ public function __construct($config, $uri = null, $body = 'php://input') $this->uri = $uri; $this->detectURI($config->uriProtocol, $config->baseURL); + + $this->detectLocale($config); + } + + //-------------------------------------------------------------------- + + /** + * Handles setting up the locale, perhaps auto-detecting through + * content negotiation. + * + * @param $config + */ + public function detectLocale($config) + { + $this->locale = $this->defaultLocale = $config->defaultLocale; + + if (! $config->negotiateLocale) + { + return; + } + + $this->locale = $this->negotiate('language', $config->supportedLocales); } //-------------------------------------------------------------------- diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index 147b123b76ad..250cda007a0e 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -57,22 +57,6 @@ class Request extends Message implements RequestInterface */ protected $proxyIPs; - /** - * The default Locale this request - * should operate under. - * - * @var string - */ - protected $defaultLocale; - - /** - * The current locale of the application. - * Default value is set in Config\App.php - * - * @var string - */ - protected $locale; - //-------------------------------------------------------------------- /** @@ -84,8 +68,6 @@ class Request extends Message implements RequestInterface public function __construct($config, $uri=null) { $this->proxyIPs = $config->proxyIPs; - - $this->locale = $this->defaultLocale = $config->defaultLocale; } //-------------------------------------------------------------------- diff --git a/system/Language/Loader.php b/system/Language/Loader.php new file mode 100644 index 000000000000..8901751a7cf9 --- /dev/null +++ b/system/Language/Loader.php @@ -0,0 +1,157 @@ +locale = $request->getLocale(); + + if (class_exists('\MessageFormatter')) + { + $this->intlSupport = true; + }; + + unset($request); + } + + //-------------------------------------------------------------------- + + /** + * + * + * @param string $line + * @param array $args + * + * @return string + */ + public function getLine(string $line, array $args = []): string + { + // Parse out the file name and the actual alias. + // Will load the language file and strings. + $line = $this->parseLine($line); + + $output = $this->language[$line] ?? ''; + + // Do advanced message formatting here + // if the 'intl' extension is available. + if ($this->intlSupport && count($args)) + { + $output = \MessageFormatter::formatMessage($this->locale, $line, $args); + } + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Parses the language string which should include the + * filename as the first segment (separated by period). + * + * @param string $line + * + * @return string + */ + protected function parseLine(string $line): string + { + if (strpos($line, '.') === false) + { + throw new \InvalidArgumentException('No language file specified in line: '. $line); + } + + $file = substr($line, 0, strpos($line, '.')); + $line = substr($line, strlen($file)); + + if (! array_key_exists($this->languages[$this->locale][$line])) + { + $this->load($file, $this->locale); + } + + return $line; + } + + //-------------------------------------------------------------------- + + /** + * Loads a language file in the current locale. If $return is true, + * will return the file's contents, otherwise will merge with + * the existing language lines. + * + * @param string $file + * @param string $locale + * @param bool $return + * + * @return array + */ + public function load(string $file, string $locale, bool $return = false): array + { + if (in_array($file, $this->loadedFiles)) + { + return; + } + + $lang = []; + + $path = APPPATH."Language/{$locale}/{$file}.php"; + + // First check the app's Language folder + if (is_file($path)) + { + $lang = require $path; + } + + // @todo - should look into loading from other locations, also probably... + + // Don't load it more than once. + $this->loadedFiles[] = $file; + + if ($return) + { + return $lang; + } + + // Merge our string + $this->language = array_merge($this->language, $lang); + } + + //-------------------------------------------------------------------- + +} diff --git a/tests/system/HTTP/IncomingRequestTest.php b/tests/system/HTTP/IncomingRequestTest.php index 138ba07139ca..b618f4f7d729 100644 --- a/tests/system/HTTP/IncomingRequestTest.php +++ b/tests/system/HTTP/IncomingRequestTest.php @@ -187,4 +187,39 @@ public function testFetchGlobalFiltersSelectedValues() } //-------------------------------------------------------------------- + + public function testStoresDefaultLocale() + { + $config = new App(); + + $this->assertEquals($config->defaultLocale, $this->request->getDefaultLocale()); + $this->assertEquals($config->defaultLocale, $this->request->getLocale()); + } + + //-------------------------------------------------------------------- + + public function testSetLocaleSaves() + { + $this->request->setLocale('en'); + + $this->assertEquals('en', $this->request->getLocale()); + } + + //-------------------------------------------------------------------- + + public function testNegotiatesLocale() + { + $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'es; q=1.0, en; q=0.5'; + + $config = new App(); + $config->negotiateLocale = true; + $config->supportedLocales = ['en', 'es']; + + $request = new IncomingRequest($config, new URI()); + + $this->assertEquals($config->defaultLocale, $request->getDefaultLocale()); + $this->assertEquals('es', $request->getLocale()); + } + + //-------------------------------------------------------------------- } diff --git a/tests/system/HTTP/RequestTest.php b/tests/system/HTTP/RequestTest.php index bc5ee88187e1..05a9a0e6e18a 100644 --- a/tests/system/HTTP/RequestTest.php +++ b/tests/system/HTTP/RequestTest.php @@ -55,24 +55,4 @@ public function testMethodReturnsRightStuff() //-------------------------------------------------------------------- - public function testStoresDefaultLocale() - { - $config = new App(); - - $this->assertEquals($config->defaultLocale, $this->request->getDefaultLocale()); - $this->assertEquals($config->defaultLocale, $this->request->getLocale()); - } - - //-------------------------------------------------------------------- - - public function testSetLocaleSaves() - { - $this->request->setLocale('en'); - - $this->assertEquals('en', $this->request->getLocale()); - } - - //-------------------------------------------------------------------- - - } diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst index 39b1606db1e2..1f28504fc2a8 100644 --- a/user_guide_src/source/general/common_functions.rst +++ b/user_guide_src/source/general/common_functions.rst @@ -54,6 +54,15 @@ Service Accessors For full details, see the :doc:`helpers` page. +.. php:function:: lang(string $line[, array $args]): string + + :param string $line: The line of text to retrieve + :param array $args: An array of data to substitute for placeholders. + + Retrieves a locale-specific file based on an alias string. + + For more information, see the :doc:`Localization ` page. + .. php:function:: session( [$key] ) :param string $key: The name of the session item to check for. @@ -72,7 +81,7 @@ Service Accessors A convenience method that provides quick access to the Timer class. You can pass in the name of a benchmark point as the only parameter. This will start timing from this point, or stop timing if a timer with this name is already running. - + Example:: // Get an instance diff --git a/user_guide_src/source/libraries/index.rst b/user_guide_src/source/libraries/index.rst index 78af41551b5d..3d22070b09a2 100644 --- a/user_guide_src/source/libraries/index.rst +++ b/user_guide_src/source/libraries/index.rst @@ -9,6 +9,7 @@ Library Reference caching cli content_negotiation + localization curlrequest incomingrequest message diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst new file mode 100644 index 000000000000..8b7e2cfd97f9 --- /dev/null +++ b/user_guide_src/source/libraries/localization.rst @@ -0,0 +1,68 @@ +============ +Localization +============ + +.. contents:: + :local: + +Introduction +============ + +CodeIgniter provides several tools to help you localize your application for different languages. While full +localization of an application is a complex subject, it's simple to swap out strings in your application +with different supported languages. + +Language strings are stored in the **application/Language** directory, with a sub-directory for each +supported language:: + + /application + /Language + /en + app.php + /fr + app.php + +Configuring the Locale +====================== + +Every site will have a default language/locale they operate in. This can be set in **Config/App.php**:: + + public $defaultLocale = 'en'; + +The value can be any string that your application uses to manage text strings and other formats. It is +recommended that a [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code, followed +by an underscore (_), and the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes) +country code is recommended. This results in language codes like en_US for American English, or fr_FR, +for French/France. + +The system is smart enough to fallback to more generic language codes if an exact match +cannot be found. If the locale code was set to **en_US** and we only have language files setup for **en** +then those will be used since nothing exists for the more specific **en_US**. If, however, a language +directory existed at **application/Language/en_US** then that we be used first. + +Locale Detection +================ + +There are two methods supported to detect the correct locale during each request. The first is a "set and forget" +method that will automatically perform :doc:`content negotiation ` for you to +determine the correct locale to use. The second method allows you to specify a segment in your routes that +will be used to set the locale. + +Content Negotiation +------------------- + +You can setup content negotiation to happen automatically by setting two additional settings in Config/App. +The first value tells the Request class that we do want to negotiate a locale, so simply set it to true:: + + public $negotiateLocale = true; + +Once this is enabled, the system will automatically negotiate the correct language based upon an array +of locales that you have defined in ``$supportLocales``. If no match is found between the languages +that you support, and the requested language, the first item in $supportedLocales will be used. In +the following example, the **en** locale would be used if no match is found:: + + public $supportedLocales = ['en', 'es', 'fr_FR']; + +In Routes +--------- + From c52cde5add2927191e40cb1ef52cda0762abca41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mi=C4=8Dulis?= Date: Fri, 12 Aug 2016 23:25:44 +0300 Subject: [PATCH 0176/1807] UploadedFile typo fix --- system/HTTP/Files/UploadedFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index 4c3092f953b3..dae0a4fc63f0 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -331,7 +331,7 @@ public function getTempName(): string */ public function getRandomName(): string { - return time().'_'.random_bytes(10).'.'.$this->$this->getExtension(); + return time().'_'.random_bytes(10).'.'.$this->getExtension(); } //-------------------------------------------------------------------- From 475268e765baf378eeb00494921d5f3e0b734a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mi=C4=8Dulis?= Date: Fri, 12 Aug 2016 23:43:02 +0300 Subject: [PATCH 0177/1807] Random file name fix --- system/HTTP/Files/UploadedFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index dae0a4fc63f0..e7f44f09e9d6 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -331,7 +331,7 @@ public function getTempName(): string */ public function getRandomName(): string { - return time().'_'.random_bytes(10).'.'.$this->getExtension(); + return time().'_'.bin2hex(random_bytes(10)).'.'.$this->getExtension(); } //-------------------------------------------------------------------- From 76e487ddb67195c23bba0ed1c08d5c692c7c5883 Mon Sep 17 00:00:00 2001 From: Davis Miculis Date: Sat, 13 Aug 2016 00:50:32 +0300 Subject: [PATCH 0178/1807] Migrations correct latest version and default group fix. --- system/Database/MigrationRunner.php | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index de4a4bc30491..2176ab7913fb 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -104,7 +104,7 @@ class MigrationRunner /** * Constructor. - * + * * @param BaseConfig $config * @param \CodeIgniter\Database\ConnectionInterface $db * @throws ConfigException @@ -198,7 +198,7 @@ public function version(string $targetVersion, $group='default') { return true; } - + $previous = false; // Validate all available migrations, and run the ones within our target range @@ -415,11 +415,19 @@ protected function getMigrationName($migration) */ protected function getVersion($group = 'default') { + if (empty($group)) + { + $config = new \Config\Database(); + $group = $config->defaultGroup; + unset($config); + } + $row = $this->db->table($this->table) - ->select('version') - ->where('group', $group) - ->get() - ->getRow(); + ->select('version') + ->where('group', $group) + ->orderBy('version', 'DESC') + ->get() + ->getRow(); return $row ? $row->version : '0'; } @@ -462,6 +470,13 @@ protected function addHistory($version, $group = 'default') */ protected function removeHistory($version, $group = 'default') { + if (empty($group)) + { + $config = new \Config\Database(); + $group = $config->defaultGroup; + unset($config); + } + $this->db->table($this->table) ->where('version', $version) ->where('group', $group) From 6ffc6ae396573da5e82957907439b5847ccbb7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mi=C4=8Dulis?= Date: Sat, 13 Aug 2016 00:56:46 +0300 Subject: [PATCH 0179/1807] Indentation fix --- system/Database/MigrationRunner.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 2176ab7913fb..36b5f5b5aee6 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -423,11 +423,11 @@ protected function getVersion($group = 'default') } $row = $this->db->table($this->table) - ->select('version') - ->where('group', $group) - ->orderBy('version', 'DESC') - ->get() - ->getRow(); + ->select('version') + ->where('group', $group) + ->orderBy('version', 'DESC') + ->get() + ->getRow(); return $row ? $row->version : '0'; } From c1a8a3cf5becc260fd29e7c9af8842699f088782 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sat, 13 Aug 2016 21:04:14 -0500 Subject: [PATCH 0180/1807] Adding missing element from MockAppConfig so that tests run correctly again. --- tests/_support/Config/MockAppConfig.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/_support/Config/MockAppConfig.php b/tests/_support/Config/MockAppConfig.php index 4280562559e0..f9bf06b3bbe8 100644 --- a/tests/_support/Config/MockAppConfig.php +++ b/tests/_support/Config/MockAppConfig.php @@ -22,4 +22,6 @@ class MockAppConfig public $CSRFExcludeURIs = ['http://example.com']; public $CSPEnabled = false; + + public $defaultLocale = 'en'; } From 026bf88e7f3b451822fe609fba0da87fba0f267b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sat, 13 Aug 2016 22:21:22 -0500 Subject: [PATCH 0181/1807] Adding in Route-based locale detection. --- system/CodeIgniter.php | 7 ++ system/HTTP/IncomingRequest.php | 71 ++++++++++++++++++- system/HTTP/Request.php | 41 ----------- system/HTTP/RequestInterface.php | 30 -------- system/Router/Router.php | 50 +++++++++++++ tests/system/Router/RouterTest.php | 17 +++++ user_guide_src/source/general/routing.rst | 21 +++--- .../source/libraries/localization.rst | 56 ++++++++++++--- 8 files changed, 203 insertions(+), 90 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 37f81f87f259..c0bc6c20c79f 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -482,6 +482,13 @@ protected function tryToRouteIt(RouteCollectionInterface $routes = null) $this->controller = $this->router->handle($path); $this->method = $this->router->methodName(); + // If a {locale} segment was matched in the final route, + // then we need to set the correct locale on our Request. + if ($this->router->hasLocale()) + { + $this->request->setLocale($this->router->getLocale()); + } + $this->benchmark->stop('routing'); } diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 84c7d01db136..0a267df5432e 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -113,6 +113,13 @@ class IncomingRequest extends Request */ protected $locale; + /** + * Stores the valid locale codes. + * + * @var array + */ + protected $validLocales = []; + //-------------------------------------------------------------------- /** @@ -140,6 +147,8 @@ public function __construct($config, $uri = null, $body = 'php://input') $this->detectURI($config->uriProtocol, $config->baseURL); + $this->validLocales = $config->supportedLocales; + $this->detectLocale($config); } @@ -160,7 +169,67 @@ public function detectLocale($config) return; } - $this->locale = $this->negotiate('language', $config->supportedLocales); + $this->setLocale($this->negotiate('language', $config->supportedLocales)); + } + + //-------------------------------------------------------------------- + + /** + * Returns the default locale as set in Config\App.php + * + * @return string + */ + public function getDefaultLocale(): string + { + return $this->defaultLocale; + } + + //-------------------------------------------------------------------- + + /** + * Gets the current locale, with a fallback to the default + * locale if none is set. + * + * @return string + */ + public function getLocale(): string + { + return $this->locale ?? $this->defaultLocale; + } + + //-------------------------------------------------------------------- + + /** + * Sets the locale string for this request. + * + * @param string $locale + * + * @return $this + */ + public function setLocale(string $locale) + { + // If it's not a valid locale, set it + // to the default locale for the site. + if (! in_array($locale, $this->validLocales)) + { + $locale = $this->defaultLocale; + } + + $this->locale = $locale; + + // If the intl extension is loaded, make sure + // that we set the locale for it... if not, though, + // don't worry about it. + try { + if (class_exists('\Locale', false)) + { + \Locale::setDefault($locale); + } + } + catch (\Exception $e) + {} + + return $this; } //-------------------------------------------------------------------- diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index 250cda007a0e..7e93b494edac 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -72,47 +72,6 @@ public function __construct($config, $uri=null) //-------------------------------------------------------------------- - /** - * Returns the default locale as set in Config\App.php - * - * @return string - */ - public function getDefaultLocale(): string - { - return $this->defaultLocale; - } - - //-------------------------------------------------------------------- - - /** - * Gets the current locale, with a fallback to the default - * locale if none is set. - * - * @return string - */ - public function getLocale(): string - { - return $this->locale ?? $this->defaultLocale; - } - - //-------------------------------------------------------------------- - - /** - * Sets the locale string for this request. - * - * @param string $locale - * - * @return $this - */ - public function setLocale(string $locale) - { - $this->locale = $locale; - - return $this; - } - - //-------------------------------------------------------------------- - /** * Gets the user's IP address. * diff --git a/system/HTTP/RequestInterface.php b/system/HTTP/RequestInterface.php index 9b8ddfa14441..7c060411b3aa 100644 --- a/system/HTTP/RequestInterface.php +++ b/system/HTTP/RequestInterface.php @@ -84,34 +84,4 @@ public function getMethod($upper = false): string; public function getServer($index = null, $filter = null); //-------------------------------------------------------------------- - - /** - * Returns the default locale as set in Config\App.php - * - * @return string - */ - public function getDefaultLocale(): string; - - //-------------------------------------------------------------------- - - /** - * Gets the current locale, with a fallback to the default - * locale if none is set. - * - * @return string - */ - public function getLocale(): string; - - //-------------------------------------------------------------------- - - /** - * Sets the locale string for this request. - * - * @param string $locale - * - * @return $this - */ - public function setLocale(string $locale); - - //-------------------------------------------------------------------- } diff --git a/system/Router/Router.php b/system/Router/Router.php index 5472fd088eb5..fb8eb58468df 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -106,6 +106,12 @@ class Router implements RouterInterface */ protected $matchedRoute = null; + /** + * The locale that was detected in a route. + * @var string + */ + protected $detectedLocale = null; + //-------------------------------------------------------------------- /** @@ -298,6 +304,31 @@ public function setTranslateURIDashes($val = false): self //-------------------------------------------------------------------- + /** + * Returns true/false based on whether the current route contained + * a {locale} placeholder. + * + * @return bool + */ + public function hasLocale() + { + return (bool)$this->detectedLocale; + } + + //-------------------------------------------------------------------- + + /** + * Returns the detected locale, if any, or null. + * + * @return string + */ + public function getLocale() + { + return $this->detectedLocale; + } + + //-------------------------------------------------------------------- + /** * Compares the uri string against the routes that the * RouteCollection class defined for us, attempting to find a match. @@ -306,6 +337,7 @@ public function setTranslateURIDashes($val = false): self * @param string $uri The URI path to compare against the routes * * @return bool Whether the route was matched or not. + * @throws \CodeIgniter\Router\RedirectException */ protected function checkRoutes(string $uri): bool { @@ -320,9 +352,27 @@ protected function checkRoutes(string $uri): bool // Loop through the route array looking for wildcards foreach ($routes as $key => $val) { + // Are we dealing with a locale? + if (strpos($key, '{locale}') !== false) + { + $localeSegment = array_search('{locale}', explode('/', $key)); + + // Replace it with a regex so it + // will actually match. + $key = str_replace('{locale}', '[^/]+', $key); + } + // Does the RegEx match? if (preg_match('#^'.$key.'$#', $uri, $matches)) { + // Store our locale so CodeIgniter object can + // assign it to the Request. + if (isset($localeSegment)) + { + $this->detectedLocale = (explode('/', $uri))[$localeSegment]; + unset($localeSegment); + } + // Are we using Closures? If so, then we need // to collect the params into an array // so it can be passed to the controller method later. diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index 48ec17e4e3c2..a1a8078b2ea7 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -26,6 +26,7 @@ public function setUp() 'posts/(:num)/edit' => 'Blog::edit/$1', 'books/(:num)/(:alpha)/(:num)' => 'Blog::show/$3/$1', 'closure/(:num)/(:alpha)' => function ($num, $str) { return $num.'-'.$str; }, + '{locale}/pages' => 'App\Pages::list_all', ]; $this->collection->map($routes); @@ -178,4 +179,20 @@ public function testAutoRouteFindsControllerWithSubfolder() } //-------------------------------------------------------------------- + + /** + * @group single + */ + public function testDetectsLocales() + { + $router = new Router($this->collection); + + $router->handle('fr/pages'); + + $this->assertTrue($router->hasLocale()); + $this->assertEquals('fr', $router->getLocale()); + } + + //-------------------------------------------------------------------- + } diff --git a/user_guide_src/source/general/routing.rst b/user_guide_src/source/general/routing.rst index f1ff7be6aef9..89f5d3a94dcc 100644 --- a/user_guide_src/source/general/routing.rst +++ b/user_guide_src/source/general/routing.rst @@ -18,7 +18,7 @@ For example, let’s say you want your URLs to have this prototype:: example.com/product/2/ example.com/product/3/ example.com/product/4/ - + Normally the second segment of the URL is reserved for the method name, but in the example above it instead has a product ID. To overcome this, CodeIgniter allows you to remap the URI handler. @@ -46,7 +46,7 @@ Placeholders A typical route might look something like this:: $routes->add('product/:num', 'App\Catalog::productLookup'); - + In a route, the first parameter contains the URI to be matched, while the second parameter contains the destination it should be re-routed to. In the above example, if the literal word "product" is found in the first segment of the URL, and a number is found in the second segment, @@ -56,15 +56,18 @@ Placeholders are simply strings that represent a Regular Expression pattern. Dur process, these placeholders are replaced with the value of the Regular Expression. They are primarily used for readability. -The following placeholders are available for you to use in your routes: +The following placeholders are available for you to use in your routes: -* **(:any)** will match all characters from that point to the end of the URI. This may include multiple URI segments. +* **(:any)** will match all characters from that point to the end of the URI. This may include multiple URI segments. * **(:segment)** will match any character except for a forward slash (/) restricting the result to a single segment. * **(:num)** will match any integer. * **(:alpha)** will match any string of alphabetic characters * **(:alphanum)** will match any string of alphabetic characters or integers, or any combination of the two. * **(:hash)** is the same as **:segment**, but can be used to easily see which routes use hashed ids (see the :doc:`Model ` docs). +.. note:: **{locale}** cannot be used as a placeholder or other part of the route, as it is reserved for use + in :doc:`localization `. + Examples ======== @@ -81,12 +84,12 @@ A URL containing the segments "blog/joe" will be remapped to the “\Blogs†cl The ID will be set to “34â€:: $routes->add('product/(:any)', 'Catalog::productLookup'); - + A URL with “product†as the first segment, and anything in the second will be remapped to the “\Catalog†class and the “productLookup†method:: $routes->add('product/(:num)', 'Catalog::productLookupByID/$1'; - + A URL with “product†as the first segment, and a number in the second will be remapped to the “\Catalog†class and the “productLookupByID†method passing in the match as a variable to the method. @@ -130,7 +133,7 @@ For example, if a user accesses a password protected area of your web applicatio redirect them back to the same page after they log in, you may find this example useful:: $routes->add('login/(.+)', 'Auth::login/$1'); - + For those of you who don’t know regular expressions and want to learn more about them, `regular-expressions.info `_ might be a good starting point. @@ -161,7 +164,7 @@ define an array of routes and then pass it as the first parameter to the `map()` $routes = []; $routes['product/(:num)'] = 'Catalog::productLookupById'; $routes['product/(:alphanum)'] = 'Catalog::productLookupByName'; - + $collection->map($routes); @@ -196,7 +199,7 @@ extensive set of routes that all share the opening string, like when building an $routes->add('users', 'Admin\Users::index'); $routes->add('blog', 'Admin\Blog::index'); }); - + This would prefix the 'users' and 'blog" URIs with "admin", handling URLs like ``/admin/users`` and ``/admin/blog``. It is possible to nest groups within groups for finer organization if you need it:: diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst index 8b7e2cfd97f9..8b6835f5938d 100644 --- a/user_guide_src/source/libraries/localization.rst +++ b/user_guide_src/source/libraries/localization.rst @@ -1,12 +1,13 @@ -============ +############ Localization -============ +############ .. contents:: :local: -Introduction -============ +******************** +Working With Locales +******************** CodeIgniter provides several tools to help you localize your application for different languages. While full localization of an application is a complex subject, it's simple to swap out strings in your application @@ -22,6 +23,9 @@ supported language:: /fr app.php +.. important:: Locale detection only works for web-based requests that use the IncomingRequest class. + Command-line requests will not have these features. + Configuring the Locale ====================== @@ -30,10 +34,9 @@ Every site will have a default language/locale they operate in. This can be set public $defaultLocale = 'en'; The value can be any string that your application uses to manage text strings and other formats. It is -recommended that a [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code, followed -by an underscore (_), and the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes) -country code is recommended. This results in language codes like en_US for American English, or fr_FR, -for French/France. +recommended that a [BCP 47](http://www.rfc-editor.org/rfc/bcp/bcp47.txt) language code is used. This results in +language codes like en-US for American English, or fr-FR, for French/France. A more readable introduction +to this can be found on the [W3C's site](https://www.w3.org/International/articles/language-tags/). The system is smart enough to fallback to more generic language codes if an exact match cannot be found. If the locale code was set to **en_US** and we only have language files setup for **en** @@ -43,7 +46,7 @@ directory existed at **application/Language/en_US** then that we be used first. Locale Detection ================ -There are two methods supported to detect the correct locale during each request. The first is a "set and forget" +There are two methods supported to detect the correct locale during the request. The first is a "set and forget" method that will automatically perform :doc:`content negotiation ` for you to determine the correct locale to use. The second method allows you to specify a segment in your routes that will be used to set the locale. @@ -66,3 +69,38 @@ the following example, the **en** locale would be used if no match is found:: In Routes --------- +The second method uses a custom placeholder to detect the desired locale and set it on the Request. The +placeholder ``{locale}`` can be placed as a segment in your route. If present, the contents of the matching +segment will be your locale:: + + $routes->get('{locale}/books', 'App\Books::index'); + +In this example, if the user tried to visit ``http://example.com/fr/books``, then the locale would be +set to ``fr``, assuming it was configured as a valid locale. + +.. note:: If the value doesn't match a valid locale as defined in the App configuration file, the default + locale will be used in it's place. + +Retrieving the Current Locale +============================= + +The current locale can always be retrieved from the IncomingRequest object, through the ``getLocale()` method. +If your controller is extending ``CodeIgniter\Controller``, this will be available through ``$this->request``:: + + namespace App\Controllers; + + class UserController extends \CodeIgniter\Controller + { + public function index() + { + $locale = $this->request->getLocale(); + } + } + +Alternatively, you can use the :doc:`Services class ` to retrieve the current request:: + + $locale = service('request')->getLocale(); + +************** +Language Tools +************** \ No newline at end of file From 66909964646c0e4d71145dad09b9b7c0502247fc Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 14 Aug 2016 22:05:15 -0500 Subject: [PATCH 0182/1807] A bit of refactoring to rmeove dependencies, and getting some basic tests in place. --- application/Config/Services.php | 8 ++- system/Language/{Loader.php => Language.php} | 59 +++++++++++-------- system/Router/Router.php | 2 +- tests/_support/Language/MockLanguage.php | 48 +++++++++++++++ tests/system/Language/LanguageTest.php | 46 +++++++++++++++ .../source/libraries/localization.rst | 6 +- 6 files changed, 139 insertions(+), 30 deletions(-) rename system/Language/{Loader.php => Language.php} (73%) create mode 100644 tests/_support/Language/MockLanguage.php create mode 100644 tests/system/Language/LanguageTest.php diff --git a/application/Config/Services.php b/application/Config/Services.php index 26c8c4d4cde2..775e0e87dfe4 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -209,14 +209,16 @@ public static function iterator($getShared = true) /** * Responsible for loading the language string translations. */ - public static function language($getShared = true) + public static function language(string $locale = null, $getShared = true) { if ($getShared) { - return self::getSharedInstance('language'); + return self::getSharedInstance('language', $locale); } - return new \CodeIgniter\Language\Loader(); + $locale = ! empty($locale) ? $locale : self::request()->getLocale(); + + return new \CodeIgniter\Language\Language($locale); } //-------------------------------------------------------------------- diff --git a/system/Language/Loader.php b/system/Language/Language.php similarity index 73% rename from system/Language/Loader.php rename to system/Language/Language.php index 8901751a7cf9..8dd7b7d69a6e 100644 --- a/system/Language/Loader.php +++ b/system/Language/Language.php @@ -2,7 +2,7 @@ use Config\Services; -class Loader +class Language { /** * Stores the retrieved language lines @@ -38,24 +38,21 @@ class Loader //-------------------------------------------------------------------- - public function __construct() + public function __construct(string $locale) { - $request = Services::request(); - - $this->locale = $request->getLocale(); + $this->locale = $locale; if (class_exists('\MessageFormatter')) { $this->intlSupport = true; }; - - unset($request); } //-------------------------------------------------------------------- /** - * + * Parses the language string for a file, loads the file, if necessary, + * getting * * @param string $line * @param array $args @@ -68,7 +65,7 @@ public function getLine(string $line, array $args = []): string // Will load the language file and strings. $line = $this->parseLine($line); - $output = $this->language[$line] ?? ''; + $output = ! empty($this->language[$line]) ? $this->language[$line] : $line; // Do advanced message formatting here // if the 'intl' extension is available. @@ -94,18 +91,18 @@ protected function parseLine(string $line): string { if (strpos($line, '.') === false) { - throw new \InvalidArgumentException('No language file specified in line: '. $line); + throw new \InvalidArgumentException('No language file specified in line: '.$line); } $file = substr($line, 0, strpos($line, '.')); - $line = substr($line, strlen($file)); + $line = substr($line, strlen($file)+1); - if (! array_key_exists($this->languages[$this->locale][$line])) + if (! array_key_exists($line, $this->language)) { $this->load($file, $this->locale); } - return $line; + return $this->language[$line]; } //-------------------------------------------------------------------- @@ -119,26 +116,20 @@ protected function parseLine(string $line): string * @param string $locale * @param bool $return * - * @return array + * @return array|null */ - public function load(string $file, string $locale, bool $return = false): array + protected function load(string $file, string $locale, bool $return = false) { if (in_array($file, $this->loadedFiles)) { - return; + return []; } $lang = []; $path = APPPATH."Language/{$locale}/{$file}.php"; - // First check the app's Language folder - if (is_file($path)) - { - $lang = require $path; - } - - // @todo - should look into loading from other locations, also probably... + $lang = $this->requireFile($path); // Don't load it more than once. $this->loadedFiles[] = $file; @@ -154,4 +145,26 @@ public function load(string $file, string $locale, bool $return = false): array //-------------------------------------------------------------------- + /** + * A simple method for including files that can be + * overridden during testing. + * + * @todo - should look into loading from other locations, also probably... + * + * @param string $path + * + * @return array + */ + protected function requireFile(string $path): array + { + if (! is_file($path)) + { + return []; + } + + return require_once $path; + } + + //-------------------------------------------------------------------- + } diff --git a/system/Router/Router.php b/system/Router/Router.php index fb8eb58468df..10c23d24a107 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -455,7 +455,7 @@ public function autoRoute(string $uri) $file = APPPATH.'Controllers/'.$this->directory.$this->controller.'.php'; if (file_exists($file)) { - include $file; + include_once $file; } // Ensure the controller stores the fully-qualified class name diff --git a/tests/_support/Language/MockLanguage.php b/tests/_support/Language/MockLanguage.php new file mode 100644 index 000000000000..dae3c4ebd655 --- /dev/null +++ b/tests/_support/Language/MockLanguage.php @@ -0,0 +1,48 @@ +data = $data; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Provides an override that allows us to set custom + * data to be returned easily during testing. + * + * @param string $path + * + * @return array|mixed + */ + protected function requireFile(string $path): array + { + return $this->data ?? []; + } + + //-------------------------------------------------------------------- + +} diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php new file mode 100644 index 000000000000..0c06abb4c46d --- /dev/null +++ b/tests/system/Language/LanguageTest.php @@ -0,0 +1,46 @@ +setExpectedException('\InvalidArgumentException'); + + $lang->getLine('something'); + } + + //-------------------------------------------------------------------- + + public function testGetLineReturnsLine() + { + $lang = new MockLanguage('en'); + + $lang->setData([ + 'bookSaved' => 'We kept the book free from the boogeyman', + 'booksSaved' => 'We saved some more' + ]); + + $this->assertEquals('We saved some more', $lang->getLine('books.booksSaved')); + } + + //-------------------------------------------------------------------- + + public function testGetLineFormatsMessage() + { + // No intl extension? then we can't test this - go away.... + if (! class_exists('\MessageFormatter')) return; + + $lang = new MockLanguage('en'); + + $lang->setData([ + 'books' => '{0, number, integer} books have been saved.' + ]); + + $this->assertEquals('45 books have been saved.', $lang->getLine('books.books', [91/2])); + } + + //-------------------------------------------------------------------- + +} diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst index 8b6835f5938d..71c49ec8a671 100644 --- a/user_guide_src/source/libraries/localization.rst +++ b/user_guide_src/source/libraries/localization.rst @@ -101,6 +101,6 @@ Alternatively, you can use the :doc:`Services class ` to ret $locale = service('request')->getLocale(); -************** -Language Tools -************** \ No newline at end of file +********************* +Language Localization +********************* \ No newline at end of file From b5693ec1f9f5f753495a0459af9550c7c48ade1d Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 14 Aug 2016 22:12:22 -0500 Subject: [PATCH 0183/1807] Language class should look in both APPPATH and BASEPATH. --- system/Language/Language.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index 8dd7b7d69a6e..63a604c0348c 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -127,7 +127,7 @@ protected function load(string $file, string $locale, bool $return = false) $lang = []; - $path = APPPATH."Language/{$locale}/{$file}.php"; + $path = "Language/{$locale}/{$file}.php"; $lang = $this->requireFile($path); @@ -157,12 +157,17 @@ protected function load(string $file, string $locale, bool $return = false) */ protected function requireFile(string $path): array { - if (! is_file($path)) + foreach ([APPPATH, BASEPATH] as $folder) { - return []; + if (! is_file($folder.$path)) + { + continue; + } + + return require_once $folder.$path; } - return require_once $path; + return []; } //-------------------------------------------------------------------- From fdc9d08987baf51b67e57125d6e27837e773dd21 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 14 Aug 2016 23:27:33 -0500 Subject: [PATCH 0184/1807] [ci skip] Wrapping up docs for Language --- .../source/libraries/localization.rst | 124 +++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst index 71c49ec8a671..93282a1d70c4 100644 --- a/user_guide_src/source/libraries/localization.rst +++ b/user_guide_src/source/libraries/localization.rst @@ -103,4 +103,126 @@ Alternatively, you can use the :doc:`Services class ` to ret ********************* Language Localization -********************* \ No newline at end of file +********************* + +Creating Language Files +======================= + +Language do not have any specific naming convention that are required. The file should be named logically to +describe the type of content it holds. For example, let's say you want to create a file containing error messages. +You might name it simply: **Errors.php**. + +Within the file you would return an array, where each element in the array has a language key and the string to return:: + + 'language_key' => 'The actual message to be shown.' + +.. note:: It's good practice to use a common prefix for all messages in a given file to avoid collisions with + similarly named items in other files. For example, if you are creating error messages you might prefix them + with error\_ + +:: + + return [ + 'errorEmailMissing' => 'You must submit an email address', + 'errorURLMissing' => 'You must submit a URL', + 'errorUsernameMissing' => 'You must submit a username', + ]; + +Basic Usage +=========== + +You can use the ``lang()`` helper function to retrieve text from any of the language files, by passing the +filename and the language key as the first paremeter, separated by a period (.). For example, to load the +``errorEmailMissing`` string from the ``Errors`` language file, you would do the following:: + + echo lang('Errors.errorEmailMissing'); + +If the requested language key doesn't exist in the file for the current locale, the string will be passed +back, unchanged. In this example, it would return 'Errors.errorEmailMissing' if it didn't exist. + +Replacing Parameters +-------------------- + +.. note:: The following functions all require the `intl `_ extension to + be loaded on your system in order to work. If the extension is not loaded, no replacement will be attempted. + A great overview can be found over at `Sitepoint `_. + +You can pass an array of values to replace placeholders in the language string as the second parameter to the +``lang()`` function. This allows for very simple number translations and formatting:: + + // The language file, Tests.php: + return [ + "apples" => "I have {0, number} apples.", + "men" => "I have {1, number} men out-performed the remaining {0, number}", + "namedApples" => "I have {number_apples, number, integer} apples.", + ]; + + // Displays "I have 3 apples." + echo lang('Tests.apples', [ 3 ]); + +The first item in the placeholder corresponds to the index of the item in the array, if it's numerical:: + + // Displays "The top 23 men out-performed the remaining 20" + echo lang('Tests.men', [20, 23]); + +You can also use named keys to make it easier to keep things straight, if you'd like:: + + // Displays "I have 3 apples." + echo lang("Tests.namedApples", ['number_apples' => 3]); + +Obviously, you can do more than just number replacement. According to the +`official ICU docs `_ for the underlying +library, the following types of data can be replaced: + +* numbers - integer, currency, percent +* dates - short, medium, long, full +* time - short, medium, long, full +* spellout - spells out numbers (i.e. 34 becomes thirty-four) +* ordinal +* duration + +Here are a few examples:: + + // The language file, Tests.php + return [ + 'shortTime' => 'The time is now {0, time, short}.', + 'mediumTime' => 'The time is now {0, time, medium}.', + 'longTime' => 'The time is now {0, time, long}.', + 'fullTime' => 'The time is now {0, time, full}.', + 'shortDate' => 'The date is now {0, date, short}.', + 'mediumDate' => 'The date is now {0, date, medium}.', + 'longDate' => 'The date is now {0, date, long}.', + 'fullDate' => 'The date is now {0, date, full}.', + 'spelledOut' => '34 is {0, spellout}', + 'ordinal' => 'The ordinal is {0, ordinal}', + 'duration' => 'It has been {0, duration}', + ]; + + // Displays "The time is now 11:18 PM" + echo lang('Tests.shortTime', [time()]); + // Displays "The time is now 11:18:50 PM" + echo lang('Tests.mediumTime', [time()]); + // Displays "The time is now 11:19:09 PM CDT" + echo lang('Tests.longTime', [time()]); + // Displays "The time is now 11:19:26 PM Central Daylight Time" + echo lang('Tests.fullTime', [time()]); + + // Displays "The date is now 8/14/16" + echo lang('Tests.shortDate', [time()]); + // Displays "The date is now Aug 14, 2016" + echo lang('Tests.mediumDate', [time()]); + // Displays "The date is now August 14, 2016" + echo lang('Tests.longDate', [time()]); + // Displays "The date is now Sunday, August 14, 2016" + echo lang('Tests.fullDate', [time()]); + + // Displays "34 is thirty-four" + echo lang('Tests.spelledOut', [34]); + + // Displays "It has been 408,676:24:35" + echo lang('Tests.ordinal', [time()]); + +You should be sure to read up on the MessageFormatter class and the underlying ICU formatting to get a better +idea on what capabilities it has, like permorming conditional replacement, and more. Both of the links provided +earlier will give you an excellent idea as to the options available. + From d3c8964e3a6d5e5d31777b02b13dbfb5e973687e Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 14 Aug 2016 23:47:39 -0500 Subject: [PATCH 0185/1807] Update requirements page to reflect intl extension as a recommendation --- user_guide_src/source/intro/requirements.rst | 5 ++++- user_guide_src/source/libraries/localization.rst | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/intro/requirements.rst b/user_guide_src/source/intro/requirements.rst index ef27423e67bc..27270babd806 100644 --- a/user_guide_src/source/intro/requirements.rst +++ b/user_guide_src/source/intro/requirements.rst @@ -4,13 +4,16 @@ Server Requirements `PHP `_ version 7.0 or newer is required. +While not required, the *intl* extension is recommended, and some portions of the +framework will provided enhanced functionality when it's present. + A database is required for most web application programming. Currently supported databases are: - MySQL (5.1+) via the *MySQLi* driver - PostgreSQL via the *Postgre* driver -Not all of the drivers have been converted/rewritten for CodeIgniter4. +Not all of the drivers have been converted/rewritten for CodeIgniter4. The list below shows the outstanding ones. - MySQL (5.1+) via the *pdo* driver diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst index 93282a1d70c4..d0152267270e 100644 --- a/user_guide_src/source/libraries/localization.rst +++ b/user_guide_src/source/libraries/localization.rst @@ -223,6 +223,6 @@ Here are a few examples:: echo lang('Tests.ordinal', [time()]); You should be sure to read up on the MessageFormatter class and the underlying ICU formatting to get a better -idea on what capabilities it has, like permorming conditional replacement, and more. Both of the links provided +idea on what capabilities it has, like permorming conditional replacement, pluralization, and more. Both of the links provided earlier will give you an excellent idea as to the options available. From 4937c7bc84241114d2879acaa2576675e09fa632 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 15 Aug 2016 22:54:54 -0500 Subject: [PATCH 0186/1807] Starting to use the Language feature in system files --- system/Cache/CacheFactory.php | 6 +-- system/Commands/MigrationsCommand.php | 38 +++++++-------- system/Database/MigrationRunner.php | 18 +++---- system/Language/en/Cache.php | 43 ++++++++++++++++ system/Language/en/Migrations.php | 70 +++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 31 deletions(-) create mode 100644 system/Language/en/Cache.php create mode 100644 system/Language/en/Migrations.php diff --git a/system/Cache/CacheFactory.php b/system/Cache/CacheFactory.php index cee74ebea577..4900c6e61121 100644 --- a/system/Cache/CacheFactory.php +++ b/system/Cache/CacheFactory.php @@ -22,12 +22,12 @@ public static function getHandler($config, string $handler = null, string $backu { if (! isset($config->validHandlers) || ! is_array($config->validHandlers)) { - throw new \InvalidArgumentException('Cache config must have an array of $validHandlers.'); + throw new \InvalidArgumentException(lang('Cache.cacheInvalidHandlers')); } if (! isset($config->handler) || ! isset($config->backupHandler)) { - throw new \InvalidArgumentException('Cache config must have a handler and backupHandler set.'); + throw new \InvalidArgumentException(lang('Cache.cacheNoBackup')); } $handler = ! empty($handler) ? $handler : $config->handler; @@ -35,7 +35,7 @@ public static function getHandler($config, string $handler = null, string $backu if (! array_key_exists($handler, $config->validHandlers) || ! array_key_exists($backup, $config->validHandlers)) { - throw new \InvalidArgumentException('Cache config has an invalid handler or backup handler specified.'); + throw new \InvalidArgumentException(lang('Cache.cacheHandlerNotFound')); } // Get an instance of our handler. diff --git a/system/Commands/MigrationsCommand.php b/system/Commands/MigrationsCommand.php index ed4e76ae0992..c47b386e824e 100644 --- a/system/Commands/MigrationsCommand.php +++ b/system/Commands/MigrationsCommand.php @@ -43,14 +43,14 @@ /** * Class MigrationsCommand. - * + * * Migrations controller. */ class MigrationsCommand extends \CodeIgniter\Controller { /** * Migration runner. - * + * * @var \CodeIgniter\Database\MigrationRunner */ protected $runner; @@ -73,12 +73,12 @@ public function __construct() public function index() { CLI::write('Migration Commands', 'white'); - CLI::write(CLI::color('latest', 'yellow'). "\t\tMigrates database to latest available migration."); - CLI::write(CLI::color('current', 'yellow'). "\t\tMigrates database to version set as 'current' in configuration."); - CLI::write(CLI::color('version [v]', 'yellow'). "\tMigrates database to version {v}."); - CLI::write(CLI::color('rollback', 'yellow'). "\tRuns all migrations 'down' to version 0."); - CLI::write(CLI::color('refresh', 'yellow'). "\t\tUninstalls and re-runs all migrations to freshen database."); - CLI::write(CLI::color('seed [name]', 'yellow'). "\tRuns the seeder named [name]."); + CLI::write(CLI::color('latest', 'yellow'). lang('Migrations.migHelpLatest')); + CLI::write(CLI::color('current', 'yellow'). lang('Migrations.migHelpCurrent')); + CLI::write(CLI::color('version [v]', 'yellow'). lang('Migrations.migHelpVersion')); + CLI::write(CLI::color('rollback', 'yellow'). lang('Migrations.migHelpRollback')); + CLI::write(CLI::color('refresh', 'yellow'). lang('Migrations.migHelpRefresh')); + CLI::write(CLI::color('seed [name]', 'yellow'). lang('Migrations.migHelpSeed')); } //-------------------------------------------------------------------- @@ -89,7 +89,7 @@ public function index() */ public function latest() { - CLI::write('Migrating to latest version...', 'yellow'); + CLI::write(lang('Migrations.migToLatest'), 'yellow'); try { $this->runner->latest(); @@ -101,7 +101,7 @@ public function latest() CLI::write('Done'); } - + //-------------------------------------------------------------------- /** @@ -113,16 +113,16 @@ public function version(int $version = null) { if (is_null($version)) { - $version = CLI::prompt('Version'); + $version = CLI::prompt(lang('Migrations.version')); } if (is_null($version)) { - CLI::error('Invalid version number provided.'); + CLI::error(lang('Migrations.invalidVersion')); exit(); } - CLI::write("Migrating to version {$version}...", 'yellow'); + CLI::write(sprintf(lang('Migrations.migToVersionPH'), $version), 'yellow'); try { $this->runner->version($version); @@ -143,7 +143,7 @@ public function version(int $version = null) */ public function current() { - CLI::write("Migrating to current version...", 'yellow'); + CLI::write(lang('Migrations.migToVersion'), 'yellow'); try { $this->runner->current(); @@ -164,7 +164,7 @@ public function current() */ public function rollback() { - CLI::write("Rolling back all migrations...", 'yellow'); + CLI::write(lang('Migrations.migRollingBack'), 'yellow'); try { $this->runner->version(0); @@ -201,7 +201,7 @@ public function status() if (empty($migrations)) { - return CLI::error('No migrations were found.'); + return CLI::error(lang('Migrations.migNoneFound')); } $max = 0; @@ -214,7 +214,7 @@ public function status() $max = max($max, strlen($file)); } - CLI::write(str_pad('Filename', $max+4).'Migrated On', 'yellow'); + CLI::write(str_pad(lang('Migrations.filename'), $max+4).lang('Migrations.migOn'), 'yellow'); foreach ($migrations as $version => $file) { @@ -244,12 +244,12 @@ public function seed(string $seedName = null) if (empty($seedName)) { - $seedName = CLI::prompt('Seeder name'); + $seedName = CLI::prompt(lang('Migrations.migSeeder')); } if (empty($seedName)) { - CLI::error('You must provide a seeder name.'); + CLI::error(lang('Migrations.migMissingSeeder')); return; } diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 36b5f5b5aee6..630e78a2c808 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -121,12 +121,12 @@ public function __construct(BaseConfig $config, ConnectionInterface $db = null) if (empty($this->table)) { - throw new ConfigException('Migrations table must be set.'); + throw new ConfigException(lang('Migrations.migMissingTable')); } if ( ! in_array($this->type, ['sequential', 'timestamp'])) { - throw new ConfigException('An invalid migration numbering type was specified: '.$this->type); + throw new ConfigException(lang('Migrations.migInvalidType').$this->type); } // Migration basename regex @@ -160,7 +160,7 @@ public function version(string $targetVersion, $group='default') { if (! $this->enabled) { - throw new ConfigException('Migrations have been loaded but are disabled or setup incorrectly.'); + throw new ConfigException(lang('Migrations.migDisabled')); } // Note: We use strings, so that timestamp versions work on 32-bit systems @@ -179,7 +179,7 @@ public function version(string $targetVersion, $group='default') if ($targetVersion > 0 && ! isset($migrations[$targetVersion])) { - throw new \RuntimeException('Migration file not found: '.$targetVersion); + throw new \RuntimeException(lang('Migrations.migNotFound').$targetVersion); } if ($targetVersion > $currentVersion) @@ -207,7 +207,7 @@ public function version(string $targetVersion, $group='default') // Check for sequence gaps if ($this->type === 'sequential' && $previous !== false && abs($number - $previous) > 1) { - throw new \RuntimeException('There is a gap in the migration sequence near version number: '.$number); + throw new \RuntimeException(lang('Migration.migGap').$number); } include_once $file; @@ -216,7 +216,7 @@ public function version(string $targetVersion, $group='default') // Validate the migration file structure if ( ! class_exists($class, false)) { - throw new \RuntimeException(sprintf('The migration class "%s" could not be found.', $class)); + throw new \RuntimeException(sprintf(lang('Migrations.migClassNotFound'), $class)); } $previous = $number; @@ -231,7 +231,7 @@ public function version(string $targetVersion, $group='default') if ( ! is_callable([$instance, $method])) { - throw new \RuntimeException("The migration class is missing an \"{$method}\" method."); + throw new \RuntimeException(sprintf(lang('Migrations.migMissingMethod'), $method)); } call_user_func([$instance, $method]); @@ -260,7 +260,7 @@ public function latest() { if ($this->silent) return false; - throw new \RuntimeException('No migrations were found.'); + throw new \RuntimeException(lang('Migrations.migNotFound')); } $lastMigration = basename(end($migrations)); @@ -306,7 +306,7 @@ public function findMigrations() // There cannot be duplicate migration numbers if (isset($migrations[$number])) { - throw new \RuntimeException('There are multiple migrations with the same version number: '.$number); + throw new \RuntimeException(lang('Migrations.migMultiple').$number); } $migrations[$number] = $file; diff --git a/system/Language/en/Cache.php b/system/Language/en/Cache.php new file mode 100644 index 000000000000..767f12dcbd5a --- /dev/null +++ b/system/Language/en/Cache.php @@ -0,0 +1,43 @@ + 'Cache config must have an array of $validHandlers.', + 'cacheNoBackup' => 'Cache config must have a handler and backupHandler set.', + 'cacheHandlerNotFound' => 'Cache config has an invalid handler or backup handler specified.', +]; diff --git a/system/Language/en/Migrations.php b/system/Language/en/Migrations.php new file mode 100644 index 000000000000..459847a1d2a8 --- /dev/null +++ b/system/Language/en/Migrations.php @@ -0,0 +1,70 @@ + 'Migrations table must be set.', + 'migInvalidType' => 'An invalid migration numbering type was specified: ', + 'migDisabled' => 'Migrations have been loaded but are disabled or setup incorrectly.', + 'migNotFound' => 'Migration file not found: ', + 'migGap' => 'There is a gap in the migration sequence near version number: ', + 'migClassNotFound' => 'The migration class "%s" could not be found.', + 'migMissingMethod' => 'The migration class is missing an "%s" method.', + 'migMultiple' => 'There are multiple migrations with the same version number: ', + + // Migration Command + 'migHelpLatest' => "\t\tMigrates database to latest available migration.", + 'migHelpCurrent' => "\t\tMigrates database to version set as 'current' in configuration.", + 'migHelpVersion' => "\tMigrates database to version {v}.", + 'migHelpRollback' => "\tRuns all migrations 'down' to version 0.", + 'migHelpRefresh' => "\t\tUninstalls and re-runs all migrations to freshen database.", + 'migHelpSeed' => "\tRuns the seeder named [name].", + + 'migToLatest' => 'Migrating to latest version...', + 'migInvalidVersion' => 'Invalid version number provided.', + 'migToVersionPH' => 'Migrating to version %s...', + 'migToVersion' => 'Migrating to current version...', + 'migRollingBack' => "Rolling back all migrations...", + 'migNoneFound' => 'No migrations were found.', + 'migOn' => 'Migrated On', + 'migSeeder' => 'Seeder name', + 'migMissingSeeder' => 'You must provide a seeder name.', + + 'version' => 'Version', + 'filename' => 'Filename', +]; From 87780cb20ee0de1c9668f8c3641156f94eac789c Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Wed, 17 Aug 2016 11:40:02 +0700 Subject: [PATCH 0187/1807] fix missing double backquote Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/libraries/localization.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst index d0152267270e..5255a95136aa 100644 --- a/user_guide_src/source/libraries/localization.rst +++ b/user_guide_src/source/libraries/localization.rst @@ -84,7 +84,7 @@ set to ``fr``, assuming it was configured as a valid locale. Retrieving the Current Locale ============================= -The current locale can always be retrieved from the IncomingRequest object, through the ``getLocale()` method. +The current locale can always be retrieved from the IncomingRequest object, through the ``getLocale()`` method. If your controller is extending ``CodeIgniter\Controller``, this will be available through ``$this->request``:: namespace App\Controllers; From 24bf6f59c698b704bc468cba4be19aca7f6327d5 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 17 Aug 2016 23:09:58 -0500 Subject: [PATCH 0188/1807] Fixing up some issues with moving UploadedFiles. Fixes #236 --- system/HTTP/Files/UploadedFile.php | 11 +++++------ system/HTTP/Files/UploadedFileInterface.php | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index e7f44f09e9d6..3a1d8ee4cfcf 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -152,13 +152,12 @@ public function __construct(string $path, string $originalName, string $mimeType * * @param string $targetPath Path to which to move the uploaded file. * @param string $name the name to rename the file to. - * @return bool * * @throws \InvalidArgumentException if the $path specified is invalid. * @throws \RuntimeException on any error during the move operation. * @throws \RuntimeException on the second or subsequent call to the method. */ - public function move(string $targetPath, string $name = null): bool + public function move(string $targetPath, string $name = null) { if ($this->hasMoved) { @@ -179,14 +178,14 @@ public function move(string $targetPath, string $name = null): bool throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', basename($this->path), $targetPath, strip_tags($error['message']))); } - @chmod($targetPath, 0666 & ~umask()); - - unlink($this->path); + @chmod($targetPath, 0777 & ~umask()); // Success, so store our new information $this->path = $targetPath; $this->name = $name; $this->hasMoved = true; + + return true; } //-------------------------------------------------------------------- @@ -270,7 +269,7 @@ public function getError(): int /** * Get error string - * + * * @staticvar array $errors * @return type */ diff --git a/system/HTTP/Files/UploadedFileInterface.php b/system/HTTP/Files/UploadedFileInterface.php index d5f21c9658e3..d37f910255cc 100644 --- a/system/HTTP/Files/UploadedFileInterface.php +++ b/system/HTTP/Files/UploadedFileInterface.php @@ -89,7 +89,7 @@ public function __construct(string $path, string $originalName, string $mimeType * @throws \RuntimeException on any error during the move operation. * @throws \RuntimeException on the second or subsequent call to the method. */ - public function move(string $targetPath, string $name = null): bool; + public function move(string $targetPath, string $name = null); //-------------------------------------------------------------------- From abb771897011ec2d79bec1cdf712a9c3e6f9e328 Mon Sep 17 00:00:00 2001 From: Mitha Aprilia Date: Fri, 19 Aug 2016 09:57:25 +0700 Subject: [PATCH 0189/1807] to make bold text, should use double asterik Signed-off-by: Mitha Aprilia --- user_guide_src/source/libraries/pagination.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/pagination.rst b/user_guide_src/source/libraries/pagination.rst index 1da33564ee29..846c4874e2cb 100644 --- a/user_guide_src/source/libraries/pagination.rst +++ b/user_guide_src/source/libraries/pagination.rst @@ -124,7 +124,7 @@ should be used. The *default_full* and *default_simple* views are used for the ` methods, respectively. To change the way those are displayed application-wide, you could assign a new view here. For example, say you create a new view file that works with the Foundation CSS framework, instead of Bootstrap, and -you place that file at *application/Views/Pagers/foundation_full.php**. Since the **application** directory is +you place that file at **application/Views/Pagers/foundation_full.php**. Since the **application** directory is namespaced as ``App``, and all directories underneath it map directly to segments of the namespace, you can locate the view file through it's namespace:: From 3687744a955d0479da531859b5a5f5f13863e8aa Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Sat, 20 Aug 2016 12:25:08 +0700 Subject: [PATCH 0190/1807] fix ordinalize example Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/helpers/inflector_helper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/helpers/inflector_helper.rst b/user_guide_src/source/helpers/inflector_helper.rst index aaaf254af67e..963bd651cff8 100755 --- a/user_guide_src/source/helpers/inflector_helper.rst +++ b/user_guide_src/source/helpers/inflector_helper.rst @@ -127,4 +127,4 @@ The following functions are available: to denote the position such as 1st, 2nd, 3rd, 4th. Example:: - ordinal(1); // Returns '1st' \ No newline at end of file + ordinalize(1); // Returns '1st' \ No newline at end of file From 9b576022c98b4be0635feae8fcc39dcf43a04900 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 21 Aug 2016 22:44:24 -0500 Subject: [PATCH 0191/1807] Properly apply base_url to incomingRequest class' URI. Fixes #238 --- system/HTTP/IncomingRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 0a267df5432e..3b334e276d1e 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -484,7 +484,7 @@ protected function detectURI($protocol, $baseURL) $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->setPath(parse_url($baseURL, PHP_URL_PATH)); + $this->uri->resolveRelativeURI(parse_url($baseURL, PHP_URL_PATH)); } else { From 932e60c1bc59319f6593f0b2a571aadfe55677c1 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 23 Aug 2016 22:50:34 -0500 Subject: [PATCH 0192/1807] Stop creating new instances of App Config all over the place and actually keep a single source. --- application/Config/Services.php | 64 ++++++++++++++++++++------------- system/CodeIgniter.php | 11 ++++++ system/Common.php | 16 +++++++++ 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/application/Config/Services.php b/application/Config/Services.php index 775e0e87dfe4..ff97bd083952 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -71,22 +71,6 @@ public static function cache(\Config\Cache $config = null, $getShared = true) //-------------------------------------------------------------------- - /** - * View cells are intended to let you insert HTML into view - * that has been generated by any callable in the system. - */ - public static function viewcell($getShared = true) - { - if ($getShared) - { - return self::getSharedInstance('viewcell'); - } - - return new \CodeIgniter\View\Cell(self::cache()); - } - - //-------------------------------------------------------------------- - /** * The CLI Request class provides for ways to interact with * a command line request. @@ -100,7 +84,7 @@ public static function clirequest(App $config=null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } return new \CodeIgniter\HTTP\CLIRequest( @@ -124,7 +108,7 @@ public static function curlrequest(array $options = [], $response = null, App $c if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } if ( ! is_object($response)) @@ -158,7 +142,7 @@ public static function exceptions(App $config = null, $getShared = true) if (empty($config)) { - $config = new App(); + $config = codeigniter()->config; } return new \CodeIgniter\Debug\Exceptions($config); @@ -344,7 +328,7 @@ public static function request(App $config = null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } return new \CodeIgniter\HTTP\IncomingRequest( @@ -367,7 +351,7 @@ public static function response(App $config = null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } return new \CodeIgniter\HTTP\Response($config); @@ -425,7 +409,7 @@ public static function security(App $config = null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } return new \CodeIgniter\Security\Security($config); @@ -448,7 +432,7 @@ public static function session(App $config = null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } $logger = self::logger(true); @@ -490,7 +474,7 @@ public static function toolbar(App $config = null, $getShared = true) if (! is_object($config)) { - $config = new App(); + $config = codeigniter()->config; } return new \CodeIgniter\Debug\Toolbar($config); @@ -513,6 +497,38 @@ public static function uri($uri = null, $getShared = true) //-------------------------------------------------------------------- + public static function validation($config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('uri', $config); + } + + if (empty($config)) + { + $config = new \Config\Validation(); + } + + return new \CodeIgniter\HTTP\URI($config); + } + + //-------------------------------------------------------------------- + + /** + * View cells are intended to let you insert HTML into view + * that has been generated by any callable in the system. + */ + public static function viewcell($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('viewcell'); + } + + return new \CodeIgniter\View\Cell(self::cache()); + } + + //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Utility Methods - DO NOT EDIT diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index c0bc6c20c79f..e4f467eadb3c 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -708,4 +708,15 @@ protected function callExit($code) } //-------------------------------------------------------------------- + + public function __get(string $name) + { + if (isset($this->$name)) + { + return $this->$name; + } + } + + //-------------------------------------------------------------------- + } diff --git a/system/Common.php b/system/Common.php index 7c34f282ade7..6b89df070446 100644 --- a/system/Common.php +++ b/system/Common.php @@ -85,6 +85,22 @@ function cache(string $key = null) //-------------------------------------------------------------------- +if (! function_exists('codeigniter')) +{ + /** + * Grabs the current CodeIgniter instance. + * Primarily used by Services to get current App config. + * + * @return \CodeIgniter\CodeIgniter + */ + function codeigniter() + { + global $codeigniter; + + return $codeigniter; + } +} + if ( ! function_exists('view')) { /** From cd0e594ee3ac5d72d2a33399aa9e61fd94ddd136 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 23 Aug 2016 23:08:07 -0500 Subject: [PATCH 0193/1807] REverting previous change since this kills so many tests... --- application/Config/Services.php | 16 ++++++++-------- system/CodeIgniter.php | 11 ----------- system/Common.php | 16 ---------------- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/application/Config/Services.php b/application/Config/Services.php index ff97bd083952..f0ba40c6bc05 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -84,7 +84,7 @@ public static function clirequest(App $config=null, $getShared = true) if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } return new \CodeIgniter\HTTP\CLIRequest( @@ -108,7 +108,7 @@ public static function curlrequest(array $options = [], $response = null, App $c if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } if ( ! is_object($response)) @@ -142,7 +142,7 @@ public static function exceptions(App $config = null, $getShared = true) if (empty($config)) { - $config = codeigniter()->config; + $config = new App(); } return new \CodeIgniter\Debug\Exceptions($config); @@ -328,7 +328,7 @@ public static function request(App $config = null, $getShared = true) if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } return new \CodeIgniter\HTTP\IncomingRequest( @@ -351,7 +351,7 @@ public static function response(App $config = null, $getShared = true) if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } return new \CodeIgniter\HTTP\Response($config); @@ -409,7 +409,7 @@ public static function security(App $config = null, $getShared = true) if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } return new \CodeIgniter\Security\Security($config); @@ -432,7 +432,7 @@ public static function session(App $config = null, $getShared = true) if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } $logger = self::logger(true); @@ -474,7 +474,7 @@ public static function toolbar(App $config = null, $getShared = true) if (! is_object($config)) { - $config = codeigniter()->config; + $config = new App(); } return new \CodeIgniter\Debug\Toolbar($config); diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index e4f467eadb3c..c0bc6c20c79f 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -708,15 +708,4 @@ protected function callExit($code) } //-------------------------------------------------------------------- - - public function __get(string $name) - { - if (isset($this->$name)) - { - return $this->$name; - } - } - - //-------------------------------------------------------------------- - } diff --git a/system/Common.php b/system/Common.php index 6b89df070446..7c34f282ade7 100644 --- a/system/Common.php +++ b/system/Common.php @@ -85,22 +85,6 @@ function cache(string $key = null) //-------------------------------------------------------------------- -if (! function_exists('codeigniter')) -{ - /** - * Grabs the current CodeIgniter instance. - * Primarily used by Services to get current App config. - * - * @return \CodeIgniter\CodeIgniter - */ - function codeigniter() - { - global $codeigniter; - - return $codeigniter; - } -} - if ( ! function_exists('view')) { /** From d8c87c808670be139b37c8cffbf0a3baa9c0c662 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 23 Aug 2016 23:20:13 -0500 Subject: [PATCH 0194/1807] BaseURL actually reads the baseURL setting now... Fixes #240 --- system/HTTP/IncomingRequest.php | 8 ++++- system/Helpers/url_helper.php | 22 +++++++++++-- tests/system/Helpers/URLHelperTest.php | 44 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 3b334e276d1e..a7f9b9730ebf 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -120,6 +120,11 @@ class IncomingRequest extends Request */ protected $validLocales = []; + /** + * @var \Config\App + */ + public $config; + //-------------------------------------------------------------------- /** @@ -137,7 +142,8 @@ public function __construct($config, $uri = null, $body = 'php://input') $body = file_get_contents('php://input'); } - $this->body = $body; + $this->body = $body; + $this->config = $config; parent::__construct($config, $uri); diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index 9248b72a8143..ccccd23e8bb3 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -103,8 +103,24 @@ function base_url($path = '', string $scheme = null): string $path = implode('/', $path); } - $url = \CodeIgniter\Services::request()->uri; + // We should be using the set baseURL the user set + // otherwise get rid of the path because we have + // no way of knowing the intent... + $config = \CodeIgniter\Services::request()->config; + if (! empty($config->baseURL)) + { + $url = new \CodeIgniter\HTTP\URI($config->baseURL); + } + else + { + $url = \CodeIgniter\Services::request()->uri; + $url->setPath('/'); + } + + unset($config); + + // Merge in the path set by the user, if any if ( ! empty($path)) { $url = $url->resolveRelativeURI($path); @@ -132,7 +148,7 @@ function base_url($path = '', string $scheme = null): string * function is placed * * @param boolean $returnObject True to return an object instead of a strong - * + * * @return string|URI */ function current_url(bool $returnObject = false) @@ -503,7 +519,7 @@ function auto_link($str, $type = 'both', $popup = false): string * * Formerly used URI, but that does not play nicely with URIs missing * the scheme. - * + * * @param string the URL * @return string */ diff --git a/tests/system/Helpers/URLHelperTest.php b/tests/system/Helpers/URLHelperTest.php index 3d148022615e..2eecce6ed9fd 100644 --- a/tests/system/Helpers/URLHelperTest.php +++ b/tests/system/Helpers/URLHelperTest.php @@ -12,6 +12,7 @@ class URLHelperTest extends \CIUnitTestCase public function setUp() { helper('url'); + Services::reset(); } //-------------------------------------------------------------------- @@ -196,6 +197,49 @@ public function testBaseURLExample() $this->assertEquals('http://example.com/blog/post/123', base_url('blog/post/123')); } + /** + * @see https://github.com/bcit-ci/CodeIgniter4/issues/240 + */ + public function testBaseURLWithSegments() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/test'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = 'http://example.com/'; + $request = Services::request($config, false); + $request->uri = new URI('http://example.com/test'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/', base_url()); + } + + //-------------------------------------------------------------------- + + /** + * @see https://github.com/bcit-ci/CodeIgniter4/issues/240 + */ + public function testBaseURLWithSegmentsAgain() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/test/page'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = ''; + $request = Services::request($config, false); + $request->uri = new URI('http://example.com/test/page'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/', base_url()); + $this->assertEquals('http://example.com/profile', base_url('profile')); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- // Test current_url From 803b44cda2ff7d79f7b354ebafcef8736a19c27b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 23 Aug 2016 23:23:09 -0500 Subject: [PATCH 0195/1807] Confirming that site_url respects that latest changes, too --- tests/system/Helpers/URLHelperTest.php | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/system/Helpers/URLHelperTest.php b/tests/system/Helpers/URLHelperTest.php index 2eecce6ed9fd..6feee6330431 100644 --- a/tests/system/Helpers/URLHelperTest.php +++ b/tests/system/Helpers/URLHelperTest.php @@ -146,6 +146,49 @@ public function testSiteURLSegments() $this->assertEquals('http://example.com/index.php/news/local/123', site_url(['news', 'local', '123'], null, $config)); } + /** + * @see https://github.com/bcit-ci/CodeIgniter4/issues/240 + */ + public function testSiteURLWithSegments() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/test'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = 'http://example.com/'; + $request = Services::request($config, false); + $request->uri = new URI('http://example.com/test'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/index.php/', site_url()); + } + + //-------------------------------------------------------------------- + + /** + * @see https://github.com/bcit-ci/CodeIgniter4/issues/240 + */ + public function testSiteURLWithSegmentsAgain() + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['REQUEST_URI'] = '/test/page'; + + // Since we're on a CLI, we must provide our own URI + $config = new App(); + $config->baseURL = ''; + $request = Services::request($config, false); + $request->uri = new URI('http://example.com/test/page'); + + Services::injectMock('request', $request); + + $this->assertEquals('http://example.com/index.php/', site_url()); + $this->assertEquals('http://example.com/index.php/profile', site_url('profile')); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- // Test base_url From d9250b7345bbe9df2c7c3030927b4d36850a5f8d Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Wed, 24 Aug 2016 21:08:38 +0800 Subject: [PATCH 0196/1807] Created cookie helper, user guide and unit test. --- system/Helpers/cookie_helper.php | 155 ++++++++++++++++++ tests/system/Helpers/CookieHelperTest.php | 83 ++++++++++ .../source/helpers/cookie_helper.rst | 79 +++++++++ 3 files changed, 317 insertions(+) create mode 100755 system/Helpers/cookie_helper.php create mode 100755 tests/system/Helpers/CookieHelperTest.php create mode 100755 user_guide_src/source/helpers/cookie_helper.rst diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php new file mode 100755 index 000000000000..4b487c419ebf --- /dev/null +++ b/system/Helpers/cookie_helper.php @@ -0,0 +1,155 @@ +setCookie() + * @see \CodeIgniter\HTTP\Response::setCookie() + * @return void + */ + function set_cookie + ( + $name, + string $value = '', + string $expire = '', + string $domain = '', + string $path = '/', + string $prefix = '', + bool $secure = false, + bool $httpOnly = false + ) + { + (\Config\Services::response(new \Config\App()))->setcookie + ( + $name, + $value, + $expire, + $domain, + $path, + $secure, + $httpOnly + ); + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('get_cookie')) +{ + + /** + * Fetch an item from the COOKIE array + * + * @param mixed $index + * @param bool $xssClean + * @see (\Config\Services::request())->getCookie() + * @see \CodeIgniter\HTTP\IncomingRequest::getCookie() + * @return mixed + */ + function get_cookie($index, bool $xssClean = false) + { + $app = new \Config\App(); + $prefix = $app->cookiePrefix; + $index = $prefix . $index; + + $request = \Config\Services::request($app); + $filter = true === $xssClean ? FILTER_SANITIZE_STRING : null; + $cookie = $request->getCookie($index, $filter); + + return $cookie; + } + +} + +//-------------------------------------------------------------------- + +if ( ! function_exists('delete_cookie')) +{ + + /** + * Delete a COOKIE + * + * @param mixed $name + * @param string $domain the cookie domain. Usually: .yourdomain.com + * @param string $path the cookie path + * @param string $prefix the cookie prefix + * @see \CodeIgniter\HTTP\Response::setcookie() + * @return void + */ + function delete_cookie + ( + $name, + string $domain = '', + string $path = '/', + string $prefix = '' + ) + { + set_cookie($name, '', '', $domain, $path, $prefix); + } + +} \ No newline at end of file diff --git a/tests/system/Helpers/CookieHelperTest.php b/tests/system/Helpers/CookieHelperTest.php new file mode 100755 index 000000000000..95359e85eae9 --- /dev/null +++ b/tests/system/Helpers/CookieHelperTest.php @@ -0,0 +1,83 @@ +name = 'greetings'; + $this->value = 'hello world'; + $this->expire = 9999; + + helper('cookie'); + } + + //-------------------------------------------------------------------- + + public function testSetCookieByDiscreteParameters() + { + set_cookie($this->name, $this->value, $this->expire); + + $this->assertEquals(get_cookie($this->name), $this->value); + + //Delete cookie to give way for other tests. + delete_cookie($this->name); + } + + //-------------------------------------------------------------------- + + public function testSetCookieByArrayParameters() + { + $cookieAttr = array + ( + 'name' => $this->name, + 'value' => $this->value, + 'expire' => $this->expire + ); + set_cookie($cookieAttr); + + $this->assertEquals(get_cookie($this->name), $this->value); + + //Delete cookie to give way for other tests. + delete_cookie($this->name); + } + + //-------------------------------------------------------------------- + + public function testGetCookie() + { + $unsecuredScript = "Hello, I try to your site"; + $securedScript = "Hello, I try to [removed]alert('Hack');[removed] your site"; + $unsecured = 'unsecured'; + $secured = 'secured'; + + set_cookie($unsecured, $unsecuredScript, $this->expire); + set_cookie($secured, $securedScript, $this->expire); + + $this->assertEquals($unsecuredScript, get_cookie($unsecured, false)); + $this->assertEquals($securedScript, get_cookie($secured, true)); + + //Delete cookies to give way for other tests. + delete_cookie($unsecured); + delete_cookie($secured); + } + + //-------------------------------------------------------------------- + + public function testDeleteCookie() + { + set_cookie($this->name, $this->value, $this->expire); + + delete_cookie($this->name); + + $this->assertEquals(get_cookie($this->name), ''); + } + +} \ No newline at end of file diff --git a/user_guide_src/source/helpers/cookie_helper.rst b/user_guide_src/source/helpers/cookie_helper.rst new file mode 100755 index 000000000000..2b573be432e1 --- /dev/null +++ b/user_guide_src/source/helpers/cookie_helper.rst @@ -0,0 +1,79 @@ +############# +Cookie Helper +############# + +The Cookie Helper file contains functions that assist in working with +cookies. + +.. contents:: + :local: + +.. raw:: html + +
+ +Loading this Helper +=================== + +This helper is loaded using the following code:: + + helper('cookie'); + +Available Functions +=================== + +The following functions are available: + + +.. php:function:: set_cookie($name[, $value = ''[, $expire = ''[, $domain = ''[, $path = '/'[, $prefix = ''[, $secure = false[, $httpOnly = false]]]]]]]) + + :param mixed $name: Cookie name *or* associative array of all of the parameters available to this function + :param string $value: Cookie value + :param int $expire: Number of seconds until expiration + :param string $domain: Cookie domain (usually: .yourdomain.com) + :param string $path: Cookie path + :param string $prefix: Cookie name prefix + :param bool $secure: Whether to only send the cookie through HTTPS + :param bool $httpOnly: Whether to hide the cookie from JavaScript + :rtype: void + + This helper function gives you friendlier syntax to set browser + cookies. Refer to the :doc:`Response Library <../libraries/response>` for + a description of its use, as this function is an alias for + ``Response::setCookie()``. + +.. php:function:: get_cookie($index[, $xssClean = false]) + + :param string $index: Cookie name + :param bool $xss_clean: Whether to apply XSS filtering to the returned value + :returns: The cookie value or NULL if not found + :rtype: mixed + + This helper function gives you friendlier syntax to get browser + cookies. Refer to the :doc:`IncomingRequest Library <../libraries/incomingrequest>` for + detailed description of its use, as this function acts very + similarly to ``IncomingRequest::getCookie()``, except it will also prepend + the ``$cookiePrefix`` that you might've set in your + *application/Config/App.php* file. + +.. php:function:: delete_cookie($name[, $domain = ''[, $path = '/'[, $prefix = '']]]) + + :param string $name: Cookie name + :param string $domain: Cookie domain (usually: .yourdomain.com) + :param string $path: Cookie path + :param string $prefix: Cookie name prefix + :rtype: void + + Lets you delete a cookie. Unless you've set a custom path or other + values, only the name of the cookie is needed. + :: + + delete_cookie('name'); + + This function is otherwise identical to ``set_cookie()``, except that it + does not have the value and expiration parameters. You can submit an + array of values in the first parameter or you can set discrete + parameters. + :: + + delete_cookie($name, $domain, $path, $prefix); \ No newline at end of file From e344be3f5f6a026f321e54ce493860ca0ee4906c Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Wed, 24 Aug 2016 22:12:48 +0800 Subject: [PATCH 0197/1807] Enhanced cookie helper. --- system/Helpers/cookie_helper.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index 4b487c419ebf..26df396ffedf 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -83,7 +83,7 @@ function set_cookie bool $httpOnly = false ) { - (\Config\Services::response(new \Config\App()))->setcookie + (\Config\Services::response())->setcookie ( $name, $value, @@ -113,13 +113,15 @@ function set_cookie */ function get_cookie($index, bool $xssClean = false) { - $app = new \Config\App(); - $prefix = $app->cookiePrefix; - $index = $prefix . $index; - - $request = \Config\Services::request($app); + $app = new \Config\App(); + $appCookiePrefix = $app->cookiePrefix; + $prefix = isset($_COOKIE[$index]) + ? '' + : $appCookiePrefix; + + $request = \Config\Services::request(); $filter = true === $xssClean ? FILTER_SANITIZE_STRING : null; - $cookie = $request->getCookie($index, $filter); + $cookie = $request->getCookie($prefix . $index, $filter); return $cookie; } @@ -138,6 +140,7 @@ function get_cookie($index, bool $xssClean = false) * @param string $domain the cookie domain. Usually: .yourdomain.com * @param string $path the cookie path * @param string $prefix the cookie prefix + * @see (\Config\Services::response())->setCookie() * @see \CodeIgniter\HTTP\Response::setcookie() * @return void */ From c89becf1cff3d1fd01ef46be947f04f04046b703 Mon Sep 17 00:00:00 2001 From: "Mark N. Amoin" Date: Wed, 24 Aug 2016 22:25:25 +0800 Subject: [PATCH 0198/1807] Added temporary PHPUnit skip test. --- tests/system/Helpers/CookieHelperTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/system/Helpers/CookieHelperTest.php b/tests/system/Helpers/CookieHelperTest.php index 95359e85eae9..c195bbf6b046 100755 --- a/tests/system/Helpers/CookieHelperTest.php +++ b/tests/system/Helpers/CookieHelperTest.php @@ -7,6 +7,8 @@ final class cookieHelperTest extends \CIUnitTestCase private $value; private $expire; + private $skipped; + public function setUp() { //Output buffering? ob_start(); @@ -16,6 +18,8 @@ public function setUp() $this->value = 'hello world'; $this->expire = 9999; + $this->skipped = 'Need to solve "Cannot modify header information - headers already sent" issue.'; + helper('cookie'); } @@ -23,6 +27,8 @@ public function setUp() public function testSetCookieByDiscreteParameters() { + $this->markTestSkipped($this->skipped); + set_cookie($this->name, $this->value, $this->expire); $this->assertEquals(get_cookie($this->name), $this->value); @@ -35,6 +41,8 @@ public function testSetCookieByDiscreteParameters() public function testSetCookieByArrayParameters() { + $this->markTestSkipped($this->skipped); + $cookieAttr = array ( 'name' => $this->name, @@ -53,6 +61,8 @@ public function testSetCookieByArrayParameters() public function testGetCookie() { + $this->markTestSkipped($this->skipped); + $unsecuredScript = "Hello, I try to your site"; $securedScript = "Hello, I try to [removed]alert('Hack');[removed] your site"; $unsecured = 'unsecured'; @@ -73,6 +83,8 @@ public function testGetCookie() public function testDeleteCookie() { + $this->markTestSkipped($this->skipped); + set_cookie($this->name, $this->value, $this->expire); delete_cookie($this->name); From 7d436bf5475e6385e6db15eed8f47b577b4b1034 Mon Sep 17 00:00:00 2001 From: Przemek Date: Wed, 24 Aug 2016 22:25:01 +0200 Subject: [PATCH 0199/1807] Fix ErrorException (Undefined index) return view('welcome_message', [], ['cache' => 60, ['cache_name' => 'welcome_message']]); // works fine return view('welcome_message', [], ['cache' => 60]); // gives ErrorException but it also should be fine (according to doc) --- system/View/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/View/View.php b/system/View/View.php index 6e76d35ca3e7..bc31a3de25be 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -134,7 +134,7 @@ public function render(string $view, array $options=null, $saveData=false): stri // Was it cached? if (isset($options['cache'])) { - $cacheName = $options['cache_name'] ?: str_replace('.php', '', $view); + $cacheName = isset($options['cache_name']) ?: str_replace('.php', '', $view); if ($output = cache($cacheName)) { From 6298918a3db0d535cf50aa554b6728c99b985431 Mon Sep 17 00:00:00 2001 From: Przemek Date: Thu, 25 Aug 2016 13:52:58 +0200 Subject: [PATCH 0200/1807] Null coalescing operator instead of ternary operator in fix --- system/View/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/View/View.php b/system/View/View.php index bc31a3de25be..1a223df170b5 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -134,7 +134,7 @@ public function render(string $view, array $options=null, $saveData=false): stri // Was it cached? if (isset($options['cache'])) { - $cacheName = isset($options['cache_name']) ?: str_replace('.php', '', $view); + $cacheName = $options['cache_name'] ?? str_replace('.php', '', $view); if ($output = cache($cacheName)) { From d834b2cdd43eac76f5821f6538892ed36ffbf128 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 25 Aug 2016 23:11:34 -0500 Subject: [PATCH 0201/1807] Initial work on new Validation class. --- application/Config/Validation.php | 22 ++ system/Language/Language.php | 22 +- system/Language/en/Validation.php | 45 +++ system/Validation/Rules.php | 17 + system/Validation/Validation.php | 384 +++++++++++++++++++++ system/Validation/ValidationInterface.php | 109 ++++++ tests/system/Language/LanguageTest.php | 7 +- tests/system/Validation/ValidationTest.php | 216 ++++++++++++ 8 files changed, 814 insertions(+), 8 deletions(-) create mode 100644 application/Config/Validation.php create mode 100644 system/Language/en/Validation.php create mode 100644 system/Validation/Rules.php create mode 100644 system/Validation/Validation.php create mode 100644 system/Validation/ValidationInterface.php create mode 100644 tests/system/Validation/ValidationTest.php diff --git a/application/Config/Validation.php b/application/Config/Validation.php new file mode 100644 index 000000000000..2e5d6cd622ee --- /dev/null +++ b/application/Config/Validation.php @@ -0,0 +1,22 @@ +parseLine($line); + list($file, $line) = $this->parseLine($line); - $output = ! empty($this->language[$line]) ? $this->language[$line] : $line; + $output = isset($this->language[$file][$line]) + ? $this->language[$file][$line] + : $line; // Do advanced message formatting here // if the 'intl' extension is available. if ($this->intlSupport && count($args)) { - $output = \MessageFormatter::formatMessage($this->locale, $line, $args); + $output = \MessageFormatter::formatMessage($this->locale, $output, $args); } return $output; @@ -87,7 +89,7 @@ public function getLine(string $line, array $args = []): string * * @return string */ - protected function parseLine(string $line): string + protected function parseLine(string $line): array { if (strpos($line, '.') === false) { @@ -102,7 +104,10 @@ protected function parseLine(string $line): string $this->load($file, $this->locale); } - return $this->language[$line]; + return [ + $file, + $this->language[$line] ?? $line + ]; } //-------------------------------------------------------------------- @@ -125,6 +130,11 @@ protected function load(string $file, string $locale, bool $return = false) return []; } + if (! array_key_exists($file, $this->language)) + { + $this->language[$file] = []; + } + $lang = []; $path = "Language/{$locale}/{$file}.php"; @@ -140,7 +150,7 @@ protected function load(string $file, string $locale, bool $return = false) } // Merge our string - $this->language = array_merge($this->language, $lang); + $this->language[$file] = $lang; } //-------------------------------------------------------------------- diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php new file mode 100644 index 000000000000..b91ba76339c8 --- /dev/null +++ b/system/Language/en/Validation.php @@ -0,0 +1,45 @@ + 'No rulesets specified in Validation configuration.', + + // Rule Messages + 'required' => 'The {field} field is required.', +]; diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php new file mode 100644 index 000000000000..a7b522f7f531 --- /dev/null +++ b/system/Validation/Rules.php @@ -0,0 +1,17 @@ +ruleSetFiles = $config->ruleSets; + } + + //-------------------------------------------------------------------- + + /** + * Runs the validation process, returning true/false determining whether + * or not validation was successful. + * + * @param array $data The array of data to validate. + * @param string $group The pre-defined group of rules to apply. + * + * @return bool + */ + public function run(array $data, string $group = null): bool + { + $this->loadRuleSets(); + + // Run through each rule. If we have any field set for + // this rule, then we need to run them through! + foreach ($this->rules as $rField => $ruleString) + { + // Blast $ruleString apart, unless it's already an array. + $rules = $ruleString; + + if (is_string($rules)) + { + $rules = explode('|', $rules); + } + + // No data with this key AND it's not required? Move on... + if (! in_array('required', $rules) && ! array_key_exists($rField, $data)) + { + continue; + } + + $this->processRules($rField, $data[$rField] ?? null, $rules); + } + + return count($this->errors) > 0 + ? false + : true; + } + + //-------------------------------------------------------------------- + + /** + * Runs all of $rules against $field, until one fails, or + * all of them have been processed. If one fails, it adds + * the error to $this->errors and moves on to the next, + * so that we can collect all of the first errors. + * + * @param $value + * @param array|null $rules + * + * @return bool + */ + protected function processRules(string $field, $value, $rules = null) + { + foreach ($rules as $rule) + { + // If the rules is a callable (like a Closure) or a function + // then we can run it and be done with it... + if (is_callable($rule)) + { + $value = $rule($value); + } + + // Check in our rulesets + foreach ($this->ruleSetInstances as $set) + { + if (! method_exists($set, $rule)) + { + continue; + } + + $value = $set->$rule($value); + break; + } + + // Set the error message if we didn't survive. + if ($value === false) + { + $this->errors[$field] = $this->getErrorMessage($rule, $field); + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Takes a Request object and grabs the data to use from its + * POST array values. + * + * @param \CodeIgniter\HTTP\RequestInterface $request + * + * @return \CodeIgniter\Validation\Validation + */ + public function withRequest(RequestInterface $request): ValidationInterface + { + $this->data = $request->getPost() ?? []; + + return $this; + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // Rules + //-------------------------------------------------------------------- + + /** + * Sets an individual rule and custom error messages for a single field. + * + * The custom error message should be just the messages that apply to + * this field, like so: + * + * [ + * 'rule' => 'message', + * 'rule' => 'message' + * ] + * + * @param string $field + * @param string $rule + * @param array $errors + * + * @return $this + */ + public function setRule(string $field, string $rule, array $errors = []) + { + $this->rules[$field] = $rule; + $this->customErrors = array_merge($this->customErrors, $errors); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Stores the rules that should be used to validate the items. + * Rules should be an array formatted like: + * + * [ + * 'field' => 'rule1|rule2' + * ] + * + * The $errors array should be formatted like: + * [ + * 'field' => [ + * 'rule' => 'message', + * 'rule' => 'message + * ], + * ] + * + * @param array $rules + * @param array $errors // An array of custom error messages + * + * @return \CodeIgniter\Validation\Validation + */ + public function setRules(array $rules, array $errors = []): ValidationInterface + { + $this->rules = $rules; + + if (! empty($errors)) + { + $this->customErrors = $errors; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns all of the rules currently defined. + * + * @return array + */ + public function getRules() + { + return $this->rules; + } + + //-------------------------------------------------------------------- + + /** + * Checks to see if the rule for key $field has been set or not. + * + * @param string $field + * + * @return bool + */ + public function hasRule(string $field): bool + { + return array_key_exists($field, $this->rules); + } + + //-------------------------------------------------------------------- + + /** + * Loads all of the rulesets classes that have been defined in the + * Config\Validation and stores them locally so we can use them. + */ + protected function loadRuleSets() + { + if (empty($this->ruleSetFiles)) + { + throw new \RuntimeException(lang('Validation.noRuleSets')); + } + + foreach ($this->ruleSetFiles as $file) + { + $this->ruleSetInstances[] = new $file; + } + } + + //-------------------------------------------------------------------- + + + //-------------------------------------------------------------------- + // Errors + //-------------------------------------------------------------------- + + /** + * Returns the error(s) for a specified $field (or empty string if not set). + * + * @param string $field + * + * @return string + */ + public function getError(string $field): string + { + return array_key_exists($field, $this->errors) + ? $this->errors[$field] + : ''; + } + + //-------------------------------------------------------------------- + + /** + * Returns the array of errors that were encountered during + * a run() call. The array should be in the followig format: + * + * [ + * 'field1' => 'error message', + * 'field2' => 'error message', + * ] + * + * @return array + */ + public function getErrors(): array + { + return $this->errors ?? []; + } + + //-------------------------------------------------------------------- + + /** + * Sets the error for a specific field. Used by custom validation methods. + * + * @param string $field + * @param string $error + * + * @return \CodeIgniter\Validation\Validation + */ + public function setError(string $field, string $error): ValidationInterface + { + $this->errors[$field] = $error; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to find the appropriate error message + * + * @param string $rule + * @param string $field + * + * @return string + */ + protected function getErrorMessage(string $rule, string $field): string + { + // Check if custom message has been defined by user + if (isset($this->customErrors[$field][$rule])) + { + return $this->customErrors[$field][$rule]; + } + + // Try to grab a localized version of the message... + // lang() will return the rule name back if not found, + // so there will always be a string being returned. + return lang('Validation.'.$rule); + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // Misc + //-------------------------------------------------------------------- + + /** + * Resets the class to a blank slate. Should be called whenever + * you need to process more than one array. + * + * @return mixed + */ + public function reset(): ValidationInterface + { + $this->data = []; + $this->rules = []; + $this->errors = []; + $this->customErrors = []; + + return $this; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Validation/ValidationInterface.php b/system/Validation/ValidationInterface.php new file mode 100644 index 000000000000..c8b51d81a9dd --- /dev/null +++ b/system/Validation/ValidationInterface.php @@ -0,0 +1,109 @@ + 'error message', + * 'field2' => 'error message', + * ] + * + * @return array + */ + public function getErrors(): array; + + //-------------------------------------------------------------------- + + /** + * Sets the error for a specific field. Used by custom validation methods. + * + * @param string $alias + * @param string $error + * + * @return \CodeIgniter\Validation\Validation + */ + public function setError(string $alias, string $error): ValidationInterface; + + //-------------------------------------------------------------------- + // Misc + //-------------------------------------------------------------------- + + /** + * Resets the class to a blank slate. Should be called whenever + * you need to process more than one array. + * + * @return mixed + */ + public function reset(): ValidationInterface; + + //-------------------------------------------------------------------- +} diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index 0c06abb4c46d..4b1015f4ef6f 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -27,6 +27,9 @@ public function testGetLineReturnsLine() //-------------------------------------------------------------------- + /** + * @group single + */ public function testGetLineFormatsMessage() { // No intl extension? then we can't test this - go away.... @@ -35,10 +38,10 @@ public function testGetLineFormatsMessage() $lang = new MockLanguage('en'); $lang->setData([ - 'books' => '{0, number, integer} books have been saved.' + 'bookCount' => '{0, number, integer} books have been saved.' ]); - $this->assertEquals('45 books have been saved.', $lang->getLine('books.books', [91/2])); + $this->assertEquals('45 books have been saved.', $lang->getLine('books.bookCount', [91/2])); } //-------------------------------------------------------------------- diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php new file mode 100644 index 000000000000..0f93ad9676ec --- /dev/null +++ b/tests/system/Validation/ValidationTest.php @@ -0,0 +1,216 @@ + [ + \CodeIgniter\Validation\Rules::class, + ] + ]; + + //-------------------------------------------------------------------- + + public function setUp() + { + parent::setUp(); + $this->validation = new Validation((object)$this->config); + $this->validation->reset(); + } + + //-------------------------------------------------------------------- + + public function testSetRulesStoresRules() + { + $rules = [ + 'foo' => 'bar|baz', + 'bar' => 'baz|belch' + ]; + + $this->validation->setRules($rules); + + $this->assertEquals($rules, $this->validation->getRules()); + } + + //-------------------------------------------------------------------- + + public function testRunReturnsTrueWithNOthingToDo() + { + $this->validation->setRules([]); + + $this->assertTrue($this->validation->run([])); + } + + //-------------------------------------------------------------------- + + public function testRunDoesTheBasics() + { + $data = [ + 'foo' => 'notanumber' + ]; + + $this->validation->setRules([ + 'foo' => 'is_numeric' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testRunReturnsLocalizedErrors() + { + $data = [ + 'foo' => 'notanumber' + ]; + + $this->validation->setRules([ + 'foo' => 'is_numeric' + ]); + + $this->assertFalse($this->validation->run($data)); + $this->assertEquals('is_numeric', $this->validation->getError('foo')); + } + + //-------------------------------------------------------------------- + + public function testRunWithCustomErrors() + { + $data = [ + 'foo' => 'notanumber' + ]; + + $messages = [ + 'foo' => [ + 'is_numeric' => 'Nope. Not a number.' + ] + ]; + + $this->validation->setRules([ + 'foo' => 'is_numeric' + ], $messages); + + $this->validation->run($data); + $this->assertEquals('Nope. Not a number.', $this->validation->getError('foo')); + } + + //-------------------------------------------------------------------- + + public function testGetErrors() + { + $data = [ + 'foo' => 'notanumber' + ]; + + $this->validation->setRules([ + 'foo' => 'is_numeric' + ]); + + $this->validation->run($data); + + $this->assertEquals(['foo' => 'is_numeric'], $this->validation->getErrors()); + } + + //-------------------------------------------------------------------- + + public function testGetErrorsWhenNone() + { + $data = [ + 'foo' => 123 + ]; + + $this->validation->setRules([ + 'foo' => 'is_numeric' + ]); + + $this->validation->run($data); + + $this->assertEquals([], $this->validation->getErrors()); + } + + //-------------------------------------------------------------------- + + public function testSetErrors() + { + $this->validation->setRules([ + 'foo' => 'is_numeric' + ]); + + $this->validation->setError('foo', 'Nadda'); + + $this->assertEquals(['foo' => 'Nadda'], $this->validation->getErrors()); + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // Rules Tests + //-------------------------------------------------------------------- + + public function testRequiredTrueString() + { + $data = [ + 'foo' => 123 + ]; + + $this->validation->setRules([ + 'foo' => 'required' + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + /** + * @group single + */ + public function testRequiredFalseString() + { + $data = [ + 'bar' => 123 + ]; + + $this->validation->setRules([ + 'foo' => 'required' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testRequiredTrueArray() + { + $data = [ + 'foo' => [123] + ]; + + $this->validation->setRules([ + 'foo' => 'required' + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testRequiredFalseArray() + { + $data = [ + 'foo' => [] + ]; + + $this->validation->setRules([ + 'foo' => 'required' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- +} \ No newline at end of file From 56e813cf415d232cbc1a100101839715abbb0e20 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 26 Aug 2016 23:42:10 -0500 Subject: [PATCH 0202/1807] Some additional rules --- system/Language/en/Validation.php | 1 + system/Validation/Rules.php | 100 +++++++++++++- system/Validation/Validation.php | 81 ++++++++--- tests/system/Validation/ValidationTest.php | 151 ++++++++++++++++++++- 4 files changed, 306 insertions(+), 27 deletions(-) diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php index b91ba76339c8..035e42b691aa 100644 --- a/system/Language/en/Validation.php +++ b/system/Language/en/Validation.php @@ -39,6 +39,7 @@ return [ // Core Messages 'noRuleSets' => 'No rulesets specified in Validation configuration.', + 'ruleNotFound' => '{field} is not a valid rule.', // Rule Messages 'required' => 'The {field} field is required.', diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index a7b522f7f531..e72947fab386 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -1,17 +1,113 @@ table($table)->where($field, $str); + + if (! empty($ignoreField) && !empty($ignoreValue)) + { + $row = $row->where("{$ignoreField} !=", $ignoreValue); + } + + return (bool)($row->get()->getRow() === null); + } + + //-------------------------------------------------------------------- + + /** + * Matches the value of another field in $data. + * + * @param string $str + * @param string $field + * @param array $data Other field/value pairs + * + * @return bool + */ + public function matches(string $str, string $field, array $data): bool + { + return isset($data[$field]) + ? ($str === $data[$field]) + : false; + } + + //-------------------------------------------------------------------- + + /** + * Compares value against a regular expression pattern. + * + * @param string $str + * @param string $pattern + * @param array $data Other field/value pairs + * + * @return bool + */ + public function regex_match(string $str, string $pattern, array $data): bool + { + if (substr($pattern, 0, 1) != '/') + { + $pattern = "/{$pattern}/"; + } + + return (bool)preg_match($pattern, $str); + } + + //-------------------------------------------------------------------- + /** * Required * * @param string * @return bool */ - public function required($str) + public function required($str): bool { return is_array($str) ? (bool) count($str) : (trim($str) !== ''); } - // -------------------------------------------------------------------- + //-------------------------------------------------------------------- + } diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index 6c7232fb464d..827156249c57 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -95,7 +95,7 @@ public function run(array $data, string $group = null): bool continue; } - $this->processRules($rField, $data[$rField] ?? null, $rules); + $this->processRules($rField, $data[$rField] ?? null, $rules, $data); } return count($this->errors) > 0 @@ -113,40 +113,69 @@ public function run(array $data, string $group = null): bool * * @param $value * @param array|null $rules + * @param array $data // All of the fields to check. * * @return bool */ - protected function processRules(string $field, $value, $rules = null) + protected function processRules(string $field, $value, $rules = null, array $data) { foreach ($rules as $rule) { - // If the rules is a callable (like a Closure) or a function - // then we can run it and be done with it... - if (is_callable($rule)) + $callable = is_callable($rule); + + // Rules can contain parameters: max_length[5] + $param = false; + if (! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match)) { - $value = $rule($value); + $rule = $match[1]; + $param = $match[2]; } - // Check in our rulesets - foreach ($this->ruleSetInstances as $set) + // If it's a callable, call and and get out of here. + if ($callable) + { + $value = $param === false + ? $rule($value) + : $rule($value, $param, $data); + } + else { - if (! method_exists($set, $rule)) + $found = false; + + // Check in our rulesets + foreach ($this->ruleSetInstances as $set) { - continue; + if (! method_exists($set, $rule)) + { + continue; + } + + $found = true; + + $value = $param === false + ? $set->$rule($value) + : $set->$rule($value, $param, $data); + break; } - $value = $set->$rule($value); - break; + // If the rule wasn't found anywhere, we + // should throw an exception so the developer can find it. + if (! $found) + { + throw new \InvalidArgumentException(lang('Validation.ruleNotFound')); + } } // Set the error message if we didn't survive. if ($value === false) { $this->errors[$field] = $this->getErrorMessage($rule, $field); + return false; } } + return true; } @@ -179,10 +208,10 @@ public function withRequest(RequestInterface $request): ValidationInterface * The custom error message should be just the messages that apply to * this field, like so: * - * [ - * 'rule' => 'message', - * 'rule' => 'message' - * ] + * [ + * 'rule' => 'message', + * 'rule' => 'message' + * ] * * @param string $field * @param string $rule @@ -341,21 +370,29 @@ public function setError(string $field, string $error): ValidationInterface * * @param string $rule * @param string $field + * @param string $param * * @return string */ - protected function getErrorMessage(string $rule, string $field): string + protected function getErrorMessage(string $rule, string $field, string $param = null): string { // Check if custom message has been defined by user if (isset($this->customErrors[$field][$rule])) { - return $this->customErrors[$field][$rule]; + $message = $this->customErrors[$field][$rule]; } + else + { + // Try to grab a localized version of the message... + // lang() will return the rule name back if not found, + // so there will always be a string being returned. + $message = lang('Validation.'.$rule); + } + + $message = str_replace('{field}', $field, $message); + $message = str_replace('{param}', $param, $message); - // Try to grab a localized version of the message... - // lang() will return the rule name back if not found, - // so there will always be a string being returned. - return lang('Validation.'.$rule); + return $message; } //-------------------------------------------------------------------- diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 0f93ad9676ec..4d09a081f0f0 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -1,5 +1,7 @@ 'abcde' + ]; + + $this->validation->setRules([ + 'foo' => 'regex_match[/[a-z]/]' + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testRegexMatchFalse() + { + $data = [ + 'foo' => 'abcde' + ]; + + $this->validation->setRules([ + 'foo' => 'regex_match[\d]' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testMatchesTrue() + { + $data = [ + 'foo' => 'match', + 'bar' => 'match' + ]; + + $this->validation->setRules([ + 'foo' => 'matches[bar]' + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testMatchesFalse() + { + $data = [ + 'foo' => 'match', + 'bar' => 'nope' + ]; + + $this->validation->setRules([ + 'foo' => 'matches[bar]' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testDiffersTrue() + { + $data = [ + 'foo' => 'match', + 'bar' => 'nope' + ]; + + $this->validation->setRules([ + 'foo' => 'differs[bar]' + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testDiffersFalse() + { + $data = [ + 'foo' => 'match', + 'bar' => 'match' + ]; + + $this->validation->setRules([ + 'foo' => 'differs[bar]' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testIsUniqueFalse() + { + $data = [ + 'email' => 'derek@world.com', + ]; + + $this->validation->setRules([ + 'email' => 'is_unique[user.email]' + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testIsUniqueTrue() + { + $data = [ + 'email' => 'derek@world.co.uk', + ]; + + $this->validation->setRules([ + 'email' => 'is_unique[user.email]' + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + /** + * @group single + */ + public function testIsUniqueIgnoresParams() + { + $db = Database::connect(); + $row = $db->table('user')->limit(1)->get()->getRow(); + + $data = [ + 'email' => 'derek@world.co.uk', + ]; + + $this->validation->setRules([ + 'email' => "is_unique[user.email,id,{$row->id}]" + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + } \ No newline at end of file From 4b8cf4041636a2c94a9f4f58eb67066956940ad5 Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Sun, 28 Aug 2016 09:24:00 +0700 Subject: [PATCH 0203/1807] fix typo sessionSavePpath Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/libraries/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst index 188c6bb6a7c9..59159f81c0ea 100644 --- a/user_guide_src/source/libraries/sessions.rst +++ b/user_guide_src/source/libraries/sessions.rst @@ -436,7 +436,7 @@ Preference Default Options **sessionCookieName** ci_session [A-Za-z\_-] characters only The name used for the session cookie. **sessionExpiration** 7200 (2 hours) Time in seconds (integer) The number of seconds you would like the session to last. If you would like a non-expiring session (until browser is closed) set the value to zero: 0 -**sessionSavePpath** NULL None Specifies the storage location, depends on the driver being used. +**sessionSavePath** NULL None Specifies the storage location, depends on the driver being used. **sessionMatchIP** FALSE TRUE/FALSE (boolean) Whether to validate the user's IP address when reading the session cookie. Note that some ISPs dynamically changes the IP, so if you want a non-expiring session you will likely set this to FALSE. From cd0871def22ad9fce8643bb76a8c69dfcfc3c09a Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Sun, 28 Aug 2016 07:55:13 -0700 Subject: [PATCH 0204/1807] Esc handling --- system/View/Parser.php | 26 +++++++++++++++++++++----- tests/system/View/ParserTest.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 1b5c42b5aace..b1096cac3242 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -188,7 +188,7 @@ public function renderString(string $template, array $options = null, bool $save * @param array $options Future options * @return string */ - protected function parse(string $template, array $data = [], array $options = null) : string + protected function parse(string $template, array $data = [], array $options = null): string { if ($template === '') { @@ -196,7 +196,6 @@ protected function parse(string $template, array $data = [], array $options = nu } // TODO processing of control structures goes here - // build the variable substitution list $replace = array (); foreach ($data as $key => $val) @@ -215,7 +214,24 @@ protected function parse(string $template, array $data = [], array $options = nu // -------------------------------------------------------------------- - // -------------------------------------------------------------------- + protected function is_assoc($arr) + { + return array_keys($arr) !== range(0, count($arr) - 1); + } + + function strpos_all($haystack, $needle) + { + $offset = 0; + $allpos = array (); + while (($pos = strpos($haystack, $needle, $offset)) !== FALSE) + { + $offset = $pos + 1; + $allpos[] = $pos; + } + return $allpos; + } + +// -------------------------------------------------------------------- /** * Parse a single key/value @@ -225,7 +241,7 @@ protected function parse(string $template, array $data = [], array $options = nu * @param string $template * @return array */ - protected function parseSingle(string $key, string $val, string $template) : array + protected function parseSingle(string $key, string $val, string $template): array { return array ($this->leftDelimiter.$key.$this->rightDelimiter => (string) $val); } @@ -242,7 +258,7 @@ protected function parseSingle(string $key, string $val, string $template) : arr * @param string $template * @return array */ - protected function parsePair(string $variable, string $data, string $template) : array + protected function parsePair(string $variable, string $data, string $template): array { $replace = array (); preg_match_all( diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index b8eee7ab876c..38a55dde7a33 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -167,4 +167,33 @@ public function testMismatchedVarPair() $this->assertEquals($result, $parser->renderString($template)); } + // Test anchor + + public function escValueTypes() + { + return [ + 'scalar' => [42], + 'string' => ['George'], + 'scalarlist' => [ [1, 2, 17, -4]], + 'stringlist' => [ ['George', 'Paul', 'John', 'Ringo']], + 'associative' => [ ['name' => 'George', 'role' => 'guitar']], + 'compound' => [ ['name' => 'George', 'address' => ['line1' => '123 Some St', 'planet' => 'Naboo']]], + 'pseudo' => [ ['name' => 'George', 'emails' => [ + ['email' => 'me@here.com', 'type' => 'home'], + ['email' => 'me@there.com', 'type' => 'work'] + ]]], + ]; + } + + /** + * @dataProvider escValueTypes + */ + public function testEscHandling($value, $expected = null) + { + if ($expected == null) $expected = $value; + $this->assertEquals($expected, \esc($value, 'html')); + } + + // ------------------------------------------------------------------------ + } From 1e0ac2a75111511752c4e85561686db51493c29d Mon Sep 17 00:00:00 2001 From: Juno_okyo Date: Sun, 28 Aug 2016 23:29:25 +0700 Subject: [PATCH 0205/1807] Add link to README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac3cdfbb0524..09664292750f 100644 --- a/README.md +++ b/README.md @@ -64,4 +64,4 @@ Please read the *Contributing to CodeIgniter* section in the user guide PHP version 7 or higher is required. ## Running CodeIgniter Tests -Information on running CodeIgniter test suite can be found in the README.md file in the tests directory. +Information on running CodeIgniter test suite can be found in the [README.md](tests/README.md) file in the tests directory. From 631c000e53c272ad8bc274e7ad0a51790124efec Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 30 Aug 2016 22:53:20 +0900 Subject: [PATCH 0206/1807] fix typo session Error Undefined index When i use the session function The following error was encountered "Undefined index: foobar" This is the $_SESSION[$val] ?: null; has been changed from $_SESSION[$val] ?? null; --- system/Common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Common.php b/system/Common.php index 7c34f282ade7..99ed88c72ea4 100644 --- a/system/Common.php +++ b/system/Common.php @@ -228,7 +228,7 @@ function session($val = null) // Returning a single item? if (is_string($val)) { - return $_SESSION[$val] ?: null; + return $_SESSION[$val] ?? null; } return \Config\Services::session(); From 6d36c54aa5c87413ca0448b571ff27918ee432d1 Mon Sep 17 00:00:00 2001 From: Portaflex Date: Tue, 30 Aug 2016 23:09:14 +0200 Subject: [PATCH 0207/1807] text_helper --- system/Helpers/text_helper.php | 754 ++++++++++++++++++ tests/system/Helpers/TextHelperTest.php | 253 ++++++ user_guide_src/source/helpers/text_helper.rst | 431 ++++++++++ 3 files changed, 1438 insertions(+) create mode 100755 system/Helpers/text_helper.php create mode 100755 tests/system/Helpers/TextHelperTest.php create mode 100755 user_guide_src/source/helpers/text_helper.rst diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php new file mode 100755 index 000000000000..803615bfdb3d --- /dev/null +++ b/system/Helpers/text_helper.php @@ -0,0 +1,754 @@ += $n) + { + $out = trim($out); + return (mb_strlen($out) === mb_strlen($str)) ? $out : $out.$end_char; + } + } + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('ascii_to_entities')) +{ + /** + * High ASCII to Entities + * + * Converts high ASCII text and MS Word special characters to character entities + * + * @param string $str + * @return string + */ + function ascii_to_entities(string $str): string + { + $out = ''; + for ($i = 0, $s = strlen($str) - 1, $count = 1, $temp = array(); $i <= $s; $i++) + { + $ordinal = ord($str[$i]); + if ($ordinal < 128) + { + /* + If the $temp array has a value but we have moved on, then it seems only + fair that we output that entity and restart $temp before continuing. -Paul + */ + if (count($temp) === 1) + { + $out .= '&#'.array_shift($temp).';'; + $count = 1; + } + $out .= $str[$i]; + } + else + { + if (count($temp) === 0) + { + $count = ($ordinal < 224) ? 2 : 3; + } + $temp[] = $ordinal; + if (count($temp) === $count) + { + $number = ($count === 3) + ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) + : (($temp[0] % 32) * 64) + ($temp[1] % 64); + $out .= '&#'.$number.';'; + $count = 1; + $temp = []; + } + // If this is the last iteration, just output whatever we have + elseif ($i === $s) + { + $out .= '&#'.implode(';', $temp).';'; + } + } + } + return $out; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('entities_to_ascii')) +{ + /** + * Entities to ASCII + * + * Converts character entities back to ASCII + * + * @param string + * @param bool + * @return string + */ + function entities_to_ascii(string $str, bool $all = TRUE): string + { + if (preg_match_all('/\&#(\d+)\;/', $str, $matches)) + { + for ($i = 0, $s = count($matches[0]); $i < $s; $i++) + { + $digits = $matches[1][$i]; + $out = ''; + if ($digits < 128) + { + $out .= chr($digits); + } + elseif ($digits < 2048) + { + $out .= chr(192 + (($digits - ($digits % 64)) / 64)).chr(128 + ($digits % 64)); + } + else + { + $out .= chr(224 + (($digits - ($digits % 4096)) / 4096)) + .chr(128 + ((($digits % 4096) - ($digits % 64)) / 64)) + .chr(128 + ($digits % 64)); + } + $str = str_replace($matches[0][$i], $out, $str); + } + } + if ($all) + { + return str_replace( + ['&', '<', '>', '"', ''', '-'], + ['&', '<', '>', '"', "'", '-'], + $str + ); + } + return $str; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('word_censor')) +{ + /** + * Word Censoring Function + * + * Supply a string and an array of disallowed words and any + * matched words will be converted to #### or to the replacement + * word you've submitted. + * + * @param string the text string + * @param string the array of censored words + * @param string the optional replacement value + * @return string + */ + function word_censor(string $str, $censored, string $replacement = ''): string + { + if ( ! is_array($censored)) + { + return $str; + } + $str = ' '.$str.' '; + // \w, \b and a few others do not match on a unicode character + // set for performance reasons. As a result words like über + // will not match on a word boundary. Instead, we'll assume that + // a bad word will be bookeneded by any of these characters. + $delim = '[-_\'\"`(){}<>\[\]|!?@#%&,.:;^~*+=\/ 0-9\n\r\t]'; + foreach ($censored as $badword) + { + $badword = str_replace('\*', '\w*?', preg_quote($badword, '/')); + if ($replacement !== '') + { + $str = preg_replace( + "/({$delim})(".$badword.")({$delim})/i", + "\\1{$replacement}\\3", + $str + ); + } + elseif (preg_match_all("/{$delim}(".$badword."){$delim}/i", $str, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE)) + { + $matches = $matches[1]; + for ($i = count($matches) - 1; $i >= 0; $i--) + { + $length = strlen($matches[$i][0]); + $str = substr_replace( + $str, + str_repeat('#', $length), + $matches[$i][1], + $length + ); + } + } + } + return trim($str); + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('highlight_code')) +{ + /** + * Code Highlighter + * + * Colorizes code strings + * + * @param string the text string + * @return string + */ + function highlight_code(string $str): string + { + /* The highlight string function encodes and highlights + * brackets so we need them to start raw. + * + * Also replace any existing PHP tags to temporary markers + * so they don't accidentally break the string out of PHP, + * and thus, thwart the highlighting. + */ + $str = str_replace( + ['<', '>', '', '<%', '%>', '\\', ''], + ['<', '>', 'phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'], + $str + ); + // The highlight_string function requires that the text be surrounded + // by PHP tags, which we will remove later + $str = highlight_string('', TRUE); + // Remove our artificially added PHP, and the syntax highlighting that came with it + $str = preg_replace( + [ + '/<\?php( | )/i', + '/(.*?)\?><\/span>\n<\/span>\n<\/code>/is', + '/<\/span>/i' + ], + [ + '', + "$1\n\n", + '' + ], + $str + ); + // Replace our markers back to PHP tags. + return str_replace( + ['phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'], + ['<?', '?>', '<%', '%>', '\\', '</script>'], + $str + ); + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('highlight_phrase')) +{ + /** + * Phrase Highlighter + * + * Highlights a phrase within a text string + * + * @param string $str the text string + * @param string $phrase the phrase you'd like to highlight + * @param string $tag_open the openging tag to precede the phrase with + * @param string $tag_close the closing tag to end the phrase with + * @return string + */ + function highlight_phrase(string $str, string $phrase, string $tag_open = '', + string $tag_close = ''): string + { + return ($str !== '' && $phrase !== '') + ? preg_replace('/('.preg_quote($phrase, '/').')/i', $tag_open.'\\1'.$tag_close, $str) + : $str; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('convert_accented_characters')) +{ + /** + * Convert Accented Foreign Characters to ASCII + * + * @param string $str Input string + * @return string + */ + function convert_accented_characters(string $str): string + { + static $array_from, $array_to; + if ( ! is_array($array_from)) + { + if (file_exists(APPPATH.'Config/ForeignChars.php')) + { + include(APPPATH.'Config/ForeignChars.php'); + } + if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/ForeignChars.php')) + { + include(APPPATH.'Config/'.ENVIRONMENT.'/ForeignChars.php'); + } + if (empty($foreign_characters) OR ! is_array($foreign_characters)) + { + $array_from = []; + $array_to = []; + return $str; + } + $array_from = array_keys($foreign_characters); + $array_to = array_values($foreign_characters); + } + return preg_replace($array_from, $array_to, $str); + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('word_wrap')) +{ + /** + * Word Wrap + * + * Wraps text at the specified character. Maintains the integrity of words. + * Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor + * will URLs. + * + * @param string $str the text string + * @param int $charlim = 76 the number of characters to wrap at + * @return string + */ + function word_wrap(string $str, int $charlim = 76): string + { + // Set the character limit + is_numeric($charlim) OR $charlim = 76; + // Reduce multiple spaces + $str = preg_replace('| +|', ' ', $str); + // Standardize newlines + if (strpos($str, "\r") !== FALSE) + { + $str = str_replace(["\r\n", "\r"], "\n", $str); + } + // If the current word is surrounded by {unwrap} tags we'll + // strip the entire chunk and replace it with a marker. + $unwrap = []; + if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches)) + { + for ($i = 0, $c = count($matches[0]); $i < $c; $i++) + { + $unwrap[] = $matches[1][$i]; + $str = str_replace($matches[0][$i], '{{unwrapped'.$i.'}}', $str); + } + } + // Use PHP's native function to do the initial wordwrap. + // We set the cut flag to FALSE so that any individual words that are + // too long get left alone. In the next step we'll deal with them. + $str = wordwrap($str, $charlim, "\n", FALSE); + // Split the string into individual lines of text and cycle through them + $output = ''; + foreach (explode("\n", $str) as $line) + { + // Is the line within the allowed character count? + // If so we'll join it to the output and continue + if (mb_strlen($line) <= $charlim) + { + $output .= $line."\n"; + continue; + } + $temp = ''; + while (mb_strlen($line) > $charlim) + { + // If the over-length word is a URL we won't wrap it + if (preg_match('!\[url.+\]|://|www\.!', $line)) + { + break; + } + // Trim the word down + $temp .= mb_substr($line, 0, $charlim - 1); + $line = mb_substr($line, $charlim - 1); + } + // If $temp contains data it means we had to split up an over-length + // word into smaller chunks so we'll add it back to our current line + if ($temp !== '') + { + $output .= $temp."\n".$line."\n"; + } + else + { + $output .= $line."\n"; + } + } + // Put our markers back + if (count($unwrap) > 0) + { + foreach ($unwrap as $key => $val) + { + $output = str_replace('{{unwrapped'.$key.'}}', $val, $output); + } + } + return $output; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('ellipsize')) +{ + /** + * Ellipsize String + * + * This function will strip tags from a string, split it at its max_length and ellipsize + * + * @param string string to ellipsize + * @param int max length of string + * @param mixed int (1|0) or float, .5, .2, etc for position to split + * @param string ellipsis ; Default '...' + * @return string ellipsized string + */ + function ellipsize(string $str, int $max_length, $position = 1, string $ellipsis = '…'): string + { + // Strip tags + $str = trim(strip_tags($str)); + // Is the string long enough to ellipsize? + if (mb_strlen($str) <= $max_length) + { + return $str; + } + $beg = mb_substr($str, 0, floor($max_length * $position)); + $position = ($position > 1) ? 1 : $position; + if ($position === 1) + { + $end = mb_substr($str, 0, -($max_length - mb_strlen($beg))); + } + else + { + $end = mb_substr($str, -($max_length - mb_strlen($beg))); + } + return $beg.$ellipsis.$end; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('strip_slashes')) +{ + /** + * Strip Slashes + * + * Removes slashes contained in a string or in an array + * + * @param mixed string or array + * @return mixed string or array + */ + function strip_slashes($str) + { + if ( ! is_array($str)) + { + return stripslashes($str); + } + foreach ($str as $key => $val) + { + $str[$key] = strip_slashes($val); + } + return $str; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('strip_quotes')) +{ + /** + * Strip Quotes + * + * Removes single and double quotes from a string + * + * @param string + * @return string + */ + function strip_quotes(string $str): string + { + return str_replace(['"', "'"], '', $str); + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('quotes_to_entities')) +{ + /** + * Quotes to Entities + * + * Converts single and double quotes to entities + * + * @param string + * @return string + */ + function quotes_to_entities(string $str): string + { + return str_replace(["\'","\"","'",'"'], ["'",""","'","""], $str); + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('reduce_double_slashes')) +{ + /** + * Reduce Double Slashes + * + * Converts double slashes in a string to a single slash, + * except those found in http:// + * + * http://www.some-site.com//index.php + * + * becomes: + * + * http://www.some-site.com/index.php + * + * @param string + * @return string + */ + function reduce_double_slashes(string $str): string + { + return preg_replace('#(^|[^:])//+#', '\\1/', $str); + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('reduce_multiples')) +{ + /** + * Reduce Multiples + * + * Reduces multiple instances of a particular character. Example: + * + * Fred, Bill,, Joe, Jimmy + * + * becomes: + * + * Fred, Bill, Joe, Jimmy + * + * @param string + * @param string the character you wish to reduce + * @param bool TRUE/FALSE - whether to trim the character from the beginning/end + * @return string + */ + function reduce_multiples(string $str, string $character = ',', bool $trim = FALSE): string + { + $str = preg_replace('#'.preg_quote($character, '#').'{2,}#', $character, $str); + return ($trim === TRUE) ? trim($str, $character) : $str; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('random_string')) +{ + /** + * Create a Random String + * + * Useful for generating passwords or hashes. + * + * @param string type of random string. basic, alpha, alnum, numeric, nozero, unique, md5, encrypt and sha1 + * @param int number of characters + * @return string + */ + function random_string(string $type = 'alnum', int $len = 8): string + { + switch ($type) + { + case 'basic': + return mt_rand(); + case 'alnum': + case 'numeric': + case 'nozero': + case 'alpha': + switch ($type) + { + case 'alpha': + $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'alnum': + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'numeric': + $pool = '0123456789'; + break; + case 'nozero': + $pool = '123456789'; + break; + } + return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len); + case 'unique': // todo: remove in 3.1+ + case 'md5': + return md5(uniqid(mt_rand())); + case 'encrypt': // todo: remove in 3.1+ + case 'sha1': + return sha1(uniqid(mt_rand(), TRUE)); + } + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('increment_string')) +{ + /** + * Add's _1 to a string or increment the ending number to allow _2, _3, etc + * + * @param string required + * @param string What should the duplicate number be appended with + * @param int Which number should be used for the first dupe increment + * @return string + */ + function increment_string(string $str, string $separator = '_', int $first = 1): string + { + preg_match('/(.+)'.preg_quote($separator, '/').'([0-9]+)$/', $str, $match); + return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first; + } +} +// ------------------------------------------------------------------------ +if ( ! function_exists('alternator')) +{ + /** + * Alternator + * + * Allows strings to be alternated. See docs... + * + * @param string (as many parameters as needed) + * @return string + */ + function alternator(): string + { + static $i; + if (func_num_args() === 0) + { + $i = 0; + return ''; + } + $args = func_get_args(); + return $args[($i++ % count($args))]; + } +} +// ---------------------------------------------------------------------- +if (! function_exists('excerpt')) +{ + /** + * Excerpt. + * + * Allows to extract a piece of text surrounding a word or phrase. + * + * @param string $text String to search the phrase + * @param string $phrase Phrase that will be searched for. + * @param int $radius The amount of characters returned arround the phrase. + * @param string $ellipsis Ending that will be appended + * @return string + * + * If no $phrase is passed, will generate an excerpt of $radius characters + * from the begining of $text. + */ + function excerpt(string $text, string $phrase = null, int $radius = 100, + string $ellipsis = '...'): string + { + if (isset($phrase)) + { + $phrasePos = strpos(strtolower($text), strtolower($phrase)); + $phraseLen = strlen($phrase); + } + elseif (! isset($phrase)) + { + $phrasePos = $radius / 2; + $phraseLen = 1; + } + + $pre = explode(' ', substr($text, 0, $phrasePos)); + $pos = explode(' ', substr($text, $phrasePos + $phraseLen)); + + $prev = ' '; + $post = ' '; + $count = 0; + + foreach (array_reverse($pre) as $pr => $e) + { + if ((strlen($e) + $count + 1) < $radius) + { + $prev = ' '.$e.$prev; + } + $count = ++$count + strlen($e); + } + + $count = 0; + + foreach ($pos as $po => $s) + { + if ((strlen($s) + $count + 1) < $radius) + { + $post .= $s.' '; + } + $count = ++$count + strlen($s); + } + + $ellPre = $phrase ? $ellipsis : ''; + + return $ellPre.$prev.$phrase.$post.$ellipsis; + } +} diff --git a/tests/system/Helpers/TextHelperTest.php b/tests/system/Helpers/TextHelperTest.php new file mode 100755 index 000000000000..57f2bb3e285b --- /dev/null +++ b/tests/system/Helpers/TextHelperTest.php @@ -0,0 +1,253 @@ +assertEquals($expected, strip_slashes($str)); + } + // -------------------------------------------------------------------- + public function test_strip_quotes() + { + $strs = [ + '"me oh my!"' => 'me oh my!', + "it's a winner!" => 'its a winner!', + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, strip_quotes($str)); + } + } + // -------------------------------------------------------------------- + public function test_quotes_to_entities() + { + $strs = [ + '"me oh my!"' => '"me oh my!"', + "it's a winner!" => 'it's a winner!', + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, quotes_to_entities($str)); + } + } + // -------------------------------------------------------------------- + public function test_reduce_double_slashes() + { + $strs = [ + 'http://codeigniter.com' => 'http://codeigniter.com', + '//var/www/html/example.com/' => '/var/www/html/example.com/', + '/var/www/html//index.php' => '/var/www/html/index.php' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, reduce_double_slashes($str)); + } + } + // -------------------------------------------------------------------- + public function test_reduce_multiples() + { + $strs = [ + 'Fred, Bill,, Joe, Jimmy' => 'Fred, Bill, Joe, Jimmy', + 'Ringo, John, Paul,,' => 'Ringo, John, Paul,' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, reduce_multiples($str)); + } + $strs = [ + 'Fred, Bill,, Joe, Jimmy' => 'Fred, Bill, Joe, Jimmy', + 'Ringo, John, Paul,,' => 'Ringo, John, Paul' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, reduce_multiples($str, ',', TRUE)); + } + } + // -------------------------------------------------------------------- + public function test_random_string() + { + $this->assertEquals(16, strlen(random_string('alnum', 16))); + $this->assertEquals(32, strlen(random_string('unique', 16))); + $this->assertInternalType('string', random_string('numeric', 16)); + } + // -------------------------------------------------------------------- + public function test_increment_string() + { + $this->assertEquals('my-test_1', increment_string('my-test')); + $this->assertEquals('my-test-1', increment_string('my-test', '-')); + $this->assertEquals('file_5', increment_string('file_4')); + $this->assertEquals('file-5', increment_string('file-4', '-')); + $this->assertEquals('file-5', increment_string('file-4', '-')); + $this->assertEquals('file-1', increment_string('file', '-', '1')); + $this->assertEquals(124, increment_string('123', '')); + } + + // ------------------------------------------------------------------- + // Functions from text_helper_test.php + // ------------------------------------------------------------------- + + public function test_word_limiter() + { + $this->assertEquals('Once upon a time,…', word_limiter($this->_long_string, 4)); + $this->assertEquals('Once upon a time,…', word_limiter($this->_long_string, 4, '…')); + $this->assertEquals('', word_limiter('', 4)); + } + // ------------------------------------------------------------------------ + public function test_character_limiter() + { + $this->assertEquals('Once upon a time, a…', character_limiter($this->_long_string, 20)); + $this->assertEquals('Once upon a time, a…', character_limiter($this->_long_string, 20, '…')); + $this->assertEquals('Short', character_limiter('Short', 20)); + $this->assertEquals('Short', character_limiter('Short', 5)); + } + // ------------------------------------------------------------------------ + public function test_ascii_to_entities() + { + $strs = [ + '“‘ “testâ€' => '“‘ “test”', + '†¥¨ˆøåß∂ƒ©˙∆˚¬' => '†¥¨ˆøåß∂ƒ©˙∆˚¬' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, ascii_to_entities($str)); + } + } + // ------------------------------------------------------------------------ + public function test_entities_to_ascii() + { + $strs = [ + '“‘ “test”' => '“‘ “testâ€', + '†¥¨ˆøåß∂ƒ©˙∆˚¬' => '†¥¨ˆøåß∂ƒ©˙∆˚¬' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, entities_to_ascii($str)); + } + } + // ------------------------------------------------------------------------ + public function test_convert_accented_characters() + { + //$this->ci_vfs_clone('application/Config/ForeignChars.php'); + $this->assertEquals('AAAeEEEIIOOEUUUeY', convert_accented_characters('ÀÂÄÈÊËÎÃÔŒÙÛÜŸ')); + $this->assertEquals('a e i o u n ue', convert_accented_characters('á é í ó ú ñ ü')); + } + // ------------------------------------------------------------------------ + public function test_censored_words() + { + $censored = ['boob', 'nerd', 'ass', 'fart']; + $strs = [ + 'Ted bobbled the ball' => 'Ted bobbled the ball', + 'Jake is a nerdo' => 'Jake is a nerdo', + 'The borg will assimilate you' => 'The borg will assimilate you', + 'Did Mary Fart?' => 'Did Mary $*#?', + 'Jake is really a boob' => 'Jake is really a $*#' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, word_censor($str, $censored, '$*#')); + } + // test censored words being sent as a string + $this->assertEquals('test', word_censor('test', 'test')); + } + // ------------------------------------------------------------------------ + public function test_highlight_code() + { + $expect = "\n<?php var_dump(\$this); ?> \n\n"; + $this->assertEquals($expect, highlight_code('')); + } + // ------------------------------------------------------------------------ + public function test_highlight_phrase() + { + $strs = [ + 'this is a phrase' => 'this is a phrase', + 'this is another' => 'this is another', + 'Gimme a test, Sally' => 'Gimme a test, Sally', + 'Or tell me what this is' => 'Or tell me what this is', + '' => '' + ]; + foreach ($strs as $str => $expect) + { + $this->assertEquals($expect, highlight_phrase($str, 'this is')); + } + $this->assertEquals('this is a strong test', highlight_phrase('this is a strong test', 'this is', '', '')); + } + // ------------------------------------------------------------------------ + public function test_ellipsize() + { + $strs = [ + '0' => [ + 'this is my string' => '… my string', + "here's another one" => '…nother one', + 'this one is just a bit longer' => '…bit longer', + 'short' => 'short' + ], + '.5' => [ + 'this is my string' => 'this …tring', + "here's another one" => "here'…r one", + 'this one is just a bit longer' => 'this …onger', + 'short' => 'short' + ], + '1' => [ + 'this is my string' => 'this is my…', + "here's another one" => "here's ano…", + 'this one is just a bit longer' => 'this one i…', + 'short' => 'short' + ], + ]; + foreach ($strs as $pos => $s) + { + foreach ($s as $str => $expect) + { + $this->assertEquals($expect, ellipsize($str, 10, $pos)); + } + } + } + // ------------------------------------------------------------------------ + public function test_word_wrap() + { + $string = 'Here is a simple string of text that will help us demonstrate this function.'; + $this->assertEquals(substr_count(word_wrap($string, 25), "\n"), 4); + } + // ------------------------------------------------------------------------ + public function test_default_word_wrap_charlim() + { + $string = "Here is a longer string of text that will help us demonstrate the default charlim of this function."; + $this->assertEquals(strpos(word_wrap($string), "\n"), 73); + } + + // ----------------------------------------------------------------------- + + public function test_excerpt() + { + $string = $this->_long_string; + $result = ' Once upon a time, a framework had no tests. It sad So some nice people began to write tests. The more time that went on, the happier it became. ...'; + $this->assertEquals(excerpt($string), $result); + } + + // ----------------------------------------------------------------------- + + public function test_excerpt_radius() + { + $string = $this->_long_string; + $phrase = 'began'; + $result = '... people began to ...'; + $this->assertEquals(excerpt($string, $phrase, 10), $result); + } +} diff --git a/user_guide_src/source/helpers/text_helper.rst b/user_guide_src/source/helpers/text_helper.rst new file mode 100755 index 000000000000..a14f4cdd8431 --- /dev/null +++ b/user_guide_src/source/helpers/text_helper.rst @@ -0,0 +1,431 @@ +########## +Text Helper +########## + +The Text Helper file contains functions that assist in working with Text. + +.. contents:: + :local: + +.. raw:: html + +
+ +Loading this Helper +=================== + +This helper is loaded using the following code:: + + helper('text'); + +Available Functions +=================== + +The following functions are available: + +.. php:function:: random_string([$type = 'alnum'[, $len = 8]]) + + :param string $type: Randomization type + :param int $len: Output string length + :returns: A random string + :rtype: string + + Generates a random string based on the type and length you specify. + Useful for creating passwords or generating random hashes. + + The first parameter specifies the type of string, the second parameter + specifies the length. The following choices are available: + + - **alpha**: A string with lower and uppercase letters only. + - **alnum**: Alpha-numeric string with lower and uppercase characters. + - **basic**: A random number based on ``mt_rand()``. + - **numeric**: Numeric string. + - **nozero**: Numeric string with no zeros. + - **md5**: An encrypted random number based on ``md5()`` (fixed length of 32). + - **sha1**: An encrypted random number based on ``sha1()`` (fixed length of 40). + + Usage example:: + + echo random_string('alnum', 16); + + .. note:: Usage of the *unique* and *encrypt* types is DEPRECATED. They + are just aliases for *md5* and *sha1* respectively. + +.. php:function:: increment_string($str[, $separator = '_'[, $first = 1]]) + + :param string $str: Input string + :param string $separator: Separator to append a duplicate number with + :param int $first: Starting number + :returns: An incremented string + :rtype: string + + Increments a string by appending a number to it or increasing the + number. Useful for creating "copies" or a file or duplicating database + content which has unique titles or slugs. + + Usage example:: + + echo increment_string('file', '_'); // "file_1" + echo increment_string('file', '-', 2); // "file-2" + echo increment_string('file_4'); // "file_5" + + +.. php:function:: alternator($args) + + :param mixed $args: A variable number of arguments + :returns: Alternated string(s) + :rtype: mixed + + Allows two or more items to be alternated between, when cycling through + a loop. Example:: + + for ($i = 0; $i < 10; $i++) + {      + echo alternator('string one', 'string two'); + } + + You can add as many parameters as you want, and with each iteration of + your loop the next item will be returned. + + :: + + for ($i = 0; $i < 10; $i++) + {      + echo alternator('one', 'two', 'three', 'four', 'five'); + } + + .. note:: To use multiple separate calls to this function simply call the + function with no arguments to re-initialize. + + +.. php:function:: reduce_double_slashes($str) + + :param string $str: Input string + :returns: A string with normalized slashes + :rtype: string + + Converts double slashes in a string to a single slash, except those + found in URL protocol prefixes (e.g. http://). + + Example:: + + $string = "http://example.com//index.php"; + echo reduce_double_slashes($string); // results in "http://example.com/index.php" + + +.. php:function:: strip_slashes($data) + + :param mixed $data: Input string or an array of strings + :returns: String(s) with stripped slashes + :rtype: mixed + + Removes any slashes from an array of strings. + + Example:: + + $str = array( + 'question'  => 'Is your name O\'reilly?', + 'answer' => 'No, my name is O\'connor.' + ); + + $str = strip_slashes($str); + + The above will return the following array:: + + array( + 'question'  => "Is your name O'reilly?", + 'answer' => "No, my name is O'connor." + ); + + .. note:: For historical reasons, this function will also accept + and handle string inputs. This however makes it just an + alias for ``stripslashes()``. + + +.. php:function:: reduce_multiples($str[, $character = ''[, $trim = FALSE]]) + + :param string $str: Text to search in + :param string $character: Character to reduce + :param bool $trim: Whether to also trim the specified character + :returns: Reduced string + :rtype: string + + Reduces multiple instances of a particular character occuring directly + after each other. Example:: + + $string = "Fred, Bill,, Joe, Jimmy"; + $string = reduce_multiples($string,","); //results in "Fred, Bill, Joe, Jimmy" + + If the third parameter is set to TRUE it will remove occurrences of the + character at the beginning and the end of the string. Example:: + + $string = ",Fred, Bill,, Joe, Jimmy,"; + $string = reduce_multiples($string, ", ", TRUE); //results in "Fred, Bill, Joe, Jimmy" + +.. php:function:: quotes_to_entities($str) + + :param string $str: Input string + :returns: String with quotes converted to HTML entities + :rtype: string + + Converts single and double quotes in a string to the corresponding HTML + entities. Example:: + + $string = "Joe's \"dinner\""; + $string = quotes_to_entities($string); //results in "Joe's "dinner"" + + +.. php:function:: strip_quotes($str) + + :param string $str: Input string + :returns: String with quotes stripped + :rtype: string + + Removes single and double quotes from a string. Example:: + + $string = "Joe's \"dinner\""; + $string = strip_quotes($string); //results in "Joes dinner" + +.. php:function:: word_limiter($str[, $limit = 100[, $end_char = '…']]) + + :param string $str: Input string + :param int $limit: Limit + :param string $end_char: End character (usually an ellipsis) + :returns: Word-limited string + :rtype: string + + Truncates a string to the number of *words* specified. Example:: + + $string = "Here is a nice text string consisting of eleven words."; + $string = word_limiter($string, 4); + // Returns: Here is a nice + + The third parameter is an optional suffix added to the string. By + default it adds an ellipsis. + + +.. php:function:: character_limiter($str[, $n = 500[, $end_char = '…']]) + + :param string $str: Input string + :param int $n: Number of characters + :param string $end_char: End character (usually an ellipsis) + :returns: Character-limited string + :rtype: string + + Truncates a string to the number of *characters* specified. It + maintains the integrity of words so the character count may be slightly + more or less than what you specify. + + Example:: + + $string = "Here is a nice text string consisting of eleven words."; + $string = character_limiter($string, 20); + // Returns: Here is a nice text string + + The third parameter is an optional suffix added to the string, if + undeclared this helper uses an ellipsis. + + .. note:: If you need to truncate to an exact number of characters please + see the :php:func:`ellipsize()` function below. + +.. php:function:: ascii_to_entities($str) + + :param string $str: Input string + :returns: A string with ASCII values converted to entities + :rtype: string + + Converts ASCII values to character entities, including high ASCII and MS + Word characters that can cause problems when used in a web page, so that + they can be shown consistently regardless of browser settings or stored + reliably in a database. There is some dependence on your server's + supported character sets, so it may not be 100% reliable in all cases, + but for the most part it should correctly identify characters outside + the normal range (like accented characters). + + Example:: + + $string = ascii_to_entities($string); + +.. php:function::entities_to_ascii($str[, $all = TRUE]) + + :param string $str: Input string + :param bool $all: Whether to convert unsafe entities as well + :returns: A string with HTML entities converted to ASCII characters + :rtype: string + + This function does the opposite of :php:func:`ascii_to_entities()`. + It turns character entities back into ASCII. + +.. php:function:: convert_accented_characters($str) + + :param string $str: Input string + :returns: A string with accented characters converted + :rtype: string + + Transliterates high ASCII characters to low ASCII equivalents. Useful + when non-English characters need to be used where only standard ASCII + characters are safely used, for instance, in URLs. + + Example:: + + $string = convert_accented_characters($string); + + .. note:: This function uses a companion config file + `application/config/foreign_chars.php` to define the to and + from array for transliteration. + +.. php:function:: word_censor($str, $censored[, $replacement = '']) + + :param string $str: Input string + :param array $censored: List of bad words to censor + :param string $replacement: What to replace bad words with + :returns: Censored string + :rtype: string + + Enables you to censor words within a text string. The first parameter + will contain the original string. The second will contain an array of + words which you disallow. The third (optional) parameter can contain + a replacement value for the words. If not specified they are replaced + with pound signs: ####. + + Example:: + + $disallowed = array('darn', 'shucks', 'golly', 'phooey'); + $string = word_censor($string, $disallowed, 'Beep!'); + +.. php:function:: highlight_code($str) + + :param string $str: Input string + :returns: String with code highlighted via HTML + :rtype: string + + Colorizes a string of code (PHP, HTML, etc.). Example:: + + $string = highlight_code($string); + + The function uses PHP's ``highlight_string()`` function, so the + colors used are the ones specified in your php.ini file. + + +.. php:function:: highlight_phrase($str, $phrase[, $tag_open = ''[, $tag_close = '']]) + + :param string $str: Input string + :param string $phrase: Phrase to highlight + :param string $tag_open: Opening tag used for the highlight + :param string $tag_close: Closing tag for the highlight + :returns: String with a phrase highlighted via HTML + :rtype: string + + Will highlight a phrase within a text string. The first parameter will + contain the original string, the second will contain the phrase you wish + to highlight. The third and fourth parameters will contain the + opening/closing HTML tags you would like the phrase wrapped in. + + Example:: + + $string = "Here is a nice text string about nothing in particular."; + echo highlight_phrase($string, "nice text", '', ''); + + The above code prints:: + + Here is a nice text string about nothing in particular. + + .. note:: This function used to use the ```` tag by default. Older browsers + might not support the new HTML5 mark tag, so it is recommended that you + insert the following CSS code into your stylesheet if you need to support + such browsers:: + + mark { + background: #ff0; + color: #000; + }; + + +.. php:function:: word_wrap($str[, $charlim = 76]) + + :param string $str: Input string + :param int $charlim: Character limit + :returns: Word-wrapped string + :rtype: string + + Wraps text at the specified *character* count while maintaining + complete words. + + Example:: + + $string = "Here is a simple string of text that will help us demonstrate this function."; + echo word_wrap($string, 25); + + // Would produce: + // Here is a simple string + // of text that will help us + // demonstrate this + // function. + + +.. php:function:: ellipsize($str, $max_length[, $position = 1[, $ellipsis = '…']]) + + :param string $str: Input string + :param int $max_length: String length limit + :param mixed $position: Position to split at (int or float) + :param string $ellipsis: What to use as the ellipsis character + :returns: Ellipsized string + :rtype: string + + This function will strip tags from a string, split it at a defined + maximum length, and insert an ellipsis. + + The first parameter is the string to ellipsize, the second is the number + of characters in the final string. The third parameter is where in the + string the ellipsis should appear from 0 - 1, left to right. For + example. a value of 1 will place the ellipsis at the right of the + string, .5 in the middle, and 0 at the left. + + An optional forth parameter is the kind of ellipsis. By default, + … will be inserted. + + Example:: + + $str = 'this_string_is_entirely_too_long_and_might_break_my_design.jpg'; + echo ellipsize($str, 32, .5); + + Produces:: + + this_string_is_e…ak_my_design.jpg + +.. php:function:: excerpt($text, $phrase = false, $radius = 100, $ellipsis = '...') + + :param string $text: Text to extract an excerpt + :param string $phrase: Phrase or word to extract the text arround + :param int $radius: Number of characters before and after $phrase + :param string $ellipsis: What to use as the ellipsis character + :returns: Excerpt. + :rtype: string + + This function will extract $radius number of characters before and after the + central $phrase with an elipsis before and after. + + The first paramenter is the text to extract an excerpt from, the second is the + central word or phrase to count before and after. The third parameter is the + number of characters to count before and after the central phrase. If no phrase + passed, the excerpt will include the first $radius characters with the elipsis + at the end. + + Example:: + + $text = 'Ut vel faucibus odio. Quisque quis congue libero. Etiam gravida + eros lorem, eget porttitor augue dignissim tincidunt. In eget risus eget + mauris faucibus molestie vitae ultricies odio. Vestibulum id ultricies diam. + Curabitur non mauris lectus. Phasellus eu sodales sem. Integer dictum purus + ac enim hendrerit gravida. Donec ac magna vel nunc tincidunt molestie sed + vitae nisl. Cras sed auctor mauris, non dictum tortor. Nulla vel scelerisque + arcu. Cras ac ipsum sit amet augue laoreet laoreet. Aenean a risus lacus. + Sed ut tortor diam.'; + + echo excerpt($str, 'Donec'); + + Produces:: + + ... non mauris lectus. Phasellus eu sodales sem. Integer dictum purus ac + enim hendrerit gravida. Donec ac magna vel nunc tincidunt molestie sed + vitae nisl. Cras sed auctor mauris, non dictum ... From 5160064b7200ef762857db8fb4a70d1ffcbe7d42 Mon Sep 17 00:00:00 2001 From: Portaflex Date: Tue, 30 Aug 2016 23:32:22 +0200 Subject: [PATCH 0208/1807] text_helper --- system/Config/ForeignChars.php | 103 +++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 system/Config/ForeignChars.php diff --git a/system/Config/ForeignChars.php b/system/Config/ForeignChars.php new file mode 100644 index 000000000000..54f15be72e56 --- /dev/null +++ b/system/Config/ForeignChars.php @@ -0,0 +1,103 @@ + 'ae', + '/ö|Å“/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ãœ/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Ã|Â|Ã|Ä|Ã…|Ǻ|Ä€|Ä‚|Ä„|Ç|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|Ð/' => 'A', + '/à|á|â|ã|Ã¥|Ç»|Ä|ă|Ä…|ÇŽ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a', + '/Б/' => 'B', + '/б/' => 'b', + '/Ç|Ć|Ĉ|ÄŠ|ÄŒ/' => 'C', + '/ç|ć|ĉ|Ä‹|Ä/' => 'c', + '/Д/' => 'D', + '/д/' => 'd', + '/Ã|ÄŽ|Ä|Δ/' => 'Dj', + '/ð|Ä|Ä‘|δ/' => 'dj', + '/È|É|Ê|Ë|Ä’|Ä”|Ä–|Ę|Äš|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E', + '/è|é|ê|ë|Ä“|Ä•|Ä—|Ä™|Ä›|έ|ε|ẽ|ẻ|ẹ|á»|ế|á»…|ể|ệ|е|Ñ/' => 'e', + '/Ф/' => 'F', + '/Ñ„/' => 'f', + '/Äœ|Äž|Ä |Ä¢|Γ|Г|Ò/' => 'G', + '/Ä|ÄŸ|Ä¡|Ä£|γ|г|Ò‘/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/Ä¥|ħ/' => 'h', + '/ÃŒ|Ã|ÃŽ|Ã|Ĩ|Ī|Ĭ|Ç|Ä®|Ä°|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I', + '/ì|í|î|ï|Ä©|Ä«|Ä­|Ç|į|ı|η|ή|ί|ι|ÏŠ|ỉ|ị|и|Ñ‹|Ñ—/' => 'i', + '/Ä´/' => 'J', + '/ĵ/' => 'j', + '/Ķ|Κ|К/' => 'K', + '/Ä·|κ|к/' => 'k', + '/Ĺ|Ä»|Ľ|Ä¿|Å|Λ|Л/' => 'L', + '/ĺ|ļ|ľ|Å€|Å‚|λ|л/' => 'l', + '/Ðœ/' => 'M', + '/м/' => 'm', + '/Ñ|Ń|Å…|Ň|Î|Ð/' => 'N', + '/ñ|Å„|ņ|ň|ʼn|ν|н/' => 'n', + '/Ã’|Ó|Ô|Õ|ÅŒ|ÅŽ|Ç‘|Å|Æ |Ø|Ǿ|Ο|ÎŒ|Ω|Î|Ỏ|Ọ|á»’|á»|á»–|á»”|Ộ|Ờ|Ớ|á» |Ở|Ợ|О/' => 'O', + '/ò|ó|ô|õ|Å|Å|Ç’|Å‘|Æ¡|ø|Ç¿|º|ο|ÏŒ|ω|ÏŽ|á»|á»|ồ|ố|á»—|ổ|á»™|á»|á»›|ỡ|ở|ợ|о/' => 'o', + '/П/' => 'P', + '/п/' => 'p', + '/Å”|Å–|Ř|Ρ|Р/' => 'R', + '/Å•|Å—|Å™|Ï|Ñ€/' => 'r', + '/Åš|Åœ|Åž|Ș|Å |Σ|С/' => 'S', + '/Å›|Å|ÅŸ|È™|Å¡|Å¿|σ|Ï‚|Ñ/' => 's', + '/Èš|Å¢|Ť|Ŧ|Ï„|Т/' => 'T', + '/È›|Å£|Å¥|ŧ|Ñ‚/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Å®|Å°|Ų|Ư|Ç“|Ç•|Ç—|Ç™|Ç›|Ũ|Ủ|Ụ|Ừ|Ứ|á»®|Ử|á»°|У/' => 'U', + '/ù|ú|û|Å©|Å«|Å­|ů|ű|ų|Æ°|Ç”|Ç–|ǘ|Çš|Çœ|Ï…|Ï|Ï‹|ủ|ụ|ừ|ứ|ữ|á»­|á»±|у/' => 'u', + '/Ã|Ÿ|Ŷ|Î¥|ÎŽ|Ϋ|Ỳ|Ỹ|Ỷ|á»´|Й/' => 'Y', + '/ý|ÿ|Å·|ỳ|ỹ|á»·|ỵ|й/' => 'y', + '/Ð’/' => 'V', + '/в/' => 'v', + '/Å´/' => 'W', + '/ŵ/' => 'w', + '/Ź|Å»|Ž|Ζ|З/' => 'Z', + '/ź|ż|ž|ζ|з/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Å’/' => 'OE', + '/Æ’/' => 'f', + '/ξ/' => 'ks', + '/Ï€/' => 'p', + '/β/' => 'v', + '/μ/' => 'm', + '/ψ/' => 'ps', + '/Ð/' => 'Yo', + '/Ñ‘/' => 'yo', + '/Є/' => 'Ye', + '/Ñ”/' => 'ye', + '/Ї/' => 'Yi', + '/Ж/' => 'Zh', + '/ж/' => 'zh', + '/Ð¥/' => 'Kh', + '/Ñ…/' => 'kh', + '/Ц/' => 'Ts', + '/ц/' => 'ts', + '/Ч/' => 'Ch', + '/ч/' => 'ch', + '/Ш/' => 'Sh', + '/ш/' => 'sh', + '/Щ/' => 'Shch', + '/щ/' => 'shch', + '/Ъ|ÑŠ|Ь|ÑŒ/' => '', + '/Ю/' => 'Yu', + '/ÑŽ/' => 'yu', + '/Я/' => 'Ya', + '/Ñ/' => 'ya' +); + +/* End of file foreign_chars.php */ +/* Location: ./application/config/foreign_chars.php */ \ No newline at end of file From d2753600944d1d58f48072045f482db7b3f839f9 Mon Sep 17 00:00:00 2001 From: Portaflex Date: Tue, 30 Aug 2016 23:33:10 +0200 Subject: [PATCH 0209/1807] text_helper --- system/Helpers/text_helper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php index 803615bfdb3d..12ba7e25be5f 100755 --- a/system/Helpers/text_helper.php +++ b/system/Helpers/text_helper.php @@ -357,9 +357,9 @@ function convert_accented_characters(string $str): string static $array_from, $array_to; if ( ! is_array($array_from)) { - if (file_exists(APPPATH.'Config/ForeignChars.php')) + if (file_exists(BASEPATH.'Config/ForeignChars.php')) { - include(APPPATH.'Config/ForeignChars.php'); + include(BASEPATH.'Config/ForeignChars.php'); } if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/ForeignChars.php')) { From 2dd3a26ed8286ead1b3ca40837cbfcb94eac884f Mon Sep 17 00:00:00 2001 From: Portaflex Date: Wed, 31 Aug 2016 00:43:17 +0200 Subject: [PATCH 0210/1807] Signed-off-by: Portaflex --- system/Config/ForeignChars.php | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/system/Config/ForeignChars.php b/system/Config/ForeignChars.php index 54f15be72e56..5db4328ab38c 100644 --- a/system/Config/ForeignChars.php +++ b/system/Config/ForeignChars.php @@ -1,4 +1,41 @@ Date: Wed, 31 Aug 2016 06:33:37 +0700 Subject: [PATCH 0211/1807] fix subjects pronouns Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/libraries/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/security.rst b/user_guide_src/source/libraries/security.rst index d1bc90cb5538..21a29a6adcc8 100644 --- a/user_guide_src/source/libraries/security.rst +++ b/user_guide_src/source/libraries/security.rst @@ -11,7 +11,7 @@ The Security Class contains methods that help protect your site against Cross-Si Loading the Library ******************* -If your only interest in loading the library is to handle CSRF protection, then you will never need to load it, +If you only interest in loading the library is to handle CSRF protection, then you will never need to load it, as it is ran as filter and has no manual interaction. If you find a case where you do need direct access, though, you may load it through the Services file:: From 6ab79aece53d12052ec12f77e580641e094a25f9 Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Wed, 31 Aug 2016 07:28:29 +0700 Subject: [PATCH 0212/1807] fix http:// characters & make it italic Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/helpers/url_helper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/helpers/url_helper.rst b/user_guide_src/source/helpers/url_helper.rst index a91873b926b6..2cba8f4ed432 100644 --- a/user_guide_src/source/helpers/url_helper.rst +++ b/user_guide_src/source/helpers/url_helper.rst @@ -331,7 +331,7 @@ The following functions are available: :returns: Protocol-prefixed URL string :rtype: string - This function will add http:// in the event that a protocol prefix + This function will add *http://* in the event that a protocol prefix is missing from a URL. Pass the URL string to the function like this:: From 3a88f9031060003ef5606b969d0b9f6057b45113 Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Wed, 31 Aug 2016 07:34:10 +0700 Subject: [PATCH 0213/1807] fix window.open() link Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/helpers/url_helper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/helpers/url_helper.rst b/user_guide_src/source/helpers/url_helper.rst index 2cba8f4ed432..c697d706cbff 100644 --- a/user_guide_src/source/helpers/url_helper.rst +++ b/user_guide_src/source/helpers/url_helper.rst @@ -213,7 +213,7 @@ The following functions are available: echo anchor_popup('news/local/123', 'Click Me!', array()); .. note:: The **window_name** is not really an attribute, but an argument to - the JavaScript `window.open() ` + the JavaScript `window.open() `_ method, which accepts either a window name or a window target. .. note:: Any other attribute than the listed above will be parsed as an From f71364505d3afaf3fa7cd72c5fc36b00a485f429 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 30 Aug 2016 22:26:11 -0500 Subject: [PATCH 0214/1807] Ensuring that a default file exists to hold hooks. --- system/Hooks/Hooks.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/system/Hooks/Hooks.php b/system/Hooks/Hooks.php index 8831596bfbd2..93cc7ef23d47 100644 --- a/system/Hooks/Hooks.php +++ b/system/Hooks/Hooks.php @@ -70,6 +70,30 @@ class Hooks //-------------------------------------------------------------------- + /** + * Ensures that we have a hooks file ready. + * + * @param string|null $file + */ + public static function initialize(string $file=null) + { + // Don't overwrite anything.... + if (! empty(self::$hooksFile)) + { + return; + } + + // Default value + if (empty($file)) + { + $file = APPPATH.'Config/Hooks.php'; + } + + self::$hooksFile = $file; + } + + //-------------------------------------------------------------------- + /** * Registers an action to happen on an event. The action can be any sort * of callable: @@ -119,6 +143,8 @@ public static function trigger($event_name, ...$arguments): bool // Read in our Config/events file so that we have them all! if ( ! self::$haveReadFromFile) { + self::initialize(); + if (is_file(self::$hooksFile)) { include self::$hooksFile; From 934689adab5584b9422fdb0e7387373eeff32003 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 30 Aug 2016 22:53:41 -0500 Subject: [PATCH 0215/1807] Reformatting and slight refactor of some text helper stuff --- application/Config/ForeignCharacters.php | 6 + system/Config/ForeignCharacters.php | 141 +++++ system/Config/ForeignChars.php | 140 ----- system/Helpers/text_helper.php | 712 +++++++++++++---------- 4 files changed, 559 insertions(+), 440 deletions(-) create mode 100644 application/Config/ForeignCharacters.php create mode 100644 system/Config/ForeignCharacters.php delete mode 100644 system/Config/ForeignChars.php diff --git a/application/Config/ForeignCharacters.php b/application/Config/ForeignCharacters.php new file mode 100644 index 000000000000..8ee6f113d78e --- /dev/null +++ b/application/Config/ForeignCharacters.php @@ -0,0 +1,6 @@ + 'ae', + '/ö|Å“/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ãœ/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Ã|Â|Ã|Ä|Ã…|Ǻ|Ä€|Ä‚|Ä„|Ç|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|Ð/' => 'A', + '/à|á|â|ã|Ã¥|Ç»|Ä|ă|Ä…|ÇŽ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a', + '/Б/' => 'B', + '/б/' => 'b', + '/Ç|Ć|Ĉ|ÄŠ|ÄŒ/' => 'C', + '/ç|ć|ĉ|Ä‹|Ä/' => 'c', + '/Д/' => 'D', + '/д/' => 'd', + '/Ã|ÄŽ|Ä|Δ/' => 'Dj', + '/ð|Ä|Ä‘|δ/' => 'dj', + '/È|É|Ê|Ë|Ä’|Ä”|Ä–|Ę|Äš|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E', + '/è|é|ê|ë|Ä“|Ä•|Ä—|Ä™|Ä›|έ|ε|ẽ|ẻ|ẹ|á»|ế|á»…|ể|ệ|е|Ñ/' => 'e', + '/Ф/' => 'F', + '/Ñ„/' => 'f', + '/Äœ|Äž|Ä |Ä¢|Γ|Г|Ò/' => 'G', + '/Ä|ÄŸ|Ä¡|Ä£|γ|г|Ò‘/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/Ä¥|ħ/' => 'h', + '/ÃŒ|Ã|ÃŽ|Ã|Ĩ|Ī|Ĭ|Ç|Ä®|Ä°|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I', + '/ì|í|î|ï|Ä©|Ä«|Ä­|Ç|į|ı|η|ή|ί|ι|ÏŠ|ỉ|ị|и|Ñ‹|Ñ—/' => 'i', + '/Ä´/' => 'J', + '/ĵ/' => 'j', + '/Ķ|Κ|К/' => 'K', + '/Ä·|κ|к/' => 'k', + '/Ĺ|Ä»|Ľ|Ä¿|Å|Λ|Л/' => 'L', + '/ĺ|ļ|ľ|Å€|Å‚|λ|л/' => 'l', + '/Ðœ/' => 'M', + '/м/' => 'm', + '/Ñ|Ń|Å…|Ň|Î|Ð/' => 'N', + '/ñ|Å„|ņ|ň|ʼn|ν|н/' => 'n', + '/Ã’|Ó|Ô|Õ|ÅŒ|ÅŽ|Ç‘|Å|Æ |Ø|Ǿ|Ο|ÎŒ|Ω|Î|Ỏ|Ọ|á»’|á»|á»–|á»”|Ộ|Ờ|Ớ|á» |Ở|Ợ|О/' => 'O', + '/ò|ó|ô|õ|Å|Å|Ç’|Å‘|Æ¡|ø|Ç¿|º|ο|ÏŒ|ω|ÏŽ|á»|á»|ồ|ố|á»—|ổ|á»™|á»|á»›|ỡ|ở|ợ|о/' => 'o', + '/П/' => 'P', + '/п/' => 'p', + '/Å”|Å–|Ř|Ρ|Р/' => 'R', + '/Å•|Å—|Å™|Ï|Ñ€/' => 'r', + '/Åš|Åœ|Åž|Ș|Å |Σ|С/' => 'S', + '/Å›|Å|ÅŸ|È™|Å¡|Å¿|σ|Ï‚|Ñ/' => 's', + '/Èš|Å¢|Ť|Ŧ|Ï„|Т/' => 'T', + '/È›|Å£|Å¥|ŧ|Ñ‚/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Å®|Å°|Ų|Ư|Ç“|Ç•|Ç—|Ç™|Ç›|Ũ|Ủ|Ụ|Ừ|Ứ|á»®|Ử|á»°|У/' => 'U', + '/ù|ú|û|Å©|Å«|Å­|ů|ű|ų|Æ°|Ç”|Ç–|ǘ|Çš|Çœ|Ï…|Ï|Ï‹|ủ|ụ|ừ|ứ|ữ|á»­|á»±|у/' => 'u', + '/Ã|Ÿ|Ŷ|Î¥|ÎŽ|Ϋ|Ỳ|Ỹ|Ỷ|á»´|Й/' => 'Y', + '/ý|ÿ|Å·|ỳ|ỹ|á»·|ỵ|й/' => 'y', + '/Ð’/' => 'V', + '/в/' => 'v', + '/Å´/' => 'W', + '/ŵ/' => 'w', + '/Ź|Å»|Ž|Ζ|З/' => 'Z', + '/ź|ż|ž|ζ|з/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Å’/' => 'OE', + '/Æ’/' => 'f', + '/ξ/' => 'ks', + '/Ï€/' => 'p', + '/β/' => 'v', + '/μ/' => 'm', + '/ψ/' => 'ps', + '/Ð/' => 'Yo', + '/Ñ‘/' => 'yo', + '/Є/' => 'Ye', + '/Ñ”/' => 'ye', + '/Ї/' => 'Yi', + '/Ж/' => 'Zh', + '/ж/' => 'zh', + '/Ð¥/' => 'Kh', + '/Ñ…/' => 'kh', + '/Ц/' => 'Ts', + '/ц/' => 'ts', + '/Ч/' => 'Ch', + '/ч/' => 'ch', + '/Ш/' => 'Sh', + '/ш/' => 'sh', + '/Щ/' => 'Shch', + '/щ/' => 'shch', + '/Ъ|ÑŠ|Ь|ÑŒ/' => '', + '/Ю/' => 'Yu', + '/ÑŽ/' => 'yu', + '/Я/' => 'Ya', + '/Ñ/' => 'ya' + ]; + +} diff --git a/system/Config/ForeignChars.php b/system/Config/ForeignChars.php deleted file mode 100644 index 5db4328ab38c..000000000000 --- a/system/Config/ForeignChars.php +++ /dev/null @@ -1,140 +0,0 @@ - 'ae', - '/ö|Å“/' => 'oe', - '/ü/' => 'ue', - '/Ä/' => 'Ae', - '/Ãœ/' => 'Ue', - '/Ö/' => 'Oe', - '/À|Ã|Â|Ã|Ä|Ã…|Ǻ|Ä€|Ä‚|Ä„|Ç|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|Ð/' => 'A', - '/à|á|â|ã|Ã¥|Ç»|Ä|ă|Ä…|ÇŽ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a', - '/Б/' => 'B', - '/б/' => 'b', - '/Ç|Ć|Ĉ|ÄŠ|ÄŒ/' => 'C', - '/ç|ć|ĉ|Ä‹|Ä/' => 'c', - '/Д/' => 'D', - '/д/' => 'd', - '/Ã|ÄŽ|Ä|Δ/' => 'Dj', - '/ð|Ä|Ä‘|δ/' => 'dj', - '/È|É|Ê|Ë|Ä’|Ä”|Ä–|Ę|Äš|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E', - '/è|é|ê|ë|Ä“|Ä•|Ä—|Ä™|Ä›|έ|ε|ẽ|ẻ|ẹ|á»|ế|á»…|ể|ệ|е|Ñ/' => 'e', - '/Ф/' => 'F', - '/Ñ„/' => 'f', - '/Äœ|Äž|Ä |Ä¢|Γ|Г|Ò/' => 'G', - '/Ä|ÄŸ|Ä¡|Ä£|γ|г|Ò‘/' => 'g', - '/Ĥ|Ħ/' => 'H', - '/Ä¥|ħ/' => 'h', - '/ÃŒ|Ã|ÃŽ|Ã|Ĩ|Ī|Ĭ|Ç|Ä®|Ä°|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I', - '/ì|í|î|ï|Ä©|Ä«|Ä­|Ç|į|ı|η|ή|ί|ι|ÏŠ|ỉ|ị|и|Ñ‹|Ñ—/' => 'i', - '/Ä´/' => 'J', - '/ĵ/' => 'j', - '/Ķ|Κ|К/' => 'K', - '/Ä·|κ|к/' => 'k', - '/Ĺ|Ä»|Ľ|Ä¿|Å|Λ|Л/' => 'L', - '/ĺ|ļ|ľ|Å€|Å‚|λ|л/' => 'l', - '/Ðœ/' => 'M', - '/м/' => 'm', - '/Ñ|Ń|Å…|Ň|Î|Ð/' => 'N', - '/ñ|Å„|ņ|ň|ʼn|ν|н/' => 'n', - '/Ã’|Ó|Ô|Õ|ÅŒ|ÅŽ|Ç‘|Å|Æ |Ø|Ǿ|Ο|ÎŒ|Ω|Î|Ỏ|Ọ|á»’|á»|á»–|á»”|Ộ|Ờ|Ớ|á» |Ở|Ợ|О/' => 'O', - '/ò|ó|ô|õ|Å|Å|Ç’|Å‘|Æ¡|ø|Ç¿|º|ο|ÏŒ|ω|ÏŽ|á»|á»|ồ|ố|á»—|ổ|á»™|á»|á»›|ỡ|ở|ợ|о/' => 'o', - '/П/' => 'P', - '/п/' => 'p', - '/Å”|Å–|Ř|Ρ|Р/' => 'R', - '/Å•|Å—|Å™|Ï|Ñ€/' => 'r', - '/Åš|Åœ|Åž|Ș|Å |Σ|С/' => 'S', - '/Å›|Å|ÅŸ|È™|Å¡|Å¿|σ|Ï‚|Ñ/' => 's', - '/Èš|Å¢|Ť|Ŧ|Ï„|Т/' => 'T', - '/È›|Å£|Å¥|ŧ|Ñ‚/' => 't', - '/Ù|Ú|Û|Ũ|Ū|Ŭ|Å®|Å°|Ų|Ư|Ç“|Ç•|Ç—|Ç™|Ç›|Ũ|Ủ|Ụ|Ừ|Ứ|á»®|Ử|á»°|У/' => 'U', - '/ù|ú|û|Å©|Å«|Å­|ů|ű|ų|Æ°|Ç”|Ç–|ǘ|Çš|Çœ|Ï…|Ï|Ï‹|ủ|ụ|ừ|ứ|ữ|á»­|á»±|у/' => 'u', - '/Ã|Ÿ|Ŷ|Î¥|ÎŽ|Ϋ|Ỳ|Ỹ|Ỷ|á»´|Й/' => 'Y', - '/ý|ÿ|Å·|ỳ|ỹ|á»·|ỵ|й/' => 'y', - '/Ð’/' => 'V', - '/в/' => 'v', - '/Å´/' => 'W', - '/ŵ/' => 'w', - '/Ź|Å»|Ž|Ζ|З/' => 'Z', - '/ź|ż|ž|ζ|з/' => 'z', - '/Æ|Ǽ/' => 'AE', - '/ß/' => 'ss', - '/IJ/' => 'IJ', - '/ij/' => 'ij', - '/Å’/' => 'OE', - '/Æ’/' => 'f', - '/ξ/' => 'ks', - '/Ï€/' => 'p', - '/β/' => 'v', - '/μ/' => 'm', - '/ψ/' => 'ps', - '/Ð/' => 'Yo', - '/Ñ‘/' => 'yo', - '/Є/' => 'Ye', - '/Ñ”/' => 'ye', - '/Ї/' => 'Yi', - '/Ж/' => 'Zh', - '/ж/' => 'zh', - '/Ð¥/' => 'Kh', - '/Ñ…/' => 'kh', - '/Ц/' => 'Ts', - '/ц/' => 'ts', - '/Ч/' => 'Ch', - '/ч/' => 'ch', - '/Ш/' => 'Sh', - '/ш/' => 'sh', - '/Щ/' => 'Shch', - '/щ/' => 'shch', - '/Ъ|ÑŠ|Ь|ÑŒ/' => '', - '/Ю/' => 'Yu', - '/ÑŽ/' => 'yu', - '/Я/' => 'Ya', - '/Ñ/' => 'ya' -); - -/* End of file foreign_chars.php */ -/* Location: ./application/config/foreign_chars.php */ \ No newline at end of file diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php index 12ba7e25be5f..53a79f976bec 100755 --- a/system/Helpers/text_helper.php +++ b/system/Helpers/text_helper.php @@ -27,37 +27,40 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * - * @package CodeIgniter - * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) - * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 1.0.0 + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ -defined('BASEPATH') OR exit('No direct script access allowed'); + /** * CodeIgniter Text Helpers * - * @package CodeIgniter - * @subpackage Helpers - * @category Helpers - * @author EllisLab Dev Team - * @link https://codeigniter.com/user_guide/helpers/text_helper.html + * @package CodeIgniter + * @subpackage Helpers + * @category Helpers + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/helpers/text_helper.html */ -// ------------------------------------------------------------------------ -if ( ! function_exists('word_limiter')) + +//-------------------------------------------------------------------- + +if (! function_exists('word_limiter')) { /** * Word Limiter * * Limits a string to X number of words. * - * @param string - * @param int - * @param string the end character. Usually an ellipsis - * @return string + * @param string + * @param int + * @param string the end character. Usually an ellipsis + * + * @return string */ function word_limiter(string $str, int $limit = 100, string $end_char = '…'): string { @@ -65,16 +68,21 @@ function word_limiter(string $str, int $limit = 100, string $end_char = '… { return $str; } - preg_match('/^\s*+(?:\S++\s*+){1,'.(int) $limit.'}/', $str, $matches); + + preg_match('/^\s*+(?:\S++\s*+){1,'.(int)$limit.'}/', $str, $matches); + if (strlen($str) === strlen($matches[0])) { $end_char = ''; } + return rtrim($matches[0]).$end_char; } } -// ------------------------------------------------------------------------ -if ( ! function_exists('character_limiter')) + +//-------------------------------------------------------------------- + +if (! function_exists('character_limiter')) { /** * Character Limiter @@ -82,10 +90,11 @@ function word_limiter(string $str, int $limit = 100, string $end_char = '… * Limits the string based on the character count. Preserves complete words * so the character count may not be exactly as specified. * - * @param string - * @param int - * @param string the end character. Usually an ellipsis - * @return string + * @param string + * @param int + * @param string the end character. Usually an ellipsis + * + * @return string */ function character_limiter(string $str, int $n = 500, string $end_char = '…'): string { @@ -93,41 +102,51 @@ function character_limiter(string $str, int $n = 500, string $end_char = '… { return $str; } + // a bit complicated, but faster than preg_replace with \s+ $str = preg_replace('/ {2,}/', ' ', str_replace(["\r", "\n", "\t", "\x0B", "\x0C"], ' ', $str)); + if (mb_strlen($str) <= $n) { return $str; } + $out = ''; + foreach (explode(' ', trim($str)) as $val) { $out .= $val.' '; if (mb_strlen($out) >= $n) { $out = trim($out); + return (mb_strlen($out) === mb_strlen($str)) ? $out : $out.$end_char; } } } } -// ------------------------------------------------------------------------ -if ( ! function_exists('ascii_to_entities')) + +//-------------------------------------------------------------------- + +if (! function_exists('ascii_to_entities')) { /** * High ASCII to Entities * * Converts high ASCII text and MS Word special characters to character entities * - * @param string $str - * @return string + * @param string $str + * + * @return string */ function ascii_to_entities(string $str): string { $out = ''; - for ($i = 0, $s = strlen($str) - 1, $count = 1, $temp = array(); $i <= $s; $i++) + + for ($i = 0, $s = strlen($str)-1, $count = 1, $temp = []; $i <= $s; $i++) { $ordinal = ord($str[$i]); + if ($ordinal < 128) { /* @@ -139,6 +158,7 @@ function ascii_to_entities(string $str): string $out .= '&#'.array_shift($temp).';'; $count = 1; } + $out .= $str[$i]; } else @@ -147,15 +167,17 @@ function ascii_to_entities(string $str): string { $count = ($ordinal < 224) ? 2 : 3; } + $temp[] = $ordinal; + if (count($temp) === $count) { $number = ($count === 3) - ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) - : (($temp[0] % 32) * 64) + ($temp[1] % 64); + ? (($temp[0]%16)*4096)+(($temp[1]%64)*64)+($temp[2]%64) + : (($temp[0]%32)*64)+($temp[1]%64); $out .= '&#'.$number.';'; $count = 1; - $temp = []; + $temp = []; } // If this is the last iteration, just output whatever we have elseif ($i === $s) @@ -164,46 +186,51 @@ function ascii_to_entities(string $str): string } } } + return $out; } } -// ------------------------------------------------------------------------ -if ( ! function_exists('entities_to_ascii')) + +//-------------------------------------------------------------------- + +if (! function_exists('entities_to_ascii')) { /** * Entities to ASCII * * Converts character entities back to ASCII * - * @param string - * @param bool - * @return string + * @param string + * @param bool + * + * @return string */ - function entities_to_ascii(string $str, bool $all = TRUE): string + function entities_to_ascii(string $str, bool $all = true): string { if (preg_match_all('/\&#(\d+)\;/', $str, $matches)) { for ($i = 0, $s = count($matches[0]); $i < $s; $i++) { $digits = $matches[1][$i]; - $out = ''; + $out = ''; if ($digits < 128) { $out .= chr($digits); } elseif ($digits < 2048) { - $out .= chr(192 + (($digits - ($digits % 64)) / 64)).chr(128 + ($digits % 64)); + $out .= chr(192+(($digits-($digits%64))/64)).chr(128+($digits%64)); } else { - $out .= chr(224 + (($digits - ($digits % 4096)) / 4096)) - .chr(128 + ((($digits % 4096) - ($digits % 64)) / 64)) - .chr(128 + ($digits % 64)); + $out .= chr(224+(($digits-($digits%4096))/4096)) + .chr(128+((($digits%4096)-($digits%64))/64)) + .chr(128+($digits%64)); } $str = str_replace($matches[0][$i], $out, $str); } } + if ($all) { return str_replace( @@ -212,11 +239,14 @@ function entities_to_ascii(string $str, bool $all = TRUE): string $str ); } + return $str; } } -// ------------------------------------------------------------------------ -if ( ! function_exists('word_censor')) + +//-------------------------------------------------------------------- + +if (! function_exists('word_censor')) { /** * Word Censoring Function @@ -225,26 +255,31 @@ function entities_to_ascii(string $str, bool $all = TRUE): string * matched words will be converted to #### or to the replacement * word you've submitted. * - * @param string the text string - * @param string the array of censored words - * @param string the optional replacement value - * @return string + * @param string the text string + * @param string the array of censored words + * @param string the optional replacement value + * + * @return string */ function word_censor(string $str, $censored, string $replacement = ''): string { - if ( ! is_array($censored)) + if (! is_array($censored)) { return $str; } + $str = ' '.$str.' '; + // \w, \b and a few others do not match on a unicode character // set for performance reasons. As a result words like über // will not match on a word boundary. Instead, we'll assume that // a bad word will be bookeneded by any of these characters. $delim = '[-_\'\"`(){}<>\[\]|!?@#%&,.:;^~*+=\/ 0-9\n\r\t]'; + foreach ($censored as $badword) { $badword = str_replace('\*', '\w*?', preg_quote($badword, '/')); + if ($replacement !== '') { $str = preg_replace( @@ -256,10 +291,11 @@ function word_censor(string $str, $censored, string $replacement = ''): string elseif (preg_match_all("/{$delim}(".$badword."){$delim}/i", $str, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE)) { $matches = $matches[1]; - for ($i = count($matches) - 1; $i >= 0; $i--) + + for ($i = count($matches)-1; $i >= 0; $i--) { $length = strlen($matches[$i][0]); - $str = substr_replace( + $str = substr_replace( $str, str_repeat('#', $length), $matches[$i][1], @@ -268,19 +304,23 @@ function word_censor(string $str, $censored, string $replacement = ''): string } } } + return trim($str); } } -// ------------------------------------------------------------------------ -if ( ! function_exists('highlight_code')) + +//-------------------------------------------------------------------- + +if (! function_exists('highlight_code')) { /** * Code Highlighter * * Colorizes code strings * - * @param string the text string - * @return string + * @param string the text string + * + * @return string */ function highlight_code(string $str): string { @@ -296,23 +336,26 @@ function highlight_code(string $str): string ['<', '>', 'phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'], $str ); + // The highlight_string function requires that the text be surrounded // by PHP tags, which we will remove later - $str = highlight_string('', TRUE); + $str = highlight_string('', true); + // Remove our artificially added PHP, and the syntax highlighting that came with it $str = preg_replace( [ - '/<\?php( | )/i', + '/<\?php( | )/i', '/(.*?)\?><\/span>\n<\/span>\n<\/code>/is', - '/<\/span>/i' + '/<\/span>/i', ], [ '', "$1\n\n", - '' + '', ], $str ); + // Replace our markers back to PHP tags. return str_replace( ['phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'], @@ -321,64 +364,71 @@ function highlight_code(string $str): string ); } } -// ------------------------------------------------------------------------ -if ( ! function_exists('highlight_phrase')) + +//-------------------------------------------------------------------- + +if (! function_exists('highlight_phrase')) { /** * Phrase Highlighter * * Highlights a phrase within a text string * - * @param string $str the text string - * @param string $phrase the phrase you'd like to highlight - * @param string $tag_open the openging tag to precede the phrase with - * @param string $tag_close the closing tag to end the phrase with - * @return string + * @param string $str the text string + * @param string $phrase the phrase you'd like to highlight + * @param string $tag_open the openging tag to precede the phrase with + * @param string $tag_close the closing tag to end the phrase with + * + * @return string */ function highlight_phrase(string $str, string $phrase, string $tag_open = '', - string $tag_close = ''): string + string $tag_close = ''): string { return ($str !== '' && $phrase !== '') ? preg_replace('/('.preg_quote($phrase, '/').')/i', $tag_open.'\\1'.$tag_close, $str) : $str; } } -// ------------------------------------------------------------------------ -if ( ! function_exists('convert_accented_characters')) + +//-------------------------------------------------------------------- + +if (! function_exists('convert_accented_characters')) { /** * Convert Accented Foreign Characters to ASCII * - * @param string $str Input string - * @return string + * @param string $str Input string + * + * @return string */ function convert_accented_characters(string $str): string { static $array_from, $array_to; - if ( ! is_array($array_from)) + + if (! is_array($array_from)) { - if (file_exists(BASEPATH.'Config/ForeignChars.php')) - { - include(BASEPATH.'Config/ForeignChars.php'); - } - if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/ForeignChars.php')) - { - include(APPPATH.'Config/'.ENVIRONMENT.'/ForeignChars.php'); - } - if (empty($foreign_characters) OR ! is_array($foreign_characters)) + $config = new Config\ForeignCharacters(); + + if (empty($config->characterList) || ! is_array($config->characterList)) { $array_from = []; - $array_to = []; + $array_to = []; + return $str; } - $array_from = array_keys($foreign_characters); - $array_to = array_values($foreign_characters); + $array_from = array_keys($config->characterList); + $array_to = array_values($config->characterList); + + unset($config); } + return preg_replace($array_from, $array_to, $str); } } -// ------------------------------------------------------------------------ -if ( ! function_exists('word_wrap')) + +//-------------------------------------------------------------------- + +if (! function_exists('word_wrap')) { /** * Word Wrap @@ -387,38 +437,46 @@ function convert_accented_characters(string $str): string * Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor * will URLs. * - * @param string $str the text string - * @param int $charlim = 76 the number of characters to wrap at - * @return string + * @param string $str the text string + * @param int $charlim = 76 the number of characters to wrap at + * + * @return string */ function word_wrap(string $str, int $charlim = 76): string { // Set the character limit is_numeric($charlim) OR $charlim = 76; + // Reduce multiple spaces $str = preg_replace('| +|', ' ', $str); + // Standardize newlines - if (strpos($str, "\r") !== FALSE) + if (strpos($str, "\r") !== false) { $str = str_replace(["\r\n", "\r"], "\n", $str); } + // If the current word is surrounded by {unwrap} tags we'll // strip the entire chunk and replace it with a marker. $unwrap = []; + if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches)) { for ($i = 0, $c = count($matches[0]); $i < $c; $i++) { $unwrap[] = $matches[1][$i]; - $str = str_replace($matches[0][$i], '{{unwrapped'.$i.'}}', $str); + $str = str_replace($matches[0][$i], '{{unwrapped'.$i.'}}', $str); } } + // Use PHP's native function to do the initial wordwrap. // We set the cut flag to FALSE so that any individual words that are // too long get left alone. In the next step we'll deal with them. - $str = wordwrap($str, $charlim, "\n", FALSE); + $str = wordwrap($str, $charlim, "\n", false); + // Split the string into individual lines of text and cycle through them $output = ''; + foreach (explode("\n", $str) as $line) { // Is the line within the allowed character count? @@ -428,7 +486,9 @@ function word_wrap(string $str, int $charlim = 76): string $output .= $line."\n"; continue; } + $temp = ''; + while (mb_strlen($line) > $charlim) { // If the over-length word is a URL we won't wrap it @@ -437,9 +497,10 @@ function word_wrap(string $str, int $charlim = 76): string break; } // Trim the word down - $temp .= mb_substr($line, 0, $charlim - 1); - $line = mb_substr($line, $charlim - 1); + $temp .= mb_substr($line, 0, $charlim-1); + $line = mb_substr($line, $charlim-1); } + // If $temp contains data it means we had to split up an over-length // word into smaller chunks so we'll add it back to our current line if ($temp !== '') @@ -451,6 +512,7 @@ function word_wrap(string $str, int $charlim = 76): string $output .= $line."\n"; } } + // Put our markers back if (count($unwrap) > 0) { @@ -459,237 +521,285 @@ function word_wrap(string $str, int $charlim = 76): string $output = str_replace('{{unwrapped'.$key.'}}', $val, $output); } } + return $output; } } -// ------------------------------------------------------------------------ -if ( ! function_exists('ellipsize')) + +//-------------------------------------------------------------------- + +if (! function_exists('ellipsize')) { /** * Ellipsize String * * This function will strip tags from a string, split it at its max_length and ellipsize * - * @param string string to ellipsize - * @param int max length of string - * @param mixed int (1|0) or float, .5, .2, etc for position to split - * @param string ellipsis ; Default '...' - * @return string ellipsized string + * @param string string to ellipsize + * @param int max length of string + * @param mixed int (1|0) or float, .5, .2, etc for position to split + * @param string ellipsis ; Default '...' + * + * @return string ellipsized string */ function ellipsize(string $str, int $max_length, $position = 1, string $ellipsis = '…'): string { // Strip tags $str = trim(strip_tags($str)); + // Is the string long enough to ellipsize? if (mb_strlen($str) <= $max_length) { return $str; } - $beg = mb_substr($str, 0, floor($max_length * $position)); - $position = ($position > 1) ? 1 : $position; + + $beg = mb_substr($str, 0, floor($max_length*$position)); + $position = ($position > 1) + ? 1 + : $position; + if ($position === 1) { - $end = mb_substr($str, 0, -($max_length - mb_strlen($beg))); + $end = mb_substr($str, 0, -($max_length-mb_strlen($beg))); } else { - $end = mb_substr($str, -($max_length - mb_strlen($beg))); + $end = mb_substr($str, -($max_length-mb_strlen($beg))); } + return $beg.$ellipsis.$end; } } -// ------------------------------------------------------------------------ -if ( ! function_exists('strip_slashes')) + +//-------------------------------------------------------------------- + +if (! function_exists('strip_slashes')) { - /** - * Strip Slashes - * - * Removes slashes contained in a string or in an array - * - * @param mixed string or array - * @return mixed string or array - */ - function strip_slashes($str) - { - if ( ! is_array($str)) - { - return stripslashes($str); - } - foreach ($str as $key => $val) - { - $str[$key] = strip_slashes($val); - } - return $str; - } + /** + * Strip Slashes + * + * Removes slashes contained in a string or in an array + * + * @param mixed string or array + * + * @return mixed string or array + */ + function strip_slashes($str) + { + if (! is_array($str)) + { + return stripslashes($str); + } + foreach ($str as $key => $val) + { + $str[$key] = strip_slashes($val); + } + + return $str; + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('strip_quotes')) + +//-------------------------------------------------------------------- + +if (! function_exists('strip_quotes')) { - /** - * Strip Quotes - * - * Removes single and double quotes from a string - * - * @param string - * @return string - */ - function strip_quotes(string $str): string - { - return str_replace(['"', "'"], '', $str); - } + /** + * Strip Quotes + * + * Removes single and double quotes from a string + * + * @param string + * + * @return string + */ + function strip_quotes(string $str): string + { + return str_replace(['"', "'"], '', $str); + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('quotes_to_entities')) + +//-------------------------------------------------------------------- + +if (! function_exists('quotes_to_entities')) { - /** - * Quotes to Entities - * - * Converts single and double quotes to entities - * - * @param string - * @return string - */ - function quotes_to_entities(string $str): string - { - return str_replace(["\'","\"","'",'"'], ["'",""","'","""], $str); - } + /** + * Quotes to Entities + * + * Converts single and double quotes to entities + * + * @param string + * + * @return string + */ + function quotes_to_entities(string $str): string + { + return str_replace(["\'", "\"", "'", '"'], ["'", """, "'", """], $str); + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('reduce_double_slashes')) + +//-------------------------------------------------------------------- + +if (! function_exists('reduce_double_slashes')) { - /** - * Reduce Double Slashes - * - * Converts double slashes in a string to a single slash, - * except those found in http:// - * - * http://www.some-site.com//index.php - * - * becomes: - * - * http://www.some-site.com/index.php - * - * @param string - * @return string - */ - function reduce_double_slashes(string $str): string - { - return preg_replace('#(^|[^:])//+#', '\\1/', $str); - } + /** + * Reduce Double Slashes + * + * Converts double slashes in a string to a single slash, + * except those found in http:// + * + * http://www.some-site.com//index.php + * + * becomes: + * + * http://www.some-site.com/index.php + * + * @param string + * + * @return string + */ + function reduce_double_slashes(string $str): string + { + return preg_replace('#(^|[^:])//+#', '\\1/', $str); + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('reduce_multiples')) + +//-------------------------------------------------------------------- + +if (! function_exists('reduce_multiples')) { - /** - * Reduce Multiples - * - * Reduces multiple instances of a particular character. Example: - * - * Fred, Bill,, Joe, Jimmy - * - * becomes: - * - * Fred, Bill, Joe, Jimmy - * - * @param string - * @param string the character you wish to reduce - * @param bool TRUE/FALSE - whether to trim the character from the beginning/end - * @return string - */ - function reduce_multiples(string $str, string $character = ',', bool $trim = FALSE): string - { - $str = preg_replace('#'.preg_quote($character, '#').'{2,}#', $character, $str); - return ($trim === TRUE) ? trim($str, $character) : $str; - } + /** + * Reduce Multiples + * + * Reduces multiple instances of a particular character. Example: + * + * Fred, Bill,, Joe, Jimmy + * + * becomes: + * + * Fred, Bill, Joe, Jimmy + * + * @param string + * @param string the character you wish to reduce + * @param bool TRUE/FALSE - whether to trim the character from the beginning/end + * + * @return string + */ + function reduce_multiples(string $str, string $character = ',', bool $trim = false): string + { + $str = preg_replace('#'.preg_quote($character, '#').'{2,}#', $character, $str); + + return ($trim === true) + ? trim($str, $character) + : $str; + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('random_string')) + +//-------------------------------------------------------------------- + +if (! function_exists('random_string')) { - /** - * Create a Random String - * - * Useful for generating passwords or hashes. - * - * @param string type of random string. basic, alpha, alnum, numeric, nozero, unique, md5, encrypt and sha1 - * @param int number of characters - * @return string - */ - function random_string(string $type = 'alnum', int $len = 8): string - { - switch ($type) - { - case 'basic': - return mt_rand(); - case 'alnum': - case 'numeric': - case 'nozero': - case 'alpha': - switch ($type) - { - case 'alpha': - $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - break; - case 'alnum': - $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - break; - case 'numeric': - $pool = '0123456789'; - break; - case 'nozero': - $pool = '123456789'; - break; - } - return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len); - case 'unique': // todo: remove in 3.1+ - case 'md5': - return md5(uniqid(mt_rand())); - case 'encrypt': // todo: remove in 3.1+ - case 'sha1': - return sha1(uniqid(mt_rand(), TRUE)); - } - } + /** + * Create a Random String + * + * Useful for generating passwords or hashes. + * + * @param string type of random string. basic, alpha, alnum, numeric, nozero, unique, md5, encrypt and sha1 + * @param int number of characters + * + * @return string + */ + function random_string(string $type = 'alnum', int $len = 8): string + { + switch ($type) + { + case 'basic': + return mt_rand(); + case 'alnum': + case 'numeric': + case 'nozero': + case 'alpha': + switch ($type) + { + case 'alpha': + $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'alnum': + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'numeric': + $pool = '0123456789'; + break; + case 'nozero': + $pool = '123456789'; + break; + } + + return substr(str_shuffle(str_repeat($pool, ceil($len/strlen($pool)))), 0, $len); + case 'unique': // todo: remove in 3.1+ + case 'md5': + return md5(uniqid(mt_rand())); + case 'encrypt': // todo: remove in 3.1+ + case 'sha1': + return sha1(uniqid(mt_rand(), true)); + } + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('increment_string')) + +//-------------------------------------------------------------------- + +if (! function_exists('increment_string')) { - /** - * Add's _1 to a string or increment the ending number to allow _2, _3, etc - * - * @param string required - * @param string What should the duplicate number be appended with - * @param int Which number should be used for the first dupe increment - * @return string - */ - function increment_string(string $str, string $separator = '_', int $first = 1): string - { - preg_match('/(.+)'.preg_quote($separator, '/').'([0-9]+)$/', $str, $match); - return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first; - } + /** + * Add's _1 to a string or increment the ending number to allow _2, _3, etc + * + * @param string required + * @param string What should the duplicate number be appended with + * @param int Which number should be used for the first dupe increment + * + * @return string + */ + function increment_string(string $str, string $separator = '_', int $first = 1): string + { + preg_match('/(.+)'.preg_quote($separator, '/').'([0-9]+)$/', $str, $match); + + return isset($match[2]) + ? $match[1].$separator.($match[2]+1) + : $str.$separator.$first; + } } -// ------------------------------------------------------------------------ -if ( ! function_exists('alternator')) + +//-------------------------------------------------------------------- + +if (! function_exists('alternator')) { - /** - * Alternator - * - * Allows strings to be alternated. See docs... - * - * @param string (as many parameters as needed) - * @return string - */ - function alternator(): string - { - static $i; - if (func_num_args() === 0) - { - $i = 0; - return ''; - } - $args = func_get_args(); - return $args[($i++ % count($args))]; - } + /** + * Alternator + * + * Allows strings to be alternated. See docs... + * + * @param string (as many parameters as needed) + * + * @return string + */ + function alternator(): string + { + static $i; + + if (func_num_args() === 0) + { + $i = 0; + + return ''; + } + + $args = func_get_args(); + + return $args[($i++%count($args))]; + } } -// ---------------------------------------------------------------------- + +//-------------------------------------------------------------------- + if (! function_exists('excerpt')) { /** @@ -697,17 +807,17 @@ function alternator(): string * * Allows to extract a piece of text surrounding a word or phrase. * - * @param string $text String to search the phrase - * @param string $phrase Phrase that will be searched for. - * @param int $radius The amount of characters returned arround the phrase. - * @param string $ellipsis Ending that will be appended + * @param string $text String to search the phrase + * @param string $phrase Phrase that will be searched for. + * @param int $radius The amount of characters returned arround the phrase. + * @param string $ellipsis Ending that will be appended + * * @return string - * + * * If no $phrase is passed, will generate an excerpt of $radius characters * from the begining of $text. */ - function excerpt(string $text, string $phrase = null, int $radius = 100, - string $ellipsis = '...'): string + function excerpt(string $text, string $phrase = null, int $radius = 100, string $ellipsis = '...'): string { if (isset($phrase)) { @@ -716,39 +826,41 @@ function excerpt(string $text, string $phrase = null, int $radius = 100, } elseif (! isset($phrase)) { - $phrasePos = $radius / 2; + $phrasePos = $radius/2; $phraseLen = 1; } $pre = explode(' ', substr($text, 0, $phrasePos)); - $pos = explode(' ', substr($text, $phrasePos + $phraseLen)); + $pos = explode(' ', substr($text, $phrasePos+$phraseLen)); - $prev = ' '; - $post = ' '; + $prev = ' '; + $post = ' '; $count = 0; foreach (array_reverse($pre) as $pr => $e) { - if ((strlen($e) + $count + 1) < $radius) + if ((strlen($e)+$count+1) < $radius) { $prev = ' '.$e.$prev; } - $count = ++$count + strlen($e); + $count = ++$count+strlen($e); } $count = 0; foreach ($pos as $po => $s) { - if ((strlen($s) + $count + 1) < $radius) + if ((strlen($s)+$count+1) < $radius) { $post .= $s.' '; } - $count = ++$count + strlen($s); + $count = ++$count+strlen($s); } - + $ellPre = $phrase ? $ellipsis : ''; return $ellPre.$prev.$phrase.$post.$ellipsis; } + + //-------------------------------------------------------------------- } From 25ef4d3091705896c6317a2c68d21a22bfafce9a Mon Sep 17 00:00:00 2001 From: "Mutasim Ridlo, S.Kom" Date: Wed, 31 Aug 2016 12:37:18 +0700 Subject: [PATCH 0216/1807] revert back to your Signed-off-by: Mutasim Ridlo, S.Kom --- user_guide_src/source/libraries/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/security.rst b/user_guide_src/source/libraries/security.rst index 21a29a6adcc8..d1bc90cb5538 100644 --- a/user_guide_src/source/libraries/security.rst +++ b/user_guide_src/source/libraries/security.rst @@ -11,7 +11,7 @@ The Security Class contains methods that help protect your site against Cross-Si Loading the Library ******************* -If you only interest in loading the library is to handle CSRF protection, then you will never need to load it, +If your only interest in loading the library is to handle CSRF protection, then you will never need to load it, as it is ran as filter and has no manual interaction. If you find a case where you do need direct access, though, you may load it through the Services file:: From af30ee0bd81208daf2f4aa0c6b25850f0fda6049 Mon Sep 17 00:00:00 2001 From: Portaflex Date: Wed, 31 Aug 2016 13:20:48 +0200 Subject: [PATCH 0217/1807] Typography --- application/Config/Services.php | 15 + system/Typography/Typography.php | 421 ++++++++++++++++++ .../source/libraries/typography.rst | 75 ++++ 3 files changed, 511 insertions(+) create mode 100644 system/Typography/Typography.php create mode 100644 user_guide_src/source/libraries/typography.rst diff --git a/application/Config/Services.php b/application/Config/Services.php index f0ba40c6bc05..8d7440517a3c 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -530,6 +530,21 @@ public static function viewcell($getShared = true) //-------------------------------------------------------------------- + /** + * The Typography class provides a way to model and manipulate URIs. + */ + public static function typography($uri = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('typography'); + } + + return new \CodeIgniter\Typography\Typography; + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- // Utility Methods - DO NOT EDIT //-------------------------------------------------------------------- diff --git a/system/Typography/Typography.php b/system/Typography/Typography.php new file mode 100644 index 000000000000..8c5e46747a41 --- /dev/null +++ b/system/Typography/Typography.php @@ -0,0 +1,421 @@ + tags + * + * @var string + */ + public $blockElements = 'address|blockquote|div|dl|fieldset|form|h\d|hr|noscript|object|ol|p|pre|script|table|ul'; + + /** + * Elements that should not have

and
tags within them. + * + * @var string + */ + public $skipElements = 'p|pre|ol|ul|dl|object|table|h\d'; + + /** + * Tags we want the parser to completely ignore when splitting the string. + * + * @var string + */ + public $inlineElements = 'a|abbr|acronym|b|bdo|big|br|button|cite|code|del|dfn|em|i|img|ins|input|label|map|kbd|q|samp|select|small|span|strong|sub|sup|textarea|tt|var'; + + /** + * array of block level elements that require inner content to be within another block level element + * + * @var array + */ + public $innerBlockRequired = ['blockquote']; + + /** + * the last block element parsed + * + * @var string + */ + public $lastBlockElement = ''; + + /** + * whether or not to protect quotes within { curly braces } + * + * @var bool + */ + public $protectBracedQuotes = FALSE; + + /** + * Auto Typography + * + * This function converts text, making it typographically correct: + * - Converts double spaces into paragraphs. + * - Converts single line breaks into
tags + * - Converts single and double quotes into correctly facing curly quote entities. + * - Converts three dots into ellipsis. + * - Converts double dashes into em-dashes. + * - Converts two spaces into entities + * + * @param string + * @param bool whether to reduce more then two consecutive newlines to two + * @return string + */ + public function autoTypography(string $str, bool $reduce_linebreaks = FALSE): string + { + if ($str === '') + { + return ''; + } + + // Standardize Newlines to make matching easier + if (strpos($str, "\r") !== FALSE) + { + $str = str_replace(["\r\n", "\r"], "\n", $str); + } + + // Reduce line breaks. If there are more than two consecutive linebreaks + // we'll compress them down to a maximum of two since there's no benefit to more. + if ($reduce_linebreaks === TRUE) + { + $str = preg_replace("/\n\n+/", "\n\n", $str); + } + + // HTML comment tags don't conform to patterns of normal tags, so pull them out separately, only if needed + $html_comments = []; + if (strpos($str, ''; + $this->assertEquals('foo ', sanitize_filename($filename)); + } + + public function testStripImageTags() + { + $this->assertEquals('http://example.com/spacer.gif', strip_image_tags('http://example.com/spacer.gif')); + + $this->assertEquals('http://example.com/spacer.gif', strip_image_tags('Who needs CSS when you have a spacer.gif?')); + } + + function test_encode_php_tags() + { + $this->assertEquals('<? echo $foo; ?>', encode_php_tags('')); + } + +} diff --git a/user_guide_src/source/helpers/security_helper.rst b/user_guide_src/source/helpers/security_helper.rst new file mode 100644 index 000000000000..fc22fcdf0976 --- /dev/null +++ b/user_guide_src/source/helpers/security_helper.rst @@ -0,0 +1,58 @@ +############### +Security Helper +############### + +The Security Helper file contains security related functions. + +.. contents:: +:local: + +Loading this Helper +=================== + +This helper is loaded using the following code:: + + helper('security'); + +Available Functions +=================== + +The following functions are available: + +.. php:function:: sanitize_filename($filename) + + :param string $filename: Filename + :returns: Sanitized file name + :rtype: string + + Provides protection against directory traversal. + + This function is an alias for ``CI_Security::sanitize_filename()``. + For more info, please see the :doc:`Security Library <../libraries/security>` + documentation. + +.. php:function:: strip_image_tags($str) + + :param string $str: Input string + :returns: The input string with no image tags + :rtype: string + + This is a security function that will strip image tags from a string. + It leaves the image URL as plain text. + + Example:: + + $string = strip_image_tags($string); + + +.. php:function:: encode_php_tags($str) + + :param string $str: Input string + :returns: Safely formatted string + :rtype: string + + This is a security function that converts PHP tags to entities. + + Example:: + + $string = encode_php_tags($string); From fdb835a58735c2f1872a45fcbd651cd3347604c9 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 9 Jan 2017 21:17:15 +0700 Subject: [PATCH 0429/1807] Various typo fixes --- system/Database/BaseBuilder.php | 4 ++-- user_guide_src/source/database/migration.rst | 5 ++--- user_guide_src/source/database/query_builder.rst | 8 ++++---- user_guide_src/source/helpers/security_helper.rst | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index edb5e088129f..bc5e165f08fc 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1350,7 +1350,7 @@ public function getCompiledSelect($reset = true) * @param string the offset clause * @param bool If true, returns the generate SQL, otherwise executes the query. * - * @return CI_DB_result + * @return ResultInterface */ public function get($limit = null, $offset = null, $returnSQL = false) { @@ -1470,7 +1470,7 @@ public function countAllResults($reset = true, $test = false) * @param int $limit * @param int $offset * - * @return CI_DB_result + * @return ResultInterface */ public function getWhere($where = null, $limit = null, $offset = null) { diff --git a/user_guide_src/source/database/migration.rst b/user_guide_src/source/database/migration.rst index f87eaf6d9384..84cee5126df7 100644 --- a/user_guide_src/source/database/migration.rst +++ b/user_guide_src/source/database/migration.rst @@ -147,12 +147,12 @@ re-usable, modular code suites. Usage Example ************* -In this example some simple code is placed in **application/controllers/Migrate.php** +In this example some simple code is placed in **application/Controllers/Migrate.php** to update the schema:: setPath($path) ->latest(); - diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index 506fe978c463..061a2cb897f7 100644 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -1009,8 +1009,8 @@ Class Reference :param int $limit: The LIMIT clause :param int $offset: The OFFSET clause - :returns: CI_DB_result instance (method chaining) - :rtype: CI_DB_result + :returns: \CodeIgniter\Database\ResultInterface instance (method chaining) + :rtype: \CodeIgniter\Database\ResultInterface Compiles and runs SELECT statement based on the already called Query Builder methods. @@ -1020,8 +1020,8 @@ Class Reference :param string $where: The WHERE clause :param int $limit: The LIMIT clause :param int $offset: The OFFSET clause - :returns: CI_DB_result instance (method chaining) - :rtype: CI_DB_result + :returns: \CodeIgniter\Database\ResultInterface instance (method chaining) + :rtype: \CodeIgniter\Database\ResultInterface Same as ``get()``, but also allows the WHERE to be added directly. diff --git a/user_guide_src/source/helpers/security_helper.rst b/user_guide_src/source/helpers/security_helper.rst index fc22fcdf0976..dce14425919e 100644 --- a/user_guide_src/source/helpers/security_helper.rst +++ b/user_guide_src/source/helpers/security_helper.rst @@ -27,7 +27,7 @@ The following functions are available: Provides protection against directory traversal. - This function is an alias for ``CI_Security::sanitize_filename()``. + This function is an alias for ``\CodeIgniter\Security::sanitize_filename()``. For more info, please see the :doc:`Security Library <../libraries/security>` documentation. From 820865f3e7414f052619e67f129cb42436caac0d Mon Sep 17 00:00:00 2001 From: Marlon Oliveira Date: Mon, 9 Jan 2017 19:00:30 -0200 Subject: [PATCH 0430/1807] Model::find(): Missing returnType when using array as parameter --- system/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Model.php b/system/Model.php index 2c21e3d33c06..06488f1eef3f 100644 --- a/system/Model.php +++ b/system/Model.php @@ -287,7 +287,7 @@ public function find($id) { $row = $builder->whereIn($this->primaryKey, $id) ->get(); - $row = $row->getResult(); + $row = $row->getResult($this->tempReturnType); } else { From 72d9893001c700a76d9049e42f40ccbc793b0ac6 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 9 Jan 2017 22:24:33 -0600 Subject: [PATCH 0431/1807] Ensure url helper is loaded when needed in form_open --- system/Helpers/form_helper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index 0f8a6345dcc3..3ea651235504 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -27,6 +27,7 @@ function form_open(string $action = '', array $attributes = [], array $hidden = } // If an action is not a full URL then turn it into one elseif (strpos($action, '://') === false) { + helper('url'); $action = site_url($action); } From 5697b2d4187ad24e85aa4def9d357d91103e24b1 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 10 Jan 2017 20:58:11 +0700 Subject: [PATCH 0432/1807] Add php 7.1 into travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index beca96b12366..b2cd0637c7bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: php php: - 7 + - 7.1 global: - CI=true @@ -29,4 +30,3 @@ before_install: before_script: - composer install --prefer-source - From f621bbe15e3ae792191b23a11c620f5cb80632b6 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 10 Jan 2017 21:37:50 +0700 Subject: [PATCH 0433/1807] Fixes PagerInterface signatures --- system/Pager/Pager.php | 2 +- system/Pager/PagerInterface.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php index c565b3aba63c..d6dd0bf527b8 100644 --- a/system/Pager/Pager.php +++ b/system/Pager/Pager.php @@ -90,7 +90,7 @@ public function __construct($config, RendererInterface $view) * * @return string */ - public function links(string $group = 'default', string $template = 'default_full'): string + public function links(string $group = null, string $template = 'default_full'): string { $this->ensureGroup($group); diff --git a/system/Pager/PagerInterface.php b/system/Pager/PagerInterface.php index ea82b3c94f12..3c4493601f59 100644 --- a/system/Pager/PagerInterface.php +++ b/system/Pager/PagerInterface.php @@ -10,19 +10,19 @@ interface PagerInterface * * @return string */ - public function links(string $template = 'default', string $group = null): string; + public function links(string $group = null, string $template = 'default'): string; //-------------------------------------------------------------------- /** * Creates simple Next/Previous links, instead of full pagination. * - * @param string $template * @param string $group + * @param string $template * * @return string */ - public function simpleLinks(string $template = 'default', string $group = 'default'): string; + public function simpleLinks(string $group = 'default', string $template = 'default'): string; //-------------------------------------------------------------------- From 4727a963ddaff13e08cb9af75b9eb54f82918a7c Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 11 Jan 2017 01:23:51 +0700 Subject: [PATCH 0434/1807] update default value for $group to "default" --- system/Pager/Pager.php | 8 ++++---- system/Pager/PagerInterface.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php index d6dd0bf527b8..92d3e4995951 100644 --- a/system/Pager/Pager.php +++ b/system/Pager/Pager.php @@ -85,12 +85,12 @@ public function __construct($config, RendererInterface $view) /** * Handles creating and displaying the * - * @param string|null $group - * @param string $template The output template alias to render. + * @param string $group + * @param string $template The output template alias to render. * * @return string */ - public function links(string $group = null, string $template = 'default_full'): string + public function links(string $group = 'default', string $template = 'default_full'): string { $this->ensureGroup($group); @@ -102,8 +102,8 @@ public function links(string $group = null, string $template = 'default_full'): /** * Creates simple Next/Previous links, instead of full pagination. * - * @param string $template * @param string $group + * @param string $template * * @return string */ diff --git a/system/Pager/PagerInterface.php b/system/Pager/PagerInterface.php index 3c4493601f59..c6542d6ad6da 100644 --- a/system/Pager/PagerInterface.php +++ b/system/Pager/PagerInterface.php @@ -5,12 +5,12 @@ interface PagerInterface /** * Handles creating and displaying the * - * @param string|null $group - * @param string $template The output template alias to render. + * @param string $group + * @param string $template The output template alias to render. * * @return string */ - public function links(string $group = null, string $template = 'default'): string; + public function links(string $group = 'default', string $template = 'default'): string; //-------------------------------------------------------------------- From aa8d54297c4c26d3a5340f27716c3245cba05a92 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 11 Jan 2017 23:14:30 -0600 Subject: [PATCH 0435/1807] Fixing up some getFieldData issues --- system/Database/BaseConnection.php | 5 +++-- system/Database/MySQLi/Connection.php | 2 +- system/Database/Postgre/Connection.php | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index bebcc7588523..ae8645dab395 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1603,8 +1603,9 @@ public function fieldExists($fieldName, $tableName) */ public function getFieldData(string $table) { - $query = $this->query($this->_fieldData($this->protect_identifiers($table, TRUE, NULL, FALSE))); - return ($query) ? $query->field_data() : FALSE; + $fields = $this->_fieldData($this->protectIdentifiers($table, true, null, false)); + + return $fields ?? false; } //-------------------------------------------------------------------- diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 4a5dcdffb557..386d7eee84d8 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -405,7 +405,7 @@ protected function _listColumns(string $table = ''): string * @param string $table * @return array */ - public function fieldData(string $table) + public function _fieldData(string $table) { if (($query = $this->query('SHOW COLUMNS FROM '.$this->protectIdentifiers($table, TRUE, NULL, FALSE))) === FALSE) { diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 6a30312c7da2..a2f077904eac 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -290,7 +290,7 @@ protected function _listColumns(string $table = ''): string * @param string $table * @return array */ - public function fieldData(string $table) + public function _fieldData(string $table) { $sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default" FROM "information_schema"."columns" From 540077014c7064ae1dd0ddd322e7fd498fce457a Mon Sep 17 00:00:00 2001 From: TAKEKOSHI Akishige Date: Sun, 15 Jan 2017 00:17:56 +0900 Subject: [PATCH 0436/1807] fix BaseBuilder::set()'s escaping param --- system/Database/BaseBuilder.php | 11 +++++++++-- tests/system/Database/Builder/UpdateTest.php | 18 ++++++++++++++++++ tests/system/Database/Live/UpdateTest.php | 16 ++++++++++++++++ .../source/database/query_builder.rst | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index bc5e165f08fc..c61bfdaba673 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1308,8 +1308,15 @@ public function set($key, $value = '', $escape = null) foreach ($key as $k => $v) { - $bind = $this->setBind($k, $v); - $this->QBSet[$this->db->protectIdentifiers($k, false, $escape)] = ':'.$bind; + if ($escape) + { + $bind = $this->setBind($k, $v); + $this->QBSet[$this->db->protectIdentifiers($k, false, $escape)] = ':'.$bind; + } + else + { + $this->QBSet[$this->db->protectIdentifiers($k, false, $escape)] = $v; + } } return $this; diff --git a/tests/system/Database/Builder/UpdateTest.php b/tests/system/Database/Builder/UpdateTest.php index bb47a3a156e2..26d98652f2b5 100644 --- a/tests/system/Database/Builder/UpdateTest.php +++ b/tests/system/Database/Builder/UpdateTest.php @@ -196,4 +196,22 @@ public function testUpdateWithWhereSameColumn3() $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledUpdate())); $this->assertEquals($expectedBinds, $builder->getBinds()); } + + //-------------------------------------------------------------------- + + // @see https://bcit-ci.github.io/CodeIgniter4/database/query_builder.html#updating-data + public function testSetWithoutEscape() + { + $builder = new BaseBuilder('mytable', $this->db); + + $builder->set('field', 'field+1', false) + ->where('id', 2) + ->update(null, null, null, true); + + $expectedSQL = 'UPDATE "mytable" SET field = field+1 WHERE "id" = :id'; + $expectedBinds = ['id' => 2]; + + $this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getCompiledUpdate())); + $this->assertEquals($expectedBinds, $builder->getBinds()); + } } diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 7c329cbe5fd9..4bb0f2bb65cf 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -209,5 +209,21 @@ public function testUpdatePeriods() ]); } + //-------------------------------------------------------------------- + + // @see https://bcit-ci.github.io/CodeIgniter4/database/query_builder.html#updating-data + public function testSetWithoutEscape() + { + $this->db->table('job') + ->set('description', 'name', false) + ->update(); + + $result = $this->db->table('user')->get()->getResultArray(); + + $this->seeInDatabase('job', [ + 'name' => 'Developer', + 'description' => 'Developer', + ]); + } } diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index 061a2cb897f7..3a877fec84e2 100644 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -751,7 +751,7 @@ parameter. $builder->set('field', 'field+1', FALSE); $builder->where('id', 2); - $builder->update(); // gives UPDATE mytable SET field = field+1 WHERE id = 2 + $builder->update(); // gives UPDATE mytable SET field = field+1 WHERE `id` = 2 $builder->set('field', 'field+1'); $builder->where('id', 2); From a3f481a8b65aab014b40c6d1b5bfbcbdfebe16b1 Mon Sep 17 00:00:00 2001 From: TAKEKOSHI Akishige Date: Sun, 15 Jan 2017 12:31:48 +0900 Subject: [PATCH 0437/1807] fix escaping on Postgre's REPLACE emulation --- system/Database/BaseBuilder.php | 8 ++++---- system/Database/Postgre/Builder.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index c61bfdaba673..8eb93ac89d8b 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1287,11 +1287,11 @@ protected function _limit($sql) /** * The "set" function. * - * Allows key/value pairs to be set for inserting or updating + * Allows key/value pairs to be set for insert(), update() or replace(). * - * @param mixed - * @param string - * @param bool + * @param string|array $key Field name, or an array of field/value pairs + * @param string $value Field value, if $key is a single field + * @param bool Whether to escape values and identifiers * * @return BaseBuilder */ diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 5ba013e47a7f..56c748e78a00 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -168,7 +168,7 @@ public function replace($set = null, $returnSQL = false) if (empty($exists)) { - $result = $builder->insert($set, false); + $result = $builder->insert($set); } else { From 2b0c35483c8bbd2cb380dcf3cf3ff630dd0907de Mon Sep 17 00:00:00 2001 From: TAKEKOSHI Akishige Date: Sun, 15 Jan 2017 12:44:37 +0900 Subject: [PATCH 0438/1807] fix testcase #348 --- tests/system/Database/Live/UpdateTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 7c329cbe5fd9..485500da10ba 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -166,6 +166,8 @@ public function testUpdateWithWhereSameColumn2() $rows[] = $row; } } + + $this->assertEquals(2, count($rows)); } //-------------------------------------------------------------------- @@ -188,6 +190,8 @@ public function testUpdateWithWhereSameColumn3() $rows[] = $row; } } + + $this->assertEquals(2, count($rows)); } //-------------------------------------------------------------------- From 97a3ff4881c52dfc73b1c4e0771aedb76f279e4b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 16 Jan 2017 01:37:16 +0700 Subject: [PATCH 0439/1807] Fixes #363: exclude various files and directory from whitelist --- phpunit.xml.dist | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6c3d38bb6ed5..137c169fe864 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -19,9 +19,12 @@ ./system + ./system/Commands/Sessions/Views/migration.tpl.php ./system/ComposerScripts.php + ./system/Debug/Toolbar/Views + ./system/Pager/Views ./system/ThirdParty - ./system/Debug/Toolbar/View + ./system/Validation/Views From b0738edc8df4ce86b46cbf0d6726ed7214cac18f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 15 Jan 2017 23:06:39 -0600 Subject: [PATCH 0440/1807] Refactor and cleanup of bootstrap process just to make things a touch cleaner. --- application/Config/Autoload.php | 2 +- application/Config/Boot/development.php | 23 +- application/Config/Boot/production.php | 7 +- application/Config/Boot/testing.php | 19 +- application/Config/Constants.php | 10 + application/Filters/DebugToolbar.php | 4 +- application/Models/TempModel.php | 8 - public/index.php | 227 +--- system/CodeIgniter.php | 1493 ++++++++++++----------- system/Database/Config.php | 6 +- system/Database/Database.php | 2 +- system/bootstrap.php | 82 ++ tests/_support/Models/EntityModel.php | 1 - tests/_support/_bootstrap.php | 188 +-- tests/system/CodeIgniterTest.php | 2 +- 15 files changed, 958 insertions(+), 1116 deletions(-) delete mode 100644 application/Models/TempModel.php create mode 100644 system/bootstrap.php diff --git a/application/Config/Autoload.php b/application/Config/Autoload.php index 67042c8f7d8d..69b2223fd779 100644 --- a/application/Config/Autoload.php +++ b/application/Config/Autoload.php @@ -54,7 +54,7 @@ public function __construct() APP_NAMESPACE => APPPATH, // For custom namespace 'App' => APPPATH, // To ensure filters, etc still found ]; - + /** * ------------------------------------------------------------------- * Class Map diff --git a/application/Config/Boot/development.php b/application/Config/Boot/development.php index 4e6be582e3c5..25f61516c87f 100644 --- a/application/Config/Boot/development.php +++ b/application/Config/Boot/development.php @@ -4,11 +4,10 @@ |-------------------------------------------------------------------------- | ERROR DISPLAY |-------------------------------------------------------------------------- +| In development, we want to show as many errors as possible to help +| make sure they don't make it to production. And save us hours of +| painful debugging. */ - -// In development, we want to show as many errors as possible to help -// make sure they don't make it to production. And save us hours of -// painful debugging. error_reporting(-1); ini_set('display_errors', 1); @@ -22,23 +21,13 @@ */ define('SHOW_DEBUG_BACKTRACE', true); -/* -|-------------------------------------------------------------------------- -| KINT -|-------------------------------------------------------------------------- -| If true, will enable the Kint PHP Debugging tool and make it available -| globally throughout your application to use while making sure things -| work the way you intend them to. -*/ -$useKint = true; - /* |-------------------------------------------------------------------------- | DEBUG MODE |-------------------------------------------------------------------------- | Debug mode is an experimental flag that can allow changes throughout -| the system. It's not widely used currently, and may not survive -| release of the framework. +| the system. This will control whether Kint is loaded, and a few other +| items. It can always be used within your own application too. */ -define('CI_DEBUG', 1); \ No newline at end of file +define('CI_DEBUG', 1); diff --git a/application/Config/Boot/production.php b/application/Config/Boot/production.php index 9f151ce973cc..bccc84b9e243 100644 --- a/application/Config/Boot/production.php +++ b/application/Config/Boot/production.php @@ -4,10 +4,9 @@ |-------------------------------------------------------------------------- | ERROR DISPLAY |-------------------------------------------------------------------------- +| Don't show ANY in production environments. Instead, let the system catch +| it and display a generic error message. */ - -// Don't show ANY in production environments. Instead, let the system catch -// it and display a generic error message. ini_set('display_errors', 0); error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED); @@ -20,4 +19,4 @@ | release of the framework. */ -define('CI_DEBUG', 0); \ No newline at end of file +define('CI_DEBUG', 0); diff --git a/application/Config/Boot/testing.php b/application/Config/Boot/testing.php index 4e6be582e3c5..f3c7f1c7c6d2 100644 --- a/application/Config/Boot/testing.php +++ b/application/Config/Boot/testing.php @@ -4,11 +4,10 @@ |-------------------------------------------------------------------------- | ERROR DISPLAY |-------------------------------------------------------------------------- +| In development, we want to show as many errors as possible to help +| make sure they don't make it to production. And save us hours of +| painful debugging. */ - -// In development, we want to show as many errors as possible to help -// make sure they don't make it to production. And save us hours of -// painful debugging. error_reporting(-1); ini_set('display_errors', 1); @@ -22,16 +21,6 @@ */ define('SHOW_DEBUG_BACKTRACE', true); -/* -|-------------------------------------------------------------------------- -| KINT -|-------------------------------------------------------------------------- -| If true, will enable the Kint PHP Debugging tool and make it available -| globally throughout your application to use while making sure things -| work the way you intend them to. -*/ -$useKint = true; - /* |-------------------------------------------------------------------------- | DEBUG MODE @@ -41,4 +30,4 @@ | release of the framework. */ -define('CI_DEBUG', 1); \ No newline at end of file +define('CI_DEBUG', 1); diff --git a/application/Config/Constants.php b/application/Config/Constants.php index 9cfb22ced281..5193dd4c4f2c 100644 --- a/application/Config/Constants.php +++ b/application/Config/Constants.php @@ -13,6 +13,16 @@ // define('APP_NAMESPACE', 'App'); +/* +|-------------------------------------------------------------------------- +| Composer Path +|-------------------------------------------------------------------------- +| +| The path that Composer's autoload file is expected to live. By default, +| the vendor folder is in the Root directory, but you can customize that here. +*/ +define('COMPOSER_PATH', ROOTPATH.'vendor/autoload.php'); + /* |-------------------------------------------------------------------------- | Timing Constants diff --git a/application/Filters/DebugToolbar.php b/application/Filters/DebugToolbar.php index 99d7128599df..e4d066793368 100644 --- a/application/Filters/DebugToolbar.php +++ b/application/Filters/DebugToolbar.php @@ -35,10 +35,10 @@ public function after(RequestInterface $request, ResponseInterface $response) { if ( ! is_cli() && CI_DEBUG) { - global $codeigniter; + global $app; $toolbar = Services::toolbar(new App()); - $stats = $codeigniter->getPerfomanceStats(); + $stats = $app->getPerformanceStats(); return $response->appendBody( $toolbar->run( diff --git a/application/Models/TempModel.php b/application/Models/TempModel.php deleted file mode 100644 index 4cd422a4ad10..000000000000 --- a/application/Models/TempModel.php +++ /dev/null @@ -1,8 +0,0 @@ -load(); -unset($env); - -/* - * ------------------------------------------------------ - * Load the framework constants - * ------------------------------------------------------ - */ -if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/Constants.php')) -{ - require_once APPPATH.'Config/'.ENVIRONMENT.'/Constants.php'; -} - -require_once(APPPATH.'Config/Constants.php'); - -/* - * ------------------------------------------------------ - * Setup the autoloader - * ------------------------------------------------------ + * This process sets up the path constants, loads and registers + * our autoloader, along with Composer's, loads our constants + * and fires up an environment-specific bootstrapping. */ -// The autoloader isn't initialized yet, so load the file manually. -require BASEPATH.'Autoloader/Autoloader.php'; -require APPPATH.'Config/Autoload.php'; -require APPPATH.'Config/Services.php'; -// Use Config\Services as CodeIgniter\Services -class_alias('Config\Services', 'CodeIgniter\Services'); - -// The Autoloader class only handles namespaces -// and "legacy" support. -$loader = CodeIgniter\Services::autoloader(); -$loader->initialize(new Config\Autoload()); - -// The register function will prepend -// the psr4 loader. -$loader->register(); - -/* - * ------------------------------------------------------ - * Load the global functions - * ------------------------------------------------------ - */ - -require_once BASEPATH.'Common.php'; - -/* - * ------------------------------------------------------ - * Set custom exception handling - * ------------------------------------------------------ - */ -$config = new \Config\App(); - -CodeIgniter\Services::exceptions($config, true) - ->initialize(); - -//-------------------------------------------------------------------- -// Should we use a Composer autoloader? -//-------------------------------------------------------------------- +// Ensure the current directory is pointing to the front controller's directory +chdir(__DIR__); -if ($composer_autoload = $config->composerAutoload) -{ - if ($composer_autoload === TRUE) - { - file_exists(APPPATH.'../vendor/autoload.php') - ? require_once(APPPATH.'../vendor/autoload.php') - : log_message('error', '$config->\'composerAutoload\' is set to TRUE but '.realpath("../").'vendor/autoload.php was not found.'); - } - elseif (file_exists($composer_autoload)) - { - require_once($composer_autoload); - } - else - { - log_message('error', 'Could not find the specified $config->\'composerAutoload\' path: '.$composer_autoload); - } -} +$app = require rtrim($system_directory,'/ ').'/bootstrap.php'; /* - * -------------------------------------------------------------------- - * LOAD THE BOOTSTRAP FILE - * -------------------------------------------------------------------- - * - * And away we go... + *--------------------------------------------------------------- + * LAUNCH THE APPLICATION + *--------------------------------------------------------------- + * Now that everything is setup, it's time to actually fire + * up the engines and make this app do it's thang. */ -$codeigniter = new CodeIgniter\CodeIgniter($startMemory, $startTime, $config); -$codeigniter->run(); +$app->run(); diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index a44bb214097d..c4fa1f1eb856 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -1,711 +1,756 @@ startMemory = $startMemory; - $this->startTime = $startTime; - $this->config = $config; - } - - //-------------------------------------------------------------------- - - /** - * The class entry point. This is where the magic happens and all - * of the framework pieces are pulled together and shown how to - * make beautiful music together. Or something like that. :) - * - * @param RouteCollectionInterface $routes - */ - public function run(RouteCollectionInterface $routes = null) - { - $this->startBenchmark(); - - //-------------------------------------------------------------------- - // Is there a "pre-system" hook? - //-------------------------------------------------------------------- - Hooks::trigger('pre_system'); - - $this->getRequestObject(); - $this->getResponseObject(); - - // Check for a cached page. Execution will stop - // if the page has been cached. - $cacheConfig = new Cache(); - $this->displayCache($cacheConfig); - - $this->forceSecureAccess(); - - try - { - $this->tryToRouteIt($routes); - - //-------------------------------------------------------------------- - // Run "before" filters - //-------------------------------------------------------------------- - $filters = Services::filters(); - $uri = $this->request instanceof CLIRequest - ? $this->request->getPath() - : $this->request->uri->getPath(); - - $filters->run($uri, 'before'); - - $returned = $this->startController(); - - // Closure controller has run in startController(). - if ( ! is_callable($this->controller)) - { - $controller = $this->createController(); - - //-------------------------------------------------------------------- - // Is there a "post_controller_constructor" hook? - //-------------------------------------------------------------------- - Hooks::trigger('post_controller_constructor'); - - $returned = $this->runController($controller); - } - - // If $returned is a string, then the controller output something, - // probably a view, instead of echoing it directly. Send it along - // so it can be used with the output. - $this->gatherOutput($cacheConfig, $returned); - - //-------------------------------------------------------------------- - // Run "after" filters - //-------------------------------------------------------------------- - $response = $filters->run($uri, 'after'); - - if ($response instanceof Response) - { - $this->response = $response; - } - - // Save our current URI as the previous URI in the session - // for safer, more accurate use with `previous_url()` helper function. - $this->storePreviousURL($this->request->uri ?? $uri); - - unset($uri); - - $this->sendResponse(); - - //-------------------------------------------------------------------- - // Is there a post-system hook? - //-------------------------------------------------------------------- - Hooks::trigger('post_system'); - } - catch (Router\RedirectException $e) - { - $logger = Services::logger(); - $logger->info('REDIRECTED ROUTE at '.$e->getMessage()); - - // If the route is a 'redirect' route, it throws - // the exception with the $to as the message - $this->response->redirect($e->getMessage(), 'auto', $e->getCode()); - $this->callExit(EXIT_SUCCESS); - } - // Catch Response::redirect() - catch (HTTP\RedirectException $e) - { - $this->callExit(EXIT_SUCCESS); - } - catch (PageNotFoundException $e) - { - $this->display404errors($e); - } - } - - //-------------------------------------------------------------------- - - /** - * Determines if a response has been cached for the given URI. - * - * @param \Config\Cache $config - * - * @return bool - */ - public function displayCache($config) - { - if ($cachedResponse = cache()->get($this->generateCacheName($config))) - { - $cachedResponse = unserialize($cachedResponse); - if (!is_array($cachedResponse) || !isset($cachedResponse['output']) || !isset($cachedResponse['headers'])) - { - throw new \Exception("Error unserializing page cache"); - } - - $headers = $cachedResponse['headers']; - $output = $cachedResponse['output']; - - // Clear all default headers - foreach($this->response->getHeaders() as $key => $val) { - $this->response->removeHeader($key); - } - - // Set cached headers - foreach($headers as $name => $value) { - $this->response->setHeader($name, $value); - } - - $output = $this->displayPerformanceMetrics($output); - $this->response->setBody($output)->send(); - $this->callExit(EXIT_SUCCESS); - }; - } - - - //-------------------------------------------------------------------- - - /** - * Tells the app that the final output should be cached. - * - * @param int $time - * - * @return $this - */ - public static function cache(int $time) - { - self::$cacheTTL = (int)$time; - } - - //-------------------------------------------------------------------- - - /** - * Caches the full response from the current request. Used for - * full-page caching for very high performance. - * - * @param \Config\Cache $config - */ - public function cachePage(Cache $config) - { - $headers = []; - foreach($this->response->getHeaders() as $header) { - $headers[$header->getName()] = $header->getValueLine(); - } - - return cache()->save( - $this->generateCacheName($config), - serialize(['headers' => $headers, 'output' => $this->output]), - self::$cacheTTL - ); - - } - - //-------------------------------------------------------------------- - - public function getPerfomanceStats() - { - return [ - 'startTime' => $this->startTime, - 'totalTime' => $this->totalTime, - 'startMemory' => $this->startMemory - ]; - } - - //-------------------------------------------------------------------- - - /** - * Generates the cache name to use for our full-page caching. - * - * @param \CodeIgniter\HTTP\URI $URI - * - * @return string - */ - protected function generateCacheName($config): string - { - if (is_cli()) - { - return md5($this->request->getPath()); - } - - $uri = $this->request->uri; - - if ($config->cacheQueryString) - { - $name = URI::createURIString( - $uri->getScheme(), - $uri->getAuthority(), - $uri->getPath(), - $uri->getQuery() - ); - } - else - { - $name = URI::createURIString( - $uri->getScheme(), - $uri->getAuthority(), - $uri->getPath() - ); - } - - return md5($name); - } - - //-------------------------------------------------------------------- - - /** - * Replaces the memory_usage and elapsed_time tags. - * - * @param string $output - * - * @return string - */ - public function displayPerformanceMetrics(string $output): string - { - $this->totalTime = $this->benchmark->getElapsedTime('total_execution'); - - $output = str_replace('{elapsed_time}', $this->totalTime, $output); - - return $output; - } - - //-------------------------------------------------------------------- - - /** - * Start the Benchmark - * - * The timer is used to display total script execution both in the - * debug toolbar, and potentially on the displayed page. - */ - protected function startBenchmark() - { - $this->startTime = microtime(true); - - $this->benchmark = Services::timer(); - $this->benchmark->start('total_execution', $this->startTime); - $this->benchmark->start('bootstrap'); - } - - //-------------------------------------------------------------------- - - /** - * Get our Request object, (either IncomingRequest or CLIRequest) - * and set the server protocol based on the information provided - * by the server. - */ - protected function getRequestObject() - { - if (is_cli()) - { - $this->request = Services::clirequest($this->config); - } - else - { - $this->request = Services::request($this->config); - $this->request->setProtocolVersion($_SERVER['SERVER_PROTOCOL']); - } - } - - //-------------------------------------------------------------------- - - /** - * Get our Response object, and set some default values, including - * the HTTP protocol version and a default successful response. - */ - protected function getResponseObject() - { - $this->response = Services::response($this->config); - - if ( ! is_cli()) - { - $this->response->setProtocolVersion($this->request->getProtocolVersion()); - } - - // Assume success until proven otherwise. - $this->response->setStatusCode(200); - } - - //-------------------------------------------------------------------- - - /** - * Force Secure Site Access? If the config value 'forceGlobalSecureRequests' - * is true, will enforce that all requests to this site are made through - * HTTPS. Will redirect the user to the current page with HTTPS, as well - * as set the HTTP Strict Transport Security header for those browsers - * that support it. - * - * @param int $duration How long the Strict Transport Security - * should be enforced for this URL. - */ - protected function forceSecureAccess($duration = 31536000) - { - if ($this->config->forceGlobalSecureRequests !== true) - { - return; - } - - force_https($duration, $this->request, $this->response); - } - - //-------------------------------------------------------------------- - - /** - * Try to Route It - As it sounds like, works with the router to - * match a route against the current URI. If the route is a - * "redirect route", will also handle the redirect. - * - * @param RouteCollectionInterface $routes An collection interface to use in place - * of the config file. - */ - protected function tryToRouteIt(RouteCollectionInterface $routes = null) - { - if (empty($routes) || ! $routes instanceof RouteCollectionInterface) - { - require APPPATH.'Config/Routes.php'; - } - - // $routes is defined in Config/Routes.php - $this->router = Services::router($routes); - - $path = is_cli() ? $this->request->getPath() : $this->request->uri->getPath(); - - $this->benchmark->stop('bootstrap'); - $this->benchmark->start('routing'); - - ob_start(); - - $this->controller = $this->router->handle($path); - $this->method = $this->router->methodName(); - - // If a {locale} segment was matched in the final route, - // then we need to set the correct locale on our Request. - if ($this->router->hasLocale()) - { - $this->request->setLocale($this->router->getLocale()); - } - - $this->benchmark->stop('routing'); - } - - //-------------------------------------------------------------------- - - /** - * Now that everything has been setup, this method attempts to run the - * controller method and make the script go. If it's not able to, will - * show the appropriate Page Not Found error. - */ - protected function startController() - { - $this->benchmark->start('controller'); - $this->benchmark->start('controller_constructor'); - - // Is it routed to a Closure? - if (is_object($this->controller) && (get_class($this->controller) == 'Closure')) - { - $controller = $this->controller; - return $controller(...$this->router->params()); - } - else - { - // No controller specified - we don't know what to do now. - if (empty($this->controller)) - { - throw new PageNotFoundException('Controller is empty.'); - } - else - { - // Try to autoload the class - if ( ! class_exists($this->controller, true) || $this->method[0] === '_') - { - throw new PageNotFoundException('Controller or its method is not found.'); - } - else if ( ! method_exists($this->controller, '_remap') && - ! is_callable([$this->controller, $this->method], false) - ) - { - throw new PageNotFoundException('Controller method is not found.'); - } - } - } - } - - //-------------------------------------------------------------------- - - /** - * Instantiates the controller class. - * - * @return mixed - */ - protected function createController() - { - $class = new $this->controller($this->request, $this->response); - - $this->benchmark->stop('controller_constructor'); - - return $class; - } - - //-------------------------------------------------------------------- - - /** - * Runs the controller, allowing for _remap methods to function. - * - * @param mixed $class - * - * @return mixed - */ - protected function runController($class) - { - if (method_exists($class, '_remap')) - { - $output = $class->_remap($this->method, ...$this->router->params()); - } - else - { - $output = $class->{$this->method}(...$this->router->params()); - } - - $this->benchmark->stop('controller'); - - return $output; - } - - //-------------------------------------------------------------------- - - /** - * Displays a 404 Page Not Found error. If set, will try to - * call the 404Override controller/method that was set in routing config. - * - * @param PageNotFoundException $e - */ - protected function display404errors(PageNotFoundException $e) - { - // Is there a 404 Override available? - if ($override = $this->router->get404Override()) - { - if ($override instanceof \Closure) - { - echo $override(); - } - else if (is_array($override)) - { - $this->benchmark->start('controller'); - $this->benchmark->start('controller_constructor'); - - $this->controller = $override[0]; - $this->method = $override[1]; - - unset($override); - - $controller = $this->createController(); - $this->runController($controller); - } - - $this->gatherOutput(); - $this->sendResponse(); - - return; - } - - // Display 404 Errors - $this->response->setStatusCode(404); - - if (ENVIRONMENT !== 'testing') { - if (ob_get_level() > 0) { - ob_end_flush(); - } - } - else - { - // When testing, one is for phpunit, another is for test case. - if (ob_get_level() > 2) { - ob_end_flush(); - } - } - - ob_start(); - - // These might show as unused here - but don't delete! - // They are used within the view files. - $heading = 'Page Not Found'; - $message = $e->getMessage(); - - // Show the 404 error page - if (is_cli()) - { - require APPPATH.'Views/errors/cli/error_404.php'; - } - else - { - require APPPATH.'Views/errors/html/error_404.php'; - } - - $buffer = ob_get_contents(); - ob_end_clean(); - - echo $buffer; - $this->callExit(EXIT_UNKNOWN_FILE); // Unknown file - } - - //-------------------------------------------------------------------- - - /** - * Gathers the script output from the buffer, replaces some execution - * time tag in the output and displays the debug toolbar, if required. - */ - protected function gatherOutput($cacheConfig = null, $returned = null) - { - $this->output = ob_get_contents(); - ob_end_clean(); - - if (is_string($returned)) - { - $this->output .= $returned; - } - - // Cache it without the performance metrics replaced - // so that we can have live speed updates along the way. - if (self::$cacheTTL > 0) - { - $this->cachePage($cacheConfig); - } - - $this->output = $this->displayPerformanceMetrics($this->output); - - $this->response->setBody($this->output); - } - - //-------------------------------------------------------------------- + /** + * The current version of CodeIgniter Framework + */ + const CI_VERSION = '4.0-dev'; + + /** + * App startup time. + * @var mixed + */ + protected $startTime; + + /** + * Amount of memory at app start. + * @var int + */ + protected $startMemory; + + /** + * Total app execution time + * @var float + */ + protected $totalTime; + + /** + * Main application configuration + * @var \Config\App + */ + protected $config; + + /** + * Timer instance. + * @var Timer + */ + protected $benchmark; + + /** + * Current request. + * @var \CodeIgniter\HTTP\Request + */ + protected $request; + + /** + * Current response. + * @var \CodeIgniter\HTTP\Response + */ + protected $response; + + /** + * Router to use. + * @var \CodeIgniter\Router\Router + */ + protected $router; + + /** + * Controller to use. + * @var string|\Closure + */ + protected $controller; + + /** + * Controller method to invoke. + * @var string + */ + protected $method; + + /** + * Output handler to use. + * @var string + */ + protected $output; + + /** + * Cache expiration time + * @var int + */ + protected static $cacheTTL = 0; + + //-------------------------------------------------------------------- + + public function __construct($config) + { + $this->startTime = microtime(true); + $this->startMemory = memory_get_usage(true); + $this->config = $config; + } + + //-------------------------------------------------------------------- + + /** + * Handles some basic app and environment setup. + */ + public function initialize() + { + // Set default timezone on the server + date_default_timezone_set($this->config->appTimezone ?? 'UTC'); + + // Setup Exception Handling + Services::exceptions($this->config, true) + ->initialize(); + + $this->detectEnvironment(); + $this->bootstrapEnvironment(); + $this->loadEnvironment(); + + if (CI_DEBUG) + { + require_once BASEPATH.'ThirdParty/Kint/Kint.class.php'; + } + } + + //-------------------------------------------------------------------- + + /** + * Launch the application! + * + * This is "the loop" if you will. The main entry point into the script + * that gets the required class instances, fires off the filters, + * tries to route the response, loads the controller and generally + * makes all of the pieces work together. + * + * @param \CodeIgniter\RouteCollectionInterface $routes + */ + public function run(RouteCollectionInterface $routes = null) + { + $this->startBenchmark(); + + $this->getRequestObject(); + $this->getResponseObject(); + + $this->forceSecureAccess(); + + // Check for a cached page. Execution will stop + // if the page has been cached. + $cacheConfig = new Cache(); + $this->displayCache($cacheConfig); + + try { + $this->handleRequest($routes, $cacheConfig); + } + catch (Router\RedirectException $e) + { + $logger = Services::logger(); + $logger->info('REDIRECTED ROUTE at '.$e->getMessage()); + + // If the route is a 'redirect' route, it throws + // the exception with the $to as the message + $this->response->redirect($e->getMessage(), 'auto', $e->getCode()); + $this->callExit(EXIT_SUCCESS); + } + // Catch Response::redirect() + catch (HTTP\RedirectException $e) + { + $this->callExit(EXIT_SUCCESS); + } + catch (PageNotFoundException $e) + { + $this->display404errors($e); + } + } + + //-------------------------------------------------------------------- + + /** + * Handles the main request logic and fires the controller. + * + * @param \CodeIgniter\Router\RouteCollectionInterface $routes + * @param $cacheConfig + */ + protected function handleRequest(RouteCollectionInterface $routes = null, $cacheConfig) + { + $this->tryToRouteIt($routes); + + // Run "before" filters + $filters = Services::filters(); + $uri = $this->request instanceof CLIRequest + ? $this->request->getPath() + : $this->request->uri->getPath(); + + $filters->run($uri, 'before'); + + $returned = $this->startController(); + + // Closure controller has run in startController(). + if ( ! is_callable($this->controller)) + { + $controller = $this->createController(); + + // Is there a "post_controller_constructor" hook? + Hooks::trigger('post_controller_constructor'); + + $returned = $this->runController($controller); + } + + // If $returned is a string, then the controller output something, + // probably a view, instead of echoing it directly. Send it along + // so it can be used with the output. + $this->gatherOutput($cacheConfig, $returned); + + // Run "after" filters + $response = $filters->run($uri, 'after'); + + if ($response instanceof Response) + { + $this->response = $response; + } + + // Save our current URI as the previous URI in the session + // for safer, more accurate use with `previous_url()` helper function. + $this->storePreviousURL($this->request->uri ?? $uri); + + unset($uri); + + $this->sendResponse(); + + //-------------------------------------------------------------------- + // Is there a post-system hook? + //-------------------------------------------------------------------- + Hooks::trigger('post_system'); + } + + //-------------------------------------------------------------------- + + /** + * You can load different configurations depending on your + * current environment. Setting the environment also influences + * things like logging and error reporting. + * + * This can be set to anything, but default usage is: + * + * development + * testing + * production + */ + protected function detectEnvironment() + { + // running under Continuous Integration server? + if (getenv('CI') !== false) + { + define('ENVIRONMENT', 'testing'); + } + else + { + define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'production'); + } + } + + //-------------------------------------------------------------------- + + /** + * Load any custom boot files based upon the current environment. + * + * If no boot file exists, we shouldn't continue because something + * is wrong. At the very least, they should have error reporting setup. + */ + protected function bootstrapEnvironment() + { + if (file_exists(APPPATH.'Config/Boot/'.ENVIRONMENT.'.php')) + { + require_once APPPATH.'Config/Boot/'.ENVIRONMENT.'.php'; + } + else + { + header('HTTP/1.1 503 Service Unavailable.', true, 503); + echo 'The application environment is not set correctly.'; + exit(1); // EXIT_ERROR + } + } + + //-------------------------------------------------------------------- + + /** + * Loads any custom server config values from the .env file. + */ + protected function loadEnvironment() + { + // Load environment settings from .env files + // into $_SERVER and $_ENV + require BASEPATH.'Config/DotEnv.php'; + + $env = new DotEnv(ROOTPATH); + $env->load(); + } + + //-------------------------------------------------------------------- + + /** + * Start the Benchmark + * + * The timer is used to display total script execution both in the + * debug toolbar, and potentially on the displayed page. + */ + protected function startBenchmark() + { + $this->startTime = microtime(true); + + $this->benchmark = Services::timer(); + $this->benchmark->start('total_execution', $this->startTime); + $this->benchmark->start('bootstrap'); + } + + //-------------------------------------------------------------------- + + /** + * Get our Request object, (either IncomingRequest or CLIRequest) + * and set the server protocol based on the information provided + * by the server. + */ + protected function getRequestObject() + { + if (is_cli()) + { + $this->request = Services::clirequest($this->config); + } + else + { + $this->request = Services::request($this->config); + $this->request->setProtocolVersion($_SERVER['SERVER_PROTOCOL']); + } + } + + //-------------------------------------------------------------------- + + /** + * Get our Response object, and set some default values, including + * the HTTP protocol version and a default successful response. + */ + protected function getResponseObject() + { + $this->response = Services::response($this->config); + + if ( ! is_cli()) + { + $this->response->setProtocolVersion($this->request->getProtocolVersion()); + } + + // Assume success until proven otherwise. + $this->response->setStatusCode(200); + } + + //-------------------------------------------------------------------- + + /** + * Force Secure Site Access? If the config value 'forceGlobalSecureRequests' + * is true, will enforce that all requests to this site are made through + * HTTPS. Will redirect the user to the current page with HTTPS, as well + * as set the HTTP Strict Transport Security header for those browsers + * that support it. + * + * @param int $duration How long the Strict Transport Security + * should be enforced for this URL. + */ + protected function forceSecureAccess($duration = 31536000) + { + if ($this->config->forceGlobalSecureRequests !== true) + { + return; + } + + force_https($duration, $this->request, $this->response); + } + + //-------------------------------------------------------------------- + + /** + * Determines if a response has been cached for the given URI. + * + * @param \Config\Cache $config + * + * @return bool + */ + public function displayCache($config) + { + if ($cachedResponse = cache()->get($this->generateCacheName($config))) + { + $cachedResponse = unserialize($cachedResponse); + if (!is_array($cachedResponse) || !isset($cachedResponse['output']) || !isset($cachedResponse['headers'])) + { + throw new \Exception("Error unserializing page cache"); + } + + $headers = $cachedResponse['headers']; + $output = $cachedResponse['output']; + + // Clear all default headers + foreach($this->response->getHeaders() as $key => $val) { + $this->response->removeHeader($key); + } + + // Set cached headers + foreach($headers as $name => $value) { + $this->response->setHeader($name, $value); + } + + $output = $this->displayPerformanceMetrics($output); + $this->response->setBody($output)->send(); + $this->callExit(EXIT_SUCCESS); + }; + } + + + //-------------------------------------------------------------------- + + /** + * Tells the app that the final output should be cached. + * + * @param int $time + * + * @return $this + */ + public static function cache(int $time) + { + self::$cacheTTL = (int)$time; + } + + //-------------------------------------------------------------------- + + /** + * Caches the full response from the current request. Used for + * full-page caching for very high performance. + * + * @param \Config\Cache $config + */ + public function cachePage(Cache $config) + { + $headers = []; + foreach($this->response->getHeaders() as $header) { + $headers[$header->getName()] = $header->getValueLine(); + } + + return cache()->save( + $this->generateCacheName($config), + serialize(['headers' => $headers, 'output' => $this->output]), + self::$cacheTTL + ); + + } + + //-------------------------------------------------------------------- + + /** + * Returns an array with our basic performance stats collected. + * + * @return array + */ + public function getPerformanceStats() + { + return [ + 'startTime' => $this->startTime, + 'totalTime' => $this->totalTime, + 'startMemory' => $this->startMemory + ]; + } + + //-------------------------------------------------------------------- + + /** + * Generates the cache name to use for our full-page caching. + * + * @param $config + * + * @return string + */ + protected function generateCacheName($config): string + { + if (is_cli()) + { + return md5($this->request->getPath()); + } + + $uri = $this->request->uri; + + if ($config->cacheQueryString) + { + $name = URI::createURIString( + $uri->getScheme(), + $uri->getAuthority(), + $uri->getPath(), + $uri->getQuery() + ); + } + else + { + $name = URI::createURIString( + $uri->getScheme(), + $uri->getAuthority(), + $uri->getPath() + ); + } + + return md5($name); + } + + //-------------------------------------------------------------------- + + /** + * Replaces the memory_usage and elapsed_time tags. + * + * @param string $output + * + * @return string + */ + public function displayPerformanceMetrics(string $output): string + { + $this->totalTime = $this->benchmark->getElapsedTime('total_execution'); + + $output = str_replace('{elapsed_time}', $this->totalTime, $output); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Try to Route It - As it sounds like, works with the router to + * match a route against the current URI. If the route is a + * "redirect route", will also handle the redirect. + * + * @param RouteCollectionInterface $routes An collection interface to use in place + * of the config file. + */ + protected function tryToRouteIt(RouteCollectionInterface $routes = null) + { + if (empty($routes) || ! $routes instanceof RouteCollectionInterface) + { + require APPPATH.'Config/Routes.php'; + } + + // $routes is defined in Config/Routes.php + $this->router = Services::router($routes); + + $path = is_cli() ? $this->request->getPath() : $this->request->uri->getPath(); + + $this->benchmark->stop('bootstrap'); + $this->benchmark->start('routing'); + + ob_start(); + + $this->controller = $this->router->handle($path); + $this->method = $this->router->methodName(); + + // If a {locale} segment was matched in the final route, + // then we need to set the correct locale on our Request. + if ($this->router->hasLocale()) + { + $this->request->setLocale($this->router->getLocale()); + } + + $this->benchmark->stop('routing'); + } + + //-------------------------------------------------------------------- + + /** + * Now that everything has been setup, this method attempts to run the + * controller method and make the script go. If it's not able to, will + * show the appropriate Page Not Found error. + */ + protected function startController() + { + $this->benchmark->start('controller'); + $this->benchmark->start('controller_constructor'); + + // Is it routed to a Closure? + if (is_object($this->controller) && (get_class($this->controller) == 'Closure')) + { + $controller = $this->controller; + return $controller(...$this->router->params()); + } + else + { + // No controller specified - we don't know what to do now. + if (empty($this->controller)) + { + throw new PageNotFoundException('Controller is empty.'); + } + else + { + // Try to autoload the class + if ( ! class_exists($this->controller, true) || $this->method[0] === '_') + { + throw new PageNotFoundException('Controller or its method is not found.'); + } + else if ( ! method_exists($this->controller, '_remap') && + ! is_callable([$this->controller, $this->method], false) + ) + { + throw new PageNotFoundException('Controller method is not found.'); + } + } + } + } + + //-------------------------------------------------------------------- + + /** + * Instantiates the controller class. + * + * @return mixed + */ + protected function createController() + { + $class = new $this->controller($this->request, $this->response); + + $this->benchmark->stop('controller_constructor'); + + return $class; + } + + //-------------------------------------------------------------------- + + /** + * Runs the controller, allowing for _remap methods to function. + * + * @param mixed $class + * + * @return mixed + */ + protected function runController($class) + { + if (method_exists($class, '_remap')) + { + $output = $class->_remap($this->method, ...$this->router->params()); + } + else + { + $output = $class->{$this->method}(...$this->router->params()); + } + + $this->benchmark->stop('controller'); + + return $output; + } + + //-------------------------------------------------------------------- + + /** + * Displays a 404 Page Not Found error. If set, will try to + * call the 404Override controller/method that was set in routing config. + * + * @param PageNotFoundException $e + */ + protected function display404errors(PageNotFoundException $e) + { + // Is there a 404 Override available? + if ($override = $this->router->get404Override()) + { + if ($override instanceof \Closure) + { + echo $override(); + } + else if (is_array($override)) + { + $this->benchmark->start('controller'); + $this->benchmark->start('controller_constructor'); + + $this->controller = $override[0]; + $this->method = $override[1]; + + unset($override); + + $controller = $this->createController(); + $this->runController($controller); + } + + $this->gatherOutput(); + $this->sendResponse(); + + return; + } + + // Display 404 Errors + $this->response->setStatusCode(404); + + if (ENVIRONMENT !== 'testing') { + if (ob_get_level() > 0) { + ob_end_flush(); + } + } + else + { + // When testing, one is for phpunit, another is for test case. + if (ob_get_level() > 2) { + ob_end_flush(); + } + } + + ob_start(); + + // These might show as unused here - but don't delete! + // They are used within the view files. + $heading = 'Page Not Found'; + $message = $e->getMessage(); + + // Show the 404 error page + if (is_cli()) + { + require APPPATH.'Views/errors/cli/error_404.php'; + } + else + { + require APPPATH.'Views/errors/html/error_404.php'; + } + + $buffer = ob_get_contents(); + ob_end_clean(); + + echo $buffer; + $this->callExit(EXIT_UNKNOWN_FILE); // Unknown file + } + + //-------------------------------------------------------------------- + + /** + * Gathers the script output from the buffer, replaces some execution + * time tag in the output and displays the debug toolbar, if required. + */ + protected function gatherOutput($cacheConfig = null, $returned = null) + { + $this->output = ob_get_contents(); + ob_end_clean(); + + if (is_string($returned)) + { + $this->output .= $returned; + } + + // Cache it without the performance metrics replaced + // so that we can have live speed updates along the way. + if (self::$cacheTTL > 0) + { + $this->cachePage($cacheConfig); + } + + $this->output = $this->displayPerformanceMetrics($this->output); + + $this->response->setBody($this->output); + } + + //-------------------------------------------------------------------- /** * If we have a session object to use, store the current URI @@ -732,30 +777,30 @@ public function storePreviousURL($uri) //-------------------------------------------------------------------- - /** - * Sends the output of this request back to the client. - * This is what they've been waiting for! - */ - protected function sendResponse() - { - $this->response->send(); - } - - //-------------------------------------------------------------------- - - /** - * Exits the application, setting the exit code for CLI-based applications - * that might be watching. - * - * Made into a separate method so that it can be mocked during testing - * without actually stopping script execution. - * - * @param $code - */ - protected function callExit($code) - { - exit($code); - } - - //-------------------------------------------------------------------- + /** + * Sends the output of this request back to the client. + * This is what they've been waiting for! + */ + protected function sendResponse() + { + $this->response->send(); + } + + //-------------------------------------------------------------------- + + /** + * Exits the application, setting the exit code for CLI-based applications + * that might be watching. + * + * Made into a separate method so that it can be mocked during testing + * without actually stopping script execution. + * + * @param $code + */ + protected function callExit($code) + { + exit($code); + } + + //-------------------------------------------------------------------- } diff --git a/system/Database/Config.php b/system/Database/Config.php index abc8e0c9b8d1..4ec2cbb9719f 100644 --- a/system/Database/Config.php +++ b/system/Database/Config.php @@ -150,11 +150,11 @@ public static function forge(string $group = null) { $db = self::connect($group); } - else + else { $db = self::$instances[$group]; } - + return self::$factory->loadForge($db); } @@ -162,7 +162,7 @@ public static function forge(string $group = null) /** * Returns a new instance of the Database Utilities class. - * + * * @param string|null $group * * @return mixed diff --git a/system/Database/Database.php b/system/Database/Database.php index 8791a3f7adab..0c5b524f01a0 100644 --- a/system/Database/Database.php +++ b/system/Database/Database.php @@ -74,7 +74,7 @@ public function load(array $params = [], string $alias) { throw new \InvalidArgumentException('You have not selected a database type to connect to.'); } - + $className = strpos($params['DBDriver'], '\\') === false ? '\CodeIgniter\Database\\'.$params['DBDriver'].'\\Connection' : $params['DBDriver'].'\\Connection'; diff --git a/system/bootstrap.php b/system/bootstrap.php new file mode 100644 index 000000000000..0ca0eeef288f --- /dev/null +++ b/system/bootstrap.php @@ -0,0 +1,82 @@ +initialize(new Config\Autoload()); +$loader->register(); // Register the loader with the SPL autoloader stack. + +// Now load Composer's if it's available +if (file_exists(COMPOSER_PATH)) +{ + require COMPOSER_PATH; +} + +/* + * --------------------------------------------------------------- + * GRAB OUR CODEIGNITER INSTANCE + * --------------------------------------------------------------- + * + * The CodeIgniter class contains the core functionality to make + * the application run, and does all of the dirty work to get + * the pieces all working together. + */ + +$app = new \CodeIgniter\CodeIgniter(new \Config\App()); +$app->initialize(); + +return $app; diff --git a/tests/_support/Models/EntityModel.php b/tests/_support/Models/EntityModel.php index 7fb7a82b9fac..9c83566540b7 100644 --- a/tests/_support/Models/EntityModel.php +++ b/tests/_support/Models/EntityModel.php @@ -7,7 +7,6 @@ class EntityModel extends Model protected $table = 'job'; protected $returnType = '\Tests\Support\Models\SimpleEntity'; -// protected $returnType = 'object'; protected $useSoftDeletes = false; diff --git a/tests/_support/_bootstrap.php b/tests/_support/_bootstrap.php index c86f6314e5f4..d0c0d294fd67 100644 --- a/tests/_support/_bootstrap.php +++ b/tests/_support/_bootstrap.php @@ -1,69 +1,35 @@ load(); -unset($env); +// Use special Services for testing. These allow +// insert mocks in place of normal services. +require SUPPORTPATH.'Services.php'; /* - * ------------------------------------------------------ - * Load the framework constants - * ------------------------------------------------------ + * --------------------------------------------------------------- + * GRAB OUR CONSTANTS & COMMON + * --------------------------------------------------------------- */ if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/Constants.php')) { - require_once APPPATH.'Config/'.ENVIRONMENT.'/Constants.php'; + require_once APPPATH.'Config/'.ENVIRONMENT.'/Constants.php'; } +require APPPATH.'Config/Constants.php'; -require_once(APPPATH.'Config/Constants.php'); +// Use special global functions for testing. +require_once SUPPORTPATH.'MockCommon.php'; +require BASEPATH.'Common.php'; /* - * ------------------------------------------------------ - * Setup the autoloader - * ------------------------------------------------------ + * --------------------------------------------------------------- + * LOAD OUR AUTOLOADER + * --------------------------------------------------------------- + * + * The autoloader allows all of the pieces to work together + * in the framework. We have to load it here, though, so + * that the config files can use the path constants. */ -// The autoloader isn't initialized yet, so load the file manually. + require BASEPATH.'Autoloader/Autoloader.php'; -require APPPATH.'Config/Autoload.php'; -require APPPATH.'Config/Services.php'; -// Use special Services for testing. -require SUPPORTPATH.'Services.php'; +require APPPATH .'Config/Autoload.php'; +require APPPATH .'Config/Services.php'; + +// Use Config\Services as CodeIgniter\Services +class_alias('Config\Services', 'CodeIgniter\Services'); -// The Autoloader class only handles namespaces -// and "legacy" support. $loader = CodeIgniter\Services::autoloader(); $loader->initialize(new Config\Autoload()); - -// The register function will prepend -// the psr4 loader. -$loader->register(); +$loader->register(); // Register the loader with the SPL autoloader stack. // Add namespace paths to autoload mocks for testing. -$loader->addNamespace('CodeIgniter', SUPPORTPATH); -$loader->addNamespace('Config', SUPPORTPATH.'Config'); - -/* - * ------------------------------------------------------ - * Load the global functions - * ------------------------------------------------------ - */ +$loader->addNamespace('CodeIgniter', SUPPORTPATH); +$loader->addNamespace('Config', SUPPORTPATH.'Config'); +$loader->addNamespace('Tests\Support', SUPPORTPATH); -// Use special global functions for testing. -require_once SUPPORTPATH.'MockCommon.php'; -require_once BASEPATH.'Common.php'; +// Now load Composer's if it's available +if (file_exists(COMPOSER_PATH)) +{ + require COMPOSER_PATH; +} /* - * ------------------------------------------------------ - * Set custom exception handling - * ------------------------------------------------------ + * --------------------------------------------------------------- + * GRAB OUR CODEIGNITER INSTANCE + * --------------------------------------------------------------- + * + * The CodeIgniter class contains the core functionality to make + * the application run, and does all of the dirty work to get + * the pieces all working together. */ -$config = new \Config\App(); - -Config\Services::exceptions($config, true) - ->initialize(); -//-------------------------------------------------------------------- -// Should we use a Composer autoloader? -//-------------------------------------------------------------------- - -if ($composer_autoload = $config->composerAutoload) -{ - if ($composer_autoload === TRUE) - { - file_exists(APPPATH.'vendor/autoload.php') - ? require_once(APPPATH.'vendor/autoload.php') - : log_message('error', '$config->\'composerAutoload\' is set to TRUE but '.APPPATH.'vendor/autoload.php was not found.'); - } - elseif (file_exists($composer_autoload)) - { - require_once($composer_autoload); - } - else - { - log_message('error', 'Could not find the specified $config->\'composerAutoload\' path: '.$composer_autoload); - } -} +$app = new \CodeIgniter\CodeIgniter(new \Config\App()); +$app->initialize(); //-------------------------------------------------------------------- // Load our TestCase //-------------------------------------------------------------------- -require_once __DIR__ .'/CIUnitTestCase.php'; +require TESTPATH.'_support/CIUnitTestCase.php'; diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index 24f3eae2881e..2fc629c82213 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -19,7 +19,7 @@ public function setUp() Services::reset(); $config = new App(); - $this->codeigniter = new MockCodeIgniter(memory_get_usage(), microtime(true), $config); + $this->codeigniter = new MockCodeIgniter($config); } //-------------------------------------------------------------------- From f1e6671fcf302233abe378c5216c5425bd62326c Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 15 Jan 2017 23:09:01 -0600 Subject: [PATCH 0441/1807] Remove Composer from Config/App since it's now based off of a constant. --- application/Config/App.php | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/application/Config/App.php b/application/Config/App.php index af05f0f36182..55876ac1ec05 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -292,28 +292,6 @@ class App extends BaseConfig */ public $errorViewPath = APPPATH.'Views/errors'; - /* - |-------------------------------------------------------------------------- - | Composer auto-loading - |-------------------------------------------------------------------------- - | - | Enabling this setting will tell CodeIgniter to look for a Composer - | package auto-loader script in /vendor/autoload.php. - | - | $composerAutoload = TRUE; - | - | Or if you have your vendor/ directory located somewhere else, you - | can opt to set a specific path as well: - | - | $composerAutoload = '/path/to/vendor/autoload.php'; - | - | For more information about Composer, please visit http://getcomposer.org/ - | - | Note: This will NOT disable or override the CodeIgniter-specific - | autoloading. - */ - public $composerAutoload = false; - /* |-------------------------------------------------------------------------- | Encryption Key From 144cc2b1aed08f95e37b49b97ca613323c5dcc03 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 15 Jan 2017 23:35:14 -0600 Subject: [PATCH 0442/1807] Move application structure paths to a config file so it can be loaded from CLI tool also. --- application/Config/Paths.php | 64 +++++++++++++++++++++++++++++++++++ public/index.php | 61 +++++---------------------------- system/CodeIgniter.php | 15 +++++--- system/bootstrap.php | 16 ++++----- tests/_support/_bootstrap.php | 26 ++++++-------- 5 files changed, 102 insertions(+), 80 deletions(-) create mode 100644 application/Config/Paths.php diff --git a/application/Config/Paths.php b/application/Config/Paths.php new file mode 100644 index 000000000000..0533cc8f7ea8 --- /dev/null +++ b/application/Config/Paths.php @@ -0,0 +1,64 @@ +systemDirectory,'/ ').'/bootstrap.php'; /* *--------------------------------------------------------------- diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index c4fa1f1eb856..1f57bfb94044 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -1,15 +1,20 @@ applicationDirectory).DIRECTORY_SEPARATOR); + +// Path to the system folder +define('BASEPATH', realpath($paths->systemDirectory).DIRECTORY_SEPARATOR); + +// Path to the writable directory. +define('WRITEPATH', realpath($paths->writableDirectory).DIRECTORY_SEPARATOR); // The path to the "tests" directory -define('TESTPATH', realpath($tests_directory).DIRECTORY_SEPARATOR); +define('TESTPATH', realpath($paths->testsDirectory).DIRECTORY_SEPARATOR); /* * --------------------------------------------------------------- diff --git a/tests/_support/_bootstrap.php b/tests/_support/_bootstrap.php index d0c0d294fd67..f858719e827b 100644 --- a/tests/_support/_bootstrap.php +++ b/tests/_support/_bootstrap.php @@ -1,12 +1,8 @@ applicationDirectory).DIRECTORY_SEPARATOR); + +// Path to the system folder +define('BASEPATH', realpath(FCPATH.$paths->systemDirectory).DIRECTORY_SEPARATOR); + +// Path to the writable directory. +define('WRITEPATH', realpath(FCPATH.$paths->writableDirectory).DIRECTORY_SEPARATOR); // The path to the "tests" directory -define('TESTPATH', realpath($tests_directory).DIRECTORY_SEPARATOR); +define('TESTPATH', realpath(FCPATH.$paths->testsDirectory).DIRECTORY_SEPARATOR); define('SUPPORTPATH', realpath(TESTPATH.'_support/').'/'); From 1dacecf9d6c95937647efa36d1d05dc610f462eb Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 16 Jan 2017 00:13:22 -0600 Subject: [PATCH 0443/1807] Refactor CLI tools to use the CodeIgniter instance directly, instead of passing through index.php. --- ci.php | 34 ++++++++--- system/CLI/Console.php | 101 +++++-------------------------- system/CodeIgniter.php | 45 +++++++++++++- system/Config/AutoloadConfig.php | 2 +- system/bootstrap.php | 4 -- tests/_support/_bootstrap.php | 4 -- 6 files changed, 85 insertions(+), 105 deletions(-) diff --git a/ci.php b/ci.php index d043a2f07102..b616b268aa15 100755 --- a/ci.php +++ b/ci.php @@ -1,9 +1,7 @@ #!/usr/bin/env php systemDirectory,'/ ').'/bootstrap.php'; + +// Grab our Console +$console = new \CodeIgniter\CLI\Console($app); + // We want errors to be shown when using it from the CLI. error_reporting(-1); ini_set('display_errors', 1); diff --git a/system/CLI/Console.php b/system/CLI/Console.php index 4fb75ff96242..5100d534fbb6 100644 --- a/system/CLI/Console.php +++ b/system/CLI/Console.php @@ -5,38 +5,16 @@ class Console { /** - * Path to the CodeIgniter index file. - * @var string + * Main CodeIgniter instance. + * @var CodeIgniter */ - protected $indexPath; - - /** - * Path to the system folder. - * @var string - */ - protected $systemPath; - - /** - * The 'URI' to use to pass onto CodeIgniter - * @var string - */ - protected $commandString; - - /** - * A string representation of all CLI options. - * @var string - */ - protected $optionString; + protected $app; //-------------------------------------------------------------------- - public function __construct() + public function __construct(CodeIgniter $app) { - $this->indexPath = $this->locateIndex(); - $this->systemPath = $this->locateSystem(); - - $this->commandString = CLI::getURI(); - $this->optionString = CLI::getOptionString(); + $this->app = $app; } //-------------------------------------------------------------------- @@ -46,7 +24,12 @@ public function __construct() */ public function run() { - return passthru("php {$this->indexPath} ci {$this->commandString} {$this->optionString}"); + $path = CLI::getURI() ?: 'help'; + + // Set the path for the application to route to. + $this->app->setPath("ci{$path}"); + + return $this->app->run(); } //-------------------------------------------------------------------- @@ -58,69 +41,13 @@ public function showHeader() { CLI::newLine(1); - CLI::write('CodeIgniter CLI Tool', 'green'); - CLI::write('Version '. $this->getVersion()); - CLI::write('Server-Time: '. date('Y-m-d H:i:sa')); + CLI::write(CLI::color('CodeIgniter CLI Tool', 'green') + . ' - Version '. CodeIgniter::CI_VERSION + . ' - Server-Time: '. date('Y-m-d H:i:sa')); CLI::newLine(1); } //-------------------------------------------------------------------- - /** - * Returns the current version of CodeIgniter. - */ - public function getVersion() - { - // The CI Version number is stored in the main CodeIgniter class. - require_once $this->systemPath.'CodeIgniter.php'; - - return CodeIgniter::CI_VERSION; - } - - //-------------------------------------------------------------------- - - /** - * Find the index path, checking the default location, - * and where it would be if "flattened" - * - * @return string - */ - protected function locateIndex() - { - $path = realpath(__DIR__.'/../../public/index.php'); - - if (empty($path)) - { - $path = __DIR__.'/../../index.php'; - - if (! is_file($path)) - { - die('Unable to locate the CodeIgniter index.php file.'); - } - } - - return $path; - } - - //-------------------------------------------------------------------- - - /** - * Attempts to locate the main application directory. - * - * @return string - */ - protected function locateSystem() - { - $path = realpath(__DIR__.'/../../system'); - - if (empty($path) || ! is_dir($path)) - { - die('Unable to locate the CodeIgniter system directory.'); - } - - return $path.'/'; - } - - //-------------------------------------------------------------------- } diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 1f57bfb94044..e8cc55358244 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -94,6 +94,12 @@ class CodeIgniter */ protected static $cacheTTL = 0; + /** + * Request path to use. + * @var string + */ + protected $path; + //-------------------------------------------------------------------- public function __construct($config) @@ -547,7 +553,7 @@ protected function tryToRouteIt(RouteCollectionInterface $routes = null) // $routes is defined in Config/Routes.php $this->router = Services::router($routes); - $path = is_cli() ? $this->request->getPath() : $this->request->uri->getPath(); + $path = $this->determinePath(); $this->benchmark->stop('bootstrap'); $this->benchmark->start('routing'); @@ -569,6 +575,43 @@ protected function tryToRouteIt(RouteCollectionInterface $routes = null) //-------------------------------------------------------------------- + /** + * Determines the path to use for us to try to route to, based + * on user input (setPath), or the CLI/IncomingRequest path. + */ + protected function determinePath() + { + if (! empty($this->path)) + { + return $this->path; + } + + return is_cli() + ? $this->request->getPath() + : $this->request->uri->getPath(); + } + + //-------------------------------------------------------------------- + + /** + * Allows the request path to be set from outside the class, + * instead of relying on CLIRequest or IncomingRequest for the path. + * + * This is primarily used by the Console. + * + * @param string $path + * + * @return $this + */ + public function setPath(string $path) + { + $this->path = $path; + + return $this; + } + + //-------------------------------------------------------------------- + /** * Now that everything has been setup, this method attempts to run the * controller method and make the script go. If it's not able to, will diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index 13d602da4ba7..34ce4b4ad9b5 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -89,7 +89,7 @@ public function __construct() 'CodeIgniter' => realpath(BASEPATH) ]; - if (ENVIRONMENT == 'testing') + if (isset($_SERVER['CI_ENV']) && $_SERVER['CI_ENV'] === 'testing') { $this->psr4['Tests\Support'] = BASEPATH.'../tests/_support/'; } diff --git a/system/bootstrap.php b/system/bootstrap.php index 16be0c276686..07fbcbd95cab 100644 --- a/system/bootstrap.php +++ b/system/bootstrap.php @@ -31,10 +31,6 @@ * GRAB OUR CONSTANTS & COMMON * --------------------------------------------------------------- */ -if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/Constants.php')) -{ - require_once APPPATH.'Config/'.ENVIRONMENT.'/Constants.php'; -} require APPPATH.'Config/Constants.php'; require BASEPATH.'Common.php'; diff --git a/tests/_support/_bootstrap.php b/tests/_support/_bootstrap.php index f858719e827b..f869c17a2c7c 100644 --- a/tests/_support/_bootstrap.php +++ b/tests/_support/_bootstrap.php @@ -47,10 +47,6 @@ * GRAB OUR CONSTANTS & COMMON * --------------------------------------------------------------- */ -if (file_exists(APPPATH.'Config/'.ENVIRONMENT.'/Constants.php')) -{ - require_once APPPATH.'Config/'.ENVIRONMENT.'/Constants.php'; -} require APPPATH.'Config/Constants.php'; // Use special global functions for testing. From fc167b9dd413b24ab5f1c2655078faac3fdcb75d Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 16 Jan 2017 00:36:42 -0600 Subject: [PATCH 0444/1807] Added env function in preparation for removing BaseConfig. It's more explicit and less prone to errors on the framework side. --- system/Common.php | 42 +++++++++++++++++++ .../source/general/common_functions.rst | 15 +++++++ 2 files changed, 57 insertions(+) diff --git a/system/Common.php b/system/Common.php index b1ee097a36c9..70b0e8431a0a 100644 --- a/system/Common.php +++ b/system/Common.php @@ -144,6 +144,48 @@ function view_cell(string $library, $params = null, int $ttl = 0, string $cacheN //-------------------------------------------------------------------- +if ( ! function_exists('env')) +{ + /** + * Allows user to retrieve values from the environment + * variables that have been set. Especially useful for + * retrieving values set from the .env file for + * use in config files. + * + * @param string $key + * @param null $default + * + * @return array|bool|false|null|string|void + */ + function env(string $key, $default = null) + { + $value = getenv($key); + + // Not found? Return the default value + if ($value === false) + { + return $default; + } + + // Handle any boolean values + switch (strtolower($value)) + { + case 'true': + return true; + case 'false': + return false; + case 'empty': + return ''; + case 'null': + return; + } + + return $value; + } +} + +//-------------------------------------------------------------------- + if ( ! function_exists('esc')) { /** diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst index 5efb36983252..135615bd391c 100644 --- a/user_guide_src/source/general/common_functions.rst +++ b/user_guide_src/source/general/common_functions.rst @@ -30,6 +30,21 @@ Service Accessors $foo = cache('foo'); $cache = cache(); +.. php:function:: env ( $key[, $default=null]) + + :param string $key: The name of the environment variable to retrieve + :param mixed $default: The default value to return if no value is found. + :returns: The environment variable, the default value, or null. + :rtype: mixed + + Used to retrieve values that have previously been set to the environment, + or return a default value if it is not found. Will format boolean values + to actual booleans instead of string representations. + + Especially useful when used in conjunction with .env files for setting + values that are specific to the environment itself, like database + settings, API keys, etc. + .. php:function:: esc ( $data, $context='html' [, $encoding]) :param string|array $data: The information to be escaped. From 56d791aedefd9262116bca2821791874b472080f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 16 Jan 2017 23:50:10 +0700 Subject: [PATCH 0445/1807] Fixes Cannot declare class CodeIgniter\Services and already defined ROOTPATH constant in run phpunit --- phpunit.xml.dist | 1 + tests/_support/_bootstrap.php | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 137c169fe864..b1bb94f7d733 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -19,6 +19,7 @@ ./system + ./system/bootstrap.php ./system/Commands/Sessions/Views/migration.tpl.php ./system/ComposerScripts.php ./system/Debug/Toolbar/Views diff --git a/tests/_support/_bootstrap.php b/tests/_support/_bootstrap.php index f869c17a2c7c..d71eb78b1c4d 100644 --- a/tests/_support/_bootstrap.php +++ b/tests/_support/_bootstrap.php @@ -67,9 +67,6 @@ require APPPATH .'Config/Autoload.php'; require APPPATH .'Config/Services.php'; -// Use Config\Services as CodeIgniter\Services -class_alias('Config\Services', 'CodeIgniter\Services'); - $loader = CodeIgniter\Services::autoloader(); $loader->initialize(new Config\Autoload()); $loader->register(); // Register the loader with the SPL autoloader stack. From 3ecbede4c3c04966196872add74d0d125221a428 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 16 Jan 2017 14:16:00 -0600 Subject: [PATCH 0446/1807] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7beadc0e6a89..d4937f483c3c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # CodeIgniter 4 Development [![Build Status](https://travis-ci.org/bcit-ci/CodeIgniter4.svg?branch=develop)](https://travis-ci.org/bcit-ci/CodeIgniter4) +[![Coverage Status](https://coveralls.io/repos/github/bcit-ci/CodeIgniter4/badge.svg?branch=develop)](https://coveralls.io/github/bcit-ci/CodeIgniter4?branch=develop)
## What is CodeIgniter? From 9554ce6822052317d055d69f8a1de732ab5dc132 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 16 Jan 2017 23:14:15 -0600 Subject: [PATCH 0447/1807] Hooking up coveralls, hopefully. --- composer.json | 3 ++- phpunit.xml.dist | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 896aa4fe1c5f..cf39ea1e1ac2 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ }, "require-dev": { "phpunit/phpunit": "5.3.*", - "mikey179/vfsStream": "1.6.*" + "mikey179/vfsStream": "1.6.*", + "satooshi/php-coveralls": "^1.0" }, "scripts": { "post-update-cmd": [ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b1bb94f7d733..980e12b8617e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,4 +29,8 @@
+ + + + From 726567d64d0ce0fa95f348e614def3874afe31a9 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 16 Jan 2017 23:18:53 -0600 Subject: [PATCH 0448/1807] Oops. Forgot the travis config file. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index b2cd0637c7bf..1189c5b9bfd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,3 +30,6 @@ before_install: before_script: - composer install --prefer-source + +after_success: + - travis_retry php vendor/bin/coveralls From e69777b7c4573c9407348168202bbd917bab9ed5 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 16 Jan 2017 23:33:24 -0600 Subject: [PATCH 0449/1807] Testing env function --- system/Common.php | 4 ++++ tests/system/CommonFunctionsTest.php | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/system/Common.php b/system/Common.php index 70b0e8431a0a..c9e5039433b2 100644 --- a/system/Common.php +++ b/system/Common.php @@ -160,6 +160,10 @@ function view_cell(string $library, $params = null, int $ttl = 0, string $cacheN function env(string $key, $default = null) { $value = getenv($key); + if ($value === false) + { + $value = $_ENV[$key] ?? $_SERVER[$key] ?? false; + } // Not found? Return the default value if ($value === false) diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php index bad5ecc85bfd..e9603b0e62ed 100644 --- a/tests/system/CommonFunctionsTest.php +++ b/tests/system/CommonFunctionsTest.php @@ -7,6 +7,7 @@ class CommomFunctionsTest extends \CIUnitTestCase public function setUp() { + unset($_ENV['foo'], $_SERVER['foo']); } //-------------------------------------------------------------------- @@ -42,4 +43,22 @@ public function testStringifyJsAttributes() // ------------------------------------------------------------------------ + public function testEnvReturnsDefault() + { + $this->assertEquals('baz', env('foo', 'baz')); + } + + public function testEnvGetsFromSERVER() + { + $_SERVER['foo'] = 'bar'; + + $this->assertEquals('bar', env('foo', 'baz')); + } + + public function testEnvGetsFromENV() + { + $_ENV['foo'] = 'bar'; + + $this->assertEquals('bar', env('foo', 'baz')); + } } From 14ed8d936bf327d8e2e238c51fa19decda843c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Wed, 18 Jan 2017 08:04:27 +0300 Subject: [PATCH 0450/1807] php7.1 session key length fix --- system/Session/Session.php | 94 ++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index 875c59f088b7..d5ea9c5effe8 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -9,7 +9,7 @@ * * This content is released under the MIT License (MIT) * - * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * Copyright (c) 2014 - 2016, British Columbia Institute of Technology * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,7 +31,7 @@ * * @package CodeIgniter * @author CodeIgniter Dev Team - * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) * @license http://opensource.org/licenses/MIT MIT License * @link http://codeigniter.com * @since Version 3.0.0 @@ -117,7 +117,7 @@ class Session implements SessionInterface /** * Whether to destroy session data associated with the old session ID - * when auto-regenerating the session ID. When set to FALSE, the data + * when auto-regenerating the session ID. When set to false, the data * will be later deleted by the garbage collector. * @var bool */ @@ -143,6 +143,8 @@ class Session implements SessionInterface */ protected $cookieSecure = false; + protected $sidRegexp; + /** * Logger instance to record error messages and awarnings. * @var \PSR\Log\LoggerInterface @@ -208,7 +210,7 @@ public function start() // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers if (isset($_COOKIE[$this->sessionCookieName]) && ( - ! is_string($_COOKIE[$this->sessionCookieName]) || ! preg_match('/^[0-9a-f]{40}$/', $_COOKIE[$this->sessionCookieName]) + ! is_string($_COOKIE[$this->sessionCookieName]) || ! preg_match('#\A'.$this->sidRegexp.'\z#', $_COOKIE[$this->sessionCookieName]) /*! preg_match('/^[0-9a-f]{40}$/', $_COOKIE[$this->sessionCookieName])*/ ) ) { @@ -316,8 +318,82 @@ protected function configure() ini_set('session.use_strict_mode', 1); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); - ini_set('session.hash_function', 1); - ini_set('session.hash_bits_per_character', 4); + // ini_set('session.hash_bits_per_character', 4); + + $this->configureSidLength(); + } + + // ------------------------------------------------------------------------ + + /** + * Configure session ID length + * + * To make life easier, we used to force SHA-1 and 4 bits per + * character on everyone. And of course, someone was unhappy. + * + * Then PHP 7.1 broke backwards-compatibility because ext/session + * is such a mess that nobody wants to touch it with a pole stick, + * and the one guy who does, nobody has the energy to argue with. + * + * So we were forced to make changes, and OF COURSE something was + * going to break and now we have this pile of shit. -- Narf + * + * @return void + */ + protected function configureSidLength() + { + if (PHP_VERSION_ID < 70100) + { + $hash_function = ini_get('session.hash_function'); + if (ctype_digit($hash_function)) + { + if ($hash_function !== '1') + { + ini_set('session.hash_function', 1); + $bits = 160; + } + } + elseif ( ! in_array($hash_function, hash_algos(), true)) + { + ini_set('session.hash_function', 1); + $bits = 160; + } + elseif (($bits = strlen(hash($hash_function, 'dummy', false)) * 4) < 160) + { + ini_set('session.hash_function', 1); + $bits = 160; + } + + $bits_per_character = (int) ini_get('session.hash_bits_per_character'); + $sid_length = (int) ceil($bits / $bits_per_character); + } + else + { + $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) + { + // Add as many more characters as necessary to reach at least 160 bits + $sid_length += (int) ceil((160 % $bits) / $bits_per_character); + ini_set('session.sid_length', $sid_length); + } + } + + // Yes, 4,5,6 are the only known possible values as of 2016-10-27 + switch ($bits_per_character) + { + case 4: + $this->sidRegexp = '[0-9a-f]'; + break; + case 5: + $this->sidRegexp = '[0-9a-v]'; + break; + case 6: + $this->sidRegexp = '[0-9a-zA-Z,-]'; + break; + } + + $this->sidRegexp .= '{'.$sid_length.'}'; } //-------------------------------------------------------------------- @@ -608,7 +684,7 @@ public function keepFlashdata(string $key) * Mark a session property or properties as flashdata. * * @param $key Property identifier or array of them - * @return False if any of the properties are not already set + * @return false if any of the properties are not already set */ public function markAsFlashdata($key) { @@ -762,7 +838,7 @@ public function removeTempdata($key) * * @param $key Property identifier or array of them * @param int $ttl Time to live, in seconds - * @return bool False if any of the properties were not set + * @return bool false if any of the properties were not set */ public function markAsTempdata($key, $ttl = 300) { @@ -863,4 +939,4 @@ public function getTempKeys() } //-------------------------------------------------------------------- -} +} \ No newline at end of file From 7e07cb9f8de6dab4c5597bf0c74b60fdf111f1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Wed, 18 Jan 2017 08:06:50 +0300 Subject: [PATCH 0451/1807] session function comments fix --- system/Session/Session.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index d5ea9c5effe8..57d352141d76 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -9,7 +9,7 @@ * * This content is released under the MIT License (MIT) * - * Copyright (c) 2014 - 2016, British Columbia Institute of Technology + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,7 +31,7 @@ * * @package CodeIgniter * @author CodeIgniter Dev Team - * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) * @license http://opensource.org/licenses/MIT MIT License * @link http://codeigniter.com * @since Version 3.0.0 @@ -117,7 +117,7 @@ class Session implements SessionInterface /** * Whether to destroy session data associated with the old session ID - * when auto-regenerating the session ID. When set to false, the data + * when auto-regenerating the session ID. When set to FALSE, the data * will be later deleted by the garbage collector. * @var bool */ @@ -684,7 +684,7 @@ public function keepFlashdata(string $key) * Mark a session property or properties as flashdata. * * @param $key Property identifier or array of them - * @return false if any of the properties are not already set + * @return False if any of the properties are not already set */ public function markAsFlashdata($key) { From 0634ac6ded920af689d027f5058a561062f54336 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 17 Jan 2017 23:31:48 -0600 Subject: [PATCH 0452/1807] Started some tests for Session library --- system/Session/Handlers/BaseHandler.php | 2 +- system/Session/Handlers/FileHandler.php | 2 +- system/Session/Session.php | 121 ++++++----- tests/_support/Session/MockSession.php | 69 ++++++ tests/system/Session/SessionTest.php | 276 ++++++++++++++++++++++++ 5 files changed, 418 insertions(+), 52 deletions(-) create mode 100644 tests/_support/Session/MockSession.php create mode 100644 tests/system/Session/SessionTest.php diff --git a/system/Session/Handlers/BaseHandler.php b/system/Session/Handlers/BaseHandler.php index fa602f07fd69..327862327d1b 100644 --- a/system/Session/Handlers/BaseHandler.php +++ b/system/Session/Handlers/BaseHandler.php @@ -119,7 +119,7 @@ abstract class BaseHandler implements \SessionHandlerInterface * Constructor * @param BaseConfig $config */ - public function __construct(BaseConfig $config) + public function __construct($config) { $this->cookiePrefix = $config->cookiePrefix; $this->cookieDomain = $config->cookieDomain; diff --git a/system/Session/Handlers/FileHandler.php b/system/Session/Handlers/FileHandler.php index 1aca783052d7..1868cedc429a 100644 --- a/system/Session/Handlers/FileHandler.php +++ b/system/Session/Handlers/FileHandler.php @@ -77,7 +77,7 @@ class FileHandler extends BaseHandler implements \SessionHandlerInterface * Constructor * @param BaseConfig $config */ - public function __construct(BaseConfig $config) + public function __construct($config) { parent::__construct($config); diff --git a/system/Session/Session.php b/system/Session/Session.php index 875c59f088b7..1ccaf88d9a21 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -50,13 +50,6 @@ class Session implements SessionInterface use LoggerAwareTrait; - /** - * Userdata array. - * - * Just a reference to $_SESSION, for BC purposes. - */ - protected $userdata; - /** * Instance of the driver to use. * @@ -159,7 +152,7 @@ class Session implements SessionInterface * @param \SessionHandlerInterface $driver * @param \Config\App $config */ - public function __construct(\SessionHandlerInterface $driver, \Config\App $config) + public function __construct(\SessionHandlerInterface $driver, $config) { $this->driver = $driver; @@ -183,7 +176,7 @@ public function __construct(\SessionHandlerInterface $driver, \Config\App $confi */ public function start() { - if (is_cli()) + if (is_cli() && ENVIRONMENT !== 'testing') { $this->logger->debug('Session: Initialization under CLI aborted.'); @@ -204,9 +197,9 @@ public function start() $this->configure(); - session_set_save_handler($this->driver, true); + $this->setSaveHandler(); - // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers + // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers if (isset($_COOKIE[$this->sessionCookieName]) && ( ! is_string($_COOKIE[$this->sessionCookieName]) || ! preg_match('/^[0-9a-f]{40}$/', $_COOKIE[$this->sessionCookieName]) ) @@ -215,9 +208,9 @@ public function start() unset($_COOKIE[$this->sessionCookieName]); } - session_start(); + $this->startSession(); - // Is session ID auto-regeneration configured? (ignoring ajax requests) + // Is session ID auto-regeneration configured? (ignoring ajax requests) if ((empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') && ($regenerate_time = $this->sessionTimeToUpdate) > 0 ) @@ -235,16 +228,8 @@ public function start() // unless it is being currently created or regenerated elseif (isset($_COOKIE[$this->sessionCookieName]) && $_COOKIE[$this->sessionCookieName] === session_id()) { - setcookie( - $this->sessionCookieName, - session_id(), - (empty($this->sessionExpiration) ? 0 : time() + $this->sessionExpiration), - $this->cookiePath, - $this->cookieDomain, - $this->cookieSecure, - true - ); - } + $this->setCookie(); + } $this->initVars(); @@ -330,31 +315,31 @@ protected function configure() */ protected function initVars() { - if (! empty($_SESSION['__ci_vars'])) - { - $current_time = time(); - - foreach ($_SESSION['__ci_vars'] as $key => &$value) - { - if ($value === 'new') - { - $_SESSION['__ci_vars'][$key] = 'old'; - } - // Hacky, but 'old' will (implicitly) always be less than time() ;) - // DO NOT move this above the 'new' check! - elseif ($value < $current_time) - { - unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]); - } - } - - if (empty($_SESSION['__ci_vars'])) - { - unset($_SESSION['__ci_vars']); - } - } - - $this->userdata = & $_SESSION; + if (empty($_SESSION['__ci_vars'])) + { + return; + } + + $current_time = time(); + + foreach ($_SESSION['__ci_vars'] as $key => &$value) + { + if ($value === 'new') + { + $_SESSION['__ci_vars'][$key] = 'old'; + } + // Hacky, but 'old' will (implicitly) always be less than time() ;) + // DO NOT move this above the 'new' check! + elseif ($value < $current_time) + { + unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]); + } + } + + if (empty($_SESSION['__ci_vars'])) + { + unset($_SESSION['__ci_vars']); + } } //-------------------------------------------------------------------- @@ -445,7 +430,8 @@ public function get(string $key = null) ['__ci_vars'], $this->getFlashKeys(), $this->getTempKeys() ); - foreach (array_keys($_SESSION) as $key) + $keys = array_keys($_SESSION); + foreach ($keys as $key) { if (! in_array($key, $_exclude, true)) { @@ -862,5 +848,40 @@ public function getTempKeys() return $keys; } - //-------------------------------------------------------------------- + /** + * Sets the driver as the session handler in PHP. + * Extracted for easier testing. + */ + protected function setSaveHandler() + { + session_set_save_handler($this->driver, true); + } + + /** + * Starts the session. + * Extracted for testing reasons. + */ + protected function startSession() + { + session_start(); + } + + /** + * Takes care of setting the cookie on the client side. + * Extracted for testing reasons. + */ + protected function setCookie() + { + setcookie( + $this->sessionCookieName, + session_id(), + (empty($this->sessionExpiration) ? 0 : time()+$this->sessionExpiration), + $this->cookiePath, + $this->cookieDomain, + $this->cookieSecure, + true + ); + } + + //-------------------------------------------------------------------- } diff --git a/tests/_support/Session/MockSession.php b/tests/_support/Session/MockSession.php new file mode 100644 index 000000000000..28d589073148 --- /dev/null +++ b/tests/_support/Session/MockSession.php @@ -0,0 +1,69 @@ +driver, true); + } + + //-------------------------------------------------------------------- + + /** + * Starts the session. + * Extracted for testing reasons. + */ + protected function startSession() + { +// session_start(); + } + + //-------------------------------------------------------------------- + + /** + * Takes care of setting the cookie on the client side. + * Extracted for testing reasons. + */ + protected function setCookie() + { + $this->cookies[] = [ + $this->sessionCookieName, + session_id(), + (empty($this->sessionExpiration) ? 0 : time()+$this->sessionExpiration), + $this->cookiePath, + $this->cookieDomain, + $this->cookieSecure, + true + ]; + } + + //-------------------------------------------------------------------- + + public function regenerate(bool $destroy = false) + { + $this->didRegenerate = true; + $_SESSION['__ci_last_regenerate'] = time(); + } + + //-------------------------------------------------------------------- +} diff --git a/tests/system/Session/SessionTest.php b/tests/system/Session/SessionTest.php new file mode 100644 index 000000000000..16bacc2e99da --- /dev/null +++ b/tests/system/Session/SessionTest.php @@ -0,0 +1,276 @@ + 'CodeIgniter\Session\Handlers\FileHandler', + 'sessionCookieName' => 'ci_session', + 'sessionExpiration' => 7200, + 'sessionSavePath' => null, + 'sessionMatchIP' => false, + 'sessionTimeToUpdate' => 300, + 'sessionRegenerateDestroy' => false, + 'cookieDomain' => '', + 'cookiePrefix' => '', + 'cookiePath' => '/', + 'cookieSecure' => false, + ]; + + $config = array_merge($defaults, $options); + $config = (object)$config; + + $session = new MockSession(new FileHandler($config), $config); + $session->setLogger(new TestLogger(new Logger())); + + return $session; + } + + public function testSessionSetsRegenerateTime() + { + $session = $this->getInstance(); + $session->start(); + + $this->assertTrue(isset($_SESSION['__ci_last_regenerate']) && ! empty($_SESSION['__ci_last_regenerate'])); + } + + public function testWillRegenerateSessionAutomatically() + { + $session = $this->getInstance(); + + $time = time()-400; + $_SESSION['__ci_last_regenerate'] = $time; + $session->start(); + + $this->assertTrue($session->didRegenerate); + $this->assertTrue($_SESSION['__ci_last_regenerate'] > $time+90); + } + + public function testCanSetSingleValue() + { + $session = $this->getInstance(); + $session->start(); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $_SESSION['foo']); + } + + public function testCanSetArray() + { + $session = $this->getInstance(); + $session->start(); + + $session->set([ + 'foo' => 'bar', + 'bar' => 'baz' + ]); + + $this->assertEquals('bar', $_SESSION['foo']); + $this->assertEquals('baz', $_SESSION['bar']); + $this->assertFalse(isset($_SESSION['__ci_vars'])); + } + + public function testGetSimpleKey() + { + $session = $this->getInstance(); + $session->start(); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $session->get('foo')); + } + + public function testGetReturnsNullWhenNotFound() + { + $session = $this->getInstance(); + $session->start(); + + $this->assertNull($session->get('foo')); + } + + public function testGetAsProperty() + { + $session = $this->getInstance(); + $session->start(); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $session->foo); + } + + public function testGetAsNormal() + { + $session = $this->getInstance(); + $session->start(); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $_SESSION['foo']); + } + + public function testHasReturnsTrueOnSuccess() + { + $session = $this->getInstance(); + $session->start(); + + $_SESSION['foo'] = 'bar'; + + $this->assertTrue($session->has('foo')); + } + + public function testHasReturnsFalseOnNotFound() + { + $session = $this->getInstance(); + $session->start(); + + $_SESSION['foo'] = 'bar'; + + $this->assertFalse($session->has('bar')); + } + + public function testRemoveActuallyRemoves() + { + $session = $this->getInstance(); + $session->start(); + + $_SESSION['foo'] = 'bar'; + $session->remove('foo'); + + $this->assertFalse(isset($_SESSION['foo'])); + $this->assertFalse($session->has('foo')); + } + + public function testHasReturnsCanRemoveArray() + { + $session = $this->getInstance(); + $session->start(); + + $_SESSION = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $this->assertTrue($session->has('foo')); + + $session->remove(['foo', 'bar']); + + $this->assertFalse(isset($_SESSION['foo'])); + $this->assertFalse(isset($_SESSION['bar'])); + } + + public function testCanFlashData() + { + $session = $this->getInstance(); + $session->start(); + + $session->setFlashdata('foo', 'bar'); + + $this->assertTrue($session->has('foo')); + $this->assertEquals('new', $_SESSION['__ci_vars']['foo']); + + // Should reset the 'new' to 'old' + $session->start(); + + $this->assertTrue($session->has('foo')); + $this->assertEquals('old', $_SESSION['__ci_vars']['foo']); + + // Should no longer be available + $session->start(); + + $this->assertFalse($session->has('foo')); + } + + public function testCanFlashArray() + { + $session = $this->getInstance(); + $session->start(); + + $session->setFlashdata([ + 'foo' => 'bar', + 'bar' => 'baz' + ]); + + $this->assertTrue($session->has('foo')); + $this->assertEquals('new', $_SESSION['__ci_vars']['foo']); + $this->assertTrue($session->has('bar')); + $this->assertEquals('new', $_SESSION['__ci_vars']['bar']); + } + + public function testKeepFlashData() + { + $session = $this->getInstance(); + $session->start(); + + $session->setFlashdata('foo', 'bar'); + + $this->assertTrue($session->has('foo')); + $this->assertEquals('new', $_SESSION['__ci_vars']['foo']); + + // Should reset the 'new' to 'old' + $session->start(); + + $this->assertTrue($session->has('foo')); + $this->assertEquals('old', $_SESSION['__ci_vars']['foo']); + + $session->keepFlashdata('foo'); + + $this->assertEquals('new', $_SESSION['__ci_vars']['foo']); + + // Should no longer be available + $session->start(); + + $this->assertTrue($session->has('foo')); + $this->assertEquals('old', $_SESSION['__ci_vars']['foo']); + } + + public function testUnmarkFlashDataRemovesData() + { + $session = $this->getInstance(); + $session->start(); + + $session->setFlashdata('foo', 'bar'); + $session->set('bar', 'baz'); + + $this->assertTrue($session->has('foo')); + $this->assertTrue(isset($_SESSION['__ci_vars']['foo'])); + + $session->unmarkFlashdata('foo'); + + // Should still be here + $this->assertTrue($session->has('foo')); + // but no longer marked as flash + $this->assertFalse(isset($_SESSION['__ci_vars']['foo'])); + } + + public function testGetFlashKeysOnlyReturnsFlashKeys() + { + $session = $this->getInstance(); + $session->start(); + + $session->setFlashdata('foo', 'bar'); + $session->set('bar', 'baz'); + + $keys = $session->getFlashKeys(); + + $this->assertTrue(in_array('foo', $keys)); + $this->assertFalse(in_array('bar', $keys)); + } + +} From cebc5bca8f2c2f001700fae2865614f158c65700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Thu, 19 Jan 2017 00:02:17 +0300 Subject: [PATCH 0453/1807] php7.1 session key length fix --- system/Session/Session.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index b02576da00c7..8ecb142d8def 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -922,10 +922,8 @@ public function getTempKeys() return $keys; } -<<<<<<< HEAD //-------------------------------------------------------------------- -} -======= + /** * Sets the driver as the session handler in PHP. * Extracted for easier testing. @@ -962,5 +960,4 @@ protected function setCookie() } //-------------------------------------------------------------------- -} ->>>>>>> upstream/develop +} \ No newline at end of file From 1acaa67202af621cd7c69a0c91d95ef1872bde96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Thu, 19 Jan 2017 00:03:28 +0300 Subject: [PATCH 0454/1807] php7.1 session key length fix --- system/Session/Session.php.orig | 968 -------------------------------- 1 file changed, 968 deletions(-) delete mode 100644 system/Session/Session.php.orig diff --git a/system/Session/Session.php.orig b/system/Session/Session.php.orig deleted file mode 100644 index 65d9b09ce509..000000000000 --- a/system/Session/Session.php.orig +++ /dev/null @@ -1,968 +0,0 @@ -driver = $driver; - - $this->sessionDriverName = $config->sessionDriver; - $this->sessionCookieName = $config->sessionCookieName; - $this->sessionExpiration = $config->sessionExpiration; - $this->sessionSavePath = $config->sessionSavePath; - $this->sessionMatchIP = $config->sessionMatchIP; - $this->sessionTimeToUpdate = $config->sessionTimeToUpdate; - $this->sessionRegenerateDestroy = $config->sessionRegenerateDestroy; - - $this->cookieDomain = $config->cookieDomain; - $this->cookiePath = $config->cookiePath; - $this->cookieSecure = $config->cookieSecure; - } - - //-------------------------------------------------------------------- - - /** - * Initialize the session container and starts up the session. - */ - public function start() - { - if (is_cli() && ENVIRONMENT !== 'testing') - { - $this->logger->debug('Session: Initialization under CLI aborted.'); - - return; - } - elseif ((bool) ini_get('session.auto_start')) - { - $this->logger->error('Session: session.auto_start is enabled in php.ini. Aborting.'); - - return; - } - - if (! $this->driver instanceof \SessionHandlerInterface) - { - $this->logger->error("Session: Handler '".$this->driver. - "' doesn't implement SessionHandlerInterface. Aborting."); - } - - $this->configure(); - - $this->setSaveHandler(); - - // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers - if (isset($_COOKIE[$this->sessionCookieName]) && ( - ! is_string($_COOKIE[$this->sessionCookieName]) || ! preg_match('#\A'.$this->sidRegexp.'\z#', $_COOKIE[$this->sessionCookieName]) /*! preg_match('/^[0-9a-f]{40}$/', $_COOKIE[$this->sessionCookieName])*/ - ) - ) - { - unset($_COOKIE[$this->sessionCookieName]); - } - - $this->startSession(); - - // Is session ID auto-regeneration configured? (ignoring ajax requests) - if ((empty($_SERVER['HTTP_X_REQUESTED_WITH']) || - strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') && ($regenerate_time = $this->sessionTimeToUpdate) > 0 - ) - { - if (! isset($_SESSION['__ci_last_regenerate'])) - { - $_SESSION['__ci_last_regenerate'] = time(); - } - elseif ($_SESSION['__ci_last_regenerate'] < (time() - $regenerate_time)) - { - $this->regenerate((bool) $this->sessionRegenerateDestroy); - } - } - // Another work-around ... PHP doesn't seem to send the session cookie - // unless it is being currently created or regenerated - elseif (isset($_COOKIE[$this->sessionCookieName]) && $_COOKIE[$this->sessionCookieName] === session_id()) - { - $this->setCookie(); - } - - $this->initVars(); - - $this->logger->info("Session: Class initialized using '".$this->sessionDriverName."' driver."); - } - - //-------------------------------------------------------------------- - - /** - * Does a full stop of the session: - * - * - destroys the session - * - unsets the session id - * - destroys the session cookie - */ - public function stop() - { - setcookie( - $this->sessionCookieName, - session_id(), - 1, - $this->cookiePath, - $this->cookieDomain, - $this->cookieSecure, - true - ); - - session_regenerate_id(true); - } - - //-------------------------------------------------------------------- - - - /** - * Configuration. - * - * Handle input binds and configuration defaults. - */ - protected function configure() - { - if (empty($this->sessionCookieName)) - { - $this->sessionCookieName = ini_get('session.name'); - } - else - { - ini_set('session.name', $this->sessionCookieName); - } - - session_set_cookie_params( - $this->sessionExpiration, - $this->cookiePath, - $this->cookieDomain, - $this->cookieSecure, - true // HTTP only; Yes, this is intentional and not configurable for security reasons. - ); - - if (empty($this->sessionExpiration)) - { - $this->sessionExpiration = (int) ini_get('session.gc_maxlifetime'); - } - else - { - ini_set('session.gc_maxlifetime', (int) $this->sessionExpiration); - } - - // Security is king - ini_set('session.use_trans_sid', 0); - ini_set('session.use_strict_mode', 1); - ini_set('session.use_cookies', 1); - ini_set('session.use_only_cookies', 1); - // ini_set('session.hash_bits_per_character', 4); - - $this->configureSidLength(); - } - - // ------------------------------------------------------------------------ - - /** - * Configure session ID length - * - * To make life easier, we used to force SHA-1 and 4 bits per - * character on everyone. And of course, someone was unhappy. - * - * Then PHP 7.1 broke backwards-compatibility because ext/session - * is such a mess that nobody wants to touch it with a pole stick, - * and the one guy who does, nobody has the energy to argue with. - * - * So we were forced to make changes, and OF COURSE something was - * going to break and now we have this pile of shit. -- Narf - * - * @return void - */ - protected function configureSidLength() - { - if (PHP_VERSION_ID < 70100) - { - $hash_function = ini_get('session.hash_function'); - if (ctype_digit($hash_function)) - { - if ($hash_function !== '1') - { - ini_set('session.hash_function', 1); - $bits = 160; - } - } - elseif ( ! in_array($hash_function, hash_algos(), true)) - { - ini_set('session.hash_function', 1); - $bits = 160; - } - elseif (($bits = strlen(hash($hash_function, 'dummy', false)) * 4) < 160) - { - ini_set('session.hash_function', 1); - $bits = 160; - } - - $bits_per_character = (int) ini_get('session.hash_bits_per_character'); - $sid_length = (int) ceil($bits / $bits_per_character); - } - else - { - $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) - { - // Add as many more characters as necessary to reach at least 160 bits - $sid_length += (int) ceil((160 % $bits) / $bits_per_character); - ini_set('session.sid_length', $sid_length); - } - } - - // Yes, 4,5,6 are the only known possible values as of 2016-10-27 - switch ($bits_per_character) - { - case 4: - $this->sidRegexp = '[0-9a-f]'; - break; - case 5: - $this->sidRegexp = '[0-9a-v]'; - break; - case 6: - $this->sidRegexp = '[0-9a-zA-Z,-]'; - break; - } - - $this->sidRegexp .= '{'.$sid_length.'}'; - } - - //-------------------------------------------------------------------- - - /** - * Handle temporary variables - * - * Clears old "flash" data, marks the new one for deletion and handles - * "temp" data deletion. - */ - protected function initVars() - { - if (empty($_SESSION['__ci_vars'])) - { - return; - } - - $current_time = time(); - - foreach ($_SESSION['__ci_vars'] as $key => &$value) - { - if ($value === 'new') - { - $_SESSION['__ci_vars'][$key] = 'old'; - } - // Hacky, but 'old' will (implicitly) always be less than time() ;) - // DO NOT move this above the 'new' check! - elseif ($value < $current_time) - { - unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]); - } - } - - if (empty($_SESSION['__ci_vars'])) - { - unset($_SESSION['__ci_vars']); - } - } - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - // Session Utility Methods - //-------------------------------------------------------------------- - - /** - * Regenerates the session ID. - * - * @param bool $destroy Should old session data be destroyed? - */ - public function regenerate(bool $destroy = false) - { - $_SESSION['__ci_last_regenerate'] = time(); - session_regenerate_id($destroy); - } - - //-------------------------------------------------------------------- - - /** - * Destroys the current session. - */ - public function destroy() - { - session_destroy(); - } - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - // Basic Setters and Getters - //-------------------------------------------------------------------- - - /** - * Sets user data into the session. - * - * If $data is a string, then it is interpreted as a session property - * key, and $value is expected to be non-null. - * - * If $data is an array, it is expected to be an array of key/value pairs - * to be set as session properties. - * - * @param $data Property name or associative array of properties - * @param null $value Property value if single key provided - */ - public function set($data, $value = null) - { - if (is_array($data)) - { - foreach ($data as $key => &$value) - { - $_SESSION[$key] = $value; - } - - return; - } - - $_SESSION[$data] = $value; - } - - //-------------------------------------------------------------------- - - /** - * Get user data that has been set in the session. - * - * If the property exists as "normal", returns it. - * Otherwise, returns an array of any temp or flash data values with the - * property key. - * - * Replaces the legacy method $session->userdata(); - * - * @param $key Identifier of the session property to retrieve - * @return array|null The property value(s) - */ - public function get(string $key = null) - { - if (isset($key)) - { - return isset($_SESSION[$key]) ? $_SESSION[$key] : null; - } - elseif (empty($_SESSION)) - { - return []; - } - - $userdata = []; - $_exclude = array_merge( - ['__ci_vars'], $this->getFlashKeys(), $this->getTempKeys() - ); - - $keys = array_keys($_SESSION); - foreach ($keys as $key) - { - if (! in_array($key, $_exclude, true)) - { - $userdata[$key] = $_SESSION[$key]; - } - } - - return $userdata; - } - - //-------------------------------------------------------------------- - - /** - * Returns whether an index exists in the session array. - * - * @param string $key Identifier of the session property we are interested in. - * - * @return bool - */ - public function has(string $key) - { - return isset($_SESSION[$key]); - } - - //-------------------------------------------------------------------- - - /** - * Remove one or more session properties. - * - * If $key is an array, it is interpreted as an array of string property - * identifiers to remove. Otherwise, it is interpreted as the identifier - * of a specific session property to remove. - * - * @param $key Identifier of the session property or properties to remove. - */ - public function remove($key) - { - if (is_array($key)) - { - foreach ($key as $k) - { - unset($_SESSION[$k]); - } - - return; - } - - unset($_SESSION[$key]); - } - - //-------------------------------------------------------------------- - - /** - * Magic method to set variables in the session by simply calling - * $session->foo = bar; - * - * @param $key Identifier of the session property to set. - * @param $value - */ - public function __set($key, $value) - { - $_SESSION[$key] = $value; - } - - //-------------------------------------------------------------------- - - /** - * Magic method to get session variables by simply calling - * $foo = $session->foo; - * - * @param $key Identifier of the session property to remove. - * - * @return null|string - */ - public function __get($key) - { - // Note: Keep this order the same, just in case somebody wants to - // use 'session_id' as a session data key, for whatever reason - if (isset($_SESSION[$key])) - { - return $_SESSION[$key]; - } - elseif ($key === 'session_id') - { - return session_id(); - } - - return null; - } - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - // Flash Data Methods - //-------------------------------------------------------------------- - - /** - * Sets data into the session that will only last for a single request. - * Perfect for use with single-use status update messages. - * - * If $data is an array, it is interpreted as an associative array of - * key/value pairs for flashdata properties. - * Otherwise, it is interpreted as the identifier of a specific - * flashdata property, with $value containing the property value. - * - * @param $data Property identifier or associative array of properties - * @param null $value Property value if $data is a scalar - */ - public function setFlashdata($data, $value = null) - { - $this->set($data, $value); - $this->markAsFlashdata(is_array($data) ? array_keys($data) : $data); - } - - //-------------------------------------------------------------------- - - /** - * Retrieve one or more items of flash data from the session. - * - * If the item key is null, return all flashdata. - * - * @param string $key Property identifier - * @return array|null The requested property value, or an associative array of them - */ - public function getFlashdata(string $key = null) - { - if (isset($key)) - { - return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key]) && - ! is_int($_SESSION['__ci_vars'][$key])) ? $_SESSION[$key] : null; - } - - $flashdata = []; - - if (! empty($_SESSION['__ci_vars'])) - { - foreach ($_SESSION['__ci_vars'] as $key => &$value) - { - is_int($value) OR $flashdata[$key] = $_SESSION[$key]; - } - } - - return $flashdata; - } - - //-------------------------------------------------------------------- - - /** - * Keeps a single piece of flash data alive for one more request. - * - * @param string $key Property identifier or array of them - */ - public function keepFlashdata(string $key) - { - $this->markAsFlashdata($key); - } - - //-------------------------------------------------------------------- - - /** - * Mark a session property or properties as flashdata. - * - * @param $key Property identifier or array of them - * @return False if any of the properties are not already set - */ - public function markAsFlashdata($key) - { - if (is_array($key)) - { - for ($i = 0, $c = count($key); $i < $c; $i++) - { - if (! isset($_SESSION[$key[$i]])) - { - return false; - } - } - - $new = array_fill_keys($key, 'new'); - - $_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars']) ? array_merge($_SESSION['__ci_vars'], $new) : $new; - - return true; - } - - if (! isset($_SESSION[$key])) - { - return false; - } - - $_SESSION['__ci_vars'][$key] = 'new'; - - return true; - } - - //-------------------------------------------------------------------- - - /** - * Unmark data in the session as flashdata. - * - * @param mixed $key Property identifier or array of them - */ - public function unmarkFlashdata($key) - { - if (empty($_SESSION['__ci_vars'])) - { - return; - } - - is_array($key) OR $key = [$key]; - - foreach ($key as $k) - { - if (isset($_SESSION['__ci_vars'][$k]) && ! is_int($_SESSION['__ci_vars'][$k])) - { - unset($_SESSION['__ci_vars'][$k]); - } - } - - if (empty($_SESSION['__ci_vars'])) - { - unset($_SESSION['__ci_vars']); - } - } - - //-------------------------------------------------------------------- - - /** - * Retrieve all of the keys for session data marked as flashdata. - * - * @return array The property names of all flashdata - */ - public function getFlashKeys() - { - if (! isset($_SESSION['__ci_vars'])) - { - return []; - } - - $keys = []; - foreach (array_keys($_SESSION['__ci_vars']) as $key) - { - is_int($_SESSION['__ci_vars'][$key]) OR $keys[] = $key; - } - - return $keys; - } - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - // Temp Data Methods - //-------------------------------------------------------------------- - - /** - * Sets new data into the session, and marks it as temporary data - * with a set lifespan. - * - * @param $data Session data key or associative array of items - * @param null $value Value to store - * @param int $ttl Time-to-live in seconds - */ - public function setTempdata($data, $value = null, $ttl = 300) - { - $this->set($data, $value); - $this->markAsTempdata(is_array($data) ? array_keys($data) : $data, $ttl); - } - - //-------------------------------------------------------------------- - - /** - * Returns either a single piece of tempdata, or all temp data currently - * in the session. - * - * @param $key Session data key - * @return mixed Session data value or null if not found. - */ - public function getTempdata($key = null) - { - if (isset($key)) - { - return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key]) && - is_int($_SESSION['__ci_vars'][$key])) ? $_SESSION[$key] : null; - } - - $tempdata = []; - - if (! empty($_SESSION['__ci_vars'])) - { - foreach ($_SESSION['__ci_vars'] as $key => &$value) - { - is_int($value) && $tempdata[$key] = $_SESSION[$key]; - } - } - - return $tempdata; - } - - //-------------------------------------------------------------------- - - /** - * Removes a single piece of temporary data from the session. - * - * @param string $key Session data key - */ - public function removeTempdata($key) - { - $this->unmarkTempdata($key); - unset($_SESSION[$key]); - } - - //-------------------------------------------------------------------- - - /** - * Mark one of more pieces of data as being temporary, meaning that - * it has a set lifespan within the session. - * - * @param $key Property identifier or array of them - * @param int $ttl Time to live, in seconds - * @return bool false if any of the properties were not set - */ - public function markAsTempdata($key, $ttl = 300) - { - $ttl += time(); - - if (is_array($key)) - { - $temp = []; - - foreach ($key as $k => $v) - { - // Do we have a key => ttl pair, or just a key? - if (is_int($k)) - { - $k = $v; - $v = $ttl; - } - else - { - $v += time(); - } - - if (! isset($_SESSION[$k])) - { - return false; - } - - $temp[$k] = $v; - } - - $_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars']) ? array_merge($_SESSION['__ci_vars'], $temp) : $temp; - - return true; - } - - if (! isset($_SESSION[$key])) - { - return false; - } - - $_SESSION['__ci_vars'][$key] = $ttl; - - return true; - } - - //-------------------------------------------------------------------- - - /** - * Unmarks temporary data in the session, effectively removing its - * lifespan and allowing it to live as long as the session does. - * - * @param $key Property identifier or array of them - */ - public function unmarkTempdata($key) - { - if (empty($_SESSION['__ci_vars'])) - { - return; - } - - is_array($key) OR $key = [$key]; - - foreach ($key as $k) - { - if (isset($_SESSION['__ci_vars'][$k]) && is_int($_SESSION['__ci_vars'][$k])) - { - unset($_SESSION['__ci_vars'][$k]); - } - } - - if (empty($_SESSION['__ci_vars'])) - { - unset($_SESSION['__ci_vars']); - } - } - - //-------------------------------------------------------------------- - - /** - * Retrieve the keys of all session data that have been marked as temporary data. - * - * @return array - */ - public function getTempKeys() - { - if (! isset($_SESSION['__ci_vars'])) - { - return []; - } - - $keys = []; - foreach (array_keys($_SESSION['__ci_vars']) as $key) - { - is_int($_SESSION['__ci_vars'][$key]) && $keys[] = $key; - } - - return $keys; - } - -<<<<<<< HEAD - //-------------------------------------------------------------------- -} -======= - /** - * Sets the driver as the session handler in PHP. - * Extracted for easier testing. - */ - protected function setSaveHandler() - { - session_set_save_handler($this->driver, true); - } - - /** - * Starts the session. - * Extracted for testing reasons. - */ - protected function startSession() - { - session_start(); - } - - /** - * Takes care of setting the cookie on the client side. - * Extracted for testing reasons. - */ - protected function setCookie() - { - setcookie( - $this->sessionCookieName, - session_id(), - (empty($this->sessionExpiration) ? 0 : time()+$this->sessionExpiration), - $this->cookiePath, - $this->cookieDomain, - $this->cookieSecure, - true - ); - } - - //-------------------------------------------------------------------- -} ->>>>>>> upstream/develop From 5beecd78e1d570f46ec3f619feb2811881c2fbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Thu, 19 Jan 2017 00:17:18 +0300 Subject: [PATCH 0455/1807] php7.1 session key length fix --- system/Session/Session.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index 8ecb142d8def..3b3b4c7e7d9c 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -201,9 +201,10 @@ public function start() $this->setSaveHandler(); - // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers + // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers if (isset($_COOKIE[$this->sessionCookieName]) && ( - ! is_string($_COOKIE[$this->sessionCookieName]) || ! preg_match('#\A'.$this->sidRegexp.'\z#', $_COOKIE[$this->sessionCookieName])) + ! is_string($_COOKIE[$this->sessionCookieName]) || ! preg_match('#\A'.$this->sidRegexp.'\z#', $_COOKIE[$this->sessionCookieName]) + ) ) { unset($_COOKIE[$this->sessionCookieName]); @@ -302,10 +303,10 @@ protected function configure() ini_set('session.use_strict_mode', 1); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); - + $this->configureSidLength(); } - + // ------------------------------------------------------------------------ /** @@ -822,7 +823,7 @@ public function removeTempdata($key) * * @param $key Property identifier or array of them * @param int $ttl Time to live, in seconds - * @return bool false if any of the properties were not set + * @return bool False if any of the properties were not set */ public function markAsTempdata($key, $ttl = 300) { @@ -922,8 +923,6 @@ public function getTempKeys() return $keys; } - //-------------------------------------------------------------------- - /** * Sets the driver as the session handler in PHP. * Extracted for easier testing. From c198b0651dd8eca1e92805b948d913e6cadae1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Thu, 19 Jan 2017 00:32:20 +0300 Subject: [PATCH 0456/1807] php7.1 session key length fix --- system/Session/Session.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index 3b3b4c7e7d9c..06d2d4e04526 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -355,8 +355,9 @@ protected function configureSidLength() { $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 (($sid_length * $bits_per_character) < 160) { + $bits = ($sid_length * $bits_per_character); // Add as many more characters as necessary to reach at least 160 bits $sid_length += (int) ceil((160 % $bits) / $bits_per_character); ini_set('session.sid_length', $sid_length); From cab349ea13f8f1eb7708cc697d265d30f6fd66d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Thu, 19 Jan 2017 01:29:56 +0300 Subject: [PATCH 0457/1807] php7.1 session key length fix --- system/Session/Session.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Session/Session.php b/system/Session/Session.php index 06d2d4e04526..a874e39e4a74 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -328,6 +328,7 @@ protected function configureSidLength() { if (PHP_VERSION_ID < 70100) { + $bits = 160; $hash_function = ini_get('session.hash_function'); if (ctype_digit($hash_function)) { From bb8eb3550719ebac31284fa27fe1626b3477b521 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 18 Jan 2017 23:17:11 -0600 Subject: [PATCH 0458/1807] More Session tests. Fixing up tempdata logic. --- system/Session/Session.php | 15 ++- tests/system/Session/SessionTest.php | 135 +++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/system/Session/Session.php b/system/Session/Session.php index a874e39e4a74..87d9e723d1fe 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -468,7 +468,14 @@ public function set($data, $value = null) { foreach ($data as $key => &$value) { - $_SESSION[$key] = $value; + if (is_int($key)) + { + $_SESSION[$value] = null; + } + else + { + $_SESSION[$key] = $value; + } } return; @@ -771,7 +778,7 @@ public function getFlashKeys() public function setTempdata($data, $value = null, $ttl = 300) { $this->set($data, $value); - $this->markAsTempdata(is_array($data) ? array_keys($data) : $data, $ttl); + $this->markAsTempdata($data, $ttl); } //-------------------------------------------------------------------- @@ -848,7 +855,7 @@ public function markAsTempdata($key, $ttl = 300) $v += time(); } - if (! isset($_SESSION[$k])) + if (! array_key_exists($k, $_SESSION)) { return false; } @@ -961,4 +968,4 @@ protected function setCookie() } //-------------------------------------------------------------------- -} \ No newline at end of file +} diff --git a/tests/system/Session/SessionTest.php b/tests/system/Session/SessionTest.php index 16bacc2e99da..adf10c789616 100644 --- a/tests/system/Session/SessionTest.php +++ b/tests/system/Session/SessionTest.php @@ -273,4 +273,139 @@ public function testGetFlashKeysOnlyReturnsFlashKeys() $this->assertFalse(in_array('bar', $keys)); } + public function testSetTempDataWorks() + { + $session = $this->getInstance(); + $session->start(); + + $session->setTempdata('foo', 'bar', 300); + $this->assertTrue((time() + 300) >= $_SESSION['__ci_vars']['foo']); + } + + public function testSetTempDataArrayMultiTTL() + { + $session = $this->getInstance(); + $session->start(); + + $time = time(); + + $session->setTempdata([ + 'foo' => 300, + 'bar' => 400, + 'baz' => 100 + ]); + + $this->assertTrue(($time + 300) <= $_SESSION['__ci_vars']['foo']); + $this->assertTrue(($time + 400) <= $_SESSION['__ci_vars']['bar']); + $this->assertTrue(($time + 100) <= $_SESSION['__ci_vars']['baz']); + } + + public function testSetTempDataArraySingleTTL() + { + $session = $this->getInstance(); + $session->start(); + + $time = time(); + + $session->setTempdata(['foo', 'bar', 'baz'], null, 200); + + $this->assertTrue(($time + 200) <= $_SESSION['__ci_vars']['foo']); + $this->assertTrue(($time + 200) <= $_SESSION['__ci_vars']['bar']); + $this->assertTrue(($time + 200) <= $_SESSION['__ci_vars']['baz']); + } + + public function testGetTestDataReturnsAll() + { + $session = $this->getInstance(); + $session->start(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $session->setTempdata($data); + $session->set('baz', 'ballywhoo'); + + $this->assertEquals($data, $session->getTempdata()); + } + + public function testGetTestDataReturnsSingle() + { + $session = $this->getInstance(); + $session->start(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $session->setTempdata($data); + + $this->assertEquals('bar', $session->getTempdata('foo')); + } + + public function testRemoveTempDataActuallyDeletes() + { + $session = $this->getInstance(); + $session->start(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $session->setTempdata($data); + $session->removeTempdata('foo'); + + $this->assertEquals(['bar' => 'baz'], $session->getTempdata()); + } + + public function testUnMarkTempDataSingle() + { + $session = $this->getInstance(); + $session->start(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $session->setTempdata($data); + $session->unmarkTempdata('foo'); + + $this->assertEquals(['bar' => 'baz'], $session->getTempdata()); + } + + public function testUnMarkTempDataArray() + { + $session = $this->getInstance(); + $session->start(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $session->setTempdata($data); + $session->unmarkTempdata(['foo', 'bar']); + + $this->assertEquals([], $session->getTempdata()); + } + + public function testGetTempdataKeys() + { + $session = $this->getInstance(); + $session->start(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz' + ]; + + $session->setTempdata($data); + $session->set('baz', 'ballywhoo'); + + $this->assertEquals(['foo', 'bar'], $session->getTempKeys()); + } } From 5f13ed27ebb085bbc7fcc07c0e93685d57e59615 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 18 Jan 2017 23:20:17 -0600 Subject: [PATCH 0459/1807] [ci skip] pretty comment --- system/Session/Session.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/system/Session/Session.php b/system/Session/Session.php index 87d9e723d1fe..9a4c701f4fbb 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -932,6 +932,8 @@ public function getTempKeys() return $keys; } + //-------------------------------------------------------------------- + /** * Sets the driver as the session handler in PHP. * Extracted for easier testing. @@ -941,6 +943,8 @@ protected function setSaveHandler() session_set_save_handler($this->driver, true); } + //-------------------------------------------------------------------- + /** * Starts the session. * Extracted for testing reasons. @@ -950,6 +954,8 @@ protected function startSession() session_start(); } + //-------------------------------------------------------------------- + /** * Takes care of setting the cookie on the client side. * Extracted for testing reasons. From c8d54363e539be56ebed7a239f787c3947a60449 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 18 Jan 2017 23:32:12 -0600 Subject: [PATCH 0460/1807] Session tempdata fix for 7.1 --- system/Session/Session.php | 4 ++++ tests/system/Session/SessionTest.php | 3 +++ 2 files changed, 7 insertions(+) diff --git a/system/Session/Session.php b/system/Session/Session.php index 9a4c701f4fbb..aa25762d5228 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -850,6 +850,10 @@ public function markAsTempdata($key, $ttl = 300) $k = $v; $v = $ttl; } + elseif (is_string($v)) + { + $v = time() + $ttl; + } else { $v += time(); diff --git a/tests/system/Session/SessionTest.php b/tests/system/Session/SessionTest.php index adf10c789616..c06d123a7e3e 100644 --- a/tests/system/Session/SessionTest.php +++ b/tests/system/Session/SessionTest.php @@ -314,6 +314,9 @@ public function testSetTempDataArraySingleTTL() $this->assertTrue(($time + 200) <= $_SESSION['__ci_vars']['baz']); } + /** + * @group single + */ public function testGetTestDataReturnsAll() { $session = $this->getInstance(); From 308d993fc744e8188f885c845e90049f702af343 Mon Sep 17 00:00:00 2001 From: TAKEKOSHI Akishige Date: Thu, 19 Jan 2017 21:55:14 +0900 Subject: [PATCH 0461/1807] avoid PHP bug: use Config\Services --- system/CodeIgniter.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index e8cc55358244..3506ebbd6d61 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -1,7 +1,6 @@ config->appTimezone ?? 'UTC'); // Setup Exception Handling - Services::exceptions($this->config, true) + Config\Services::exceptions($this->config, true) ->initialize(); $this->detectEnvironment(); @@ -164,7 +163,7 @@ public function run(RouteCollectionInterface $routes = null) } catch (Router\RedirectException $e) { - $logger = Services::logger(); + $logger = Config\Services::logger(); $logger->info('REDIRECTED ROUTE at '.$e->getMessage()); // If the route is a 'redirect' route, it throws @@ -196,7 +195,7 @@ protected function handleRequest(RouteCollectionInterface $routes = null, $cache $this->tryToRouteIt($routes); // Run "before" filters - $filters = Services::filters(); + $filters = Config\Services::filters(); $uri = $this->request instanceof CLIRequest ? $this->request->getPath() : $this->request->uri->getPath(); @@ -318,7 +317,7 @@ protected function startBenchmark() { $this->startTime = microtime(true); - $this->benchmark = Services::timer(); + $this->benchmark = Config\Services::timer(); $this->benchmark->start('total_execution', $this->startTime); $this->benchmark->start('bootstrap'); } @@ -334,11 +333,11 @@ protected function getRequestObject() { if (is_cli()) { - $this->request = Services::clirequest($this->config); + $this->request = Config\Services::clirequest($this->config); } else { - $this->request = Services::request($this->config); + $this->request = Config\Services::request($this->config); $this->request->setProtocolVersion($_SERVER['SERVER_PROTOCOL']); } } @@ -351,7 +350,7 @@ protected function getRequestObject() */ protected function getResponseObject() { - $this->response = Services::response($this->config); + $this->response = Config\Services::response($this->config); if ( ! is_cli()) { @@ -551,7 +550,7 @@ protected function tryToRouteIt(RouteCollectionInterface $routes = null) } // $routes is defined in Config/Routes.php - $this->router = Services::router($routes); + $this->router = Config\Services::router($routes); $path = $this->determinePath(); From 79471c52c701e035ba06d075d08e07fbdbfc7ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Fri, 20 Jan 2017 03:32:15 +0300 Subject: [PATCH 0462/1807] Session RedisHandler lockSession, write fingerprint fix. --- system/Session/Handlers/RedisHandler.php | 33 +++++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/system/Session/Handlers/RedisHandler.php b/system/Session/Handlers/RedisHandler.php index 9e8513a0e7cd..d2a6a73d2653 100644 --- a/system/Session/Handlers/RedisHandler.php +++ b/system/Session/Handlers/RedisHandler.php @@ -65,6 +65,13 @@ class RedisHandler extends BaseHandler implements \SessionHandlerInterface */ protected $lockKey; + /** + * Key exists flag + * + * @var bool + */ + protected $keyExists = FALSE; + /** * Number of seconds until the session ends. * @@ -174,9 +181,12 @@ public function read($sessionID) // Needed by write() to detect session_regenerate_id() calls $this->sessionID = $sessionID; - $session_data = (string) $this->redis->get($this->keyPrefix.$sessionID); - $this->fingerprint = md5($session_data); + $session_data = $this->redis->get($this->keyPrefix.$sessionID); + is_string($session_data) + ? $this->keyExists = TRUE + : $session_data = ''; + $this->fingerprint = md5($session_data); return $session_data; } @@ -209,19 +219,20 @@ public function write($sessionID, $sessionData) return FALSE; } - $this->fingerprint = md5(''); - $this->sessionID = $sessionID; + $this->keyExists = FALSE; + $this->sessionID = $sessionID; } if (isset($this->lockKey)) { $this->redis->setTimeout($this->lockKey, 300); - if ($this->fingerprint !== ($fingerprint = md5($sessionData))) + if ($this->fingerprint !== ($fingerprint = md5($sessionData)) OR $this->keyExists === FALSE) { if ($this->redis->set($this->keyPrefix.$sessionID, $sessionData, $this->sessionExpiration)) { $this->fingerprint = $fingerprint; + $this->keyExists = TRUE; return TRUE; } @@ -281,11 +292,11 @@ public function close() * @param string $session_id Session ID * @return bool */ - public function destroy($session_id) + public function destroy($sessionID) { if (isset($this->redis, $this->lockKey)) { - if (($result = $this->redis->delete($this->keyPrefix.$session_id)) !== 1) + if (($result = $this->redis->delete($this->keyPrefix.$sessionID)) !== 1) { $this->logger->debug('Session: Redis::delete() expected to return 1, got '.var_export($result, TRUE).' instead.'); } @@ -325,7 +336,10 @@ public function gc($maxlifetime) */ protected function lockSession(string $sessionID): bool { - if (isset($this->lockKey)) + // PHP 7 reuses the SessionHandler object on regeneration, + // so we need to check here if the lock key is for the + // correct session ID. + if ($this->lockKey === $this->keyPrefix.$sessionID.':lock') { return $this->redis->setTimeout($this->lockKey, 300); } @@ -333,7 +347,6 @@ protected function lockSession(string $sessionID): bool // 30 attempts to obtain a lock, in case another request already has it $lock_key = $this->keyPrefix.$sessionID.':lock'; $attempt = 0; - do { if (($ttl = $this->redis->ttl($lock_key)) > 0) @@ -395,4 +408,4 @@ protected function releaseLock(): bool //-------------------------------------------------------------------- -} +} \ No newline at end of file From fd2961b33d2835e1fbf82309438819c3b5c3cec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C3=87etin?= Date: Fri, 20 Jan 2017 03:48:27 +0300 Subject: [PATCH 0463/1807] Session RedisHandler lockSession, write fingerprint fix. --- system/Session/Handlers/RedisHandler.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/Session/Handlers/RedisHandler.php b/system/Session/Handlers/RedisHandler.php index d2a6a73d2653..17a5f2730de1 100644 --- a/system/Session/Handlers/RedisHandler.php +++ b/system/Session/Handlers/RedisHandler.php @@ -227,7 +227,7 @@ public function write($sessionID, $sessionData) { $this->redis->setTimeout($this->lockKey, 300); - if ($this->fingerprint !== ($fingerprint = md5($sessionData)) OR $this->keyExists === FALSE) + if ($this->fingerprint !== ($fingerprint = md5($sessionData)) || $this->keyExists === FALSE) { if ($this->redis->set($this->keyPrefix.$sessionID, $sessionData, $this->sessionExpiration)) { @@ -347,6 +347,7 @@ protected function lockSession(string $sessionID): bool // 30 attempts to obtain a lock, in case another request already has it $lock_key = $this->keyPrefix.$sessionID.':lock'; $attempt = 0; + do { if (($ttl = $this->redis->ttl($lock_key)) > 0) @@ -408,4 +409,4 @@ protected function releaseLock(): bool //-------------------------------------------------------------------- -} \ No newline at end of file +} From 1019ab0882df726355e58523d8a43a6d18b22685 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 20 Jan 2017 08:54:33 +0700 Subject: [PATCH 0464/1807] use setScheme() and fixes undefined property "parts" on HTTP\URI::applyParts() --- system/HTTP/URI.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 05c407ea8915..1b52e7e2a47e 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -932,11 +932,10 @@ protected function applyParts($parts) // Scheme if (isset($parts['scheme'])) { - $this->scheme = rtrim(strtolower($parts['scheme']), ':/'); + $this->setScheme(rtrim(strtolower($parts['scheme']), ':/')); } else { - $this->parts['scheme'] = 'http://'; $this->setScheme('http'); } From 89337187fe30dfdd8bb1fb9677d28270d1000407 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 20 Jan 2017 08:59:45 +0700 Subject: [PATCH 0465/1807] remove unused $uri parameter in HTTP\Request::__construct() --- system/HTTP/Request.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index ade76f91ef16..121b6415ea76 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -63,9 +63,8 @@ class Request extends Message implements RequestInterface * Constructor. * * @param type $config - * @param type $uri */ - public function __construct($config, $uri=null) + public function __construct($config) { $this->proxyIPs = $config->proxyIPs; } From 1df323e1bd884a701ddd97892fbcf0d292fc0457 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 20 Jan 2017 09:14:32 +0700 Subject: [PATCH 0466/1807] set default $text parameter value as "" in CLI::write() --- system/CLI/CLI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index bf55c8e9bafe..9e33bdc50919 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -288,7 +288,7 @@ public static function prompt(): string * @param string $foreground * @param string $background */ - public static function write(string $text, string $foreground = null, string $background = null) + public static function write(string $text = '', string $foreground = null, string $background = null) { if ($foreground || $background) { From 4b2fd3243f48ca1e09d92157ffe91155a4822f01 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 20 Jan 2017 09:29:07 +0700 Subject: [PATCH 0467/1807] testWait() --- tests/system/CLI/CLITest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/system/CLI/CLITest.php b/tests/system/CLI/CLITest.php index f1fc4307d69c..edf3fbbdfb9b 100644 --- a/tests/system/CLI/CLITest.php +++ b/tests/system/CLI/CLITest.php @@ -63,6 +63,11 @@ public function testShowProgressWithoutBar() EOT; $this->assertEquals($expected, CLITestStreamFilter::$buffer); } + + public function testWait() + { + CLI::wait(1, true); + } } From 59c5420e91f4bf3de0b2ce465d75f04a878d9b3f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 20 Jan 2017 09:31:09 +0700 Subject: [PATCH 0468/1807] exclude system/Config/Routes in phpunit.xml --- phpunit.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 980e12b8617e..c0b4ffefc64b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -22,6 +22,7 @@ ./system/bootstrap.php ./system/Commands/Sessions/Views/migration.tpl.php ./system/ComposerScripts.php + ./system/Config/Routes.php ./system/Debug/Toolbar/Views ./system/Pager/Views ./system/ThirdParty From 62e9fc53ae2cdb81d994c3354c873d95716ab0df Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 19 Jan 2017 21:45:08 -0600 Subject: [PATCH 0469/1807] Fixing missing fileNew determination in Session FileHandler. Fixes #375 --- system/Session/Handlers/FileHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Session/Handlers/FileHandler.php b/system/Session/Handlers/FileHandler.php index 1868cedc429a..bba5bcc0af47 100644 --- a/system/Session/Handlers/FileHandler.php +++ b/system/Session/Handlers/FileHandler.php @@ -146,6 +146,7 @@ public function read($sessionID) // which re-reads session data if ($this->fileHandle === null) { + $this->fileNew = ! file_exists($this->filePath.$sessionID); if (($this->fileHandle = fopen($this->filePath.$sessionID, 'c+b')) === false) { From 8ddd356f0b0a397b69046effb00e97e603e636f5 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 19 Jan 2017 22:02:16 -0600 Subject: [PATCH 0470/1807] Cleaning up use of , and making getQueryString use the query object. Fixes #379 --- system/Database/BasePreparedQuery.php | 7 ++++++- system/Database/MySQLi/PreparedQuery.php | 4 ++-- system/Database/Postgre/PreparedQuery.php | 4 ++-- tests/system/Database/Live/PreparedQueryTest.php | 3 --- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php index 4c19a962d8d4..cdabc0e205a5 100644 --- a/system/Database/BasePreparedQuery.php +++ b/system/Database/BasePreparedQuery.php @@ -172,7 +172,12 @@ public function close() */ public function getQueryString(): string { - return $this->sql; + if (! $this->query instanceof QueryInterface) + { + throw new \BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.'); + } + + return $this->query->getQuery(); } //-------------------------------------------------------------------- diff --git a/system/Database/MySQLi/PreparedQuery.php b/system/Database/MySQLi/PreparedQuery.php index 88a1e171d8b6..f3dc1d69257d 100644 --- a/system/Database/MySQLi/PreparedQuery.php +++ b/system/Database/MySQLi/PreparedQuery.php @@ -22,9 +22,9 @@ public function _prepare(string $sql, array $options = []) { // Mysqli driver doesn't like statements // with terminating semicolons. - $this->sql = rtrim($sql, ';'); + $sql = rtrim($sql, ';'); - if (! $this->statement = $this->db->mysqli->prepare($this->sql)) + if (! $this->statement = $this->db->mysqli->prepare($sql)) { $this->errorCode = $this->db->mysqli->errno; $this->errorString = $this->db->mysqli->error; diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php index ee2aa4374f0b..122129c99914 100644 --- a/system/Database/Postgre/PreparedQuery.php +++ b/system/Database/Postgre/PreparedQuery.php @@ -39,9 +39,9 @@ public function _prepare(string $sql, array $options = []) { $this->name = mt_rand(1, 10000000000000000); - $this->sql = $this->parameterize($sql); + $sql = $this->parameterize($sql); - if (! $this->statement = pg_prepare($this->db->connID, $this->name, $this->sql)) + if (! $this->statement = pg_prepare($this->db->connID, $this->name, $sql)) { $this->errorCode = 0; $this->errorString = pg_last_error($this->db->connID); diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index 9c126805014c..2d8ae3dcac88 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -39,9 +39,6 @@ public function testPrepareReturnsPreparedQuery() //-------------------------------------------------------------------- - /** - * @group single - */ public function testExecuteRunsQueryAndReturnsResultObject() { $query = $this->db->prepare(function($db){ From 85b03c3d2ef787b759101c088075e362fac822ec Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 19 Jan 2017 22:10:57 -0600 Subject: [PATCH 0471/1807] Store Postgre prepared query after parameterization. --- system/Database/Postgre/PreparedQuery.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php index 122129c99914..2b0da3ba237c 100644 --- a/system/Database/Postgre/PreparedQuery.php +++ b/system/Database/Postgre/PreparedQuery.php @@ -41,6 +41,10 @@ public function _prepare(string $sql, array $options = []) $sql = $this->parameterize($sql); + // Update the query object since the parameters are slightly different + // than what was put in. + $this->query->setQuery($sql); + if (! $this->statement = pg_prepare($this->db->connID, $this->name, $sql)) { $this->errorCode = 0; From b6247d1196cb916e63e829e56bd73b3b7647dc9d Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Fri, 20 Jan 2017 00:45:11 -0800 Subject: [PATCH 0472/1807] Update license & site link to https:// --- system/Autoloader/Autoloader.php | 4 ++-- system/Autoloader/FileLocator.php | 4 ++-- system/CLI/CLI.php | 4 ++-- system/Common.php | 4 ++-- system/ComposerScripts.php | 4 ++-- system/Config/AutoloadConfig.php | 4 ++-- system/Config/BaseConfig.php | 4 ++-- system/Config/DotEnv.php | 4 ++-- system/Config/ForeignCharacters.php | 4 ++-- system/Config/Routes.php | 4 ++-- system/Controller.php | 4 ++-- system/Database/BaseBuilder.php | 4 ++-- system/Database/BaseConnection.php | 4 ++-- system/Database/BaseResult.php | 4 ++-- system/Database/BaseUtils.php | 4 ++-- system/Database/Config.php | 4 ++-- system/Database/ConnectionInterface.php | 4 ++-- system/Database/Database.php | 4 ++-- system/Database/Forge.php | 4 ++-- system/Database/Migration.php | 4 ++-- system/Database/MigrationRunner.php | 4 ++-- system/Database/MySQLi/Builder.php | 4 ++-- system/Database/MySQLi/Connection.php | 4 ++-- system/Database/MySQLi/Forge.php | 4 ++-- system/Database/MySQLi/Result.php | 4 ++-- system/Database/Postgre/Builder.php | 4 ++-- system/Database/Postgre/Connection.php | 4 ++-- system/Database/Postgre/Forge.php | 4 ++-- system/Database/Postgre/Result.php | 4 ++-- system/Database/Postgre/Utils.php | 4 ++-- system/Database/Query.php | 4 ++-- system/Database/QueryInterface.php | 4 ++-- system/Database/ResultInterface.php | 4 ++-- system/Database/Seeder.php | 4 ++-- system/Debug/CustomExceptions.php | 4 ++-- system/Debug/Exceptions.php | 4 ++-- system/Debug/Iterator.php | 4 ++-- system/Debug/Timer.php | 4 ++-- system/Debug/Toolbar/Collectors/BaseCollector.php | 4 ++-- system/Debug/Toolbar/Collectors/Database.php | 4 ++-- system/Debug/Toolbar/Collectors/Files.php | 4 ++-- system/Debug/Toolbar/Collectors/Logs.php | 4 ++-- system/Debug/Toolbar/Collectors/Routes.php | 4 ++-- system/Debug/Toolbar/Collectors/Timers.php | 4 ++-- system/Debug/Toolbar/Collectors/Views.php | 4 ++-- system/HTTP/CLIRequest.php | 4 ++-- system/HTTP/CURLRequest.php | 4 ++-- system/HTTP/ContentSecurityPolicy.php | 4 ++-- system/HTTP/Files/FileCollection.php | 4 ++-- system/HTTP/Files/UploadedFile.php | 4 ++-- system/HTTP/Files/UploadedFileInterface.php | 4 ++-- system/HTTP/Header.php | 4 ++-- system/HTTP/IncomingRequest.php | 4 ++-- system/HTTP/Message.php | 4 ++-- system/HTTP/Negotiate.php | 4 ++-- system/HTTP/Request.php | 4 ++-- system/HTTP/RequestInterface.php | 4 ++-- system/HTTP/Response.php | 4 ++-- system/HTTP/ResponseInterface.php | 4 ++-- system/HTTP/URI.php | 4 ++-- system/Helpers/cookie_helper.php | 6 +++--- system/Helpers/filesystem_helper.php | 2 +- system/Helpers/html_helper.php | 6 +++--- system/Helpers/inflector_helper.php | 6 +++--- system/Helpers/number_helper.php | 2 +- system/Helpers/security_helper.php | 4 ++-- system/Helpers/text_helper.php | 2 +- system/Helpers/url_helper.php | 4 ++-- system/Hooks/Hooks.php | 4 ++-- system/Language/en/Cache.php | 4 ++-- system/Language/en/Migrations.php | 4 ++-- system/Language/en/Number.php | 2 +- system/Language/en/Validation.php | 4 ++-- system/Log/Handlers/BaseHandler.php | 4 ++-- system/Log/Handlers/ChromeLoggerHandler.php | 4 ++-- system/Log/Handlers/FileHandler.php | 4 ++-- system/Log/Handlers/HandlerInterface.php | 4 ++-- system/Log/Logger.php | 4 ++-- system/Model.php | 4 ++-- system/Pager/Pager.php | 4 ++-- system/Pager/PagerRenderer.php | 4 ++-- system/Router/RouteCollection.php | 4 ++-- system/Router/RouteCollectionInterface.php | 4 ++-- system/Router/Router.php | 4 ++-- system/Router/RouterInterface.php | 4 ++-- system/Security/Security.php | 4 ++-- system/Session/Handlers/BaseHandler.php | 4 ++-- system/Session/Handlers/DatabaseHandler.php | 4 ++-- system/Session/Handlers/FileHandler.php | 4 ++-- system/Session/Handlers/MemcachedHandler.php | 4 ++-- system/Session/Handlers/RedisHandler.php | 4 ++-- system/Session/Session.php | 4 ++-- system/Session/SessionInterface.php | 4 ++-- system/Test/CIDatabaseTestCase.php | 4 ++-- system/Test/CIUnitTestCase.php | 4 ++-- system/Test/ReflectionHelper.php | 4 ++-- system/Typography/Typography.php | 4 ++-- system/View/Parser.php | 4 ++-- system/View/RendererInterface.php | 4 ++-- system/View/View.php | 4 ++-- 100 files changed, 199 insertions(+), 199 deletions(-) diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 4e137c29f67e..073db6a18ab4 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 6c5d0c5fe187..5d91ca821fc1 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 9e33bdc50919..8f5e06c4f787 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Common.php b/system/Common.php index c9e5039433b2..a0121b914037 100644 --- a/system/Common.php +++ b/system/Common.php @@ -29,8 +29,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php index ed56a65828a2..c785c43cf730 100644 --- a/system/ComposerScripts.php +++ b/system/ComposerScripts.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index 34ce4b4ad9b5..4ee1d8110ec3 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php index 04db53e28470..f5c81a080112 100644 --- a/system/Config/BaseConfig.php +++ b/system/Config/BaseConfig.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index 0afe7e02ea8b..b5b89e00b174 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Config/ForeignCharacters.php b/system/Config/ForeignCharacters.php index b908fd7242fe..9467f2886b11 100644 --- a/system/Config/ForeignCharacters.php +++ b/system/Config/ForeignCharacters.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Config/Routes.php b/system/Config/Routes.php index 7f97f368068d..90db14ebdb79 100644 --- a/system/Config/Routes.php +++ b/system/Config/Routes.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Controller.php b/system/Controller.php index e193e112617e..072f85caec80 100644 --- a/system/Controller.php +++ b/system/Controller.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 8eb93ac89d8b..d987259c6374 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index ae8645dab395..4894d33ad57d 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php index 643ab61f63e9..fb5995d79f90 100644 --- a/system/Database/BaseResult.php +++ b/system/Database/BaseResult.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/BaseUtils.php b/system/Database/BaseUtils.php index 49d4f6836c5c..73940db2fb0a 100644 --- a/system/Database/BaseUtils.php +++ b/system/Database/BaseUtils.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Config.php b/system/Database/Config.php index 4ec2cbb9719f..23e85a257827 100644 --- a/system/Database/Config.php +++ b/system/Database/Config.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/ConnectionInterface.php b/system/Database/ConnectionInterface.php index b1ca17a9c0a0..6b656f7bdf72 100644 --- a/system/Database/ConnectionInterface.php +++ b/system/Database/ConnectionInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Database.php b/system/Database/Database.php index 0c5b524f01a0..73177274365b 100644 --- a/system/Database/Database.php +++ b/system/Database/Database.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Forge.php b/system/Database/Forge.php index 621a796310b3..ff9feb256c77 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Migration.php b/system/Database/Migration.php index b4c4952a7f70..e8f4a5510ada 100644 --- a/system/Database/Migration.php +++ b/system/Database/Migration.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index f88ee437926a..92ff6eb473be 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/MySQLi/Builder.php b/system/Database/MySQLi/Builder.php index 372bb20fe253..072dcc69330a 100644 --- a/system/Database/MySQLi/Builder.php +++ b/system/Database/MySQLi/Builder.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 386d7eee84d8..e520e404eacc 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/MySQLi/Forge.php b/system/Database/MySQLi/Forge.php index 722964b2e488..2dcad6a3723f 100644 --- a/system/Database/MySQLi/Forge.php +++ b/system/Database/MySQLi/Forge.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/MySQLi/Result.php b/system/Database/MySQLi/Result.php index b33fe7c3953b..0e9cba7c0b89 100644 --- a/system/Database/MySQLi/Result.php +++ b/system/Database/MySQLi/Result.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 56c748e78a00..1a2da6424bdc 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index a2f077904eac..655185c11e96 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Postgre/Forge.php b/system/Database/Postgre/Forge.php index d74795dcbf2f..5dfb028d9f1e 100644 --- a/system/Database/Postgre/Forge.php +++ b/system/Database/Postgre/Forge.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Postgre/Result.php b/system/Database/Postgre/Result.php index 062a7451e1b2..e26c1ea85955 100644 --- a/system/Database/Postgre/Result.php +++ b/system/Database/Postgre/Result.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Postgre/Utils.php b/system/Database/Postgre/Utils.php index edce57f9fe57..ca415053d599 100644 --- a/system/Database/Postgre/Utils.php +++ b/system/Database/Postgre/Utils.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Query.php b/system/Database/Query.php index 1822057c3dfc..54136bfe456e 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/QueryInterface.php b/system/Database/QueryInterface.php index 1ac5757480ef..da9a0b0cb86c 100644 --- a/system/Database/QueryInterface.php +++ b/system/Database/QueryInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/ResultInterface.php b/system/Database/ResultInterface.php index ca00643e178d..36cdc64cb809 100644 --- a/system/Database/ResultInterface.php +++ b/system/Database/ResultInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php index 582e62125008..e5c1deab3921 100644 --- a/system/Database/Seeder.php +++ b/system/Database/Seeder.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Debug/CustomExceptions.php b/system/Debug/CustomExceptions.php index 8d49e4af5e42..6a1d169aa99a 100644 --- a/system/Debug/CustomExceptions.php +++ b/system/Debug/CustomExceptions.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php index 85ace7322951..e3ff11e1067a 100644 --- a/system/Debug/Exceptions.php +++ b/system/Debug/Exceptions.php @@ -29,8 +29,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Debug/Iterator.php b/system/Debug/Iterator.php index dc67baf1d8ab..2a45a9b5d68b 100644 --- a/system/Debug/Iterator.php +++ b/system/Debug/Iterator.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Debug/Timer.php b/system/Debug/Timer.php index 57809885f385..e2ca925f58c3 100644 --- a/system/Debug/Timer.php +++ b/system/Debug/Timer.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/BaseCollector.php b/system/Debug/Toolbar/Collectors/BaseCollector.php index a10a566c9368..accbdd0725c4 100644 --- a/system/Debug/Toolbar/Collectors/BaseCollector.php +++ b/system/Debug/Toolbar/Collectors/BaseCollector.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php index e01e57aa06a3..e185fcf17fcb 100644 --- a/system/Debug/Toolbar/Collectors/Database.php +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/Files.php b/system/Debug/Toolbar/Collectors/Files.php index 176ae64f0028..e3d384c60b64 100644 --- a/system/Debug/Toolbar/Collectors/Files.php +++ b/system/Debug/Toolbar/Collectors/Files.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/Logs.php b/system/Debug/Toolbar/Collectors/Logs.php index 9cc08e2170b8..ea3116afca36 100644 --- a/system/Debug/Toolbar/Collectors/Logs.php +++ b/system/Debug/Toolbar/Collectors/Logs.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index 56d7769df45d..2f460953f352 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/Timers.php b/system/Debug/Toolbar/Collectors/Timers.php index 7cc55f491bf0..1655ebc557ca 100644 --- a/system/Debug/Toolbar/Collectors/Timers.php +++ b/system/Debug/Toolbar/Collectors/Timers.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/Debug/Toolbar/Collectors/Views.php b/system/Debug/Toolbar/Collectors/Views.php index 459668670059..79e6f3243033 100644 --- a/system/Debug/Toolbar/Collectors/Views.php +++ b/system/Debug/Toolbar/Collectors/Views.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 4.0.0 * @filesource */ diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php index 05fe492f6b38..2450c3bec3b6 100644 --- a/system/HTTP/CLIRequest.php +++ b/system/HTTP/CLIRequest.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 0b5125c6538a..cf6cac585bf2 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index 2619134531fd..b193b91f6233 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Files/FileCollection.php b/system/HTTP/Files/FileCollection.php index 983971a49ede..1eec26e29456 100644 --- a/system/HTTP/Files/FileCollection.php +++ b/system/HTTP/Files/FileCollection.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index 92278cb84314..488321634c7e 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Files/UploadedFileInterface.php b/system/HTTP/Files/UploadedFileInterface.php index 043c4cb29584..34d1778005f0 100644 --- a/system/HTTP/Files/UploadedFileInterface.php +++ b/system/HTTP/Files/UploadedFileInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Header.php b/system/HTTP/Header.php index e4ecda78659e..79e8c74a4b1f 100644 --- a/system/HTTP/Header.php +++ b/system/HTTP/Header.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 29c0a2915e02..10ddce4f4328 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Message.php b/system/HTTP/Message.php index d0f5c45913e6..6322721a5e4a 100644 --- a/system/HTTP/Message.php +++ b/system/HTTP/Message.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index ce2c4ba0a4be..1406df4d8faa 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php index 121b6415ea76..72f032d0880d 100644 --- a/system/HTTP/Request.php +++ b/system/HTTP/Request.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/RequestInterface.php b/system/HTTP/RequestInterface.php index 2eac7028ef10..4b4ba515b5cb 100644 --- a/system/HTTP/RequestInterface.php +++ b/system/HTTP/RequestInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index f0b4971b0197..ce0f7fea1056 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php index 4583a06cc0e8..0939262fca06 100644 --- a/system/HTTP/ResponseInterface.php +++ b/system/HTTP/ResponseInterface.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 1b52e7e2a47e..1ce5ac87e44b 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index 5aecf96b6d1d..48bfe5e113e6 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ @@ -44,7 +44,7 @@ * @subpackage Helpers * @category Helpers * @author CodeIgniter Dev Team - * @link http://codeigniter.com/user_guide/helpers/cookie_helper.html + * @link https://codeigniter.com/user_guide/helpers/cookie_helper.html */ if (!function_exists('set_cookie')) { diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php index a8201e438d67..054f3b857cfd 100644 --- a/system/Helpers/filesystem_helper.php +++ b/system/Helpers/filesystem_helper.php @@ -30,7 +30,7 @@ * @author EllisLab Dev Team * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License + * @license https://opensource.org/licenses/MIT MIT License * @link https://codeigniter.com * @since Version 1.0.0 * @filesource diff --git a/system/Helpers/html_helper.php b/system/Helpers/html_helper.php index af427dd2fbec..bb1cde03fde6 100755 --- a/system/Helpers/html_helper.php +++ b/system/Helpers/html_helper.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ @@ -45,7 +45,7 @@ * @subpackage Helpers * @category Helpers * @author CodeIgniter Dev Team - * @link http://codeigniter.com/user_guide/helpers/html_helper.html + * @link https://codeigniter.com/user_guide/helpers/html_helper.html */ if ( ! function_exists('ul')) diff --git a/system/Helpers/inflector_helper.php b/system/Helpers/inflector_helper.php index 778650f1fb1a..bad6aa1f1c15 100755 --- a/system/Helpers/inflector_helper.php +++ b/system/Helpers/inflector_helper.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ @@ -45,7 +45,7 @@ * @subpackage Helpers * @category Helpers * @author CodeIgniter Dev Team - * @link http://codeigniter.com/user_guide/helpers/inflector_helper.html + * @link https://codeigniter.com/user_guide/helpers/inflector_helper.html */ if ( ! function_exists('singular')) diff --git a/system/Helpers/number_helper.php b/system/Helpers/number_helper.php index a2db3831518b..597406826f00 100644 --- a/system/Helpers/number_helper.php +++ b/system/Helpers/number_helper.php @@ -30,7 +30,7 @@ * @author EllisLab Dev Team * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License + * @license https://opensource.org/licenses/MIT MIT License * @link https://codeigniter.com * @since Version 1.0.0 * @filesource diff --git a/system/Helpers/security_helper.php b/system/Helpers/security_helper.php index b3bff32b91d1..01ae5cc57910 100644 --- a/system/Helpers/security_helper.php +++ b/system/Helpers/security_helper.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php index 3ce31758ba69..13ee89b1978b 100755 --- a/system/Helpers/text_helper.php +++ b/system/Helpers/text_helper.php @@ -31,7 +31,7 @@ * @author EllisLab Dev Team * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License + * @license https://opensource.org/licenses/MIT MIT License * @link https://codeigniter.com * @since Version 1.0.0 * @filesource diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index 3d6b272378be..b5e70a6ccb6c 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Hooks/Hooks.php b/system/Hooks/Hooks.php index 33c94df0bd5a..0a13c9d5ecbd 100644 --- a/system/Hooks/Hooks.php +++ b/system/Hooks/Hooks.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Language/en/Cache.php b/system/Language/en/Cache.php index 6ef60061d7d0..0be7711bc9cd 100644 --- a/system/Language/en/Cache.php +++ b/system/Language/en/Cache.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Language/en/Migrations.php b/system/Language/en/Migrations.php index a5ce5b0713fc..a6954913f9fe 100644 --- a/system/Language/en/Migrations.php +++ b/system/Language/en/Migrations.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Language/en/Number.php b/system/Language/en/Number.php index 7848f26f37c8..127fbcea99fa 100644 --- a/system/Language/en/Number.php +++ b/system/Language/en/Number.php @@ -30,7 +30,7 @@ * @author EllisLab Dev Team * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License + * @license https://opensource.org/licenses/MIT MIT License * @link https://codeigniter.com * @since Version 1.0.0 * @filesource diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php index 5f67af064205..ebceabd6f637 100644 --- a/system/Language/en/Validation.php +++ b/system/Language/en/Validation.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Log/Handlers/BaseHandler.php b/system/Log/Handlers/BaseHandler.php index 8c47950d4af7..6dfd156a7ae0 100644 --- a/system/Log/Handlers/BaseHandler.php +++ b/system/Log/Handlers/BaseHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Log/Handlers/ChromeLoggerHandler.php b/system/Log/Handlers/ChromeLoggerHandler.php index 6facd2377f1d..3c720a14a93e 100644 --- a/system/Log/Handlers/ChromeLoggerHandler.php +++ b/system/Log/Handlers/ChromeLoggerHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Log/Handlers/FileHandler.php b/system/Log/Handlers/FileHandler.php index 4a91e4a89bf0..119ed2df8b70 100644 --- a/system/Log/Handlers/FileHandler.php +++ b/system/Log/Handlers/FileHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Log/Handlers/HandlerInterface.php b/system/Log/Handlers/HandlerInterface.php index b60a1e2b8214..7035476d6717 100644 --- a/system/Log/Handlers/HandlerInterface.php +++ b/system/Log/Handlers/HandlerInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Log/Logger.php b/system/Log/Logger.php index 0562df082d0e..f2d97ab200f8 100644 --- a/system/Log/Logger.php +++ b/system/Log/Logger.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Model.php b/system/Model.php index 06488f1eef3f..37e1d75f189f 100644 --- a/system/Model.php +++ b/system/Model.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php index 92d3e4995951..79b86a03f35b 100644 --- a/system/Pager/Pager.php +++ b/system/Pager/Pager.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Pager/PagerRenderer.php b/system/Pager/PagerRenderer.php index b0d3685e5d12..b582b4e86182 100644 --- a/system/Pager/PagerRenderer.php +++ b/system/Pager/PagerRenderer.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 4d68db103772..40a4ffcc246e 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Router/RouteCollectionInterface.php b/system/Router/RouteCollectionInterface.php index 6c5f837ae9da..589a09b4ee25 100644 --- a/system/Router/RouteCollectionInterface.php +++ b/system/Router/RouteCollectionInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Router/Router.php b/system/Router/Router.php index cad125ed6e2d..c6f3961c6300 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Router/RouterInterface.php b/system/Router/RouterInterface.php index 0ae853fd83dc..f4e5a0c49d2e 100644 --- a/system/Router/RouterInterface.php +++ b/system/Router/RouterInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Security/Security.php b/system/Security/Security.php index 0b30578e4515..6468c74883e1 100644 --- a/system/Security/Security.php +++ b/system/Security/Security.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/Handlers/BaseHandler.php b/system/Session/Handlers/BaseHandler.php index 327862327d1b..77b85fa500c8 100644 --- a/system/Session/Handlers/BaseHandler.php +++ b/system/Session/Handlers/BaseHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/Handlers/DatabaseHandler.php b/system/Session/Handlers/DatabaseHandler.php index a5a4d82bfd27..dcd606a0d55d 100644 --- a/system/Session/Handlers/DatabaseHandler.php +++ b/system/Session/Handlers/DatabaseHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/Handlers/FileHandler.php b/system/Session/Handlers/FileHandler.php index bba5bcc0af47..68a3f412b6b2 100644 --- a/system/Session/Handlers/FileHandler.php +++ b/system/Session/Handlers/FileHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/Handlers/MemcachedHandler.php b/system/Session/Handlers/MemcachedHandler.php index c3bf73e9aaa3..a712f7561981 100644 --- a/system/Session/Handlers/MemcachedHandler.php +++ b/system/Session/Handlers/MemcachedHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/Handlers/RedisHandler.php b/system/Session/Handlers/RedisHandler.php index 17a5f2730de1..b57e36c34f5c 100644 --- a/system/Session/Handlers/RedisHandler.php +++ b/system/Session/Handlers/RedisHandler.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/Session.php b/system/Session/Session.php index aa25762d5228..f3112fd013e8 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Session/SessionInterface.php b/system/Session/SessionInterface.php index ae3b24889ef4..accdaa2d1689 100644 --- a/system/Session/SessionInterface.php +++ b/system/Session/SessionInterface.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Test/CIDatabaseTestCase.php b/system/Test/CIDatabaseTestCase.php index 4f20aec98861..bec08b87c9cd 100644 --- a/system/Test/CIDatabaseTestCase.php +++ b/system/Test/CIDatabaseTestCase.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index 097dcca31bf0..47ce961a77a4 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Test/ReflectionHelper.php b/system/Test/ReflectionHelper.php index 0106087aa38f..2722fe51ca43 100644 --- a/system/Test/ReflectionHelper.php +++ b/system/Test/ReflectionHelper.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/Typography/Typography.php b/system/Typography/Typography.php index a13f7054049d..911e7a7805d3 100644 --- a/system/Typography/Typography.php +++ b/system/Typography/Typography.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/View/Parser.php b/system/View/Parser.php index ec071eb52d94..a7d00d96a5a3 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/View/RendererInterface.php b/system/View/RendererInterface.php index a338b57e45f2..fc1dfa9233fb 100644 --- a/system/View/RendererInterface.php +++ b/system/View/RendererInterface.php @@ -30,8 +30,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ diff --git a/system/View/View.php b/system/View/View.php index acd0b6888ade..f185876c1f2f 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -32,8 +32,8 @@ * @package CodeIgniter * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license http://opensource.org/licenses/MIT MIT License - * @link http://codeigniter.com + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com * @since Version 3.0.0 * @filesource */ From ef24a9a519335f31ead21a3fd90f3864627a51f1 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Fri, 20 Jan 2017 00:57:53 -0800 Subject: [PATCH 0473/1807] Add missing copyright header blocks --- system/API/FormatterInterface.php | 36 +++++++++++++++++++ system/API/JSONFormatter.php | 36 +++++++++++++++++++ system/API/ResponseTrait.php | 36 +++++++++++++++++++ system/API/XMLFormatter.php | 36 +++++++++++++++++++ system/CLI/BaseCommand.php | 36 +++++++++++++++++++ system/CLI/CommandRunner.php | 36 +++++++++++++++++++ system/CLI/Console.php | 36 +++++++++++++++++++ system/Cache/CacheFactory.php | 36 +++++++++++++++++++ system/Cache/CacheInterface.php | 36 +++++++++++++++++++ system/Cache/Handlers/DummyHandler.php | 36 +++++++++++++++++++ system/Cache/Handlers/FileHandler.php | 36 +++++++++++++++++++ system/Cache/Handlers/MemcachedHandler.php | 36 +++++++++++++++++++ system/Cache/Handlers/PredisHandler.php | 36 +++++++++++++++++++ system/Cache/Handlers/RedisHandler.php | 36 +++++++++++++++++++ system/Cache/Handlers/WincacheHandler.php | 36 +++++++++++++++++++ system/CodeIgniter.php | 36 +++++++++++++++++++ system/Commands/Database/CreateMigration.php | 36 +++++++++++++++++++ system/Commands/Database/MigrateCurrent.php | 36 +++++++++++++++++++ system/Commands/Database/MigrateLatest.php | 36 +++++++++++++++++++ system/Commands/Database/MigrateRefresh.php | 36 +++++++++++++++++++ system/Commands/Database/MigrateRollback.php | 36 +++++++++++++++++++ system/Commands/Database/MigrateStatus.php | 36 +++++++++++++++++++ system/Commands/Database/MigrateVersion.php | 36 +++++++++++++++++++ system/Commands/Database/Seed.php | 36 +++++++++++++++++++ system/Commands/Help.php | 36 +++++++++++++++++++ system/Commands/ListCommands.php | 36 +++++++++++++++++++ system/Commands/Sessions/CreateMigration.php | 36 +++++++++++++++++++ .../Commands/Sessions/Views/migration.tpl.php | 36 +++++++++++++++++++ system/Config/Services.php | 36 +++++++++++++++++++ system/Database/BasePreparedQuery.php | 36 +++++++++++++++++++ system/Database/MySQLi/PreparedQuery.php | 36 +++++++++++++++++++ system/Database/Postgre/PreparedQuery.php | 36 +++++++++++++++++++ system/Database/PreparedQueryInterface.php | 36 +++++++++++++++++++ system/Debug/Toolbar.php | 36 +++++++++++++++++++ system/Filters/FilterInterface.php | 36 +++++++++++++++++++ system/Filters/Filters.php | 36 +++++++++++++++++++ system/Helpers/form_helper.php | 36 +++++++++++++++++++ system/Language/Language.php | 36 +++++++++++++++++++ system/Pager/PagerInterface.php | 36 +++++++++++++++++++ system/Throttle/Throttler.php | 36 +++++++++++++++++++ system/Throttle/ThrottlerInterface.php | 36 +++++++++++++++++++ system/Validation/CreditCardRules.php | 36 +++++++++++++++++++ system/Validation/FileRules.php | 36 +++++++++++++++++++ system/Validation/Rules.php | 36 +++++++++++++++++++ system/Validation/Validation.php | 36 +++++++++++++++++++ system/Validation/ValidationInterface.php | 36 +++++++++++++++++++ system/View/Cell.php | 36 +++++++++++++++++++ system/bootstrap.php | 36 +++++++++++++++++++ 48 files changed, 1728 insertions(+) diff --git a/system/API/FormatterInterface.php b/system/API/FormatterInterface.php index 177de0bcaa65..c9c3745df59b 100644 --- a/system/API/FormatterInterface.php +++ b/system/API/FormatterInterface.php @@ -1,5 +1,41 @@ Date: Sat, 21 Jan 2017 00:17:48 +0700 Subject: [PATCH 0474/1807] remove unused $this->_csrf_hash assignment in Security::CSRFVerify() --- system/Security/Security.php | 1 - 1 file changed, 1 deletion(-) diff --git a/system/Security/Security.php b/system/Security/Security.php index 0b30578e4515..a8a6c46170c3 100644 --- a/system/Security/Security.php +++ b/system/Security/Security.php @@ -201,7 +201,6 @@ public function CSRFVerify(RequestInterface $request) { // Nothing should last forever unset($_COOKIE[$this->CSRFCookieName]); - $this->_csrf_hash = null; } $this->CSRFSetHash(); From 3c6fb8c7596b0eade98714ffec1d8014ec3d69d9 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 22 Jan 2017 22:53:31 -0600 Subject: [PATCH 0475/1807] Allow View's saveData to read from config again. --- system/Common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Common.php b/system/Common.php index c9e5039433b2..49f280ff06bf 100644 --- a/system/Common.php +++ b/system/Common.php @@ -109,7 +109,7 @@ function view(string $name, array $data = [], array $options = []) */ $renderer = Services::renderer(); - $saveData = false; + $saveData = null; if (array_key_exists('saveData', $options) && $options['saveData'] === true) { $saveData = (bool)$options['saveData']; From 135ec3722faa8ebc8cc0a40fc17a2b56735c895a Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 30 Jan 2017 23:01:44 -0600 Subject: [PATCH 0476/1807] Allow returned response objects to work from controllers. --- system/CodeIgniter.php | 15 ++++++++++ tests/system/CodeIgniterTest.php | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 4c6b97f62e42..a18db7ed2178 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -810,12 +810,27 @@ protected function display404errors(PageNotFoundException $e) /** * Gathers the script output from the buffer, replaces some execution * time tag in the output and displays the debug toolbar, if required. + * + * @param null $cacheConfig + * @param null $returned */ protected function gatherOutput($cacheConfig = null, $returned = null) { $this->output = ob_get_contents(); ob_end_clean(); + // If the controller returned a response object, + // we need to grab the body from it so it can + // be added to anything else that might have been + // echoed already. + // We also need to save the instance locally + // so that any status code changes, etc, take place. + if ($returned instanceof Response) + { + $this->response = $returned; + $returned = $returned->getBody(); + } + if (is_string($returned)) { $this->output .= $returned; diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index 2fc629c82213..8bc7b3be31b0 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -162,4 +162,53 @@ public function testRun404OverrideByClosure() //-------------------------------------------------------------------- + public function testControllersCanReturnString() + { + $_SERVER['argv'] = [ + 'index.php', + 'pages/about', + ]; + $_SERVER['argc'] = 2; + + // Inject mock router. + $routes = Services::routes(); + $routes->add('pages/(:segment)', function($segment) + { + return 'You want to see "'.esc($segment).'" page.'; + }); + $router = Services::router($routes); + Services::injectMock('router', $router); + + ob_start(); + $this->codeigniter->run(); + $output = ob_get_clean(); + + $this->assertContains('You want to see "about" page.', $output); + } + + public function testControllersCanReturnResponseObject() + { + $_SERVER['argv'] = [ + 'index.php', + 'pages/about', + ]; + $_SERVER['argc'] = 2; + + // Inject mock router. + $routes = Services::routes(); + $routes->add('pages/(:segment)', function($segment) + { + $response = Services::response(); + $string = "You want to see 'about' page."; + return $response->setBody($string); + }); + $router = Services::router($routes); + Services::injectMock('router', $router); + + ob_start(); + $this->codeigniter->run(); + $output = ob_get_clean(); + + $this->assertContains("You want to see 'about' page.", $output); + } } From a0422d158ba44e20226b7384261e204953e8b190 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 2 Feb 2017 00:09:53 -0600 Subject: [PATCH 0477/1807] Allow public folder to be specified for more flexible hosting --- application/Config/Paths.php | 14 ++++++++++++++ system/bootstrap.php | 6 ++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/application/Config/Paths.php b/application/Config/Paths.php index 0533cc8f7ea8..cbfe72d705f4 100644 --- a/application/Config/Paths.php +++ b/application/Config/Paths.php @@ -61,4 +61,18 @@ class Paths * system directories. */ public $testsDirectory = '../tests'; + + /* + * --------------------------------------------------------------- + * PUBLIC DIRECTORY NAME + * --------------------------------------------------------------- + * + * This variable must contain the name of the directory that + * contains the main index.php front-controller. By default, + * this is the `public` directory, but some hosts may not + * be able to map a primary domain to a sub-directory so you + * can change this to `public_html`, for example, to comply + * with your host's needs. + */ + public $publicDirectory = 'public'; } diff --git a/system/bootstrap.php b/system/bootstrap.php index 2d13a666b2a8..d41b35b6e5e5 100644 --- a/system/bootstrap.php +++ b/system/bootstrap.php @@ -46,9 +46,11 @@ * so they are available in the config files that are loaded. */ +$public = trim($paths->publicDirectory, '/'); + // Path to code root folder (just up from public) -$pos = strrpos(FCPATH, 'public'.DIRECTORY_SEPARATOR); -define('ROOTPATH', substr_replace(FCPATH, '', $pos, strlen('public'.DIRECTORY_SEPARATOR))); +$pos = strrpos(FCPATH, $public.DIRECTORY_SEPARATOR); +define('ROOTPATH', substr_replace(FCPATH, '', $pos, strlen($public.DIRECTORY_SEPARATOR))); // The path to the "application" folder define('APPPATH', realpath($paths->applicationDirectory).DIRECTORY_SEPARATOR); From af7eb039b5c54024e69b229d1cf6ac3a2d8a4b6f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 2 Feb 2017 23:27:55 -0600 Subject: [PATCH 0478/1807] Additional Timer tests --- tests/system/Debug/TimerTest.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/system/Debug/TimerTest.php b/tests/system/Debug/TimerTest.php index 28852debf0c0..797570721356 100644 --- a/tests/system/Debug/TimerTest.php +++ b/tests/system/Debug/TimerTest.php @@ -83,5 +83,36 @@ public function testThrowsExceptionStoppingNonTimer() //-------------------------------------------------------------------- + public function testLongExecutionTime() + { + $timer = new Timer(); + $timer->start('longjohn', strtotime('-11 minutes')); + + // Use floor here to account for fractional differences in seconds. + $this->assertEquals(11 * 60, floor($timer->getElapsedTime('longjohn'))); + } + + //-------------------------------------------------------------------- + + public function testLongExecutionTimeThroughCommonFunc() + { + timer()->start('longjohn', strtotime('-11 minutes')); + + // Use floor here to account for fractional differences in seconds. + $this->assertEquals(11 * 60, floor(timer()->getElapsedTime('longjohn'))); + } + + //-------------------------------------------------------------------- + + public function testCommonStartStop() + { + timer('test1'); + sleep(1); + timer('test1'); + + $this->assertGreaterThanOrEqual(1.0, timer()->getElapsedTime('test1')); + } + + //-------------------------------------------------------------------- } From eec8d032be0f14628f1130c2275cc2051b9357da Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Fri, 3 Feb 2017 14:26:07 +0000 Subject: [PATCH 0479/1807] Show Debug Toolbar Only For HTML Responses Only show the debug toolbar for responses containing HTML, allowing other responses (such as JSON and XML) to show clean. Signed-off-by: Kristian Matthews --- application/Filters/DebugToolbar.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/Filters/DebugToolbar.php b/application/Filters/DebugToolbar.php index e4d066793368..dd3c83c6e3a3 100644 --- a/application/Filters/DebugToolbar.php +++ b/application/Filters/DebugToolbar.php @@ -33,7 +33,9 @@ public function before(RequestInterface $request) */ public function after(RequestInterface $request, ResponseInterface $response) { - if ( ! is_cli() && CI_DEBUG) + $format = $response->getHeaderLine('content-type'); + + if ( ! is_cli() && CI_DEBUG && strpos($format, 'html') !== false) { global $app; From d2c1e128adfd0f427538a23277a24454597b7b4e Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 4 Feb 2017 16:36:08 +0900 Subject: [PATCH 0480/1807] Fix indentation Replace white spaces with tabs --- system/API/FormatterInterface.php | 16 +- system/API/JSONFormatter.php | 72 ++-- system/API/ResponseTrait.php | 604 +++++++++++++++--------------- system/API/XMLFormatter.php | 106 +++--- system/Autoloader/FileLocator.php | 256 ++++++------- system/CLI/BaseCommand.php | 196 +++++----- system/CLI/CLI.php | 342 ++++++++--------- system/CLI/CommandRunner.php | 258 ++++++------- system/Config/Services.php | 112 +++--- 9 files changed, 981 insertions(+), 981 deletions(-) diff --git a/system/API/FormatterInterface.php b/system/API/FormatterInterface.php index c9c3745df59b..38bd1f507f3d 100644 --- a/system/API/FormatterInterface.php +++ b/system/API/FormatterInterface.php @@ -38,12 +38,12 @@ interface FormatterInterface { - /** - * Takes the given data and formats it. - * - * @param $data - * - * @return mixed - */ - public function format(array $data); + /** + * Takes the given data and formats it. + * + * @param $data + * + * @return mixed + */ + public function format(array $data); } diff --git a/system/API/JSONFormatter.php b/system/API/JSONFormatter.php index 6acc602febc7..48c650350adf 100644 --- a/system/API/JSONFormatter.php +++ b/system/API/JSONFormatter.php @@ -38,46 +38,46 @@ class JSONFormatter implements FormatterInterface { - /** - * The error strings to use if encoding hits an error. - * - * @var array - */ - protected $errors = [ - JSON_ERROR_NONE => 'No error has occurred', - JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', - JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', - JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', - JSON_ERROR_SYNTAX => 'Syntax error', - JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', - ]; + /** + * The error strings to use if encoding hits an error. + * + * @var array + */ + protected $errors = [ + JSON_ERROR_NONE => 'No error has occurred', + JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', + JSON_ERROR_SYNTAX => 'Syntax error', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + ]; - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - /** - * Takes the given data and formats it. - * - * @param $data - * - * @return mixed - */ - public function format(array $data) - { - $options = ENVIRONMENT == 'production' - ? JSON_NUMERIC_CHECK - : JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT; + /** + * Takes the given data and formats it. + * + * @param $data + * + * @return mixed + */ + public function format(array $data) + { + $options = ENVIRONMENT == 'production' + ? JSON_NUMERIC_CHECK + : JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT; - $result = json_encode($data, 512, $options); + $result = json_encode($data, 512, $options); - // If result is NULL, then an error happened. - // Let them know. - if ($result === null) - { - throw new \RuntimeException($this->errors[json_last_error()]); - } + // If result is NULL, then an error happened. + // Let them know. + if ($result === null) + { + throw new \RuntimeException($this->errors[json_last_error()]); + } - return utf8_encode($result); - } + return utf8_encode($result); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- } diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index a358afdcd58e..0ff83ffd9104 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -50,306 +50,306 @@ */ trait ResponseTrait { - /** - * Allows child classes to override the - * status code that is used in their API. - * - * @var array - */ - protected $codes = [ - 'created' => 201, - 'deleted' => 200, - 'invalid_request' => 400, - 'unsupported_response_type' => 400, - 'invalid_scope' => 400, - 'temporarily_unavailable' => 400, - 'invalid_grant' => 400, - 'invalid_credentials' => 400, - 'invalid_refresh' => 400, - 'no_data' => 400, - 'invalid_data' => 400, - 'access_denied' => 401, - 'unauthorized' => 401, - 'invalid_client' => 401, - 'forbidden' => 403, - 'resource_not_found' => 404, - 'not_acceptable' => 406, - 'resource_exists' => 409, - 'conflict' => 409, - 'resource_gone' => 410, - 'payload_too_large' => 413, - 'unsupported_media_type' => 415, - 'too_many_requests' => 429, - 'server_error' => 500, - 'unsupported_grant_type' => 501, - 'not_implemented' => 501, - ]; - - //-------------------------------------------------------------------- - - /** - * Provides a single, simple method to return an API response, formatted - * to match the requested format, with proper content-type and status code. - * - * @param null $data - * @param int $status - * @param string $message - * - * @return mixed - */ - public function respond($data = null, int $status = null, string $message = '') - { - // If data is null and status code not provided, exit and bail - if ($data === null && $status === null) - { - $status = 404; - - // Create the output var here in case of $this->response([]); - $output = null; - } // If data is null but status provided, keep the output empty. - elseif ($data === null && is_numeric($status)) - { - $output = null; - } else - { - $status = empty($status) ? 200 : $status; - $output = $this->format($data); - } - - return $this->response->setBody($output) - ->setStatusCode($status, $message); - } - - //-------------------------------------------------------------------- - - /** - * Used for generic failures that no custom methods exist for. - * - * @param $messages - * @param int|null $status HTTP status code - * @param string|null $code Custom, API-specific, error code - * @param string $customMessage - * - * @return mixed - */ - public function fail($messages, int $status = 400, string $code = null, string $customMessage = '') - { - if (! is_array($messages)) - { - $messages = [$messages]; - } - - $response = [ - 'status' => $status, - 'error' => $code === null ? $status : $code, - 'messages' => $messages, - ]; - - return $this->respond($response, $status, $customMessage); - } - - //-------------------------------------------------------------------- - - //-------------------------------------------------------------------- - // Response Helpers - //-------------------------------------------------------------------- - - /** - * Used after successfully creating a new resource. - * - * @param $data - * @param string $message - * - * @return mixed - */ - public function respondCreated($data, string $message = '') - { - return $this->respond($data, $this->codes['created'], $message); - } - - //-------------------------------------------------------------------- - - /** - * Used after a resource has been successfully deleted. - * - * @param $data - * @param string $message - * - * @return mixed - */ - public function respondDeleted($data, string $message = '') - { - return $this->respond($data, $this->codes['deleted'], $message); - } - - //-------------------------------------------------------------------- - - /** - * Used when the client is either didn't send authorization information, - * or had bad authorization credentials. User is encouraged to try again - * with the proper information. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failUnauthorized(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['unauthorized'], $code, $message); - } - - //-------------------------------------------------------------------- - - /** - * Used when access is always denied to this resource and no amount - * of trying again will help. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failForbidden(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['forbidden'], $code, $message); - } - - //-------------------------------------------------------------------- - - /** - * Used when a specified resource cannot be found. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failNotFound(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['resource_not_found'], $code, $message); - } - - //-------------------------------------------------------------------- - - /** - * Used when the data provided by the client cannot be validated. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failValidationError(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['invalid_data'], $code, $message); - } - - //-------------------------------------------------------------------- - - /** - * Use when trying to create a new resource and it already exists. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failResourceExists(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['resource_exists'], $code, $message); - } - - //-------------------------------------------------------------------- - - /** - * Use when a resource was previously deleted. This is different than - * Not Found, because here we know the data previously existed, but is now gone, - * where Not Found means we simply cannot find any information about it. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failResourceGone(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['resource_gone'], $code, $message); - } - - //-------------------------------------------------------------------- - - /** - * Used when the user has made too many requests for the resource recently. - * - * @param string $description - * @param string $message - * - * @return mixed - */ - public function failTooManyRequests(string $description, string $code=null, string $message = '') - { - return $this->fail($description, $this->codes['too_many_requests'], $code, $message); - } - - //-------------------------------------------------------------------- - - - //-------------------------------------------------------------------- - // Utility Methods - //-------------------------------------------------------------------- - - /** - * Handles formatting a response. Currently makes some heavy assumptions - * and needs updating! :) - * - * @param null $data - * - * @return null|string - */ - protected function format($data = null) - { - // If the data is a string, there's not much we can do to it... - if (is_string($data)) - { - $this->setContentType('text/html'); - - return $data; - } - - $config = new \Config\API(); - - // Determine correct response type through content negotiation - $format = $this->request->negotiate('media', $config->supportedResponseFormats); - - $this->setContentType($format); - - $formatter = $config->getFormatter($format); - - return $formatter->format($data); - } - - //-------------------------------------------------------------------- - - /** - * Sets the response's content type. If a type is permitted - * ('html', 'json', or 'xml'), the appropriate content type is set. - * - * @param string $type - */ - protected function setContentType(string $type = null) - { - switch ($type) - { - case 'text/html': - $this->response = $this->response->setContentType('text/html'); - break; - case 'application/json': - $this->response = $this->response->setContentType('application/json'); - break; - case 'application/xml': - $this->response = $this->response->setContentType('text/xml'); - break; - } - } + /** + * Allows child classes to override the + * status code that is used in their API. + * + * @var array + */ + protected $codes = [ + 'created' => 201, + 'deleted' => 200, + 'invalid_request' => 400, + 'unsupported_response_type' => 400, + 'invalid_scope' => 400, + 'temporarily_unavailable' => 400, + 'invalid_grant' => 400, + 'invalid_credentials' => 400, + 'invalid_refresh' => 400, + 'no_data' => 400, + 'invalid_data' => 400, + 'access_denied' => 401, + 'unauthorized' => 401, + 'invalid_client' => 401, + 'forbidden' => 403, + 'resource_not_found' => 404, + 'not_acceptable' => 406, + 'resource_exists' => 409, + 'conflict' => 409, + 'resource_gone' => 410, + 'payload_too_large' => 413, + 'unsupported_media_type' => 415, + 'too_many_requests' => 429, + 'server_error' => 500, + 'unsupported_grant_type' => 501, + 'not_implemented' => 501, + ]; + + //-------------------------------------------------------------------- + + /** + * Provides a single, simple method to return an API response, formatted + * to match the requested format, with proper content-type and status code. + * + * @param null $data + * @param int $status + * @param string $message + * + * @return mixed + */ + public function respond($data = null, int $status = null, string $message = '') + { + // If data is null and status code not provided, exit and bail + if ($data === null && $status === null) + { + $status = 404; + + // Create the output var here in case of $this->response([]); + $output = null; + } // If data is null but status provided, keep the output empty. + elseif ($data === null && is_numeric($status)) + { + $output = null; + } else + { + $status = empty($status) ? 200 : $status; + $output = $this->format($data); + } + + return $this->response->setBody($output) + ->setStatusCode($status, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used for generic failures that no custom methods exist for. + * + * @param $messages + * @param int|null $status HTTP status code + * @param string|null $code Custom, API-specific, error code + * @param string $customMessage + * + * @return mixed + */ + public function fail($messages, int $status = 400, string $code = null, string $customMessage = '') + { + if (! is_array($messages)) + { + $messages = [$messages]; + } + + $response = [ + 'status' => $status, + 'error' => $code === null ? $status : $code, + 'messages' => $messages, + ]; + + return $this->respond($response, $status, $customMessage); + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // Response Helpers + //-------------------------------------------------------------------- + + /** + * Used after successfully creating a new resource. + * + * @param $data + * @param string $message + * + * @return mixed + */ + public function respondCreated($data, string $message = '') + { + return $this->respond($data, $this->codes['created'], $message); + } + + //-------------------------------------------------------------------- + + /** + * Used after a resource has been successfully deleted. + * + * @param $data + * @param string $message + * + * @return mixed + */ + public function respondDeleted($data, string $message = '') + { + return $this->respond($data, $this->codes['deleted'], $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when the client is either didn't send authorization information, + * or had bad authorization credentials. User is encouraged to try again + * with the proper information. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failUnauthorized(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['unauthorized'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when access is always denied to this resource and no amount + * of trying again will help. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failForbidden(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['forbidden'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when a specified resource cannot be found. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failNotFound(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['resource_not_found'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when the data provided by the client cannot be validated. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failValidationError(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['invalid_data'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Use when trying to create a new resource and it already exists. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failResourceExists(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['resource_exists'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Use when a resource was previously deleted. This is different than + * Not Found, because here we know the data previously existed, but is now gone, + * where Not Found means we simply cannot find any information about it. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failResourceGone(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['resource_gone'], $code, $message); + } + + //-------------------------------------------------------------------- + + /** + * Used when the user has made too many requests for the resource recently. + * + * @param string $description + * @param string $message + * + * @return mixed + */ + public function failTooManyRequests(string $description, string $code=null, string $message = '') + { + return $this->fail($description, $this->codes['too_many_requests'], $code, $message); + } + + //-------------------------------------------------------------------- + + + //-------------------------------------------------------------------- + // Utility Methods + //-------------------------------------------------------------------- + + /** + * Handles formatting a response. Currently makes some heavy assumptions + * and needs updating! :) + * + * @param null $data + * + * @return null|string + */ + protected function format($data = null) + { + // If the data is a string, there's not much we can do to it... + if (is_string($data)) + { + $this->setContentType('text/html'); + + return $data; + } + + $config = new \Config\API(); + + // Determine correct response type through content negotiation + $format = $this->request->negotiate('media', $config->supportedResponseFormats); + + $this->setContentType($format); + + $formatter = $config->getFormatter($format); + + return $formatter->format($data); + } + + //-------------------------------------------------------------------- + + /** + * Sets the response's content type. If a type is permitted + * ('html', 'json', or 'xml'), the appropriate content type is set. + * + * @param string $type + */ + protected function setContentType(string $type = null) + { + switch ($type) + { + case 'text/html': + $this->response = $this->response->setContentType('text/html'); + break; + case 'application/json': + $this->response = $this->response->setContentType('application/json'); + break; + case 'application/xml': + $this->response = $this->response->setContentType('text/xml'); + break; + } + } } diff --git a/system/API/XMLFormatter.php b/system/API/XMLFormatter.php index 51ec2afc04dd..1d1251338321 100644 --- a/system/API/XMLFormatter.php +++ b/system/API/XMLFormatter.php @@ -38,64 +38,64 @@ class XMLFormatter implements FormatterInterface { - /** - * Takes the given data and formats it. - * - * @param $data - * - * @return mixed - */ - public function format(array $data) - { - $result = null; + /** + * Takes the given data and formats it. + * + * @param $data + * + * @return mixed + */ + public function format(array $data) + { + $result = null; - // SimpleXML is installed but default - // but best to check, and then provide a fallback. - if (! extension_loaded('simplexml')) - { - throw new \RuntimeException('The SimpleXML extension is required to format XML.'); - } + // SimpleXML is installed but default + // but best to check, and then provide a fallback. + if (! extension_loaded('simplexml')) + { + throw new \RuntimeException('The SimpleXML extension is required to format XML.'); + } - $output = new \SimpleXMLElement(""); + $output = new \SimpleXMLElement(""); - $this->arrayToXML($data, $output); + $this->arrayToXML($data, $output); - return $output->asXML(); - } + return $output->asXML(); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - /** - * A recursive method to convert an array into a valid XML string. - * - * Written by CodexWorld. Received permission by email on Nov 24, 2016 to use this code. - * - * @see http://www.codexworld.com/convert-array-to-xml-in-php/ - * - * @param array $data - * @param $output - */ - protected function arrayToXML(array $data, &$output) - { - foreach ($data as $key => $value) - { - if (is_array($value)) - { - if (! is_numeric($key)) - { - $subnode = $output->addChild("$key"); - $this->arrayToXML($value, $subnode); - } else - { - $subnode = $output->addChild("item{$key}"); - $this->arrayToXML($value, $subnode); - } - } else - { - $output->addChild("$key", htmlspecialchars("$value")); - } - } - } + /** + * A recursive method to convert an array into a valid XML string. + * + * Written by CodexWorld. Received permission by email on Nov 24, 2016 to use this code. + * + * @see http://www.codexworld.com/convert-array-to-xml-in-php/ + * + * @param array $data + * @param $output + */ + protected function arrayToXML(array $data, &$output) + { + foreach ($data as $key => $value) + { + if (is_array($value)) + { + if (! is_numeric($key)) + { + $subnode = $output->addChild("$key"); + $this->arrayToXML($value, $subnode); + } else + { + $subnode = $output->addChild("item{$key}"); + $this->arrayToXML($value, $subnode); + } + } else + { + $output->addChild("$key", htmlspecialchars("$value")); + } + } + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- } diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 5d91ca821fc1..e264890dfac8 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -66,7 +66,7 @@ class FileLocator { */ public function __construct(Autoload $autoload) { - $this->namespaces = $autoload->psr4; + $this->namespaces = $autoload->psr4; unset($autoload); @@ -154,133 +154,133 @@ public function locateFile(string $file, string $folder=null, string $ext = 'php //-------------------------------------------------------------------- - /** - * Searches through all of the defined namespaces looking for a file. - * Returns an array of all found locations for the defined file. - * - * Example: - * - * $locator->search('Config/Routes.php'); - * // Assuming PSR4 namespaces include foo and bar, might return: - * [ - * 'application/modules/foo/Config/Routes.php', - * 'application/modules/bar/Config/Routes.php', - * ] - * - * @param string $path - * @param string $ext - * - * @return array - */ - public function search(string $path, string $ext = 'php'): array - { - $foundPaths = []; - - // Ensure the extension is on the filename - $path = strpos($path, '.'.$ext) !== false - ? $path - : $path.'.'.$ext; - - foreach ($this->namespaces as $name => $folder) - { - $folder = rtrim($folder, '/').'/'; - - if (file_exists($folder.$path)) - { - $foundPaths[] = $folder.$path; - } - } - - // Remove any duplicates - $foundPaths = array_unique($foundPaths); - - return $foundPaths; - } - - //-------------------------------------------------------------------- - - /** - * Attempts to load a file and instantiate a new class by looking - * at its full path and comparing that to our existing psr4 namespaces - * in Autoloader config file. - * - * @param string $path - * - * @return string|void - */ - public function findQualifiedNameFromPath(string $path) - { - $path = realpath($path); - - if (! $path) - { - return; - } - - foreach ($this->namespaces as $namespace => $nsPath) - { - if (is_numeric($namespace)) continue; - - if (mb_strpos($path, $nsPath) === 0) - { - $className = '\\'.$namespace.'\\'. - ltrim(str_replace('/', '\\', mb_substr($path, mb_strlen($nsPath))), '\\'); - - // Remove the file extension (.php) - $className = mb_substr($className, 0, -4); - - return $className; - } - } - } - - //-------------------------------------------------------------------- - - /** - * Scans the defined namespaces, returning a list of all files - * that are contained within the subpath specifed by $path. - * - * @param string $path - * - * @return array - */ - public function listFiles(string $path): array - { - if (empty($path)) return []; - - $files = []; - helper('filesystem'); - - foreach ($this->namespaces as $namespace => $nsPath) - { - $fullPath = rtrim($nsPath, '/') .'/'. $path; - - if (! is_dir($fullPath)) continue; - - $tempFiles = get_filenames($fullPath, true); - - if (! count($tempFiles)) - { - continue; - } - - $files = array_merge($files, $tempFiles); - } - - return $files; - } - - /** - * Checks the application folder to see if the file can be found. - * Only for use with filenames that DO NOT include namespacing. - * - * @param string $file - * @param string|null $folder - * - * @return string - * @internal param string $ext - * - */ + /** + * Searches through all of the defined namespaces looking for a file. + * Returns an array of all found locations for the defined file. + * + * Example: + * + * $locator->search('Config/Routes.php'); + * // Assuming PSR4 namespaces include foo and bar, might return: + * [ + * 'application/modules/foo/Config/Routes.php', + * 'application/modules/bar/Config/Routes.php', + * ] + * + * @param string $path + * @param string $ext + * + * @return array + */ + public function search(string $path, string $ext = 'php'): array + { + $foundPaths = []; + + // Ensure the extension is on the filename + $path = strpos($path, '.'.$ext) !== false + ? $path + : $path.'.'.$ext; + + foreach ($this->namespaces as $name => $folder) + { + $folder = rtrim($folder, '/').'/'; + + if (file_exists($folder.$path)) + { + $foundPaths[] = $folder.$path; + } + } + + // Remove any duplicates + $foundPaths = array_unique($foundPaths); + + return $foundPaths; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to load a file and instantiate a new class by looking + * at its full path and comparing that to our existing psr4 namespaces + * in Autoloader config file. + * + * @param string $path + * + * @return string|void + */ + public function findQualifiedNameFromPath(string $path) + { + $path = realpath($path); + + if (! $path) + { + return; + } + + foreach ($this->namespaces as $namespace => $nsPath) + { + if (is_numeric($namespace)) continue; + + if (mb_strpos($path, $nsPath) === 0) + { + $className = '\\'.$namespace.'\\'. + ltrim(str_replace('/', '\\', mb_substr($path, mb_strlen($nsPath))), '\\'); + + // Remove the file extension (.php) + $className = mb_substr($className, 0, -4); + + return $className; + } + } + } + + //-------------------------------------------------------------------- + + /** + * Scans the defined namespaces, returning a list of all files + * that are contained within the subpath specifed by $path. + * + * @param string $path + * + * @return array + */ + public function listFiles(string $path): array + { + if (empty($path)) return []; + + $files = []; + helper('filesystem'); + + foreach ($this->namespaces as $namespace => $nsPath) + { + $fullPath = rtrim($nsPath, '/') .'/'. $path; + + if (! is_dir($fullPath)) continue; + + $tempFiles = get_filenames($fullPath, true); + + if (! count($tempFiles)) + { + continue; + } + + $files = array_merge($files, $tempFiles); + } + + return $files; + } + + /** + * Checks the application folder to see if the file can be found. + * Only for use with filenames that DO NOT include namespacing. + * + * @param string $file + * @param string|null $folder + * + * @return string + * @internal param string $ext + * + */ protected function legacyLocate(string $file, string $folder=null): string { $paths = [APPPATH, BASEPATH]; diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php index 0cb24db670de..4c7ff1d0bf68 100644 --- a/system/CLI/BaseCommand.php +++ b/system/CLI/BaseCommand.php @@ -49,102 +49,102 @@ */ abstract class BaseCommand { - /** - * The group the command is lumped under - * when listing commands. - * - * @var string - */ - protected $group; - - /** - * The Command's name - * - * @var string - */ - protected $name; - - /** - * the Command's short description - * - * @var string - */ - protected $description; - - /** - * @var \Psr\Log\LoggerInterface - */ - protected $logger; - - /** - * Instance of the CommandRunner controller - * so commands can call other commands. - * - * @var \CodeIgniter\CLI\CommandRunner - */ - protected $commands; - - //-------------------------------------------------------------------- - - public function __construct(LoggerInterface $logger, CommandRunner $commands) - { - $this->logger = $logger; - $this->commands = $commands; - } - - //-------------------------------------------------------------------- - - abstract public function run(array $params); - - //-------------------------------------------------------------------- - - /** - * Can be used by a command to run other commands. - * - * @param string $command - * @param array $params - */ - protected function call(string $command, array $params=[]) - { - // The CommandRunner will grab the first element - // for the command name. - array_unshift($params, $command); - - return $this->commands->index($params); - } - - //-------------------------------------------------------------------- - - /** - * A simple method to display an error with line/file, - * in child commands. - * - * @param \Exception $e - */ - protected function showError(\Exception $e) - { - CLI::newLine(); - CLI::error($e->getMessage()); - CLI::write($e->getFile().' - '.$e->getLine()); - CLI::newLine(); - } - - //-------------------------------------------------------------------- - - /** - * Makes it simple to access our protected properties. - * - * @param string $key - * - * @return mixed - */ - public function __get(string $key) - { - if (isset($this->$key)) - { - return $this->$key; - } - } - - //-------------------------------------------------------------------- + /** + * The group the command is lumped under + * when listing commands. + * + * @var string + */ + protected $group; + + /** + * The Command's name + * + * @var string + */ + protected $name; + + /** + * the Command's short description + * + * @var string + */ + protected $description; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * Instance of the CommandRunner controller + * so commands can call other commands. + * + * @var \CodeIgniter\CLI\CommandRunner + */ + protected $commands; + + //-------------------------------------------------------------------- + + public function __construct(LoggerInterface $logger, CommandRunner $commands) + { + $this->logger = $logger; + $this->commands = $commands; + } + + //-------------------------------------------------------------------- + + abstract public function run(array $params); + + //-------------------------------------------------------------------- + + /** + * Can be used by a command to run other commands. + * + * @param string $command + * @param array $params + */ + protected function call(string $command, array $params=[]) + { + // The CommandRunner will grab the first element + // for the command name. + array_unshift($params, $command); + + return $this->commands->index($params); + } + + //-------------------------------------------------------------------- + + /** + * A simple method to display an error with line/file, + * in child commands. + * + * @param \Exception $e + */ + protected function showError(\Exception $e) + { + CLI::newLine(); + CLI::error($e->getMessage()); + CLI::write($e->getFile().' - '.$e->getLine()); + CLI::newLine(); + } + + //-------------------------------------------------------------------- + + /** + * Makes it simple to access our protected properties. + * + * @param string $key + * + * @return mixed + */ + public function __get(string $key) + { + if (isset($this->$key)) + { + return $this->$key; + } + } + + //-------------------------------------------------------------------- } diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 8f5e06c4f787..6773636afb12 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -112,10 +112,10 @@ class CLI 'light_gray' => '47', ]; - /** - * List of array segments. - * @var array - */ + /** + * List of array segments. + * @var array + */ protected static $segments = []; protected static $options = []; @@ -127,9 +127,9 @@ class CLI */ public static function init() { - // Readline is an extension for PHP that makes interactivity with PHP - // much more bash-like. - // http://www.php.net/manual/en/readline.installation.php + // Readline is an extension for PHP that makes interactivity with PHP + // much more bash-like. + // http://www.php.net/manual/en/readline.installation.php static::$readline_support = extension_loaded('readline'); static::parseCommandLine(); @@ -607,170 +607,170 @@ public static function wrap(string $string = null, int $max = 0, int $pad_left = //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - // Command-Line 'URI' support - //-------------------------------------------------------------------- - - /** - * Parses the command line it was called from and collects all - * options and valid segments. - * - * I tried to use getopt but had it fail occassionally to find any - * options but argc has always had our back. We don't have all of the power - * of getopt but this does us just fine. - */ - protected static function parseCommandLine() - { - $optionsFound = false; - - 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) - { - 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; - - if (mb_substr($_SERVER['argv'][$i], 0, 1) != '-') - { - continue; - } - - $arg = str_replace('-', '', $_SERVER['argv'][$i]); - $value = null; - - // if the next item doesn't have a dash it's a value. - if (isset($_SERVER['argv'][$i+1]) && mb_substr($_SERVER['argv'][$i+1], 0, 1) != '-') - { - $value = $_SERVER['argv'][$i+1]; - $i++; - } - - static::$options[$arg] = $value; - - // Reset $optionsFound so it can collect segments - // past any options. - $optionsFound = false; - } - } - - //-------------------------------------------------------------------- - - /** - * Returns the command line string portions of the arguments, minus - * any options, as a string. This is used to pass along to the main - * CodeIgniter application. - * - * @return string - */ - public static function getURI() - { - return implode(' ', static::$segments); - } - - //-------------------------------------------------------------------- - - /** - * Returns an individual segment. - * - * This ignores any options that might have been dispersed between - * valid segments in the command: - * - * // segment(3) is 'three', not '-f' or 'anOption' - * > ci.php one two -f anOption three - * - * @param int $index - * - * @return mixed|null - */ - public static function getSegment(int $index) - { - if (! isset(static::$segments[$index-1])) - { - return null; - } - - return static::$segments[$index-1]; - } - - //-------------------------------------------------------------------- - - /** - * Gets a single command-line option. Returns TRUE if the option - * exists, but doesn't have a value, and is simply acting as a flag. - * - * @param string $name - * - * @return bool|mixed|null - */ - public static function getOption(string $name) - { - if (! array_key_exists($name, static::$options)) - { - return null; - } - - // If the option didn't have a value, simply return TRUE - // so they know it was set, otherwise return the actual value. - $val = static::$options[$name] === null - ? true - : static::$options[$name]; - - return $val; - } - - //-------------------------------------------------------------------- - - /** - * Returns the raw array of options found. - * - * @return array - */ - public static function getOptions() - { - return static::$options; - } - - //-------------------------------------------------------------------- - - /** - * Returns the options a string, suitable for passing along on - * the CLI to other commands. - * - * @return string - */ - public static function getOptionString(): string - { - if (! count(static::$options)) - { - return ''; - } - - $out = ''; - - foreach (static::$options as $name => $value) - { - // If there's a space, we need to group - // so it will pass correctly. - if (mb_strpos($value, ' ') !== false) - { - $value = '"'.$value.'"'; - } - - $out .= "-{$name} $value "; - } - - return $out; - } - - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // Command-Line 'URI' support + //-------------------------------------------------------------------- + + /** + * Parses the command line it was called from and collects all + * options and valid segments. + * + * I tried to use getopt but had it fail occassionally to find any + * options but argc has always had our back. We don't have all of the power + * of getopt but this does us just fine. + */ + protected static function parseCommandLine() + { + $optionsFound = false; + + 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) + { + 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; + + if (mb_substr($_SERVER['argv'][$i], 0, 1) != '-') + { + continue; + } + + $arg = str_replace('-', '', $_SERVER['argv'][$i]); + $value = null; + + // if the next item doesn't have a dash it's a value. + if (isset($_SERVER['argv'][$i+1]) && mb_substr($_SERVER['argv'][$i+1], 0, 1) != '-') + { + $value = $_SERVER['argv'][$i+1]; + $i++; + } + + static::$options[$arg] = $value; + + // Reset $optionsFound so it can collect segments + // past any options. + $optionsFound = false; + } + } + + //-------------------------------------------------------------------- + + /** + * Returns the command line string portions of the arguments, minus + * any options, as a string. This is used to pass along to the main + * CodeIgniter application. + * + * @return string + */ + public static function getURI() + { + return implode(' ', static::$segments); + } + + //-------------------------------------------------------------------- + + /** + * Returns an individual segment. + * + * This ignores any options that might have been dispersed between + * valid segments in the command: + * + * // segment(3) is 'three', not '-f' or 'anOption' + * > ci.php one two -f anOption three + * + * @param int $index + * + * @return mixed|null + */ + public static function getSegment(int $index) + { + if (! isset(static::$segments[$index-1])) + { + return null; + } + + return static::$segments[$index-1]; + } + + //-------------------------------------------------------------------- + + /** + * Gets a single command-line option. Returns TRUE if the option + * exists, but doesn't have a value, and is simply acting as a flag. + * + * @param string $name + * + * @return bool|mixed|null + */ + public static function getOption(string $name) + { + if (! array_key_exists($name, static::$options)) + { + return null; + } + + // If the option didn't have a value, simply return TRUE + // so they know it was set, otherwise return the actual value. + $val = static::$options[$name] === null + ? true + : static::$options[$name]; + + return $val; + } + + //-------------------------------------------------------------------- + + /** + * Returns the raw array of options found. + * + * @return array + */ + public static function getOptions() + { + return static::$options; + } + + //-------------------------------------------------------------------- + + /** + * Returns the options a string, suitable for passing along on + * the CLI to other commands. + * + * @return string + */ + public static function getOptionString(): string + { + if (! count(static::$options)) + { + return ''; + } + + $out = ''; + + foreach (static::$options as $name => $value) + { + // If there's a space, we need to group + // so it will pass correctly. + if (mb_strpos($value, ' ') !== false) + { + $value = '"'.$value.'"'; + } + + $out .= "-{$name} $value "; + } + + return $out; + } + + //-------------------------------------------------------------------- } diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php index 9dccbd8590d7..4ccc27b6a77b 100644 --- a/system/CLI/CommandRunner.php +++ b/system/CLI/CommandRunner.php @@ -40,133 +40,133 @@ class CommandRunner extends Controller { - /** - * Stores the info about found Commands. - * - * @var array - */ - protected $commands = []; - - //-------------------------------------------------------------------- - - /** - * We map all un-routed CLI methods through this function - * so we have the chance to look for a Command first. - * - * @param $method - * @param array ...$params - */ - public function _remap($method, ...$params) - { - // The first param is usually empty, so scrap it. - if (empty($params[0])) - { - array_shift($params); - } - - $this->index($params); - } - - //-------------------------------------------------------------------- - - public function index(array $params) - { - $command = array_shift($params); - - $this->createCommandList($command); - - if (is_null($command)) - { - $command = 'help'; - } - - return $this->runCommand($command, $params); - } - - //-------------------------------------------------------------------- - - /** - * Actually runs the command. - * - * @param string $command - */ - protected function runCommand(string $command, array $params) - { - if (! isset($this->commands[$command])) - { - CLI::error('Command \''.$command.'\' not found'); - CLI::newLine(); - return; - } - - // The file would have already been loaded during the - // createCommandList function... - $className = $this->commands[$command]['class']; - $class = new $className($this->logger, $this); - - return $class->run($params); - } - - //-------------------------------------------------------------------- - - /** - * Scans all Commands directories and prepares a list - * of each command with it's group and file. - * - * @return null|void - */ - protected function createCommandList() - { - $files = service('locator')->listFiles("Commands/"); - - // If no matching command files were found, bail - if (empty($files)) - { - return; - } - - // Loop over each file checking to see if a command with that - // alias exists in the class. If so, return it. Otherwise, try the next. - foreach ($files as $file) - { - $className = service('locator')->findQualifiedNameFromPath($file); - - if (empty($className) || ! class_exists($className)) - { - continue; - } - - $class = new $className($this->logger, $this); - - // Store it! - if ($class->group !== null) - { - $this->commands[$class->name] = [ - 'class' => $className, - 'file' => $file, - 'group' => $class->group, - 'description' => $class->description - ]; - } - - $class = null; - unset($class); - } - - asort($this->commands); - } - - //-------------------------------------------------------------------- - - /** - * Allows access to the current commands that have been found. - * - * @return array - */ - public function getCommands() - { - return $this->commands; - } - - //-------------------------------------------------------------------- + /** + * Stores the info about found Commands. + * + * @var array + */ + protected $commands = []; + + //-------------------------------------------------------------------- + + /** + * We map all un-routed CLI methods through this function + * so we have the chance to look for a Command first. + * + * @param $method + * @param array ...$params + */ + public function _remap($method, ...$params) + { + // The first param is usually empty, so scrap it. + if (empty($params[0])) + { + array_shift($params); + } + + $this->index($params); + } + + //-------------------------------------------------------------------- + + public function index(array $params) + { + $command = array_shift($params); + + $this->createCommandList($command); + + if (is_null($command)) + { + $command = 'help'; + } + + return $this->runCommand($command, $params); + } + + //-------------------------------------------------------------------- + + /** + * Actually runs the command. + * + * @param string $command + */ + protected function runCommand(string $command, array $params) + { + if (! isset($this->commands[$command])) + { + CLI::error('Command \''.$command.'\' not found'); + CLI::newLine(); + return; + } + + // The file would have already been loaded during the + // createCommandList function... + $className = $this->commands[$command]['class']; + $class = new $className($this->logger, $this); + + return $class->run($params); + } + + //-------------------------------------------------------------------- + + /** + * Scans all Commands directories and prepares a list + * of each command with it's group and file. + * + * @return null|void + */ + protected function createCommandList() + { + $files = service('locator')->listFiles("Commands/"); + + // If no matching command files were found, bail + if (empty($files)) + { + return; + } + + // Loop over each file checking to see if a command with that + // alias exists in the class. If so, return it. Otherwise, try the next. + foreach ($files as $file) + { + $className = service('locator')->findQualifiedNameFromPath($file); + + if (empty($className) || ! class_exists($className)) + { + continue; + } + + $class = new $className($this->logger, $this); + + // Store it! + if ($class->group !== null) + { + $this->commands[$class->name] = [ + 'class' => $className, + 'file' => $file, + 'group' => $class->group, + 'description' => $class->description + ]; + } + + $class = null; + unset($class); + } + + asort($this->commands); + } + + //-------------------------------------------------------------------- + + /** + * Allows access to the current commands that have been found. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + + //-------------------------------------------------------------------- } diff --git a/system/Config/Services.php b/system/Config/Services.php index f8194d8b8c23..43024dd79f07 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -249,10 +249,10 @@ public static function language(string $locale = null, $getShared = true) */ public static function locator($getShared = true) { - if ($getShared) - { - return self::getSharedInstance('locator'); - } + if ($getShared) + { + return self::getSharedInstance('locator'); + } return new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); } @@ -277,10 +277,10 @@ public static function logger($getShared = true) public static function migrations(BaseConfig $config = null, ConnectionInterface $db = null, bool $getShared = true) { - if ($getShared) - { - return self::getSharedInstance('migrations', $config, $db); - } + if ($getShared) + { + return self::getSharedInstance('migrations', $config, $db); + } $config = empty($config) ? new \Config\Migrations() : $config; @@ -334,25 +334,25 @@ public static function pager($config = null, RendererInterface $view = null, $ge //-------------------------------------------------------------------- - /** - * The Parser is a simple template parser. - */ - public static function parser($viewPath = APPPATH.'Views/', $config = null, $getShared = true) - { - if ($getShared) - { - return self::getSharedInstance('parser', $viewPath, $config); - } + /** + * The Parser is a simple template parser. + */ + public static function parser($viewPath = APPPATH.'Views/', $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('parser', $viewPath, $config); + } - if (is_null($config)) - { - $config = new \Config\View(); - } + if (is_null($config)) + { + $config = new \Config\View(); + } - return new \CodeIgniter\View\Parser($config, $viewPath, self::locator(true), CI_DEBUG, self::logger(true)); - } + return new \CodeIgniter\View\Parser($config, $viewPath, self::locator(true), CI_DEBUG, self::logger(true)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- /** * The Renderer class is the class that actually displays a file to the user. @@ -367,9 +367,9 @@ public static function renderer($viewPath = APPPATH.'Views/', $config = null, $g } if (is_null($config)) - { - $config = new \Config\View(); - } + { + $config = new \Config\View(); + } return new \CodeIgniter\View\View($config, $viewPath, self::locator(true), CI_DEBUG, self::logger(true)); } @@ -509,21 +509,21 @@ public static function session(\Config\App $config = null, $getShared = true) //-------------------------------------------------------------------- - /** - * The Throttler class provides a simple method for implementing - * rate limiting in your applications. - */ - public static function throttler($getShared = true) - { - if ($getShared) - { - return self::getSharedInstance('throttler'); - } + /** + * The Throttler class provides a simple method for implementing + * rate limiting in your applications. + */ + public static function throttler($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('throttler'); + } - return new \CodeIgniter\Throttle\Throttler(self::cache()); - } + return new \CodeIgniter\Throttle\Throttler(self::cache()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- /** * The Timer class provides a simple way to Benchmark portions of your @@ -573,25 +573,25 @@ public static function uri($uri = null, $getShared = true) //-------------------------------------------------------------------- - /** - * The Validation class provides tools for validating input data. - */ - public static function validation(\Config\Validation $config = null, $getShared = true) - { - if ($getShared) - { - return self::getSharedInstance('validation', $config); - } + /** + * The Validation class provides tools for validating input data. + */ + public static function validation(\Config\Validation $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('validation', $config); + } - if (is_null($config)) - { - $config = new \Config\Validation(); - } + if (is_null($config)) + { + $config = new \Config\Validation(); + } - return new \CodeIgniter\Validation\Validation($config, self::renderer()); - } + return new \CodeIgniter\Validation\Validation($config, self::renderer()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- /** * View cells are intended to let you insert HTML into view From 26a1a71478402c8f0979562efba9cd30398d487f Mon Sep 17 00:00:00 2001 From: TAKEKOSHI Akishige Date: Sun, 5 Feb 2017 14:17:55 +0900 Subject: [PATCH 0481/1807] fix forge using composite key and add getting indexes metadata --- system/Database/BaseConnection.php | 15 ++++++ system/Database/Forge.php | 30 ++++-------- system/Database/MySQLi/Connection.php | 51 +++++++++++++++++++++ system/Database/Postgre/Connection.php | 35 ++++++++++++++ tests/system/Database/Live/ForgeTest.php | 43 +++++++++++++++++ user_guide_src/source/database/metadata.rst | 9 +++- 6 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 tests/system/Database/Live/ForgeTest.php diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 4894d33ad57d..5a76e4969a51 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1610,6 +1610,21 @@ public function getFieldData(string $table) //-------------------------------------------------------------------- + /** + * Returns an object with key data + * + * @param string $table the table name + * @return array + */ + public function getIndexData(string $table) + { + $fields = $this->_indexData($this->protectIdentifiers($table, true, false, false)); + + return $fields ?? false; + } + + //-------------------------------------------------------------------- + /** * Allows the engine to be set into a mode where queries are not * actually executed, but they are still generated, timed, etc. diff --git a/system/Database/Forge.php b/system/Database/Forge.php index ff9feb256c77..fc31e1a4d08a 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -271,19 +271,12 @@ public function dropDatabase($db_name) */ public function addKey($key, $primary = false) { - if (is_array($key)) + if ($primary === true) { - foreach ($key as $one) + foreach ((array) $key as $one) { - $this->addKey($one, $primary); + $this->primaryKeys[] = $one; } - - return $this; - } - - if ($primary === true) - { - $this->primaryKeys[] = $key; } else { @@ -1068,25 +1061,20 @@ protected function _processIndexes($table) for ($i = 0, $c = count($this->keys); $i < $c; $i++) { - if (is_array($this->keys[$i])) + $this->keys[$i] = (array) $this->keys[$i]; + + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) { - for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + if ( ! isset($this->fields[$this->keys[$i][$i2]])) { - if ( ! isset($this->fields[$this->keys[$i][$i2]])) - { - unset($this->keys[$i][$i2]); - continue; - } + unset($this->keys[$i][$i2]); } } - elseif ( ! isset($this->fields[$this->keys[$i]])) + if (count($this->keys[$i]) <= 0) { - unset($this->keys[$i]); continue; } - is_array($this->keys[$i]) OR $this->keys[$i] = [$this->keys[$i]]; - $sqls[] = 'CREATE INDEX '.$this->db->escapeIdentifiers($table.'_'.implode('_', $this->keys[$i])) .' ON '.$this->db->escapeIdentifiers($table) .' ('.implode(', ', $this->db->escapeIdentifiers($this->keys[$i])).');'; diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index e520e404eacc..ef71a6ef9521 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -431,6 +431,57 @@ public function _fieldData(string $table) return $retval; } + //-------------------------------------------------------------------- + + /** + * Returns an object with index data + * + * @param string $table + * @return array + */ + public function _indexData(string $table) + { + if (($query = $this->query('SHOW CREATE TABLE '.$this->protectIdentifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $row = $query->getRowArray(); + if ( ! $row) { + return FALSE; + } + + $retval = array(); + foreach (explode("\n", $row['Create Table']) as $line) + { + $line = trim($line); + if (strpos($line, 'PRIMARY KEY') === 0) { + $obj = new \stdClass(); + $obj->name = 'PRIMARY KEY'; + $_fields = explode(',', preg_replace('/^.*\((.+)\).*$/', '$1', $line)); + $obj->fields = array_map(function($v){ return trim($v, '`'); }, $_fields); + + $retval[] = $obj; + } + elseif (strpos($line, 'UNIQUE KEY') === 0 || strpos($line, 'KEY') === 0) + { + if (preg_match('/KEY `([^`]+)` \((.+)\)/', $line, $matches)) { + $obj = new \stdClass(); + $obj->name = $matches[1]; + $obj->fields = array_map(function($v){ return trim($v, '`'); }, explode(',', $matches[2])); + + $retval[] = $obj; + } + else + { + throw new \LogicException('parsing key string failed.'); + } + } + } + + return $retval; + } + + //-------------------------------------------------------------------- /** diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 655185c11e96..5b7d4a8d5206 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -320,6 +320,41 @@ public function _fieldData(string $table) //-------------------------------------------------------------------- + /** + * Returns an object with index data + * + * @param string $table + * @return array + */ + public function _indexData(string $table) + { + $sql = 'SELECT "indexname", "indexdef" + FROM "pg_indexes" + WHERE LOWER("tablename") = '.$this->escape(strtolower($table)).' + AND "schemaname" = '.$this->escape('public'); + + if (($query = $this->query($sql)) === false) + { + return false; + } + $query = $query->getResultObject(); + + $retval = []; + foreach ($query as $row) + { + $obj = new \stdClass(); + $obj->name = $row->indexname; + $_fields = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); + $obj->fields = array_map(function($v){ return trim($v); }, $_fields); + + $retval[] = $obj; + } + + return $retval; + } + + //-------------------------------------------------------------------- + /** * Returns the last error code and message. * diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php new file mode 100644 index 000000000000..41378b709375 --- /dev/null +++ b/tests/system/Database/Live/ForgeTest.php @@ -0,0 +1,43 @@ +forge = \Config\Database::forge($this->DBGroup); + } + + public function testCompositeKey() + { + $this->forge->addField([ + 'id' => [ + 'type' => 'INTEGER', + 'constraint' => 3, + 'auto_increment' => true, + ], + 'code' => [ + 'type' => 'VARCHAR', + 'constraint' => 40, + ], + 'company' => [ + 'type' => 'VARCHAR', + 'constraint' => 40, + ], + ]); + $this->forge->addKey('id', true); + $this->forge->addKey(['code', 'company']); + $this->forge->createTable('forge_test_1', true); + + $keys = $this->db->getIndexData('forge_test_1'); + $this->assertEquals($keys[0]->fields, ['id']); + $this->assertEquals($keys[1]->fields, ['code', 'company']); + } +} \ No newline at end of file diff --git a/user_guide_src/source/database/metadata.rst b/user_guide_src/source/database/metadata.rst index abaaa95cdd29..5fdc33d74fe0 100644 --- a/user_guide_src/source/database/metadata.rst +++ b/user_guide_src/source/database/metadata.rst @@ -94,7 +94,7 @@ performing an action. Returns a boolean TRUE/FALSE. Usage example:: Retrieve Field Metadata ======================= -**$db->fieldData()** +**$db->getFieldData()** Returns an array of objects containing field information. @@ -128,3 +128,10 @@ database: - max_length - maximum length of the column - primary_key - 1 if the column is a primary key - type - the type of the column + +List the Indexes in a Table +========================== + +**$db->getIndexData()** + +please write this, someone... \ No newline at end of file From b49cb515f23eb98949b7dcf0d9d043abcf733558 Mon Sep 17 00:00:00 2001 From: TAKEKOSHI Akishige Date: Sun, 5 Feb 2017 14:38:55 +0900 Subject: [PATCH 0482/1807] fix error on unittest when re-do --- tests/system/Database/Live/ForgeTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 41378b709375..33f9a23661ed 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -39,5 +39,7 @@ public function testCompositeKey() $keys = $this->db->getIndexData('forge_test_1'); $this->assertEquals($keys[0]->fields, ['id']); $this->assertEquals($keys[1]->fields, ['code', 'company']); + + $this->forge->dropTable('forge_test_1', true); } } \ No newline at end of file From f6512c537030964f1fb92005af9802f28b74da1f Mon Sep 17 00:00:00 2001 From: Ryan Wu Date: Mon, 6 Feb 2017 17:15:35 +0800 Subject: [PATCH 0483/1807] Add getPut for IncomingRequest Add function to get Data from PUT method. ex: $request = \Config\Services::request(); $data = $request->getPut(); --- system/HTTP/IncomingRequest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 10ddce4f4328..c8f543cee782 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -336,6 +336,20 @@ public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0) //-------------------------------------------------------------------- + /** + * Fetch an item from PUT data. + * + * @return array + */ + public function getPut() + { + parse_str($this->body, $data); + + return $data; + } + + //-------------------------------------------------------------------- + /** * Fetch an item from GET data. * From 0d6d55eee25367a8f939b63538ba2dbc8574118b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 6 Feb 2017 23:16:06 -0600 Subject: [PATCH 0484/1807] Making URI relative path resolution a non-strict affair. Fixes #396 --- system/HTTP/URI.php | 90 ++++++++++++++++------------------- tests/system/HTTP/URITest.php | 19 ++++++++ 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 1ce5ac87e44b..83442833858b 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -995,56 +995,46 @@ public function resolveRelativeURI(string $uri) $transformed = clone $relative; - // 5.2.2 Transform References - if (! empty($relative->getScheme())) - { - $transformed->setScheme($relative->getScheme()) - ->setAuthority($relative->getAuthority()) - ->setPath($relative->getPath()) - ->setQuery($relative->getQuery()); - } - else - { - if (! empty($relative->getAuthority())) - { - $transformed->setAuthority($relative->getAuthority()) - ->setPath($relative->getPath()) - ->setQuery($relative->getQuery()); - } - else - { - if ($relative->getPath() == '') - { - $transformed->setPath($this->getPath()); - - if (! is_null($relative->getQuery())) - { - $transformed->setQuery($relative->getQuery()); - } - else - { - $transformed->setQuery($this->getQuery()); - } - } - else - { - if (substr($relative->getPath(), 0, 1) == '/') - { - $transformed->setPath($relative->getPath()); - } - else - { - $transformed->setPath($this->mergePaths($this, $relative)); - } - - $transformed->setQuery($relative->getQuery()); - } - - $transformed->setAuthority($this->getAuthority()); - } - - $transformed->setScheme($this->getScheme()); - } + // 5.2.2 Transform References in a non-strict method (no scheme) + if (! empty($relative->getAuthority())) + { + $transformed->setAuthority($relative->getAuthority()) + ->setPath($relative->getPath()) + ->setQuery($relative->getQuery()); + } + else + { + if ($relative->getPath() == '') + { + $transformed->setPath($this->getPath()); + + if (! is_null($relative->getQuery())) + { + $transformed->setQuery($relative->getQuery()); + } + else + { + $transformed->setQuery($this->getQuery()); + } + } + else + { + if (substr($relative->getPath(), 0, 1) == '/') + { + $transformed->setPath($relative->getPath()); + } + else + { + $transformed->setPath($this->mergePaths($this, $relative)); + } + + $transformed->setQuery($relative->getQuery()); + } + + $transformed->setAuthority($this->getAuthority()); + } + + $transformed->setScheme($this->getScheme()); $transformed->setFragment($relative->getFragment()); diff --git a/tests/system/HTTP/URITest.php b/tests/system/HTTP/URITest.php index 76f6b365805a..6231bd54ea8e 100644 --- a/tests/system/HTTP/URITest.php +++ b/tests/system/HTTP/URITest.php @@ -440,6 +440,25 @@ public function testResolveRelativeURI($rel, $expected) //-------------------------------------------------------------------- + /** + * @dataProvider defaultResolutions + * @group single + */ + public function testResolveRelativeURIHTTPS($rel, $expected) + { + $base = 'https://a/b/c/d'; + + $expected = str_replace('http:', 'https:', $expected); + + $uri = new URI($base); + + $new = $uri->resolveRelativeURI($rel); + + $this->assertEquals($expected, (string) $new); + } + + //-------------------------------------------------------------------- + public function testAddQueryVar() { $base = 'http://example.com/foo'; From 0aef8106e5027e1ab9aacda96fd351eab2b095d3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 3 Feb 2017 16:19:45 +0700 Subject: [PATCH 0485/1807] apply phpunit 6 --- composer.json | 2 +- system/Test/CIUnitTestCase.php | 8 ++++++-- tests/system/Autoloader/FileLocatorTest.php | 7 ++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index cf39ea1e1ac2..a4f112c5bcf7 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "zendframework/zend-escaper": "^2.5" }, "require-dev": { - "phpunit/phpunit": "5.3.*", + "phpunit/phpunit": "5.3.*|^6.0", "mikey179/vfsStream": "1.6.*", "satooshi/php-coveralls": "^1.0" }, diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index 47ce961a77a4..b022eefb97f6 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -36,13 +36,17 @@ * @filesource */ -use PHPUnit_Framework_TestCase; +use PHPUnit\Framework\TestCase; use CodeIgniter\Log\TestLogger; +if (! class_exists(TestCase::class)) { + class_alias(\PHPUnit_Framework_TestCase::class, TestCase::class); +} + /** * PHPunit test case. */ -class CIUnitTestCase extends PHPUnit_Framework_TestCase +class CIUnitTestCase extends TestCase { use ReflectionHelper; diff --git a/tests/system/Autoloader/FileLocatorTest.php b/tests/system/Autoloader/FileLocatorTest.php index 17f482ea3011..1482125ac296 100644 --- a/tests/system/Autoloader/FileLocatorTest.php +++ b/tests/system/Autoloader/FileLocatorTest.php @@ -1,8 +1,13 @@ Date: Fri, 3 Feb 2017 22:19:41 +0700 Subject: [PATCH 0486/1807] expectException() and ExpectExceptionMessage() usage --- tests/system/Config/DotEnvTest.php | 13 +++++++------ tests/system/Database/BaseConnectionTest.php | 3 ++- tests/system/Database/Builder/InsertTest.php | 9 ++++++--- tests/system/Database/Builder/ReplaceTest.php | 13 +++++++------ tests/system/Database/Builder/SelectTest.php | 5 +++-- tests/system/Database/Builder/UpdateTest.php | 11 +++++++---- tests/system/Database/Live/DeleteTest.php | 2 +- tests/system/Filters/FiltersTest.php | 4 ++-- tests/system/HTTP/MessageTest.php | 2 +- tests/system/HTTP/ResponseTest.php | 14 +++++++++----- tests/system/HTTP/URITest.php | 11 +++++++---- tests/system/Language/LanguageTest.php | 2 +- tests/system/Log/LoggerTest.php | 10 ++++++---- tests/system/Pager/PagerTest.php | 2 +- tests/system/Router/RouteCollectionTest.php | 6 +++--- tests/system/Security/SecurityTest.php | 2 +- tests/system/View/ViewTest.php | 2 +- 17 files changed, 65 insertions(+), 46 deletions(-) diff --git a/tests/system/Config/DotEnvTest.php b/tests/system/Config/DotEnvTest.php index 752aa9ab90c8..347e16069804 100644 --- a/tests/system/Config/DotEnvTest.php +++ b/tests/system/Config/DotEnvTest.php @@ -4,18 +4,18 @@ class DotEnvTest extends \CIUnitTestCase { - + protected $fixturesFolder; - + //-------------------------------------------------------------------- - + public function setup() { $this->fixturesFolder = __DIR__.'/fixtures'; } - + //-------------------------------------------------------------------- - + public function testReturnsFalseIfCannotFindFile() { $dotenv = new DotEnv(__DIR__); @@ -67,7 +67,8 @@ public function testQuotedDotenvLoadsEnvironmentVars() public function testSpacedValuesWithoutQuotesThrowsException() { - $this->setExpectedException('InvalidArgumentException', '.env values containing spaces must be surrounded by quotes.'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('.env values containing spaces must be surrounded by quotes.'); $dotenv = new Dotenv($this->fixturesFolder, 'spaced-wrong.env'); $dotenv->load(); diff --git a/tests/system/Database/BaseConnectionTest.php b/tests/system/Database/BaseConnectionTest.php index 5318afadda9e..d40b33d37b27 100644 --- a/tests/system/Database/BaseConnectionTest.php +++ b/tests/system/Database/BaseConnectionTest.php @@ -76,7 +76,8 @@ public function testConnectionThrowExceptionWhenCannotConnect() { $db = new MockConnection($this->options); - $this->setExpectedException('CodeIgniter\DatabaseException', 'Unable to connect to the database.'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('Unable to connect to the database.'); $db->shouldReturn('connect', false) ->initialize(); diff --git a/tests/system/Database/Builder/InsertTest.php b/tests/system/Database/Builder/InsertTest.php index ca24690e4b85..1c412c10adfa 100644 --- a/tests/system/Database/Builder/InsertTest.php +++ b/tests/system/Database/Builder/InsertTest.php @@ -40,7 +40,8 @@ public function testThrowsExceptionOnNoValuesSet() { $builder = $this->db->table('jobs'); - $this->setExpectedException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('You must use the "set" method to update an entry.'); $builder->insert(null, true, true); } @@ -80,7 +81,8 @@ public function testInsertBatchThrowsExceptionOnNoData() { $builder = $this->db->table('jobs'); - $this->setExpectedException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); + $this->expectException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); + $this->expectExceptionMessage('You must use the "set" method to update an entry.'); $builder->insertBatch(); } @@ -90,7 +92,8 @@ public function testInsertBatchThrowsExceptionOnEmptData() { $builder = $this->db->table('jobs'); - $this->setExpectedException('CodeIgniter\DatabaseException', 'insertBatch() called with no data'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('insertBatch() called with no data'); $builder->insertBatch([]); } diff --git a/tests/system/Database/Builder/ReplaceTest.php b/tests/system/Database/Builder/ReplaceTest.php index b05013d4f15b..3534ab0de456 100644 --- a/tests/system/Database/Builder/ReplaceTest.php +++ b/tests/system/Database/Builder/ReplaceTest.php @@ -15,10 +15,10 @@ public function setUp() //-------------------------------------------------------------------- - public function testSimpleReplace() + public function testSimpleReplace() { $builder = $this->db->table('jobs'); - + $expected = "REPLACE INTO \"jobs\" (\"title\", \"name\", \"date\") VALUES (:title, :name, :date)"; $data = array( @@ -29,14 +29,15 @@ public function testSimpleReplace() $this->assertSame($expected, $builder->replace($data, true)); } - + //-------------------------------------------------------------------- public function testReplaceThrowsExceptionWithNoData() { $builder = $this->db->table('jobs'); - $this->setExpectedException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('You must use the "set" method to update an entry.'); $builder->replace(); } @@ -44,5 +45,5 @@ public function testReplaceThrowsExceptionWithNoData() //-------------------------------------------------------------------- - -} \ No newline at end of file + +} diff --git a/tests/system/Database/Builder/SelectTest.php b/tests/system/Database/Builder/SelectTest.php index caf6ed0f2059..bab721625ae2 100644 --- a/tests/system/Database/Builder/SelectTest.php +++ b/tests/system/Database/Builder/SelectTest.php @@ -200,7 +200,8 @@ public function testSelectMinThrowsExceptionOnEmptyValue() { $builder = new BaseBuilder('invoices', $this->db); - $this->setExpectedException('CodeIgniter\DatabaseException', 'The query you submitted is not valid.'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('The query you submitted is not valid.'); $builder->selectSum(''); } @@ -219,4 +220,4 @@ public function testSelectMaxWithDotNameAndNoAlias() } //-------------------------------------------------------------------- -} \ No newline at end of file +} diff --git a/tests/system/Database/Builder/UpdateTest.php b/tests/system/Database/Builder/UpdateTest.php index 26d98652f2b5..1b9b0d37e62d 100644 --- a/tests/system/Database/Builder/UpdateTest.php +++ b/tests/system/Database/Builder/UpdateTest.php @@ -66,7 +66,7 @@ public function testUpdateThrowsExceptionWithNoData() { $builder = new BaseBuilder('jobs', $this->db); - $this->setExpectedException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); + $this->expectException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); $builder->update(null, null, null, true); } @@ -120,7 +120,8 @@ public function testUpdateBatchThrowsExceptionWithNoData() { $builder = new BaseBuilder('jobs', $this->db); - $this->setExpectedException('CodeIgniter\DatabaseException', 'You must use the "set" method to update an entry.'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('You must use the "set" method to update an entry.'); $builder->updateBatch(null, 'id'); } @@ -131,7 +132,8 @@ public function testUpdateBatchThrowsExceptionWithNoID() { $builder = new BaseBuilder('jobs', $this->db); - $this->setExpectedException('CodeIgniter\DatabaseException', 'You must specify an index to match on for batch updates.'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('You must specify an index to match on for batch updates.'); $builder->updateBatch([]); } @@ -142,7 +144,8 @@ public function testUpdateBatchThrowsExceptionWithEmptySetArray() { $builder = new BaseBuilder('jobs', $this->db); - $this->setExpectedException('CodeIgniter\DatabaseException', 'updateBatch() called with no data'); + $this->expectException('CodeIgniter\DatabaseException'); + $this->expectExceptionMessage('updateBatch() called with no data'); $builder->updateBatch([], 'id'); } diff --git a/tests/system/Database/Live/DeleteTest.php b/tests/system/Database/Live/DeleteTest.php index 2cdcf973a4ea..1f0213a75c9e 100644 --- a/tests/system/Database/Live/DeleteTest.php +++ b/tests/system/Database/Live/DeleteTest.php @@ -13,7 +13,7 @@ class DeleteTest extends \CIDatabaseTestCase public function testDeleteThrowExceptionWithNoCriteria() { - $this->setExpectedException('CodeIgniter\DatabaseException'); + $this->expectException('CodeIgniter\DatabaseException'); $this->db->table('job')->delete(); } diff --git a/tests/system/Filters/FiltersTest.php b/tests/system/Filters/FiltersTest.php index d0699fe404ea..6caeb085289d 100644 --- a/tests/system/Filters/FiltersTest.php +++ b/tests/system/Filters/FiltersTest.php @@ -250,7 +250,7 @@ public function testRunThrowsWithInvalidAlias() $filters = new Filters((object)$config, $this->request, $this->response); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $uri = 'admin/foo/bar'; $filters->run($uri); @@ -272,7 +272,7 @@ public function testRunThrowsWithInvalidClassType() $filters = new Filters((object)$config, $this->request, $this->response); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $uri = 'admin/foo/bar'; $filters->run($uri); diff --git a/tests/system/HTTP/MessageTest.php b/tests/system/HTTP/MessageTest.php index 666d2330626f..532abe0c6d70 100644 --- a/tests/system/HTTP/MessageTest.php +++ b/tests/system/HTTP/MessageTest.php @@ -138,7 +138,7 @@ public function testSetProtocolWorks() public function testSetProtocolThrowsExceptionWithInvalidProtocol() { - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $this->message->setProtocolVersion('1.2'); } diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index cb638f24f1dd..100fbca49432 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -21,7 +21,7 @@ public function testSetStatusCodeThrowsExceptionForBadCodes() { $response = new Response(new App()); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $response->setStatusCode(54322); } @@ -54,7 +54,8 @@ public function testRequiresMessageWithUnknownStatusCode() { $response = new Response(new App()); - $this->setExpectedException('InvalidArgumentException', 'Unknown HTTP status code provided with no message'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Unknown HTTP status code provided with no message'); $response->setStatusCode(115); } @@ -64,7 +65,8 @@ public function testRequiresMessageWithSmallStatusCode() { $response = new Response(new App()); - $this->setExpectedException('InvalidArgumentException', '95 is not a valid HTTP return status code'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('95 is not a valid HTTP return status code'); $response->setStatusCode(95); } @@ -74,7 +76,8 @@ public function testRequiresMessageWithLargeStatusCode() { $response = new Response(new App()); - $this->setExpectedException('InvalidArgumentException', '695 is not a valid HTTP return status code'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('695 is not a valid HTTP return status code'); $response->setStatusCode(695); } @@ -84,7 +87,8 @@ public function testExceptionThrownWhenNoStatusCode() { $response = new Response(new App()); - $this->setExpectedException('BadMethodCallException', 'HTTP Response is missing a status code'); + $this->expectException('BadMethodCallException'); + $this->expectExceptionMessage('HTTP Response is missing a status code'); $response->getStatusCode(); } diff --git a/tests/system/HTTP/URITest.php b/tests/system/HTTP/URITest.php index 6231bd54ea8e..8095897a1f4e 100644 --- a/tests/system/HTTP/URITest.php +++ b/tests/system/HTTP/URITest.php @@ -194,7 +194,8 @@ public function testSetPortInvalidValues() $url = 'http://example.com/path'; $uri = new URI($url); - $this->setExpectedException('InvalidArgumentException', 'Invalid port given.'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Invalid port given.'); $uri->setPort(70000); } @@ -205,7 +206,8 @@ public function testSetPortTooSmall() $url = 'http://example.com/path'; $uri = new URI($url); - $this->setExpectedException('InvalidArgumentException', 'Invalid port given.'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Invalid port given.'); $uri->setPort(-1); } @@ -216,7 +218,8 @@ public function testSetPortZero() $url = 'http://example.com/path'; $uri = new URI($url); - $this->setExpectedException('InvalidArgumentException', 'Invalid port given.'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Invalid port given.'); $uri->setPort(0); } @@ -311,7 +314,7 @@ public function testSetQueryThrowsErrorWhenFragmentPresent() $expected = 'http://example.com/path?key=value'; - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $uri->setQuery('?key=value#fragment'); } diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index 4b1015f4ef6f..c10df3a343f3 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -6,7 +6,7 @@ public function testThrowsWithNoFileInMessage() { $lang = new MockLanguage('en'); - $this->setExpectedException('\InvalidArgumentException'); + $this->expectException('\InvalidArgumentException'); $lang->getLine('something'); } diff --git a/tests/system/Log/LoggerTest.php b/tests/system/Log/LoggerTest.php index 52f78d531ad2..75438f01204d 100644 --- a/tests/system/Log/LoggerTest.php +++ b/tests/system/Log/LoggerTest.php @@ -9,15 +9,16 @@ class LoggerTest extends \CIUnitTestCase public function setUp() { } - + //-------------------------------------------------------------------- - + public function testThrowsExceptionWithBadHandlerSettings() { $config = new LoggerConfig(); $config->handlers = null; - $this->setExpectedException('RuntimeException', 'LoggerConfig must provide at least one Handler.'); + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('LoggerConfig must provide at least one Handler.'); $logger = new Logger($config); } @@ -28,7 +29,8 @@ public function testLogThrowsExceptionOnInvalidLevel() { $config = new LoggerConfig(); - $this->setExpectedException('InvalidArgumentException', 'foo is an invalid log level.'); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('foo is an invalid log level.'); $logger = new Logger($config); diff --git a/tests/system/Pager/PagerTest.php b/tests/system/Pager/PagerTest.php index 8e0de83ef09a..4ee3910f8a70 100644 --- a/tests/system/Pager/PagerTest.php +++ b/tests/system/Pager/PagerTest.php @@ -72,7 +72,7 @@ public function testGetDetailsRecognizesGroupedPageQueryVar() public function testGetDetailsThrowExceptionIfGroupNotFound() { - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $this->pager->getDetails('foo'); } diff --git a/tests/system/Router/RouteCollectionTest.php b/tests/system/Router/RouteCollectionTest.php index 6cb4682f4971..06c0940d0814 100644 --- a/tests/system/Router/RouteCollectionTest.php +++ b/tests/system/Router/RouteCollectionTest.php @@ -581,7 +581,7 @@ public function testReverseRoutingThrowsExceptionWithBadParamCount() $routes->add('path/(:any)/to/(:num)', 'myController::goto/$1'); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $match = $routes->reverseRoute('myController::goto', 'string', 13); } @@ -593,7 +593,7 @@ public function testReverseRoutingThrowsExceptionWithNoMatch() $routes->add('path/(:any)/to/(:num)', 'myController::goto/$1/$2'); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $match = $routes->reverseRoute('myBadController::goto', 'string', 13); } @@ -605,7 +605,7 @@ public function testReverseRoutingThrowsExceptionWithBadParamTypes() $routes->add('path/(:any)/to/(:num)', 'myController::goto/$1/$2'); - $this->setExpectedException('LogicException'); + $this->expectException('LogicException'); $match = $routes->reverseRoute('myController::goto', 13, 'string'); } diff --git a/tests/system/Security/SecurityTest.php b/tests/system/Security/SecurityTest.php index fa3edb099c81..f962c0452edb 100644 --- a/tests/system/Security/SecurityTest.php +++ b/tests/system/Security/SecurityTest.php @@ -65,7 +65,7 @@ public function testCSRFVerifyThrowsExceptionOnNoMatch() 'csrf_cookie_name' => '8b9218a55906f9dcc1dc263dce7f005b' ]; - $this->setExpectedException('LogicException'); + $this->expectException('LogicException'); $security->CSRFVerify($request); } diff --git a/tests/system/View/ViewTest.php b/tests/system/View/ViewTest.php index ace0e468f0e1..285518d55b7f 100644 --- a/tests/system/View/ViewTest.php +++ b/tests/system/View/ViewTest.php @@ -157,7 +157,7 @@ public function testRendersThrowsExceptionIfFileNotFound() { $view = new View($this->config, $this->viewsDir, $this->loader); - $this->setExpectedException('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $view->setVar('testString', 'Hello World'); $view->render('missing'); From baac734bd5ff66ea8a5a6804a924a918af97ff65 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 4 Feb 2017 13:36:01 +0700 Subject: [PATCH 0487/1807] added missing SCRIPT_NAME index --- tests/system/Helpers/URLHelperTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/system/Helpers/URLHelperTest.php b/tests/system/Helpers/URLHelperTest.php index b73dd255642d..d0ed7993c2ac 100644 --- a/tests/system/Helpers/URLHelperTest.php +++ b/tests/system/Helpers/URLHelperTest.php @@ -370,6 +370,7 @@ public function testPreviousURLUsesRefererIfNeeded() $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; + $_SERVER['SCRIPT_NAME'] = __FILE__; $_SERVER['HTTP_REFERER'] = $uri1; // Since we're on a CLI, we must provide our own URI From 5d6f7429db69b7afebeda0484c52c30e4d5dedd1 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 6 Feb 2017 02:43:27 +0700 Subject: [PATCH 0488/1807] update to phpunit ^6.0 --- composer.json | 2 +- system/Test/CIUnitTestCase.php | 4 ---- tests/system/Autoloader/FileLocatorTest.php | 4 ---- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/composer.json b/composer.json index a4f112c5bcf7..d1500a8b7c97 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "zendframework/zend-escaper": "^2.5" }, "require-dev": { - "phpunit/phpunit": "5.3.*|^6.0", + "phpunit/phpunit": "^6.0", "mikey179/vfsStream": "1.6.*", "satooshi/php-coveralls": "^1.0" }, diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index b022eefb97f6..9791211d59c1 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -39,10 +39,6 @@ use PHPUnit\Framework\TestCase; use CodeIgniter\Log\TestLogger; -if (! class_exists(TestCase::class)) { - class_alias(\PHPUnit_Framework_TestCase::class, TestCase::class); -} - /** * PHPunit test case. */ diff --git a/tests/system/Autoloader/FileLocatorTest.php b/tests/system/Autoloader/FileLocatorTest.php index 1482125ac296..d81115b0dc79 100644 --- a/tests/system/Autoloader/FileLocatorTest.php +++ b/tests/system/Autoloader/FileLocatorTest.php @@ -3,10 +3,6 @@ use Config\MockAutoload; use PHPUnit\Framework\TestCase; -if (! class_exists(TestCase::class)) { - class_alias(\PHPUnit_Framework_TestCase::class, TestCase::class); -} - class FileLocatorTest extends TestCase { /** From 4d9df226a05cfc43df71a68b9b72bd8221c42f14 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 6 Feb 2017 02:59:25 +0700 Subject: [PATCH 0489/1807] remove $_SERVER[SCRIPT_NAME] definition --- tests/system/Helpers/URLHelperTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/system/Helpers/URLHelperTest.php b/tests/system/Helpers/URLHelperTest.php index d0ed7993c2ac..b73dd255642d 100644 --- a/tests/system/Helpers/URLHelperTest.php +++ b/tests/system/Helpers/URLHelperTest.php @@ -370,7 +370,6 @@ public function testPreviousURLUsesRefererIfNeeded() $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/'; - $_SERVER['SCRIPT_NAME'] = __FILE__; $_SERVER['HTTP_REFERER'] = $uri1; // Since we're on a CLI, we must provide our own URI From 149ea16b53ba950e79dab94201a4649e9985416b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 7 Feb 2017 07:20:56 +0700 Subject: [PATCH 0490/1807] use backupGlobals="true" --- phpunit.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c0b4ffefc64b..d1a1d389aff6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,6 @@ Date: Wed, 8 Feb 2017 08:28:08 +0900 Subject: [PATCH 0491/1807] Fix path of phpunit commnad --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index 90159a8f3d0f..0856e484fbe3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -40,7 +40,7 @@ You can run the tests without running the live database tests. To generate coverage information, including HTML reports you can view in your browser, you can use the following command: - > phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ + > ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ This runs all of the tests again, collecting information about how many lines, functions, and files are tested, and the percent of the code that is covered by the tests. It is collected in two formats: a simple text file that provides an overview, as well as comprehensive collection of HTML files that show the status of every line of code in the project. From 1d6f869b948ba6faaec37b91c8084eccdaf37c54 Mon Sep 17 00:00:00 2001 From: tianhe1986 Date: Wed, 8 Feb 2017 18:46:58 +0800 Subject: [PATCH 0492/1807] Handler object and resource in \CodeIgniter\View\Parser::parsePair. Signed-off-by: tianhe1986 --- system/View/Parser.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/View/Parser.php b/system/View/Parser.php index a7d00d96a5a3..148981590ea8 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -292,6 +292,14 @@ protected function parsePair(string $variable, array $data, string $template): a continue; } + else if (is_object($val)) + { + $val = 'Class: ' . get_class($val); + } + else if (is_resource($val)) + { + $val = 'Resource'; + } $temp[$this->leftDelimiter . $key . $this->rightDelimiter] = $val; } From df78c1ea1acf4a6d6f5dba6fed81513fbcc0c313 Mon Sep 17 00:00:00 2001 From: tianhe1986 Date: Wed, 8 Feb 2017 18:48:41 +0800 Subject: [PATCH 0493/1807] Reflect closure controller. Signed-off-by: tianhe1986 --- system/Debug/Toolbar/Collectors/Routes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index 2f460953f352..8c76a9d1e6f9 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -88,8 +88,8 @@ public function display(): string $route = $router->getMatchedRoute(); // Get our parameters - $method = new \ReflectionMethod($router->controllerName(), $router->methodName()); - $rawParams = $method->getParameters(); + $method = is_callable($router->controllerName()) ? new \ReflectionFunction($router->controllerName()) : new \ReflectionMethod($router->controllerName(), $router->methodName()); + $rawParams = $method->getParameters(); $params = []; foreach ($rawParams as $key => $param) From a8fcf675455c4bf2e593156659a7994544923110 Mon Sep 17 00:00:00 2001 From: tianhe1986 Date: Wed, 8 Feb 2017 18:55:13 +0800 Subject: [PATCH 0494/1807] Stop timer after running closure controller. Signed-off-by: tianhe1986 --- system/CodeIgniter.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index a18db7ed2178..47193d277b54 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -250,6 +250,11 @@ protected function handleRequest(RouteCollectionInterface $routes = null, $cache $returned = $this->runController($controller); } + else + { + $this->benchmark->stop('controller_constructor'); + $this->benchmark->stop('controller'); + } // If $returned is a string, then the controller output something, // probably a view, instead of echoing it directly. Send it along From 6d0ae12d372e07e4eae1fe61578f121b3e66310f Mon Sep 17 00:00:00 2001 From: Ryan Wu Date: Wed, 8 Feb 2017 18:55:41 +0800 Subject: [PATCH 0495/1807] Rename function getPut to getRawString Fetch data from raw stream when http request send by PUT, PATCH, DELETE --- system/HTTP/IncomingRequest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index c8f543cee782..81e784b5a66a 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -337,15 +337,16 @@ public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0) //-------------------------------------------------------------------- /** - * Fetch an item from PUT data. + * A convenience method that grabs the raw input stream and decodes + * the String into an array. * - * @return array + * @return mixed */ - public function getPut() + public function getRawString() { - parse_str($this->body, $data); + parse_str($this->body, $output); - return $data; + return $output; } //-------------------------------------------------------------------- From 302d90592b7c44399abc6c8c2d1fa7b88651df17 Mon Sep 17 00:00:00 2001 From: Ryan Wu Date: Wed, 8 Feb 2017 19:05:31 +0800 Subject: [PATCH 0496/1807] Modify note Add (send method in PUT, PATCH, DELETE) --- system/HTTP/IncomingRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 81e784b5a66a..ae836a274392 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -337,7 +337,7 @@ public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0) //-------------------------------------------------------------------- /** - * A convenience method that grabs the raw input stream and decodes + * A convenience method that grabs the raw input stream(send method in PUT, PATCH, DELETE) and decodes * the String into an array. * * @return mixed From a14a4ffc0a89408f5ce24aab3bb138dec2d676b3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 9 Feb 2017 19:33:03 +0900 Subject: [PATCH 0497/1807] Set backupGlobals false Add @backupGlobals enabled to tests which set $_SERVER --- phpunit.xml.dist | 2 +- tests/system/CodeIgniterTest.php | 3 +++ tests/system/CommonFunctionsTest.php | 3 +++ tests/system/Config/DotEnvTest.php | 3 +++ tests/system/Filters/FiltersTest.php | 3 +++ tests/system/HTTP/IncomingRequestTest.php | 3 +++ tests/system/HTTP/RequestTest.php | 3 +++ tests/system/Helpers/URLHelperTest.php | 3 +++ tests/system/Pager/PagerTest.php | 3 +++ tests/system/Router/RouteCollectionTest.php | 3 +++ tests/system/Security/SecurityTest.php | 3 +++ 11 files changed, 31 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d1a1d389aff6..d720c67e4040 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ Date: Thu, 9 Feb 2017 19:52:12 +0900 Subject: [PATCH 0498/1807] Fix a risky test: CodeIgniter\CLI\CLITest::testWait --- tests/system/CLI/CLITest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/system/CLI/CLITest.php b/tests/system/CLI/CLITest.php index edf3fbbdfb9b..2cbcd71f2c61 100644 --- a/tests/system/CLI/CLITest.php +++ b/tests/system/CLI/CLITest.php @@ -66,7 +66,9 @@ public function testShowProgressWithoutBar() public function testWait() { + $time = time(); CLI::wait(1, true); + $this->assertEquals(1, time() - $time); } } From 1942d9c1ebd9c5a22cc31579949cb5f76c251e68 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 9 Feb 2017 23:23:25 -0600 Subject: [PATCH 0499/1807] Small fix to rewrite script to allow images and css and the like to actually be served. --- rewrite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite.php b/rewrite.php index f0bee90f77ea..1de87a0d19e1 100644 --- a/rewrite.php +++ b/rewrite.php @@ -10,7 +10,7 @@ $uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)); -$path = __DIR__.'public/'.ltrim($uri,'/'); +$path = __DIR__.'/public/'.ltrim($uri,'/'); // If $path is an existing file or folder within the public folder // then let the request handle it like normal. From 278e3ee1f7fb1b8431878917f0b5bbee808ad697 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Thu, 9 Feb 2017 23:27:52 -0600 Subject: [PATCH 0500/1807] Allow closures in method-specific routes --- system/Router/RouteCollection.php | 170 +++++++++++++++++------------- 1 file changed, 94 insertions(+), 76 deletions(-) diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 40a4ffcc246e..0eee9b3fca67 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -693,18 +693,20 @@ public function resource(string $name, array $options = null): RouteCollectionIn //-------------------------------------------------------------------- - /** - * Specifies a single route to match for multiple HTTP Verbs. - * - * Example: - * $route->match( ['get', 'post'], 'users/(:num)', 'users/$1); - * - * @param array $verbs - * @param $from - * @param $to - * @param array $options - */ - public function match(array $verbs = [], string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a single route to match for multiple HTTP Verbs. + * + * Example: + * $route->match( ['get', 'post'], 'users/(:num)', 'users/$1); + * + * @param array $verbs + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function match(array $verbs = [], string $from, $to, array $options = null): RouteCollectionInterface { foreach ($verbs as $verb) { @@ -718,14 +720,16 @@ public function match(array $verbs = [], string $from, string $to, array $option //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to GET requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function get(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to GET requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function get(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'get') { @@ -737,14 +741,16 @@ public function get(string $from, string $to, array $options = null): RouteColle //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to POST requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function post(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to POST requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function post(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'post') { @@ -756,14 +762,16 @@ public function post(string $from, string $to, array $options = null): RouteColl //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to PUT requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function put(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to PUT requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function put(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'put') { @@ -775,14 +783,16 @@ public function put(string $from, string $to, array $options = null): RouteColle //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to DELETE requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function delete(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to DELETE requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function delete(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'delete') { @@ -794,14 +804,16 @@ public function delete(string $from, string $to, array $options = null): RouteCo //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to HEAD requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function head(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to HEAD requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function head(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'head') { @@ -813,14 +825,16 @@ public function head(string $from, string $to, array $options = null): RouteColl //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to PATCH requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function patch(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to PATCH requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function patch(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'patch') { @@ -832,14 +846,16 @@ public function patch(string $from, string $to, array $options = null): RouteCol //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to OPTIONS requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function options(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to OPTIONS requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function options(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'options') { @@ -851,14 +867,16 @@ public function options(string $from, string $to, array $options = null): RouteC //-------------------------------------------------------------------- - /** - * Specifies a route that is only available to command-line requests. - * - * @param $from - * @param $to - * @param array $options - */ - public function cli(string $from, string $to, array $options = null): RouteCollectionInterface + /** + * Specifies a route that is only available to command-line requests. + * + * @param $from + * @param $to + * @param array $options + * + * @return \CodeIgniter\Router\RouteCollectionInterface + */ + public function cli(string $from, $to, array $options = null): RouteCollectionInterface { if ($this->HTTPVerb == 'cli') { From 3162f65a852a89fb193c00fc8cc7148d7ea83124 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 10 Feb 2017 00:03:29 -0600 Subject: [PATCH 0501/1807] Fixing View saveData so it's not reset when multiple views are rendered within one that sets the saveData value. --- system/View/View.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/system/View/View.php b/system/View/View.php index f185876c1f2f..c4db6a4f32f5 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -95,6 +95,13 @@ class View implements RendererInterface { */ protected $config; + /** + * Whether data should be saved between renders. + * + * @var bool + */ + protected $saveData; + //-------------------------------------------------------------------- /** @@ -113,6 +120,7 @@ public function __construct($config, string $viewPath = null, $loader = null, bo $this->loader = is_null($loader) ? Services::locator() : $loader; $this->logger = is_null($logger) ? Services::logger() : $logger; $this->debug = is_null($debug) ? CI_DEBUG : $debug; + $this->saveData = $config->saveData ?: null; } //-------------------------------------------------------------------- @@ -135,9 +143,12 @@ public function render(string $view, array $options = null, $saveData = null): s { $start = microtime(true); - if (is_null($saveData)) + // Store the results here so even if + // multiple views are called in a view, it won't + // clean it unless we mean it to. + if ($saveData !== null) { - $saveData = $this->config->saveData; + $this->saveData = $saveData; } $view = str_replace('.php', '', $view).'.php'; @@ -170,7 +181,7 @@ public function render(string $view, array $options = null, $saveData = null): s // Make our view data available to the view. extract($this->data); - if ( ! $saveData) + if ( ! $this->saveData) { $this->data = []; } From ecc15972ad25a2c0c02af89b92e66b78b985aaff Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 10 Feb 2017 00:11:08 -0600 Subject: [PATCH 0502/1807] Automatically load the URL helper since 90 percent of apps will need to use it. --- system/bootstrap.php | 4 ++++ user_guide_src/source/helpers/url_helper.rst | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/system/bootstrap.php b/system/bootstrap.php index d41b35b6e5e5..30929afea1b1 100644 --- a/system/bootstrap.php +++ b/system/bootstrap.php @@ -100,6 +100,10 @@ class_alias('Config\Services', 'CodeIgniter\Services'); require COMPOSER_PATH; } +// Always load the URL helper - +// it should be used in 90% of apps. +helper('url'); + /* * --------------------------------------------------------------- * GRAB OUR CODEIGNITER INSTANCE diff --git a/user_guide_src/source/helpers/url_helper.rst b/user_guide_src/source/helpers/url_helper.rst index ff0b097cd5fe..7a5b3f477a2f 100644 --- a/user_guide_src/source/helpers/url_helper.rst +++ b/user_guide_src/source/helpers/url_helper.rst @@ -14,9 +14,7 @@ The URL Helper file contains functions that assist in working with URLs. Loading this Helper =================== -This helper is loaded using the following code:: - - helper('url'); +This helper is automatically loaded by the framework on every request. Available Functions =================== From 1d59e04fa0b251a4bbcdffcccb0d4662309e660d Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 10 Feb 2017 20:31:15 +0900 Subject: [PATCH 0503/1807] Fix SelectTest to enable --exclude-group=DatabaseLive without database It seems PHPUnit calls the constructor in SelectTest even if I add `--exclude-group=DatabaseLive`. So it causes the following error: An uncaught Exception was encountered Type: InvalidArgumentException Message: You have not selected a database type to connect to. Filename: /.../CodeIgniter4/system/Database/Database.php Line Number: 75 --- tests/system/Database/Live/SelectTest.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/system/Database/Live/SelectTest.php b/tests/system/Database/Live/SelectTest.php index d5a03d52ec4d..40cf48d01e83 100644 --- a/tests/system/Database/Live/SelectTest.php +++ b/tests/system/Database/Live/SelectTest.php @@ -11,20 +11,11 @@ class SelectTest extends \CIDatabaseTestCase protected $seed = 'CITestSeeder'; - public function __construct() - { - parent::__construct(); - - $this->db = \Config\Database::connect($this->DBGroup); - $this->db->initialize(); - } - //-------------------------------------------------------------------- - public function testSelectAllByDefault() { - $row = $this->db->table('job')->get()->getRowArray(); + $row = $this->db->table('job')->get()->getRowArray(); $this->assertArrayHasKey('id', $row); $this->assertArrayHasKey('name', $row); From 7ac1adf3eb60531a8a2b9291a31ca77869ca7138 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 10 Feb 2017 20:32:16 +0900 Subject: [PATCH 0504/1807] Fix PretendTest changes the state of `$this->db` This causes the error: 1) CodeIgniter\Database\Live\SelectTest::testSelectAllByDefault Error: Call to undefined method CodeIgniter\Database\Query::getRowArray() /.../CodeIgniter4/tests/system/Database/Live/SelectTest.php:18 --- tests/system/Database/Live/PretendTest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/system/Database/Live/PretendTest.php b/tests/system/Database/Live/PretendTest.php index f872b2fef563..54aea72923b6 100644 --- a/tests/system/Database/Live/PretendTest.php +++ b/tests/system/Database/Live/PretendTest.php @@ -7,6 +7,12 @@ */ class PretendTest extends \CIDatabaseTestCase { + public function tearDown() + { + // We share `$this->db` in testing, so we need to restore the state. + $this->db->pretend(false); + } + public function testPretendReturnsQueryObject() { $result = $this->db->pretend(false) @@ -17,11 +23,11 @@ public function testPretendReturnsQueryObject() $result = $this->db->pretend(true) ->table('user') - ->get(); + ->get(); $this->assertTrue($result instanceof Query); } //-------------------------------------------------------------------- -} \ No newline at end of file +} From 26d8c6dd38144c2bf3475efcff3e0e7f3e98f5e9 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 10 Feb 2017 20:45:03 +0900 Subject: [PATCH 0505/1807] Add missing @group DatabaseLive --- tests/system/Database/Live/PreparedQueryTest.php | 3 +++ tests/system/Validation/ValidationTest.php | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index 2d8ae3dcac88..245cdc9e651a 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -2,6 +2,9 @@ use CodeIgniter\Database\BasePreparedQuery; +/** + * @group DatabaseLive + */ class PreparedQueryTest extends \CIDatabaseTestCase { protected $refresh = true; diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index b4bfac74026f..54aaf0be963d 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -360,6 +360,9 @@ public function testDiffersFalse() //-------------------------------------------------------------------- + /** + * @group DatabaseLive + */ public function testIsUniqueFalse() { $data = [ @@ -375,6 +378,9 @@ public function testIsUniqueFalse() //-------------------------------------------------------------------- + /** + * @group DatabaseLive + */ public function testIsUniqueTrue() { $data = [ @@ -390,6 +396,9 @@ public function testIsUniqueTrue() //-------------------------------------------------------------------- + /** + * @group DatabaseLive + */ public function testIsUniqueIgnoresParams() { $db = Database::connect(); From 5bf331f09670332a482c96459da348a121a87bd3 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 10 Feb 2017 22:32:28 -0600 Subject: [PATCH 0506/1807] Providing default text/html content-type for responses. #392 --- application/Controllers/Checks.php | 7 +++++++ system/HTTP/Response.php | 3 +++ system/View/View.php | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php index 3bedc16c7f54..72bbdda91429 100644 --- a/application/Controllers/Checks.php +++ b/application/Controllers/Checks.php @@ -103,4 +103,11 @@ public function db() $query->execute('foo', 'foo@example.com', 'US'); } + public function format() + { + echo '

';
+        var_dump($this->response->getHeaderLine('content-type'));
+    }
+
+
 }
diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php
index ce0f7fea1056..64de2a69d19a 100644
--- a/system/HTTP/Response.php
+++ b/system/HTTP/Response.php
@@ -231,6 +231,9 @@ public function __construct($config)
 		$this->cookiePath     = $config->cookiePath;
 		$this->cookieSecure   = $config->cookieSecure;
 		$this->cookieHTTPOnly = $config->cookieHTTPOnly;
+
+		// Default to an HTML Content-Type. Devs can override if needed.
+        $this->setContentType('text/html');
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/View/View.php b/system/View/View.php
index c4db6a4f32f5..3305f00c96e1 100644
--- a/system/View/View.php
+++ b/system/View/View.php
@@ -120,7 +120,7 @@ public function __construct($config, string $viewPath = null, $loader = null, bo
 		$this->loader   = is_null($loader) ? Services::locator() : $loader;
 		$this->logger   = is_null($logger) ? Services::logger() : $logger;
 		$this->debug    = is_null($debug) ? CI_DEBUG : $debug;
-		$this->saveData = $config->saveData ?: null;
+		$this->saveData = $config->saveData ?? null;
 	}
 
 	//--------------------------------------------------------------------

From 319e47c979a901fe45393451a9243aa021b2ab3a Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Fri, 10 Feb 2017 22:56:33 -0600
Subject: [PATCH 0507/1807] Fixing risky test

---
 tests/system/Database/Live/ModelTest.php | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tests/system/Database/Live/ModelTest.php b/tests/system/Database/Live/ModelTest.php
index 1a324a94d981..b14bfb10aa29 100644
--- a/tests/system/Database/Live/ModelTest.php
+++ b/tests/system/Database/Live/ModelTest.php
@@ -327,8 +327,11 @@ public function testSaveProtected()
 		$data->id = 1;
 		$data->name = 'Engineer';
 		$data->description = 'A fancier term for Developer.';
+		$data->random_thing = 'Something wicked'; // If not protected, this would kill the script.
 
-		$model->protect(true)->save($data);
+		$result = $model->protect(true)->save($data);
+
+        $this->assertTrue($result);
 	}
 
 	//--------------------------------------------------------------------

From c31b969f38e716a99d527f2bc53f912e4f08dc30 Mon Sep 17 00:00:00 2001
From: kenjis 
Date: Sat, 11 Feb 2017 19:02:12 +0900
Subject: [PATCH 0508/1807] Escape $message in HTML 404 error template

---
 application/Views/errors/html/error_404.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/application/Views/errors/html/error_404.php b/application/Views/errors/html/error_404.php
index ad3e12f66fb7..47f238c96339 100644
--- a/application/Views/errors/html/error_404.php
+++ b/application/Views/errors/html/error_404.php
@@ -76,7 +76,7 @@
 
 		

- + Sorry! Cannot seem to find the page you were looking for. From 2978052bf4efaf684c94b15595d07096eabb036c Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 11 Feb 2017 19:19:05 +0900 Subject: [PATCH 0509/1807] Fix indentation Replace white spaces with tabs --- application/Config/API.php | 108 +++++----- application/Config/App.php | 24 +-- application/Config/Autoload.php | 2 +- application/Config/Hooks.php | 2 +- application/Config/Paths.php | 122 +++++------ application/Config/Validation.php | 22 +- application/Config/View.php | 18 +- application/Controllers/Checks.php | 204 +++++++++---------- application/Filters/DebugToolbar.php | 4 +- application/Filters/Throttle.php | 18 +- application/Views/errors/html/production.php | 6 +- ci.php | 2 +- 12 files changed, 266 insertions(+), 266 deletions(-) diff --git a/application/Config/API.php b/application/Config/API.php index 8e3278b99a42..e559d6824833 100644 --- a/application/Config/API.php +++ b/application/Config/API.php @@ -4,66 +4,66 @@ class API extends BaseConfig { - /* - |-------------------------------------------------------------------------- - | Available Response Formats - |-------------------------------------------------------------------------- - | - | When you perform content negotiation with the request, these are the - | available formats that your application supports. This is currently - | only used with the API\ResponseTrait. A valid Formatter must exist - | for the specified format. - | - | These formats are only checked when the data passed to the respond() - | method is an array. - | - */ - public $supportedResponseFormats = [ - 'application/json', - 'application/xml' - ]; + /* + |-------------------------------------------------------------------------- + | Available Response Formats + |-------------------------------------------------------------------------- + | + | When you perform content negotiation with the request, these are the + | available formats that your application supports. This is currently + | only used with the API\ResponseTrait. A valid Formatter must exist + | for the specified format. + | + | These formats are only checked when the data passed to the respond() + | method is an array. + | + */ + public $supportedResponseFormats = [ + 'application/json', + 'application/xml' + ]; - /* - |-------------------------------------------------------------------------- - | Formatters - |-------------------------------------------------------------------------- - | - | Lists the class to use to format responses with of a particular type. - | For each mime type, list the class that should be used. Formatters - | can be retrieved through the getFormatter() method. - | - */ - public $formatters = [ - 'application/json' => \CodeIgniter\API\JSONFormatter::class, - 'application/xml' => \CodeIgniter\API\XMLFormatter::class - ]; + /* + |-------------------------------------------------------------------------- + | Formatters + |-------------------------------------------------------------------------- + | + | Lists the class to use to format responses with of a particular type. + | For each mime type, list the class that should be used. Formatters + | can be retrieved through the getFormatter() method. + | + */ + public $formatters = [ + 'application/json' => \CodeIgniter\API\JSONFormatter::class, + 'application/xml' => \CodeIgniter\API\XMLFormatter::class + ]; - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - /** - * A Factory method to return the appropriate formatter for the given mime type. - * - * @param string $mime - * - * @return mixed - */ - public function getFormatter(string $mime) - { - if (! array_key_exists($mime, $this->formatters)) - { - throw new \InvalidArgumentException('No Formatter defined for mime type: '. $mime); - } + /** + * A Factory method to return the appropriate formatter for the given mime type. + * + * @param string $mime + * + * @return mixed + */ + public function getFormatter(string $mime) + { + if (! array_key_exists($mime, $this->formatters)) + { + throw new \InvalidArgumentException('No Formatter defined for mime type: '. $mime); + } - $class = $this->formatters[$mime]; + $class = $this->formatters[$mime]; - if (! class_exists($class)) - { - throw new \BadMethodCallException($class.' is not a valid Formatter.'); - } + if (! class_exists($class)) + { + throw new \BadMethodCallException($class.' is not a valid Formatter.'); + } - return new $class(); - } + return new $class(); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- } diff --git a/application/Config/App.php b/application/Config/App.php index 55876ac1ec05..7c270de791ff 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -101,18 +101,18 @@ class App extends BaseConfig */ public $appTimezone = 'America/Chicago'; - /* - |-------------------------------------------------------------------------- - | Default Character Set - |-------------------------------------------------------------------------- - | - | This determines which character set is used by default in various methods - | that require a character set to be provided. - | - | See http://php.net/htmlspecialchars for a list of supported charsets. - | - */ - public $charset = 'UTF-8'; + /* + |-------------------------------------------------------------------------- + | Default Character Set + |-------------------------------------------------------------------------- + | + | This determines which character set is used by default in various methods + | that require a character set to be provided. + | + | See http://php.net/htmlspecialchars for a list of supported charsets. + | + */ + public $charset = 'UTF-8'; /* |-------------------------------------------------------------------------- diff --git a/application/Config/Autoload.php b/application/Config/Autoload.php index 69b2223fd779..67042c8f7d8d 100644 --- a/application/Config/Autoload.php +++ b/application/Config/Autoload.php @@ -54,7 +54,7 @@ public function __construct() APP_NAMESPACE => APPPATH, // For custom namespace 'App' => APPPATH, // To ensure filters, etc still found ]; - + /** * ------------------------------------------------------------------- * Class Map diff --git a/application/Config/Hooks.php b/application/Config/Hooks.php index 10f262400d72..50d8da8ce666 100644 --- a/application/Config/Hooks.php +++ b/application/Config/Hooks.php @@ -29,5 +29,5 @@ */ if (ENVIRONMENT != 'production') { - Hooks::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); + Hooks::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); } diff --git a/application/Config/Paths.php b/application/Config/Paths.php index cbfe72d705f4..c58f91a1adf2 100644 --- a/application/Config/Paths.php +++ b/application/Config/Paths.php @@ -10,69 +10,69 @@ */ class Paths { - /* - *--------------------------------------------------------------- - * SYSTEM FOLDER NAME - *--------------------------------------------------------------- - * - * This variable must contain the name of your "system" folder. - * Include the path if the folder is not in the same directory - * as this file. - */ - public $systemDirectory = '../system'; + /* + *--------------------------------------------------------------- + * SYSTEM FOLDER NAME + *--------------------------------------------------------------- + * + * This variable must contain the name of your "system" folder. + * Include the path if the folder is not in the same directory + * as this file. + */ + public $systemDirectory = '../system'; - /* - *--------------------------------------------------------------- - * APPLICATION FOLDER NAME - *--------------------------------------------------------------- - * - * If you want this front controller to use a different "application" - * folder than the default one you can set its name here. The folder - * can also be renamed or relocated anywhere on your getServer. If - * you do, use a full getServer path. For more info please see the user guide: - * http://codeigniter.com/user_guide/general/managing_apps.html - * - * NO TRAILING SLASH! - */ - public $applicationDirectory = '../application'; + /* + *--------------------------------------------------------------- + * APPLICATION FOLDER NAME + *--------------------------------------------------------------- + * + * If you want this front controller to use a different "application" + * folder than the default one you can set its name here. The folder + * can also be renamed or relocated anywhere on your getServer. If + * you do, use a full getServer path. For more info please see the user guide: + * http://codeigniter.com/user_guide/general/managing_apps.html + * + * NO TRAILING SLASH! + */ + public $applicationDirectory = '../application'; - /* - * --------------------------------------------------------------- - * WRITABLE DIRECTORY NAME - * --------------------------------------------------------------- - * - * This variable must contain the name of your "writable" directory. - * The writable directory allows you to group all directories that - * need write permission to a single place that can be tucked away - * for maximum security, keeping it out of the application and/or - * system directories. - */ - public $writableDirectory = '../writable'; + /* + * --------------------------------------------------------------- + * WRITABLE DIRECTORY NAME + * --------------------------------------------------------------- + * + * This variable must contain the name of your "writable" directory. + * The writable directory allows you to group all directories that + * need write permission to a single place that can be tucked away + * for maximum security, keeping it out of the application and/or + * system directories. + */ + public $writableDirectory = '../writable'; - /* - * --------------------------------------------------------------- - * TESTS DIRECTORY NAME - * --------------------------------------------------------------- - * - * This variable must contain the name of your "tests" directory. - * The writable directory allows you to group all directories that - * need write permission to a single place that can be tucked away - * for maximum security, keeping it out of the application and/or - * system directories. - */ - public $testsDirectory = '../tests'; + /* + * --------------------------------------------------------------- + * TESTS DIRECTORY NAME + * --------------------------------------------------------------- + * + * This variable must contain the name of your "tests" directory. + * The writable directory allows you to group all directories that + * need write permission to a single place that can be tucked away + * for maximum security, keeping it out of the application and/or + * system directories. + */ + public $testsDirectory = '../tests'; - /* - * --------------------------------------------------------------- - * PUBLIC DIRECTORY NAME - * --------------------------------------------------------------- - * - * This variable must contain the name of the directory that - * contains the main index.php front-controller. By default, - * this is the `public` directory, but some hosts may not - * be able to map a primary domain to a sub-directory so you - * can change this to `public_html`, for example, to comply - * with your host's needs. - */ - public $publicDirectory = 'public'; + /* + * --------------------------------------------------------------- + * PUBLIC DIRECTORY NAME + * --------------------------------------------------------------- + * + * This variable must contain the name of the directory that + * contains the main index.php front-controller. By default, + * this is the `public` directory, but some hosts may not + * be able to map a primary domain to a sub-directory so you + * can change this to `public_html`, for example, to comply + * with your host's needs. + */ + public $publicDirectory = 'public'; } diff --git a/application/Config/Validation.php b/application/Config/Validation.php index 52e50261e95e..e9f6e29e33ee 100644 --- a/application/Config/Validation.php +++ b/application/Config/Validation.php @@ -15,19 +15,19 @@ class Validation public $ruleSets = [ \CodeIgniter\Validation\Rules::class, \CodeIgniter\Validation\FileRules::class, - \CodeIgniter\Validation\CreditCardRules::class, + \CodeIgniter\Validation\CreditCardRules::class, ]; - /** - * Specifies the views that are used to display the - * errors. - * - * @var array - */ - public $templates = [ - 'list' => 'CodeIgniter\Validation\Views\list', - 'single' => 'CodeIgniter\Validation\Views\single' - ]; + /** + * Specifies the views that are used to display the + * errors. + * + * @var array + */ + public $templates = [ + 'list' => 'CodeIgniter\Validation\Views\list', + 'single' => 'CodeIgniter\Validation\Views\single' + ]; //-------------------------------------------------------------------- // Rules diff --git a/application/Config/View.php b/application/Config/View.php index d49398f377a1..41da2946138d 100644 --- a/application/Config/View.php +++ b/application/Config/View.php @@ -2,13 +2,13 @@ class View { - /** - * When false, the view method will clear the data between each - * call. This keeps your data safe and ensures there is no accidental - * leaking between calls, so you would need to explicity pass the data - * to each view. You might prefer to have the data stick around between - * calls so that it is available to all views. If that is the case, - * set $saveData to true. - */ - public $saveData = false; + /** + * When false, the view method will clear the data between each + * call. This keeps your data safe and ensures there is no accidental + * leaking between calls, so you would need to explicity pass the data + * to each view. You might prefer to have the data stick around between + * calls so that it is available to all views. If that is the case, + * set $saveData to true. + */ + public $saveData = false; } diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php index 72bbdda91429..fcbd711289f3 100644 --- a/application/Controllers/Checks.php +++ b/application/Controllers/Checks.php @@ -6,108 +6,108 @@ class Checks extends Controller { - use ResponseTrait; - - public function index() - { - session()->start(); - } - - - public function escape() - { - $db = Database::connect(); - $db->initialize(); - - $jobs = $db->table('job') - ->whereNotIn('name', ['Politician', 'Accountant']) - ->get() - ->getResult(); - - die(var_dump($jobs)); - } - - public function password() - { - $db = Database::connect(); - $db->initialize(); - - $result = $db->table('misc') - ->insert([ - 'key' => 'password', - 'value' => '$2y$10$ErQlCj/Mo10il.FthAm0WOjYdf3chZEGPFqaPzjqOX2aj2uYf5Ihq' - ]); - - die(var_dump($result)); - } - - - public function forms() - { - helper('form'); - - var_dump(form_open()); - } - - public function api() - { - $data = array( - "total_users" => 3, - "users" => array( - array( - "id" => 1, - "name" => "Nitya", - "address" => array( - "country" => "India", - "city" => "Kolkata", - "zip" => 700102, - ) - ), - array( - "id" => 2, - "name" => "John", - "address" => array( - "country" => "USA", - "city" => "Newyork", - "zip" => "NY1234", - ) - ), - array( - "id" => 3, - "name" => "Viktor", - "address" => array( - "country" => "Australia", - "city" => "Sydney", - "zip" => 123456, - ) - ), - ) - ); - - return $this->respond($data); - } - - public function db() - { - $db = Database::connect(); - $db->initialize(); - - $query = $db->prepare(function($db){ - return $db->table('user')->insert([ - 'name' => 'a', - 'email' => 'b@example.com', - 'country' => 'x' - ]); - }); - - $query->execute('foo', 'foo@example.com', 'US'); - } - - public function format() - { - echo '

';
-        var_dump($this->response->getHeaderLine('content-type'));
-    }
+	use ResponseTrait;
+
+	public function index()
+	{
+		session()->start();
+	}
+
+
+	public function escape()
+	{
+		$db = Database::connect();
+		$db->initialize();
+
+		$jobs = $db->table('job')
+						 ->whereNotIn('name', ['Politician', 'Accountant'])
+						 ->get()
+						 ->getResult();
+
+		die(var_dump($jobs));
+	}
+
+	public function password()
+	{
+		$db = Database::connect();
+		$db->initialize();
+
+		$result = $db->table('misc')
+					->insert([
+						'key' => 'password',
+						'value' => '$2y$10$ErQlCj/Mo10il.FthAm0WOjYdf3chZEGPFqaPzjqOX2aj2uYf5Ihq'
+					]);
+
+		die(var_dump($result));
+	}
+
+
+	public function forms()
+	{
+		helper('form');
+
+		var_dump(form_open());
+	}
+
+	public function api()
+	{
+		$data = array(
+			"total_users" => 3,
+			"users" => array(
+				array(
+					"id" => 1,
+					"name" => "Nitya",
+					"address" => array(
+						"country" => "India",
+						"city" => "Kolkata",
+						"zip" => 700102,
+					)
+				),
+				array(
+					"id" => 2,
+					"name" => "John",
+					"address" => array(
+						"country" => "USA",
+						"city" => "Newyork",
+						"zip" => "NY1234",
+					)
+				),
+				array(
+					"id" => 3,
+					"name" => "Viktor",
+					"address" => array(
+						"country" => "Australia",
+						"city" => "Sydney",
+						"zip" => 123456,
+					)
+				),
+			)
+		);
+
+		return $this->respond($data);
+	}
+
+	public function db()
+	{
+		$db = Database::connect();
+		$db->initialize();
+
+		$query = $db->prepare(function($db){
+			return $db->table('user')->insert([
+				'name' => 'a',
+				'email' => 'b@example.com',
+				'country' => 'x'
+			]);
+		});
+
+		$query->execute('foo', 'foo@example.com', 'US');
+	}
+
+	public function format()
+	{
+		echo '
';
+		var_dump($this->response->getHeaderLine('content-type'));
+	}
 
 
 }
diff --git a/application/Filters/DebugToolbar.php b/application/Filters/DebugToolbar.php
index dd3c83c6e3a3..8fe4341ccc05 100644
--- a/application/Filters/DebugToolbar.php
+++ b/application/Filters/DebugToolbar.php
@@ -33,9 +33,9 @@ public function before(RequestInterface $request)
 	 */
 	public function after(RequestInterface $request, ResponseInterface $response)
 	{
-        $format = $response->getHeaderLine('content-type');
+		$format = $response->getHeaderLine('content-type');
 
-        if ( ! is_cli() && CI_DEBUG && strpos($format, 'html') !== false)
+		if ( ! is_cli() && CI_DEBUG && strpos($format, 'html') !== false)
 		{
 			global $app;
 
diff --git a/application/Filters/Throttle.php b/application/Filters/Throttle.php
index 61f50a1d1814..e88a8bc34eed 100644
--- a/application/Filters/Throttle.php
+++ b/application/Filters/Throttle.php
@@ -9,7 +9,7 @@ class Throttle implements FilterInterface
 {
 	/**
 	 * This is a demo implementation of using the Throttler class
-     * to implement rate limiting for your application.
+	 * to implement rate limiting for your application.
 	 *
 	 * @param \CodeIgniter\HTTP\RequestInterface $request
 	 *
@@ -17,15 +17,15 @@ class Throttle implements FilterInterface
 	 */
 	public function before(RequestInterface $request)
 	{
-        $throttler = Services::throttler();
+		$throttler = Services::throttler();
 
-        // Restrict an IP address to no more
-        // than 1 request per second across the
-        // entire site.
-        if ($throttler->check($request->getIPAddress(), 60, MINUTE) === false)
-        {
-            return Services::response()->setStatusCode(429);
-        }
+		// Restrict an IP address to no more
+		// than 1 request per second across the
+		// entire site.
+		if ($throttler->check($request->getIPAddress(), 60, MINUTE) === false)
+		{
+			return Services::response()->setStatusCode(429);
+		}
 	}
 
 	//--------------------------------------------------------------------
diff --git a/application/Views/errors/html/production.php b/application/Views/errors/html/production.php
index bb9bfa5ca072..b912a0152c44 100644
--- a/application/Views/errors/html/production.php
+++ b/application/Views/errors/html/production.php
@@ -1,12 +1,12 @@
 
 
 
-    
+	
 	
 
-    Whoops!
+	Whoops!
 
-    
 
diff --git a/ci.php b/ci.php
index b616b268aa15..33d05adfd6de 100755
--- a/ci.php
+++ b/ci.php
@@ -30,7 +30,7 @@
 // Refuse to run when called from php-cgi
 if (substr(php_sapi_name(), 0, 3) == 'cgi')
 {
-    die("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n");
+	die("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n");
 }
 
 // Ensure the current directory is pointing to the front controller's directory

From 5f621f2f7af41e373ea9deef6694b352f8b5109c Mon Sep 17 00:00:00 2001
From: kenjis 
Date: Sat, 11 Feb 2017 19:30:56 +0900
Subject: [PATCH 0510/1807] Fix indentation

Replace white spaces with tabs
---
 user_guide_src/autodoc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/user_guide_src/autodoc.php b/user_guide_src/autodoc.php
index aa9c9a0c6329..498a67fe67fb 100644
--- a/user_guide_src/autodoc.php
+++ b/user_guide_src/autodoc.php
@@ -66,7 +66,7 @@ public function readSource(string $source)
 			die();
 		}
 
-	    $this->source = $source;
+		$this->source = $source;
 
 		return $this;
 	}

From 3a14d67c4526225808c77ebfcd9f419413372d45 Mon Sep 17 00:00:00 2001
From: kenjis 
Date: Sat, 11 Feb 2017 19:40:39 +0900
Subject: [PATCH 0511/1807] Fix about XSS in Security Guidelines

---
 user_guide_src/source/concepts/security.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/user_guide_src/source/concepts/security.rst b/user_guide_src/source/concepts/security.rst
index 78639a61a368..2f6aa65e73f5 100644
--- a/user_guide_src/source/concepts/security.rst
+++ b/user_guide_src/source/concepts/security.rst
@@ -70,15 +70,15 @@ that can be malicious when viewed by other users to the web site.
 OWASP recommendations
 ---------------------
 
-- Presentation: validate authentication & role; set input constraints
+- Presentation: output encode all user data as per output context; set input constraints
 - Controller: positive input validation
 - Tips: only process trustworthy data; do not store data HTML encoded in DB
 
 CodeIgniter provisions
 ----------------------
 
+- esc function
 - Form validation library
-- Easy to add third party authentication
 
 ***********************************
 A4 Insecure Direct Object Reference

From bea24cf4932cbe6c8ea79907445b97459355b00a Mon Sep 17 00:00:00 2001
From: Ryan Wu 
Date: Sun, 12 Feb 2017 20:06:09 +0800
Subject: [PATCH 0512/1807] Rename function getRawString to getRawInput

Rename function getRawString to getRawInput
---
 system/HTTP/IncomingRequest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php
index ae836a274392..d7bc1812da0c 100644
--- a/system/HTTP/IncomingRequest.php
+++ b/system/HTTP/IncomingRequest.php
@@ -342,7 +342,7 @@ public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0)
 	 *
 	 * @return mixed
 	 */
-	public function getRawString()
+	public function getRawInput()
 	{
 	    parse_str($this->body, $output);
 

From 29a10f4da3320bdd913fb1f4bb703379903b2390 Mon Sep 17 00:00:00 2001
From: Jon LaBelle 
Date: Wed, 15 Feb 2017 06:14:22 -0600
Subject: [PATCH 0513/1807] Change "inbuilt" to "built-in"

---
 user_guide_src/source/concepts/security.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/user_guide_src/source/concepts/security.rst b/user_guide_src/source/concepts/security.rst
index 2f6aa65e73f5..aba173c358b0 100644
--- a/user_guide_src/source/concepts/security.rst
+++ b/user_guide_src/source/concepts/security.rst
@@ -48,7 +48,7 @@ OWASP recommendations
 ---------------------
 
 - Presentation: validate authentication & role; send CSRF token with forms
-- Design: only use inbuilt session management
+- Design: only use built-in session management
 - Controller: validate user, role, CSRF token
 - Model: validate role
 - Tip: consider the use of a request governor

From a696bdee14ce8111b226f3f81fd81dc0fd597502 Mon Sep 17 00:00:00 2001
From: Ryan Wu 
Date: Thu, 16 Feb 2017 15:03:04 +0800
Subject: [PATCH 0514/1807] Add JSON/RawInput test to IncomingRequestTest, add
 getRawInput to docs

---
 tests/system/HTTP/IncomingRequestTest.php     | 32 +++++++++++++++++++
 .../source/libraries/incomingrequest.rst      | 10 +++++-
 2 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/tests/system/HTTP/IncomingRequestTest.php b/tests/system/HTTP/IncomingRequestTest.php
index 9d329fbd3e35..53e9d0a0e66e 100644
--- a/tests/system/HTTP/IncomingRequestTest.php
+++ b/tests/system/HTTP/IncomingRequestTest.php
@@ -263,4 +263,36 @@ public function testNegotiatesLocale()
 	}
 
 	//--------------------------------------------------------------------
+
+	public function testCanGrabGetRawJSON()
+	{
+		$json = '{"code":1, "message":"ok"}';
+
+		$expected = [
+			'code' => 1,
+			'message' => 'ok'
+		];
+
+		$request = new IncomingRequest(new App(), new URI(), $json);
+		
+		$this->assertEquals($expected, $request->getJSON(true));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testCanGrabGetRawInput()
+	{
+		$rawstring = 'username=admin001&role=administrator&usepass=0';
+
+		$expected = [
+			'username' => 'admin001',
+			'role' => 'administrator',
+			'usepass' => 0
+		];
+		$request = new IncomingRequest(new App(), new URI(), $rawstring);
+
+		$this->assertEquals($expected, $request->getRawInput());
+	}
+
+	//--------------------------------------------------------------------
 }
diff --git a/user_guide_src/source/libraries/incomingrequest.rst b/user_guide_src/source/libraries/incomingrequest.rst
index b2265d00d9b3..7493d3584ee1 100644
--- a/user_guide_src/source/libraries/incomingrequest.rst
+++ b/user_guide_src/source/libraries/incomingrequest.rst
@@ -118,7 +118,7 @@ maintaining the ability to control the order you look for it:
 
 **Getting JSON data**
 
-Finally, you can grab the contents of php://input as a JSON stream with ``getJSON()``.
+You can grab the contents of php://input as a JSON stream with ``getJSON()``.
 
 .. note::  This has no way of checking if the incoming data is valid JSON or not, you should only use this
     method if you know that you're expecting JSON.
@@ -133,6 +133,14 @@ arrays, pass in ``true`` as the first parameter.
 The second and third parameters match up to the ``depth`` and ``options`` arguments of the
 `json_decode `_ PHP function.
 
+**Getting Raw data (PUT, PATCH, DELETE)**
+
+Finally, you can grab the contents of php://input as a raw stream with ``getRawInput()``.
+
+	$data = $request->getRawInput();
+
+By default, this will convert param string to an array. 
+
 Filtering Input Data
 --------------------
 

From 685219cce0040051aa9b827e7879369495c8b623 Mon Sep 17 00:00:00 2001
From: Ryan Wu 
Date: Sat, 18 Feb 2017 11:16:11 +0800
Subject: [PATCH 0515/1807] Modify getRawInput`s docs IncomingRequest.rst

---
 .../source/libraries/incomingrequest.rst         | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/user_guide_src/source/libraries/incomingrequest.rst b/user_guide_src/source/libraries/incomingrequest.rst
index 7493d3584ee1..dd4c6b998e7e 100644
--- a/user_guide_src/source/libraries/incomingrequest.rst
+++ b/user_guide_src/source/libraries/incomingrequest.rst
@@ -133,14 +133,24 @@ arrays, pass in ``true`` as the first parameter.
 The second and third parameters match up to the ``depth`` and ``options`` arguments of the
 `json_decode `_ PHP function.
 
-**Getting Raw data (PUT, PATCH, DELETE)**
+**Retrieving Raw data (PUT, PATCH, DELETE)**
 
 Finally, you can grab the contents of php://input as a raw stream with ``getRawInput()``.
 
 	$data = $request->getRawInput();
 
-By default, this will convert param string to an array. 
-
+This will retrieve data and convert it to an array. Like this::
+	
+ + + +
+ var_dump($request->getRawInput()); + + [ + 'Username' => 'Admin', + 'Role' => 'Administrator', + ] Filtering Input Data -------------------- From 1ca90234e8b233832b4f9276f2cfb8a5c2b5f5ce Mon Sep 17 00:00:00 2001 From: Ryan Wu Date: Tue, 21 Feb 2017 15:14:59 +0800 Subject: [PATCH 0516/1807] Modify getRawInput`s docs IncomingRequest.rst --- user_guide_src/source/libraries/incomingrequest.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/user_guide_src/source/libraries/incomingrequest.rst b/user_guide_src/source/libraries/incomingrequest.rst index dd4c6b998e7e..7fb94fb88527 100644 --- a/user_guide_src/source/libraries/incomingrequest.rst +++ b/user_guide_src/source/libraries/incomingrequest.rst @@ -140,16 +140,11 @@ Finally, you can grab the contents of php://input as a raw stream with ``getRawI $data = $request->getRawInput(); This will retrieve data and convert it to an array. Like this:: -
- - - -
var_dump($request->getRawInput()); [ - 'Username' => 'Admin', - 'Role' => 'Administrator', + 'Param1' => 'Value1', + 'Param2' => 'Value2', ] Filtering Input Data -------------------- From 2ff28564c7ecea30719da5e2413b13fb556839be Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 22 Feb 2017 13:39:45 +0900 Subject: [PATCH 0517/1807] Fix indentation --- system/Test/CIDatabaseTestCase.php | 48 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/system/Test/CIDatabaseTestCase.php b/system/Test/CIDatabaseTestCase.php index bec08b87c9cd..71297f1d02fb 100644 --- a/system/Test/CIDatabaseTestCase.php +++ b/system/Test/CIDatabaseTestCase.php @@ -178,15 +178,15 @@ public function setUp() */ public function tearDown() { - if (! empty($this->insertCache)) - { - foreach ($this->insertCache as $row) - { - $this->db->table($row[0]) - ->where($row[1]) - ->delete(); - } - } + if (! empty($this->insertCache)) + { + foreach ($this->insertCache as $row) + { + $this->db->table($row[0]) + ->where($row[1]) + ->delete(); + } + } } //-------------------------------------------------------------------- @@ -199,7 +199,7 @@ public function tearDown() */ public function seed(string $name) { - return $this->seeder->call($name); + return $this->seeder->call($name); } //-------------------------------------------------------------------- @@ -220,10 +220,10 @@ public function seed(string $name) public function dontSeeInDatabase(string $table, array $where) { $count = $this->db->table($table) - ->where($where) - ->countAllResults(); + ->where($where) + ->countAllResults(); - $this->assertTrue($count == 0, 'Row was found in database'); + $this->assertTrue($count == 0, 'Row was found in database'); } //-------------------------------------------------------------------- @@ -241,8 +241,8 @@ public function dontSeeInDatabase(string $table, array $where) public function seeInDatabase(string $table, array $where) { $count = $this->db->table($table) - ->where($where) - ->countAllResults(); + ->where($where) + ->countAllResults(); $this->assertTrue($count > 0, 'Row not found in database: '. $this->db->showLastQuery()); } @@ -262,10 +262,10 @@ public function seeInDatabase(string $table, array $where) */ public function grabFromDatabase(string $table, string $column, array $where) { - $query = $this->db->table($table) - ->select($column) - ->where($where) - ->get(); + $query = $this->db->table($table) + ->select($column) + ->where($where) + ->get(); $query = $query->getRow(); @@ -289,8 +289,8 @@ public function hasInDatabase(string $table, array $data) $table, $data ]; - $this->db->table($table) - ->insert($data); + $this->db->table($table) + ->insert($data); } //-------------------------------------------------------------------- @@ -308,9 +308,9 @@ public function hasInDatabase(string $table, array $data) */ public function seeNumRecords(int $expected, string $table, array $where) { - $count = $this->db->table($table) - ->where($where) - ->countAllResults(); + $count = $this->db->table($table) + ->where($where) + ->countAllResults(); $this->assertEquals($expected, $count, 'Wrong number of matching rows in database.'); } From 2d64d651566f72127bb5860ca41300fc651173d4 Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Thu, 23 Feb 2017 19:38:05 +0000 Subject: [PATCH 0518/1807] Add Language File Array Support Allows a language file to contain an array of strings to make working with lists easier. Signed-off-by: Kristian Matthews --- system/Language/Language.php | 43 ++++++++++++++++++++------ system/Language/en/Language.php | 5 +++ tests/system/Language/LanguageTest.php | 39 +++++++++++++++++++++++ 3 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 system/Language/en/Language.php diff --git a/system/Language/Language.php b/system/Language/Language.php index 92f4d48cb92a..b42d0d53e843 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -88,14 +88,14 @@ public function __construct(string $locale) /** * Parses the language string for a file, loads the file, if necessary, - * getting + * getting the line. * - * @param string $line - * @param array $args + * @param string $line Line. + * @param array $args Arguments. * - * @return string + * @return string|string[] Returns line. */ - public function getLine(string $line, array $args = []): string + public function getLine(string $line, array $args = []) { // Parse out the file name and the actual alias. // Will load the language file and strings. @@ -105,11 +105,8 @@ public function getLine(string $line, array $args = []): string ? $this->language[$file][$line] : $line; - // Do advanced message formatting here - // if the 'intl' extension is available. - if ($this->intlSupport && count($args)) - { - $output = \MessageFormatter::formatMessage($this->locale, $output, $args); + if (count($args)) { + $output = $this->formatMessage($output, $args); } return $output; @@ -148,6 +145,32 @@ protected function parseLine(string $line): array //-------------------------------------------------------------------- + /** + * Advanced message formatting. + * + * @param string|array $message Message. + * @param array $args Arguments. + * + * @return string|array Returns formatted message. + */ + protected function formatMessage($message, array $args = []) + { + if (! $this->intlSupport || ! count($args)) { + return $message; + } + + if (is_array($message)) { + foreach ($message as $index => $value) { + $message[$index] = $this->formatMessage($value, $args); + } + return $message; + } + + return \MessageFormatter::formatMessage($this->locale, $message, $args); + } + + //-------------------------------------------------------------------- + /** * Loads a language file in the current locale. If $return is true, * will return the file's contents, otherwise will merge with diff --git a/system/Language/en/Language.php b/system/Language/en/Language.php new file mode 100644 index 000000000000..a20e1ce03314 --- /dev/null +++ b/system/Language/en/Language.php @@ -0,0 +1,5 @@ + 'Get line must be a string or array of strings.' +]; diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index c10df3a343f3..74d490e96313 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -27,6 +27,25 @@ public function testGetLineReturnsLine() //-------------------------------------------------------------------- + public function testGetLineArrayReturnsLineArray() + { + $lang = new MockLanguage('en'); + + $lang->setData([ + 'booksList' => [ + 'The Boogeyman', + 'We Saved' + ] + ]); + + $this->assertEquals([ + 'The Boogeyman', + 'We Saved' + ], $lang->getLine('books.booksList')); + } + + //-------------------------------------------------------------------- + /** * @group single */ @@ -46,4 +65,24 @@ public function testGetLineFormatsMessage() //-------------------------------------------------------------------- + public function testGetLineArrayFormatsMessages() + { + // No intl extension? then we can't test this - go away.... + if (! class_exists('\MessageFormatter')) { + return; + } + + $lang = new MockLanguage('en'); + + $lang->setData([ + 'bookList' => [ + '{0, number, integer} related books.' + ] + ]); + + $this->assertEquals(['45 related books.'], $lang->getLine('books.bookList', [91/2])); + } + + //-------------------------------------------------------------------- + } From 206a8cb30e3c7368260d79989b682b39e1b8811b Mon Sep 17 00:00:00 2001 From: baselbj Date: Fri, 24 Feb 2017 20:26:04 +0200 Subject: [PATCH 0519/1807] new migration strategy fix all issues and add support migrations per module. The idea is to make the migration class supporting migration per module so that each module (Namespace) have it's own versioning sequence. --- system/Database/MigrationRunner.php | 1028 ++++++++++++++------------- 1 file changed, 552 insertions(+), 476 deletions(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 92ff6eb473be..17e2223c31f5 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -27,12 +27,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 3.0.0 + * @package CodeIgniter + * @author CodeIgniter Dev Team + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 * @filesource */ @@ -45,490 +45,566 @@ */ class MigrationRunner { - /** - * Whether or not migrations are allowed to run. - * - * @var bool - */ - protected $enabled = false; - - /** - * The type of migrations to use (sequential or timestamp) - * - * @var string - */ - protected $type; - - /** - * Name of table to store meta information - * - * @var string - */ - protected $table; - - /** - * The version that current() will take us to. - * - * @var int - */ - protected $currentVersion = 0; - - /** - * The location where migrations can be found. - * - * @var string - */ - protected $path; - - /** - * The pattern used to locate migration file versions. - * - * @var string - */ - protected $regex; - - /** - * The main database connection. Used to store - * migration information in. - * @var ConnectionInterface - */ - protected $db; - - /** - * If true, will continue instead of throwing - * exceptions. - * @var bool - */ - protected $silent = false; - - //-------------------------------------------------------------------- - - /** - * Constructor. - * - * @param BaseConfig $config - * @param \CodeIgniter\Database\ConnectionInterface $db - * @throws ConfigException - */ - public function __construct(BaseConfig $config, ConnectionInterface $db = null) - { - $this->enabled = $config->enabled ?? false; - $this->type = $config->type ?? 'timestamp'; - $this->table = $config->table ?? 'migrations'; - $this->currentVersion = $config->currentVersion ?? 0; - - if (empty($this->table)) - { - throw new ConfigException(lang('Migrations.migMissingTable')); - } - - if ( ! in_array($this->type, ['sequential', 'timestamp'])) - { - throw new ConfigException(lang('Migrations.migInvalidType').$this->type); - } - - // Migration basename regex - $this->regex = ($this->type === 'timestamp') - ? '/^\d{14}_(\w+)$/' - : '/^\d{3}_(\w+)$/'; - - // If no db connection passed in, use - // default database group. - $this->db = ! empty($db) - ? $db - : \Config\Database::connect(); - - $this->ensureTable(); - } - - //-------------------------------------------------------------------- - - /** - * Migrate to a schema version - * - * Calls each migration step required to get to the schema version of - * choice - * - * @param string $targetVersion Target schema version - * @param $group - * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure - * @throws ConfigException - */ - public function version(string $targetVersion, $group='default') - { - if (! $this->enabled) - { - throw new ConfigException(lang('Migrations.migDisabled')); - } - - // Note: We use strings, so that timestamp versions work on 32-bit systems - $currentVersion = $this->getVersion($group); - - $migrations = $this->findMigrations(); - - if ($targetVersion > 0 && ! isset($migrations[$targetVersion])) - { - throw new \RuntimeException(lang('Migrations.migNotFound').$targetVersion); - } - - if ($targetVersion > $currentVersion) - { - // Moving Up - $method = 'up'; - } - else - { - // Moving Down, apply in reverse order - $method = 'down'; - krsort($migrations); - } - - if (empty($migrations)) - { - return true; - } - - $previous = false; - - // Validate all available migrations, and run the ones within our target range - foreach ($migrations as $number => $file) - { - // Check for sequence gaps - if ($this->type === 'sequential' && $previous !== false && abs($number - $previous) > 1) - { - throw new \RuntimeException(lang('Migration.migGap').$number); - } - - include_once $file; - $class = 'Migration_'.($this->getMigrationName(basename($file, '.php'))); - - // Validate the migration file structure - if ( ! class_exists($class, false)) - { - throw new \RuntimeException(sprintf(lang('Migrations.migClassNotFound'), $class)); - } - - $previous = $number; - - // Run migrations that are inside the target range - if ( - ($method === 'up' && $number > $currentVersion && $number <= $targetVersion) OR - ($method === 'down' && $number <= $currentVersion && $number > $targetVersion) - ) - { - $instance = new $class(); - - if ( ! is_callable([$instance, $method])) - { - throw new \RuntimeException(sprintf(lang('Migrations.migMissingMethod'), $method)); - } - - $instance->{$method}(); - - $currentVersion = $number; - if ($method === 'up') $this->addHistory(basename($file, '.php'), $instance->getDBGroup()); - elseif ($method === 'down') $this->removeHistory(basename($file, '.php'), $instance->getDBGroup()); - } - } - - return $currentVersion; - } - - //-------------------------------------------------------------------- - - /** - * Sets the schema to the latest migration - * - * @return mixed Current version string on success, FALSE on failure - */ - public function latest() - { - $migrations = $this->findMigrations(); - - if (empty($migrations)) - { - if ($this->silent) return false; - - throw new \RuntimeException(lang('Migrations.migNotFound')); - } - - $lastMigration = basename(end($migrations), '.php'); - - // Calculate the last migration step from existing migration - // filenames and proceed to the standard version migration - return $this->version($lastMigration); - } - - //-------------------------------------------------------------------- - - /** - * Sets the schema to the migration version set in config - * - * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure - */ - public function current() - { - return $this->version($this->currentVersion); - } - - //-------------------------------------------------------------------- - - /** - * Retrieves list of available migration scripts - * - * @return array list of migration file paths sorted by version - */ - public function findMigrations() - { - $migrations = []; - - // If $path has been set, ONLY do that one since - // the user has specified their will. Otherwise, - // scan all PSR4 paths. This is required so - // when tests are ran, it will only look in it's - // location. - $paths = []; - if (empty($this->path)) - { - $config = new Autoload(); - foreach ($config->psr4 as $dir) - { - $paths[] = rtrim($dir, '/').'/Database/Migrations/'; + /** + * Whether or not migrations are allowed to run. + * + * @var bool + */ + protected $enabled = false; + + /** + * The type of migrations to use (sequential or timestamp) + * + * @var string + */ + protected $type; + + /** + * Name of table to store meta information + * + * @var string + */ + protected $table; + + /** + * The version that current() will take us to. + * + * @var int + */ + protected $currentVersion = 0; + + /** + * The Namespace where migrations can be found. + * + * @var string + */ + protected $namespace; + + /** + * The database Group to migrate. + * + * @var string + */ + protected $group; + + + /** + * The pattern used to locate migration file versions. + * + * @var string + */ + protected $regex; + + /** + * The main database connection. Used to store + * migration information in. + * @var ConnectionInterface + */ + protected $db; + + /** + * If true, will continue instead of throwing + * exceptions. + * @var bool + */ + protected $silent = false; + + //-------------------------------------------------------------------- + + /** + * Constructor. + * + * @param BaseConfig $config + * @param \CodeIgniter\Database\ConnectionInterface $db + * @throws ConfigException + */ + public function __construct(BaseConfig $config, ConnectionInterface $db = null) + { + $this->enabled = $config->enabled ?? false; + $this->type = $config->type ?? 'timestamp'; + $this->table = $config->table ?? 'migrations'; + $this->currentVersion = $config->currentVersion ?? 0; + + // Default name space is the app namespace + $this->namespace = APP_NAMESPACE; + + // get default database group + $config = new \Config\Database(); + $this->group = $config->defaultGroup; + unset($config); + + if (empty($this->table)) { + throw new ConfigException(lang('Migrations.migMissingTable')); + } + + if (!in_array($this->type, ['sequential', 'timestamp'])) { + throw new ConfigException(lang('Migrations.migInvalidType') . $this->type); + } + + // Migration basename regex + $this->regex = ($this->type === 'timestamp') + ? '/^\d{14}_(\w+)$/' + : '/^\d{3}_(\w+)$/'; + + // If no db connection passed in, use + // default database group. + $this->db = !empty($db) + ? $db + : \Config\Database::connect(); + + $this->ensureTable(); + } + + //-------------------------------------------------------------------- + + /** + * Migrate to a schema version + * + * Calls each migration step required to get to the schema version of + * choice + * + * @param string $targetVersion Target schema version + * @param $group + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure + * @throws ConfigException + */ + public function version(string $targetVersion, $namespace = null, $group = null) + { + if (!$this->enabled) { + throw new ConfigException(lang('Migrations.migDisabled')); + } + // Set Namespace if not null + if (!is_null($namespace)) { + $this->setNamespace($namespace); + } + + // Set database group if not null + if (!is_null($group)) { + $this->setGroup($group); + } + + $migrations = $this->findMigrations(); + + if (empty($migrations)) { + return true; + } + + // Get Namespace current version + // Note: We use strings, so that timestamp versions work on 32-bit systems + $currentVersion = $this->getVersion(); + if ($targetVersion > $currentVersion) { + // Moving Up + $method = 'up'; + ksort($migrations); + + } else { + // Moving Down, apply in reverse order + $method = 'down'; + krsort($migrations); + } + + // Check Migration consistency + $this->CheckMigrations($migrations,$method); + + // loop migration for each namespace (module) + foreach ($migrations as $version => $migration) { + + // Only include migrations within the scoop + if (($method === 'up' && $version > $currentVersion && $version <= $targetVersion) OR + ($method === 'down' && $version <= $currentVersion && $version > $targetVersion)) { + + include_once $migration->path; + // Get namespaced class name + $class = $this->namespace . '\Database\Migrations\Migration_' . ($migration->name); + + // Validate the migration file structure + if (!class_exists($class, false)) { + throw new \RuntimeException(sprintf(lang('Migrations.migClassNotFound'), $class)); + } + + // Forcing migration to selected database group + $instance = new $class(\Config\Database::forge($this->group)); + + if (!is_callable([$instance, $method])) { + throw new \RuntimeException(sprintf(lang('Migrations.migMissingMethod'), $method)); + } + + $instance->{$method}(); + if ($method === 'up') $this->addHistory($migration->version); + elseif ($method === 'down') $this->removeHistory($migration->version); } } - else - { - $paths[] = $this->path; + } + + //-------------------------------------------------------------------- + + /** + * Sets the schema to the latest migration + * + * @return mixed Current version string on success, FALSE on failure + */ + public function latest($namespace = null) + { + + // Set Namespace if not null + if (!is_null($namespace)) { + $this->setNamespace($namespace); } - // Loop through all of our namespaced folders - // searching for migration directories. - foreach ($paths as $dir) - { - if (! is_dir($dir)) - { + $migrations = $this->findMigrations(); + + if (empty($migrations)) { + if ($this->silent) return false; + + throw new \RuntimeException(lang('Migrations.migNotFound')); + } + + $lastMigration = end($migrations)->version; + + // Calculate the last migration step from existing migration + // filenames and proceed to the standard version migration + return $this->version($lastMigration); + } + + //-------------------------------------------------------------------- + + /** + * Sets the schema to the latest migration for all namespaces + * + * @return void + */ + public function latestAll() + { + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + foreach ($namespaces as $namespace => $path) { + + $this->setNamespace($namespace); + $migrations = $this->findMigrations(); + + if (empty($migrations)) { continue; } - // Load all *_*.php files in the migrations path - foreach (glob($dir.'*_*.php') as $file) - { - $name = basename($file, '.php'); + $lastMigration = end($migrations)->version; + + // Calculate the last migration step from existing migration + // filenames and proceed to the standard version migration + $this->version($lastMigration); + } + } + //-------------------------------------------------------------------- + + /** + * Sets the schema to the migration version set in config + * + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure + */ + public function current($namespace = null) + { + // Set Namespace if not null + if (!is_null($namespace)) { + $this->setNamespace($namespace); + } - // Filter out non-migration files - if (preg_match($this->regex, $name)) - { - $migrations[$name] = $file; + return $this->version($this->currentVersion); + } + + //-------------------------------------------------------------------- + + /** + * Sets the schema to the migration version set in config + * + * @return void + */ + public function currentAll() + { + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + foreach ($namespaces as $namespace => $path) { + $this->setNamespace($namespace); + return $this->version($this->currentVersion); + } + } + //-------------------------------------------------------------------- + + /** + * Retrieves list of available migration scripts + * + * @return array list of migrations as $version for one namespace + */ + public function findMigrations() + { + $migrations = []; + // Get namespace location form PSR4 paths. + $config = new Autoload(); + $location = $config->psr4[$this->namespace]; + + // Setting migration directories. + $dir = rtrim($location, '/') . '/Database/Migrations/'; + + // Load all *_*.php files in the migrations path + foreach (glob($dir . '*_*.php') as $file) { + $name = basename($file, '.php'); + // Filter out non-migration files + if (preg_match($this->regex, $name)) { + // Create migration object using stdClass + $migration = new \stdClass(); + // Get migration version number + $migration->version = $this->getMigrationNumber($name); + $migration->name = $this->getMigrationName($name); + $migration->path = $file; + + // Add to migrations[version] + $migrations[$migration->version] = $migration; + } + } + return $migrations; + } + + //-------------------------------------------------------------------- + + /** + * checks if the list of available migration scripts list are consistent + * if sequential check if no gaps and check if all consistent with migrations table if downgrading + * if timestamp check if consistent with migrations table if downgrading + * + * @return bool + */ + protected function CheckMigrations($migrations, $method) + { + ksort($migrations); + + if ($method === 'down'){ + $old_migrations=$this->getHistory($this->group); + $old_size= count($old_migrations) -1; + } + // Check for sequence gaps + $previous = 0; + foreach ($migrations as $migration) { + if ($this->type === 'sequential' && abs($migration->version - $previous) > 1) { + throw new \RuntimeException(lang('Migration.migGap') . $migration->version); + } + // Check if old migration files are all available to do downgrading + if ($method === 'down') { + if ($previous <= $old_size && $old_migrations[$previous]['version'] != $migration->version){ + throw new \RuntimeException(lang('Migration.migGap') . $migration->version); } } + $previous = (int)$migration->version; + } + return true; + } + + //-------------------------------------------------------------------- + + /** + * Set namespace. + * Allows other scripts to modify on the fly as needed. + * + * @param string $namespace + * + * @return $this + */ + public function setNamespace(string $namespace) + { + $this->namespace = $namespace; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Set database Group. + * Allows other scripts to modify on the fly as needed. + * + * @param string $group + * + * @return $this + */ + public function setGroup(string $group) + { + $this->group = $group; + + return $this; + } + + + //-------------------------------------------------------------------- + + /** + * Grabs the full migration history from the database. + * + * @param $group + * @return mixed + */ + public function getHistory($group = 'default') + { + $query = $this->db->table($this->table) + ->where('group', $group) + ->orderBy('version', 'ASC') + ->get(); + + if (!$query) return []; + + return $query->getResultArray(); + } + + //-------------------------------------------------------------------- + + /** + * If $silent == true, then will not throw exceptions and will + * attempt to continue gracefully. + * + * @param bool $silent + * + * @return $this + */ + public function setSilent(bool $silent) + { + $this->silent = $silent; + + return $this; + } + + //-------------------------------------------------------------------- + + + /** + * Extracts the migration number from a filename + * + * @param string $migration + * + * @return string Numeric portion of a migration filename + */ + protected function getMigrationNumber($migration) + { + return sscanf($migration, '%[0-9]+', $number) + ? $number : '0'; + } + + //-------------------------------------------------------------------- + + /** + * Extracts the migration class name from a filename + * + * @param string $migration + * + * @return string text portion of a migration filename + */ + protected function getMigrationName($migration) + { + $parts = explode('_', $migration); + array_shift($parts); + + return implode('_', $parts); + } + + //-------------------------------------------------------------------- + + /** + * Retrieves current schema version + * + * @param $group + * @return string Current migration version + */ + protected function getVersion() + { + $row = $this->db->table($this->table) + ->select('version') + ->where('group', $this->group) + ->where('namespace', $this->namespace) + ->orderBy('version', 'DESC') + ->get() + ->getRow(); + + return $row ? $row->version : '0'; + } + + //-------------------------------------------------------------------- + + /** + * Stores the current schema version. + * + * @param string $version + * @param string $group The database group + * + * @internal param string $migration Migration reached + * + */ + protected function addHistory($version) + { + $this->db->table($this->table) + ->insert([ + 'version' => $version, + 'group' => $this->group, + 'namespace' => $this->namespace, + 'time' => time() + ]); + } + + //-------------------------------------------------------------------- + + /** + * Removes a single history + * + * @param string $version + * @param string $group The database group + */ + protected function removeHistory($version) + { + $this->db->table($this->table) + ->where('version', $version) + ->where('group', $this->group) + ->where('namespace', $this->namespace) + ->delete(); + } + + //-------------------------------------------------------------------- + + /** + * Ensures that we have created our migrations table + * in the database. + */ + protected function ensureTable() + { + if ($this->db->tableExists($this->table)) { + return; } - ksort($migrations); - - return $migrations; - } - - //-------------------------------------------------------------------- - - /** - * Updates the expected location of the migration files. - * Allows other scripts to modify on the fly as needed. - * - * @param string $path - * - * @return $this - */ - public function setPath(string $path) - { - $this->path = rtrim($path, '/').'/'; - - return $this; - } - - //-------------------------------------------------------------------- - - /** - * Grabs the full migration history from the database. - * - * @param $group - * @return mixed - */ - public function getHistory($group = 'default') - { - $query = $this->db->table($this->table) - ->where('group', $group) - ->get(); - - if (! $query) return []; - - return $query->getResultArray(); - } - - //-------------------------------------------------------------------- - - /** - * If $silent == true, then will not throw exceptions and will - * attempt to continue gracefully. - * - * @param bool $silent - * - * @return $this - */ - public function setSilent(bool $silent) - { - $this->silent = $silent; - - return $this; - } - - //-------------------------------------------------------------------- - - - - /** - * Extracts the migration number from a filename - * - * @param string $migration - * - * @return string Numeric portion of a migration filename - */ - protected function getMigrationNumber($migration) - { - return sscanf($migration, '%[0-9]+', $number) - ? $number : '0'; - } - - //-------------------------------------------------------------------- - - /** - * Extracts the migration class name from a filename - * - * @param string $migration - * - * @return string text portion of a migration filename - */ - protected function getMigrationName($migration) - { - $parts = explode('_', $migration); - array_shift($parts); - - return implode('_', $parts); - } - - //-------------------------------------------------------------------- - - /** - * Retrieves current schema version - * - * @param $group - * @return string Current migration version - */ - protected function getVersion($group = 'default') - { - if (empty($group)) - { - $config = new \Config\Database(); - $group = $config->defaultGroup; - unset($config); - } - - $row = $this->db->table($this->table) - ->select('version') - ->where('group', $group) - ->orderBy('version', 'DESC') - ->get() - ->getRow(); - - return $row ? $row->version : '0'; - } - - //-------------------------------------------------------------------- - - /** - * Stores the current schema version. - * - * @param string $version - * @param string $group The database group - * - * @internal param string $migration Migration reached - * - */ - protected function addHistory($version, $group = 'default') - { - if (empty($group)) - { - $config = new \Config\Database(); - $group = $config->defaultGroup; - unset($config); - } - - $this->db->table($this->table) - ->insert([ - 'version' => $version, - 'group' => $group, - 'time' => time() - ]); - } - - //-------------------------------------------------------------------- - - /** - * Removes a single history - * - * @param string $version - * @param string $group The database group - */ - protected function removeHistory($version, $group = 'default') - { - if (empty($group)) - { - $config = new \Config\Database(); - $group = $config->defaultGroup; - unset($config); - } - - $this->db->table($this->table) - ->where('version', $version) - ->where('group', $group) - ->delete(); - } - - //-------------------------------------------------------------------- - - /** - * Ensures that we have created our migrations table - * in the database. - */ - protected function ensureTable() - { - if ($this->db->tableExists($this->table)) - { - return; - } - - $forge = \Config\Database::forge(); - - $forge->addField([ - 'version' => [ - 'type' => 'VARCHAR', - 'constraint' => 255, - 'null' => false - ], - 'group' => [ - 'type' => 'VARCHAR', - 'constraint' => 255, - 'null' => false - ], - 'time' => [ - 'type' => 'INT', + $forge = \Config\Database::forge(); + + $forge->addField([ + 'version' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false + ], + 'group' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false + ], + 'namespace' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false + ], + 'time' => [ + 'type' => 'INT', 'constraint' => 11, - 'null' => false - ] - ]); + 'null' => false + ] + ]); - $forge->createTable($this->table, true); - } + $forge->createTable($this->table, true); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- } From 44742c97724b69d0fc96086095c250cc0fedec52 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 27 Feb 2017 17:18:53 +0900 Subject: [PATCH 0520/1807] Fix indentation Replace white spaces with tabs --- system/Language/Language.php | 2 +- system/Language/en/Migrations.php | 8 +-- system/Language/en/Number.php | 22 ++++---- system/Language/en/Validation.php | 92 +++++++++++++++---------------- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index 92f4d48cb92a..cb357945b11a 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -201,7 +201,7 @@ protected function load(string $file, string $locale, bool $return = false) */ protected function requireFile(string $path): array { - $files = service('locator')->search($path); + $files = service('locator')->search($path); foreach ($files as $file) { diff --git a/system/Language/en/Migrations.php b/system/Language/en/Migrations.php index a6954913f9fe..0412816f782f 100644 --- a/system/Language/en/Migrations.php +++ b/system/Language/en/Migrations.php @@ -54,10 +54,10 @@ 'migHelpRollback' => "\tRuns all migrations 'down' to version 0.", 'migHelpRefresh' => "\t\tUninstalls and re-runs all migrations to freshen database.", 'migHelpSeed' => "\tRuns the seeder named [name].", - 'migCreate' => "\tCreates a new migration named [name]", - 'migNameMigration' => "Name the migration file", - 'migBadCreateName' => 'You must provide a migration file name.', - 'migWriteError' => 'Error trying to create file.', + 'migCreate' => "\tCreates a new migration named [name]", + 'migNameMigration' => "Name the migration file", + 'migBadCreateName' => 'You must provide a migration file name.', + 'migWriteError' => 'Error trying to create file.', 'migToLatest' => 'Migrating to latest version...', 'migInvalidVersion' => 'Invalid version number provided.', diff --git a/system/Language/en/Number.php b/system/Language/en/Number.php index 127fbcea99fa..e486c1ce2870 100644 --- a/system/Language/en/Number.php +++ b/system/Language/en/Number.php @@ -36,15 +36,15 @@ * @filesource */ return [ - 'terabyteAbbr' => 'TB', - 'gigabyteAbbr' => 'GB', - 'megabyteAbbr' => 'MB', - 'kilobyteAbbr' => 'KB', - 'bytes' => 'Bytes', - // don't forget the space in front of these! - 'thousand' => ' thousand', - 'million' => ' million', - 'billion' => ' billion', - 'trillion' => ' trillion', - 'quadrillion' => ' quadrillion', + 'terabyteAbbr' => 'TB', + 'gigabyteAbbr' => 'GB', + 'megabyteAbbr' => 'MB', + 'kilobyteAbbr' => 'KB', + 'bytes' => 'Bytes', + // don't forget the space in front of these! + 'thousand' => ' thousand', + 'million' => ' million', + 'billion' => ' billion', + 'trillion' => ' trillion', + 'quadrillion' => ' quadrillion', ]; diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php index ebceabd6f637..031e7d6c0087 100644 --- a/system/Language/en/Validation.php +++ b/system/Language/en/Validation.php @@ -37,53 +37,53 @@ */ return [ - // Core Messages - 'noRuleSets' => 'No rulesets specified in Validation configuration.', - 'ruleNotFound' => '{field} is not a valid rule.', - 'groupNotFound' => '%s is not a validation rules group.', - 'groupNotArray' => '%s rule group must be an array.', + // Core Messages + 'noRuleSets' => 'No rulesets specified in Validation configuration.', + 'ruleNotFound' => '{field} is not a valid rule.', + 'groupNotFound' => '%s is not a validation rules group.', + 'groupNotArray' => '%s rule group must be an array.', - // Rule Messages - 'alpha' => 'The {field} field may only contain alphabetical characters.', - 'alpha_dash' => 'The {field} field may only contain alpha-numeric characters, underscores, and dashes.', - 'alpha_numeric' => 'The {field} field may only contain alpha-numeric characters.', - 'alpha_numeric_spaces' => 'The {field} field may only contain alpha-numeric characters and spaces.', - 'decimal' => 'The {field} field must contain a decimal number.', - 'differs' => 'The {field} field must differ from the {param} field.', - 'exact_length' => 'The {field} field must be exactly {param} characters in length.', - 'greater_than' => 'The {field} field must contain a number greater than {param}.', - 'greater_than_equal_to' => 'The {field} field must contain a number greater than or equal to {param}.', - 'in_list' => 'The {field} field must be one of: {param}.', - 'integer' => 'The {field} field must contain an integer.', - 'is_natural' => 'The {field} field must only contain digits.', - 'is_natural_no_zero' => 'The {field} field must only contain digits and must be greater than zero.', - 'is_unique' => 'The {field} field must contain a unique value.', - 'less_than' => 'The {field} field must contain a number less than {param}.', - 'less_than_equal_to' => 'The {field} field must contain a number less than or equal to {param}.', - 'matches' => 'The {field} field does not match the {param} field.', - 'max_length' => 'The {field} field cannot exceed {param} characters in length.', - 'min_length' => 'The {field} field must be at least {param} characters in length.', - 'numeric' => 'The {field} field must contain only numbers.', - 'regex_match' => 'The {field} field is not in the correct format.', - 'required' => 'The {field} field is required.', - 'required_with' => 'The {field} field is required when {param} is present.', - 'required_without' => 'The {field} field is required when {param} in not present.', - 'timezone' => 'The {field} field must be a valid timezone.', - 'valid_base64' => 'The {field} field must be a valid base64 string.', - 'valid_email' => 'The {field} field must contain a valid email address.', - 'valid_emails' => 'The {field} field must contain all valid email addresses.', - 'valid_ip' => 'The {field} field must contain a valid IP.', - 'valid_url' => 'The {field} field must contain a valid URL.', + // Rule Messages + 'alpha' => 'The {field} field may only contain alphabetical characters.', + 'alpha_dash' => 'The {field} field may only contain alpha-numeric characters, underscores, and dashes.', + 'alpha_numeric' => 'The {field} field may only contain alpha-numeric characters.', + 'alpha_numeric_spaces' => 'The {field} field may only contain alpha-numeric characters and spaces.', + 'decimal' => 'The {field} field must contain a decimal number.', + 'differs' => 'The {field} field must differ from the {param} field.', + 'exact_length' => 'The {field} field must be exactly {param} characters in length.', + 'greater_than' => 'The {field} field must contain a number greater than {param}.', + 'greater_than_equal_to' => 'The {field} field must contain a number greater than or equal to {param}.', + 'in_list' => 'The {field} field must be one of: {param}.', + 'integer' => 'The {field} field must contain an integer.', + 'is_natural' => 'The {field} field must only contain digits.', + 'is_natural_no_zero' => 'The {field} field must only contain digits and must be greater than zero.', + 'is_unique' => 'The {field} field must contain a unique value.', + 'less_than' => 'The {field} field must contain a number less than {param}.', + 'less_than_equal_to' => 'The {field} field must contain a number less than or equal to {param}.', + 'matches' => 'The {field} field does not match the {param} field.', + 'max_length' => 'The {field} field cannot exceed {param} characters in length.', + 'min_length' => 'The {field} field must be at least {param} characters in length.', + 'numeric' => 'The {field} field must contain only numbers.', + 'regex_match' => 'The {field} field is not in the correct format.', + 'required' => 'The {field} field is required.', + 'required_with' => 'The {field} field is required when {param} is present.', + 'required_without' => 'The {field} field is required when {param} in not present.', + 'timezone' => 'The {field} field must be a valid timezone.', + 'valid_base64' => 'The {field} field must be a valid base64 string.', + 'valid_email' => 'The {field} field must contain a valid email address.', + 'valid_emails' => 'The {field} field must contain all valid email addresses.', + 'valid_ip' => 'The {field} field must contain a valid IP.', + 'valid_url' => 'The {field} field must contain a valid URL.', - // Credit Cards - 'valid_cc_num' => '{field} does not appear to be a valid credit card number.', + // Credit Cards + 'valid_cc_num' => '{field} does not appear to be a valid credit card number.', - // Files - 'uploaded' => '{field} is not a valid uploaded file.', - 'max_size' => '{field} is too large of a file.', - 'is_image' => '{field} is not a valid, uploaded image file.', - 'mime_in' => '{field} does not have a valid mime type.', - 'ext_in' => '{field} does not have a valid file extension.', - 'max_dims' => '{field} is either not an image, or it is too wide or tall.', - '', + // Files + 'uploaded' => '{field} is not a valid uploaded file.', + 'max_size' => '{field} is too large of a file.', + 'is_image' => '{field} is not a valid, uploaded image file.', + 'mime_in' => '{field} does not have a valid mime type.', + 'ext_in' => '{field} does not have a valid file extension.', + 'max_dims' => '{field} is either not an image, or it is too wide or tall.', + '', ]; From c47435552f973816e5dbf56435db98d5945afe8c Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 27 Feb 2017 14:24:09 +0000 Subject: [PATCH 0521/1807] Validation Get & Set Rule Groups Created `Validation->getRuleGroup(string $group): array` and `Validation->setRuleGroup(string $group)` methods to get and set rule groups. Signed-off-by: Kristian Matthews --- system/Validation/Validation.php | 45 +++++++++++++++++++++- tests/system/Validation/ValidationTest.php | 38 +++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index 4db9da4d3928..aa9d658ddc06 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -343,7 +343,50 @@ public function hasRule(string $field): bool //-------------------------------------------------------------------- - /** + /** + * Get rule group. + * + * @param string $group Group. + * + * @return string[] Rule group. + * + * @throws \InvalidArgumentException If group not found. + */ + public function getRuleGroup(string $group): array + { + if (!isset($this->config->$group)) { + throw new \InvalidArgumentException(sprintf(lang('Validation.groupNotFound'), $group)); + } + + if (!is_array($this->config->$group)) { + throw new \InvalidArgumentException(sprintf(lang('Validation.groupNotArray'), $group)); + } + + return $this->config->$group; + } + + //-------------------------------------------------------------------- + /** + * Set rule group. + * + * @param string $group Group. + * + * + * @throws \InvalidArgumentException If group not found. + */ + public function setRuleGroup(string $group) + { + $rules = $this->getRuleGroup($group); + $this->rules = $rules; + + $errorName = $group . '_errors'; + if (isset($this->config->$errorName)) { + $this->customErrors = $this->config->$errorName; + } + } + + +/** * Returns the rendered HTML of the errors as defined in $template. * * @param string $template diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 54aaf0be963d..be7601b175e5 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -202,7 +202,43 @@ public function testGroupsReadFromConfigValid() //-------------------------------------------------------------------- - //-------------------------------------------------------------------- + public function testGetRuleGroup() + { + $this->assertEquals([ + 'foo' => 'required|min_length[5]', + ], $this->validation->getRuleGroup('groupA')); + } + + //-------------------------------------------------------------------- + + public function testGetRuleGroupException() + { + $this->expectException('\InvalidArgumentException'); + $this->validation->getRuleGroup('groupZ'); + } + + //-------------------------------------------------------------------- + + public function testSetRuleGroup() + { + $this->validation->setRuleGroup('groupA'); + + $this->assertEquals([ + 'foo' => 'required|min_length[5]', + ], $this->validation->getRules()); + } + + //-------------------------------------------------------------------- + + public function testSetRuleGroupException() + { + $this->expectException('\InvalidArgumentException'); + + $this->validation->setRuleGroup('groupZ'); + } + + + //-------------------------------------------------------------------- // Rules Tests //-------------------------------------------------------------------- From 5de59be0ec1fce46a123aa6d10b86b5c298f074c Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 27 Feb 2017 14:39:35 +0000 Subject: [PATCH 0522/1807] Fixed Code Format Signed-off-by: Kristian Matthews --- tests/system/Validation/ValidationTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index be7601b175e5..5f41e622f368 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -237,7 +237,6 @@ public function testSetRuleGroupException() $this->validation->setRuleGroup('groupZ'); } - //-------------------------------------------------------------------- // Rules Tests //-------------------------------------------------------------------- From 4a447c19aab32a3fc7735defef8950e6c62732eb Mon Sep 17 00:00:00 2001 From: Basel Juma Date: Wed, 1 Mar 2017 16:13:48 +0200 Subject: [PATCH 0523/1807] Fix migration --- system/Database/MigrationRunner.php | 57 ++++++++----------- system/Test/CIDatabaseTestCase.php | 56 +++++++++--------- .../20160428212500_Create_test_tables.php | 2 +- .../seeds/CITestSeeder.php | 0 4 files changed, 54 insertions(+), 61 deletions(-) rename tests/_support/{_database/migrations => Database/Migrations}/20160428212500_Create_test_tables.php (97%) rename tests/_support/{_database => Database}/seeds/CITestSeeder.php (100%) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 17e2223c31f5..8d372afa8b99 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -243,13 +243,17 @@ public function version(string $targetVersion, $namespace = null, $group = null) * * @return mixed Current version string on success, FALSE on failure */ - public function latest($namespace = null) + public function latest($namespace = null, $group = null) { // Set Namespace if not null if (!is_null($namespace)) { $this->setNamespace($namespace); } + // Set database group if not null + if (!is_null($group)) { + $this->setGroup($group); + } $migrations = $this->findMigrations(); @@ -273,8 +277,13 @@ public function latest($namespace = null) * * @return void */ - public function latestAll() + public function latestAll($group = null) { + // Set database group if not null + if (!is_null($group)) { + $this->setGroup($group); + } + // Get all namespaces form PSR4 paths. $config = new Autoload(); $namespaces = $config->psr4; @@ -298,15 +307,16 @@ public function latestAll() //-------------------------------------------------------------------- /** - * Sets the schema to the migration version set in config + * Sets the (APP_NAMESPACE) schema to $currentVersion in migration config file + * * * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure */ - public function current($namespace = null) + public function current($group = null) { - // Set Namespace if not null - if (!is_null($namespace)) { - $this->setNamespace($namespace); + // Set database group if not null + if (!is_null($group)) { + $this->setGroup($group); } return $this->version($this->currentVersion); @@ -314,24 +324,6 @@ public function current($namespace = null) //-------------------------------------------------------------------- - /** - * Sets the schema to the migration version set in config - * - * @return void - */ - public function currentAll() - { - // Get all namespaces form PSR4 paths. - $config = new Autoload(); - $namespaces = $config->psr4; - - foreach ($namespaces as $namespace => $path) { - $this->setNamespace($namespace); - return $this->version($this->currentVersion); - } - } - //-------------------------------------------------------------------- - /** * Retrieves list of available migration scripts * @@ -342,6 +334,7 @@ public function findMigrations() $migrations = []; // Get namespace location form PSR4 paths. $config = new Autoload(); + $location = $config->psr4[$this->namespace]; // Setting migration directories. @@ -380,22 +373,22 @@ protected function CheckMigrations($migrations, $method) ksort($migrations); if ($method === 'down'){ - $old_migrations=$this->getHistory($this->group); - $old_size= count($old_migrations) -1; + $history_migrations=$this->getHistory($this->group); + $history_size= count($history_migrations) -1; } // Check for sequence gaps - $previous = 0; + $loop = 0; foreach ($migrations as $migration) { - if ($this->type === 'sequential' && abs($migration->version - $previous) > 1) { + if ($this->type === 'sequential' && abs($migration->version - $loop) > 1) { throw new \RuntimeException(lang('Migration.migGap') . $migration->version); } - // Check if old migration files are all available to do downgrading + // Check if all old migration files are all available to do downgrading if ($method === 'down') { - if ($previous <= $old_size && $old_migrations[$previous]['version'] != $migration->version){ + if ($loop <= $history_size && $history_migrations[$loop]['version'] != $migration->version){ throw new \RuntimeException(lang('Migration.migGap') . $migration->version); } } - $previous = (int)$migration->version; + $loop ++; } return true; } diff --git a/system/Test/CIDatabaseTestCase.php b/system/Test/CIDatabaseTestCase.php index 71297f1d02fb..cfc32333157e 100644 --- a/system/Test/CIDatabaseTestCase.php +++ b/system/Test/CIDatabaseTestCase.php @@ -69,7 +69,7 @@ class CIDatabaseTestCase extends CIUnitTestCase * * @var string */ - protected $basePath = APPPATH.'../tests/_support/_database'; + protected $basePath = APPPATH.'../tests/_support/Database'; /** * The name of the database group to connect to. @@ -151,12 +151,12 @@ public function setUp() { if (! empty($this->basePath)) { - $this->migrations->setPath(rtrim($this->basePath, '/').'/migrations'); + $this->migrations->setNamespace('Tests\Support'); } $this->db->table('migrations')->truncate(); - $this->migrations->version(0, 'tests'); - $this->migrations->latest('tests'); + $this->migrations->version(0,null, 'tests'); + $this->migrations->latest(null,'tests'); } if (! empty($this->seed)) @@ -178,15 +178,15 @@ public function setUp() */ public function tearDown() { - if (! empty($this->insertCache)) - { - foreach ($this->insertCache as $row) - { - $this->db->table($row[0]) - ->where($row[1]) - ->delete(); - } - } + if (! empty($this->insertCache)) + { + foreach ($this->insertCache as $row) + { + $this->db->table($row[0]) + ->where($row[1]) + ->delete(); + } + } } //-------------------------------------------------------------------- @@ -199,7 +199,7 @@ public function tearDown() */ public function seed(string $name) { - return $this->seeder->call($name); + return $this->seeder->call($name); } //-------------------------------------------------------------------- @@ -220,10 +220,10 @@ public function seed(string $name) public function dontSeeInDatabase(string $table, array $where) { $count = $this->db->table($table) - ->where($where) - ->countAllResults(); + ->where($where) + ->countAllResults(); - $this->assertTrue($count == 0, 'Row was found in database'); + $this->assertTrue($count == 0, 'Row was found in database'); } //-------------------------------------------------------------------- @@ -241,8 +241,8 @@ public function dontSeeInDatabase(string $table, array $where) public function seeInDatabase(string $table, array $where) { $count = $this->db->table($table) - ->where($where) - ->countAllResults(); + ->where($where) + ->countAllResults(); $this->assertTrue($count > 0, 'Row not found in database: '. $this->db->showLastQuery()); } @@ -262,10 +262,10 @@ public function seeInDatabase(string $table, array $where) */ public function grabFromDatabase(string $table, string $column, array $where) { - $query = $this->db->table($table) - ->select($column) - ->where($where) - ->get(); + $query = $this->db->table($table) + ->select($column) + ->where($where) + ->get(); $query = $query->getRow(); @@ -289,8 +289,8 @@ public function hasInDatabase(string $table, array $data) $table, $data ]; - $this->db->table($table) - ->insert($data); + $this->db->table($table) + ->insert($data); } //-------------------------------------------------------------------- @@ -308,9 +308,9 @@ public function hasInDatabase(string $table, array $data) */ public function seeNumRecords(int $expected, string $table, array $where) { - $count = $this->db->table($table) - ->where($where) - ->countAllResults(); + $count = $this->db->table($table) + ->where($where) + ->countAllResults(); $this->assertEquals($expected, $count, 'Wrong number of matching rows in database.'); } diff --git a/tests/_support/_database/migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php similarity index 97% rename from tests/_support/_database/migrations/20160428212500_Create_test_tables.php rename to tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 60603d1e7939..4c9fe93eeb71 100644 --- a/tests/_support/_database/migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -1,4 +1,4 @@ - Date: Fri, 3 Mar 2017 22:26:20 +0000 Subject: [PATCH 0524/1807] Create Response Trait Fail Server Error Method Created a `failServerError(string $description, string $code = null, string $message = ''): Response` method in the response trait to fail server errors. Signed-off-by: Kristian Matthews --- system/API/ResponseTrait.php | 17 ++++++++++++++++- tests/system/API/ResponseTraitTest.php | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index 0ff83ffd9104..f94548555f87 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -36,8 +36,10 @@ * @filesource */ +use CodeIgniter\HTTP\Response; + /** - * Class ResponseTrait + * Response trait. * * Provides common, more readable, methods to provide * consistent HTTP responses under a variety of common @@ -294,6 +296,19 @@ public function failTooManyRequests(string $description, string $code=null, stri //-------------------------------------------------------------------- + /** + * Used when there is a server error. + * + * @param string $description Description + * @param string|null $code Code. + * @param string $message Message. + * + * @return Response Response. + */ + public function failServerError(string $description, string $code = null, string $message = ''): Response + { + return $this->fail($description, $this->codes['server_error'], $code, $message); + } //-------------------------------------------------------------------- // Utility Methods diff --git a/tests/system/API/ResponseTraitTest.php b/tests/system/API/ResponseTraitTest.php index ee4f68b7beb0..127546cfcc39 100644 --- a/tests/system/API/ResponseTraitTest.php +++ b/tests/system/API/ResponseTraitTest.php @@ -276,4 +276,19 @@ public function testTooManyRequests() $this->assertEquals(json_encode($expected), $this->response->getBody()); } + public function testServerError() + { + $controller = $this->makeController(); + $controller->failServerError('Nope.', 'FAT-CHANCE', 'A custom reason.'); + + $this::assertEquals('A custom reason.', $this->response->getReason()); + $this::assertEquals(500, $this->response->getStatusCode()); + $this::assertEquals(json_encode([ + 'status' => 500, + 'error' => 'FAT-CHANCE', + 'messages' => [ + 'Nope.' + ] + ]), $this->response->getBody()); + } } From 3c8f1e088684715933fca7facdc2e26797bfbf97 Mon Sep 17 00:00:00 2001 From: Basel Juma Date: Sun, 5 Mar 2017 16:32:26 +0200 Subject: [PATCH 0525/1807] Fix CLI issue and updating migratin CLI commands --- system/Autoloader/FileLocator.php | 11 +- system/CLI/CLI.php | 2 +- system/Commands/Database/CreateMigration.php | 26 ++- system/Commands/Database/MigrateCurrent.php | 3 +- system/Commands/Database/MigrateLatest.php | 12 +- system/Commands/Database/MigrateRollback.php | 135 ++++++++------- system/Commands/Database/MigrateStatus.php | 163 ++++++++++--------- system/Commands/Database/MigrateVersion.php | 6 +- system/Database/MigrationRunner.php | 53 ++++-- system/Language/en/Migrations.php | 6 +- 10 files changed, 250 insertions(+), 167 deletions(-) diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index e264890dfac8..92342832580b 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -216,16 +216,16 @@ public function findQualifiedNameFromPath(string $path) { return; } - + foreach ($this->namespaces as $namespace => $nsPath) { + $nsPath = realpath($nsPath); if (is_numeric($namespace)) continue; - - if (mb_strpos($path, $nsPath) === 0) + + if (mb_strpos($path,$nsPath) === 0) { $className = '\\'.$namespace.'\\'. ltrim(str_replace('/', '\\', mb_substr($path, mb_strlen($nsPath))), '\\'); - // Remove the file extension (.php) $className = mb_substr($className, 0, -4); @@ -253,11 +253,12 @@ public function listFiles(string $path): array foreach ($this->namespaces as $namespace => $nsPath) { - $fullPath = rtrim($nsPath, '/') .'/'. $path; + $fullPath = realpath(rtrim($nsPath, '/') .'/'. $path); if (! is_dir($fullPath)) continue; $tempFiles = get_filenames($fullPath, true); + //CLI::newLine($tempFiles); if (! count($tempFiles)) { diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 6773636afb12..883fd0d2068e 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -672,7 +672,7 @@ protected static function parseCommandLine() */ public static function getURI() { - return implode(' ', static::$segments); + return implode('/', static::$segments); } //-------------------------------------------------------------------- diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php index 948fece201ba..12549acd37f8 100644 --- a/system/Commands/Database/CreateMigration.php +++ b/system/Commands/Database/CreateMigration.php @@ -38,6 +38,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use Config\Autoload; /** * Creates a new migration file. @@ -68,6 +69,7 @@ class CreateMigration extends BaseCommand */ public function run(array $params=[]) { + $name = array_shift($params); if (empty($name)) @@ -80,13 +82,31 @@ public function run(array $params=[]) CLI::error(lang('Migrations.migBadCreateName')); return; } + $ns = array_shift($params); + $homepath = APPPATH; + + if (!empty($ns)) + { + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + foreach ($namespaces as $namespace => $path) { + + if ($namespace == $ns ) { + $homepath =realpath($path); + } + } + }else { + $ns= "App"; + } - $path = APPPATH.'Database/Migrations/'.date('YmdHis_').$name.'.php'; + $path = $homepath.'/Database/Migrations/'.date('YmdHis_').$name.'.php'; $template =<<current(); + $runner->current($group); } catch (\Exception $e) { diff --git a/system/Commands/Database/MigrateLatest.php b/system/Commands/Database/MigrateLatest.php index 7394ce7c6f22..4ce895ad72f6 100644 --- a/system/Commands/Database/MigrateLatest.php +++ b/system/Commands/Database/MigrateLatest.php @@ -54,7 +54,7 @@ class MigrateLatest extends BaseCommand * * @var string */ - protected $name = 'migrate'; + protected $name = 'migrate:latest'; /** * the Command's short description @@ -72,8 +72,16 @@ public function run(array $params=[]) CLI::write(lang('Migrations.migToLatest'), 'yellow'); + $namespace = CLI::getOption('n'); + $group = CLI::getOption('g'); + try { - $runner->latest(); + if (! is_null(CLI::getOption('all'))){ + $runner->latestAll($group); + }else{ + $runner->latest($namespace,$group); + } + } catch (\Exception $e) { diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php index 1019eecf7751..297a0c8bf62f 100644 --- a/system/Commands/Database/MigrateRollback.php +++ b/system/Commands/Database/MigrateRollback.php @@ -1,87 +1,106 @@ setGroup($group); + } try { - $runner->version(0); + if (is_null(CLI::getOption('all'))){ + $namespace =CLI::getOption('n'); + $runner->version(0,$namespace); + }else{ + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + foreach ($namespaces as $namespace => $path) { + $runner->setNamespace($namespace); + $migrations = $runner->findMigrations(); + if (empty($migrations)) { + continue; + } + $runner->version(0,$namespace,$group); + } + } } catch (\Exception $e) { $this->showError($e); } - + CLI::write('Done'); } -} +} \ No newline at end of file diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index b024c3f1955d..91e29726b6ce 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -1,106 +1,111 @@ findMigrations(); - $history = $runner->getHistory(); - - if (empty($migrations)) - { - return CLI::error(lang('Migrations.migNoneFound')); - } - - $max = 0; - - foreach ($migrations as $version => $file) - { - $file = substr($file, strpos($file, $version.'_')); - $migrations[$version] = $file; + if(! is_null(CLI::getOption('g'))){ + $runner->setGroup(CLI::getOption('g')); + } + + // Get all namespaces form PSR4 paths. + $config = new Autoload(); + $namespaces = $config->psr4; + + // Loop for all $namespaces + foreach ($namespaces as $namespace => $path) { - $max = max($max, strlen($file)); - } - - CLI::write(str_pad(lang('Migrations.filename'), $max+4).lang('Migrations.migOn'), 'yellow'); - - foreach ($migrations as $version => $file) - { - $date = ''; - foreach ($history as $row) + $runner->setNamespace($namespace); + $migrations = $runner->findMigrations(); + $history = $runner->getHistory(); + + if (empty($migrations)) { - if ($row['version'] != $version) continue; - - $date = $row['time']; + CLI::error("$namespace: " .lang('Migrations.migNoneFound')); + continue; } - CLI::write(str_pad($file, $max+4). ($date ? $date : '---')); + ksort($migrations); + CLI::write(lang('Migrations.migHistoryFor') . "$namespace: " , 'yellow'); + + foreach ($migrations as $version => $migration) + { + $date = ''; + foreach ($history as $row) + { + if ($row['version'] != $version) continue; + + $date = $row['time']; + } + CLI::write("\t- $version ". lang('Migrations.migOn') . ($date ? $date : '---')); + } } } -} +} \ No newline at end of file diff --git a/system/Commands/Database/MigrateVersion.php b/system/Commands/Database/MigrateVersion.php index 9a1132a96302..80f1bb9968c9 100644 --- a/system/Commands/Database/MigrateVersion.php +++ b/system/Commands/Database/MigrateVersion.php @@ -85,9 +85,11 @@ public function run(array $params=[]) } CLI::write(sprintf(lang('Migrations.migToVersionPH'), $version), 'yellow'); - + + $namespace = CLI::getOption('n'); + $group = CLI::getOption('g'); try { - $runner->version($version); + $runner->version($version, $namespace, $group); } catch (\Exception $e) { diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 8d372afa8b99..20f28e2fdd46 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -39,6 +39,7 @@ use CodeIgniter\Config\BaseConfig; use CodeIgniter\ConfigException; use Config\Autoload; +use CodeIgniter\CLI\CLI; /** * Class MigrationRunner @@ -204,9 +205,12 @@ public function version(string $targetVersion, $namespace = null, $group = null) } // Check Migration consistency - $this->CheckMigrations($migrations,$method); + $this->CheckMigrations($migrations,$method, $targetVersion); // loop migration for each namespace (module) + if(is_cli()){ + CLI::write("-) $this->namespace:"); + } foreach ($migrations as $version => $migration) { // Only include migrations within the scoop @@ -231,9 +235,10 @@ public function version(string $targetVersion, $namespace = null, $group = null) $instance->{$method}(); if ($method === 'up') $this->addHistory($migration->version); - elseif ($method === 'down') $this->removeHistory($migration->version); + elseif ($method === 'down') $this->removeHistory($migration->version); } } + return true; } //-------------------------------------------------------------------- @@ -255,18 +260,12 @@ public function latest($namespace = null, $group = null) $this->setGroup($group); } - $migrations = $this->findMigrations(); - - if (empty($migrations)) { - if ($this->silent) return false; - - throw new \RuntimeException(lang('Migrations.migNotFound')); - } + $migrations = $this->findMigrations(); $lastMigration = end($migrations)->version; // Calculate the last migration step from existing migration - // filenames and proceed to the standard version migration + // filenames and proceed to the standard version migration return $this->version($lastMigration); } @@ -288,7 +287,7 @@ public function latestAll($group = null) $config = new Autoload(); $namespaces = $config->psr4; - foreach ($namespaces as $namespace => $path) { + foreach ($namespaces as $namespace => $path) { $this->setNamespace($namespace); $migrations = $this->findMigrations(); @@ -298,11 +297,16 @@ public function latestAll($group = null) } $lastMigration = end($migrations)->version; + // No New migrations to add + if($lastMigration == $this->getVersion()){ + continue; + } // Calculate the last migration step from existing migration - // filenames and proceed to the standard version migration + // filenames and proceed to the standard version migration $this->version($lastMigration); } + return true; } //-------------------------------------------------------------------- @@ -368,8 +372,20 @@ public function findMigrations() * * @return bool */ - protected function CheckMigrations($migrations, $method) + protected function CheckMigrations($migrations, $method, $targetversion) { + // Check if no migrations found + if (empty($migrations)) { + if ($this->silent) return false; + throw new \RuntimeException(lang('Migrations.migEmpty') ); + } + + // Check if $targetversion file is found + if ($targetversion != 0 && !array_key_exists($targetversion,$migrations) ) { + if ($this->silent) return false; + throw new \RuntimeException(lang('Migrations.migNotFound'). $targetversion); + } + ksort($migrations); if ($method === 'down'){ @@ -380,12 +396,12 @@ protected function CheckMigrations($migrations, $method) $loop = 0; foreach ($migrations as $migration) { if ($this->type === 'sequential' && abs($migration->version - $loop) > 1) { - throw new \RuntimeException(lang('Migration.migGap') . $migration->version); + throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version); } // Check if all old migration files are all available to do downgrading if ($method === 'down') { if ($loop <= $history_size && $history_migrations[$loop]['version'] != $migration->version){ - throw new \RuntimeException(lang('Migration.migGap') . $migration->version); + throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version); } } $loop ++; @@ -440,6 +456,7 @@ public function getHistory($group = 'default') { $query = $this->db->table($this->table) ->where('group', $group) + ->where('Namespace', $this->namespace) ->orderBy('version', 'ASC') ->get(); @@ -539,6 +556,9 @@ protected function addHistory($version) 'namespace' => $this->namespace, 'time' => time() ]); + if(is_cli()){ + CLI::write("\t- " . lang('Migrations.migAdded') . $version); + } } //-------------------------------------------------------------------- @@ -556,6 +576,9 @@ protected function removeHistory($version) ->where('group', $this->group) ->where('namespace', $this->namespace) ->delete(); + if(is_cli()){ + CLI::write("\t- " . lang('Migrations.migRemoved') . $version); + } } //-------------------------------------------------------------------- diff --git a/system/Language/en/Migrations.php b/system/Language/en/Migrations.php index a6954913f9fe..8f9bc2fb5509 100644 --- a/system/Language/en/Migrations.php +++ b/system/Language/en/Migrations.php @@ -42,6 +42,7 @@ 'migInvalidType' => 'An invalid migration numbering type was specified: ', 'migDisabled' => 'Migrations have been loaded but are disabled or setup incorrectly.', 'migNotFound' => 'Migration file not found: ', + 'migEmpty' => 'No Migration files found', 'migGap' => 'There is a gap in the migration sequence near version number: ', 'migClassNotFound' => 'The migration class "%s" could not be found.', 'migMissingMethod' => 'The migration class is missing an "%s" method.', @@ -65,9 +66,12 @@ 'migToVersion' => 'Migrating to current version...', 'migRollingBack' => "Rolling back all migrations...", 'migNoneFound' => 'No migrations were found.', - 'migOn' => 'Migrated On', + 'migOn' => 'Migrated On: ', 'migSeeder' => 'Seeder name', 'migMissingSeeder' => 'You must provide a seeder name.', + 'migHistoryFor' => 'Migration history For ', + 'migRemoved' => 'Downgrade: ', + 'migAdded' => 'Upgrade: ', 'version' => 'Version', 'filename' => 'Filename', From e98ac7abcabfd44b39d47665e205d3a817f2baec Mon Sep 17 00:00:00 2001 From: Basel Juma Date: Sun, 5 Mar 2017 19:43:42 +0200 Subject: [PATCH 0526/1807] Update Documnetation and fix MigrateStatus --- system/Commands/Database/MigrateStatus.php | 33 +++++-- user_guide_src/source/database/migration.rst | 98 ++++++++++++++++---- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php index 91e29726b6ce..e2d758facbe3 100644 --- a/system/Commands/Database/MigrateStatus.php +++ b/system/Commands/Database/MigrateStatus.php @@ -70,10 +70,10 @@ class MigrateStatus extends BaseCommand public function run(array $params=[]) { $runner = Services::migrations(); - + if(! is_null(CLI::getOption('g'))){ $runner->setGroup(CLI::getOption('g')); - } + } // Get all namespaces form PSR4 paths. $config = new Autoload(); @@ -81,7 +81,7 @@ public function run(array $params=[]) // Loop for all $namespaces foreach ($namespaces as $namespace => $path) { - + $runner->setNamespace($namespace); $migrations = $runner->findMigrations(); $history = $runner->getHistory(); @@ -91,9 +91,26 @@ public function run(array $params=[]) CLI::error("$namespace: " .lang('Migrations.migNoneFound')); continue; } + + ksort($migrations); + + CLI::newLine(1); - ksort($migrations); - CLI::write(lang('Migrations.migHistoryFor') . "$namespace: " , 'yellow'); + CLI::write(lang('Migrations.migHistoryFor') . "$namespace: " , 'green'); + + CLI::newLine(1); + + $max = 0; + foreach ($migrations as $version => $migration) + { + $file = substr($migration->name, strpos($migration->name, $version.'_')); + $migrations[$version]->name = $file; + + $max = max($max, strlen($file)); + } + + CLI::write(str_pad(lang('Migrations.filename'), $max+6).lang('Migrations.migOn'),'yellow'); + foreach ($migrations as $version => $migration) { @@ -102,9 +119,9 @@ public function run(array $params=[]) { if ($row['version'] != $version) continue; - $date = $row['time']; - } - CLI::write("\t- $version ". lang('Migrations.migOn') . ($date ? $date : '---')); + $date = date ("Y-m-d H:i:s", $row['time']); + } + CLI::write(str_pad($migration->name, $max+6). ($date ? $date : '---')); } } } diff --git a/user_guide_src/source/database/migration.rst b/user_guide_src/source/database/migration.rst index 84cee5126df7..d7c267fefaaa 100644 --- a/user_guide_src/source/database/migration.rst +++ b/user_guide_src/source/database/migration.rst @@ -97,7 +97,7 @@ below for more details. Using $currentVersion ===================== -The $currentVersion setting allows you to mark a location that your application should be set at. +The $currentVersion setting allows you to mark a location that your main application namespace should be set at. This is especially helpful for use in a production setting. In your application, you can always update the migration to the current version, and not latest to ensure your production and staging servers are running the correct schema. On your development servers, you can add additional migrations @@ -127,9 +127,11 @@ match the name of the database group exactly:: Namespaces ========== -The migration library will automatically scan all namespaces you have defined within +The migration library can automatically scan all namespaces you have defined within **application/Config/Autoload.php** and its ``$psr4`` property for matching directory -names. It will include all migrations it finds. +names. It will include all migrations it finds in Database/Migrations. + +Each namespace has it's own version sequence, this will help you upgrade and downgrade each module (namespace) without affecting other namespaces. For example, assume that we have the the following namespaces defined in our Autoload configuration file:: @@ -174,28 +176,41 @@ to update the schema:: ******************* Commnand-Line Tools ******************* - CodeIgniter ships with several :doc:`commands ` that are available from the command line to help you work with migrations. These tools are not required to use migrations but might make things easier for those of you -that wish to use them. The tools primarily provide access to the same methods that are available within the MigrationRunner -class. +that wish to use them. The tools primarily provide access to the same methods that are available within the MigrationRunner class. **latest** Migrates all database groups to the latest available migrations:: - > php ci.php migrate +> php ci.php migrate:latest + +You can use (latest) with the following options: + +- (-g) to chose database group, otherwise default database group will be used. +- (-n) to choose namespace, otherwise (App) namespace will be used. +- (all) to migrate all namespaces to the latest migration + +This example will migrate Blog namespace to latest:: + +> php ci.php migrate:latest -g test -n Blog + **current** -Migrates all database groups to match the version set in ``$currentVersion``. This will migrate both +Migrates the (App) namespace to match the version set in ``$currentVersion``. This will migrate both up and down as needed to match the specified version:: > php ci.php migrate:current +You can use (current) with the following options: + +- (-g) to chose database group, otherwise default database group will be used. + **version** -Migrates all database groups to the specified version. If no version is provided, you will be prompted +Migrates to the specified version. If no version is provided, you will be prompted for the version. :: // Asks you for the version... @@ -208,25 +223,48 @@ for the version. :: // Timestamp > php ci.php migrate:version 20161426211300 +You can use (version) with the following options: + +- (-g) to chose database group, otherwise default database group will be used. +- (-n) to choose namespace, , otherwise (App) namespace will be used. + **rollback** Rolls back all migrations, taking all database groups to a blank slate, effectively migration 0:: > php ci.php migrate:rollback +You can use (rollback) with the following options: + +- (-g) to chose database group, otherwise default database group will be used. +- (-n) to choose namespace, otherwise (App) namespace will be used. +- (all) to migrate all namespaces to the latest migration + + **refresh** Refreshes the database state by first rolling back all migrations, and then migrating to the latest version:: > php ci.php migrate:refresh +You can use (refresh) with the following options: + +- (-g) to chose database group, otherwise default database group will be used. +- (-n) to choose namespace, otherwise (App) namespace will be used. +- (all) to migrate all namespaces to the latest migration + + **status** Displays a list of all migrations and the date and time they were ran, or '--' if they have not be ran:: > php ci.php migrate:status - Filename Migrated On - 20150101101500_First_migration.php 2016-04-25 04:44:22 + Filename Migrated On + First_migration.php 2016-04-25 04:44:22 + +You can use (refresh) with the following options: + +- (-g) to chose database group, otherwise default database group will be used. **create** @@ -234,6 +272,9 @@ Creates a skeleton migration file in **application/Database/Migrations** using t > php ci.php migrate:create [filename] +You can use (refresh) with the following options: +- (-n) to choose namespace, otherwise (App) namespace will be used. + ********************* Migration Preferences ********************* @@ -256,8 +297,9 @@ Class Reference .. php:class:: CodeIgniter\Database\MigrationRunner - .. php:method:: current() + .. php:method:: current($group) + :param mixed $group: database group name, if null (App) namespace will be used. :returns: TRUE if no migrations are found, current version string on success, FALSE on failure :rtype: mixed @@ -271,17 +313,29 @@ Class Reference An array of migration filenames are returned that are found in the **path** property. - .. php:method:: latest() + .. php:method:: latest($namespace, $group) + :param mixed $namespace: application namespace, if null (App) namespace will be used. + :param mixed $group: database group name, if null default database group will be used. :returns: Current version string on success, FALSE on failure :rtype: mixed This works much the same way as ``current()`` but instead of looking for the ``$currentVersion`` the Migration class will use the very newest migration found in the filesystem. + .. php:method:: latestAll($group) + + :param mixed $group: database group name, if null default database group will be used. + :returns: TRUE on success, FALSE on failure + :rtype: mixed - .. php:method:: version($target_version) + This works much the same way as ``latest()`` but instead of looking for + one namespace, the Migration class will use the very + newest migration found for all namespaces. + .. php:method:: version($target_version, $namespace, $group) + :param mixed $namespace: application namespace, if null (App) namespace will be used. + :param mixed $group: database group name, if null default database group will be used. :param mixed $target_version: Migration version to process :returns: TRUE if no migrations are found, current version string on success, FALSE on failure :rtype: mixed @@ -292,13 +346,23 @@ Class Reference $migration->version(5); - .. php:method:: setPath($path) + .. php:method:: setNamespace($namespace) + + :param string $namespace: application namespace. + :returns: The current MigrationRunner instance + :rtype: CodeIgniter\Database\MigrationRunner + + Sets the path the library should look for migration files:: + + $migration->setNamespace($path) + ->latest(); + .. php:method:: setGroup($group) - :param string $path: The directory where migration files can be found. + :param string $group: database group name. :returns: The current MigrationRunner instance :rtype: CodeIgniter\Database\MigrationRunner Sets the path the library should look for migration files:: - $migration->setPath($path) + $migration->setNamespace($path) ->latest(); From 1ff8629f484b634f2f4bc2dbf11649b3c9e36df4 Mon Sep 17 00:00:00 2001 From: Basel Juma Date: Mon, 6 Mar 2017 00:04:49 +0200 Subject: [PATCH 0527/1807] Fix CLI issue and updating migratin CLI::write --- system/Commands/Database/CreateMigration.php | 2 +- system/Commands/Database/MigrateCurrent.php | 6 +++- system/Commands/Database/MigrateLatest.php | 6 +++- system/Commands/Database/MigrateRollback.php | 4 +++ system/Commands/Database/MigrateVersion.php | 4 +++ system/Database/MigrationRunner.php | 33 ++++++++++++++++---- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php index 12549acd37f8..497b6db5b797 100644 --- a/system/Commands/Database/CreateMigration.php +++ b/system/Commands/Database/CreateMigration.php @@ -82,7 +82,7 @@ public function run(array $params=[]) CLI::error(lang('Migrations.migBadCreateName')); return; } - $ns = array_shift($params); + $namespace = CLI::getOption('n'); $homepath = APPPATH; if (!empty($ns)) diff --git a/system/Commands/Database/MigrateCurrent.php b/system/Commands/Database/MigrateCurrent.php index 9096b5ce7e83..7020eaf04e78 100644 --- a/system/Commands/Database/MigrateCurrent.php +++ b/system/Commands/Database/MigrateCurrent.php @@ -74,8 +74,12 @@ public function run(array $params=[]) CLI::write(lang('Migrations.migToVersion'), 'yellow'); $group = CLI::getOption('g'); - try { + try { $runner->current($group); + $messages = $runner->getCliMessages(); + foreach ($messages as $message) { + CLI::write($message); + } } catch (\Exception $e) { diff --git a/system/Commands/Database/MigrateLatest.php b/system/Commands/Database/MigrateLatest.php index 4ce895ad72f6..245b234e501d 100644 --- a/system/Commands/Database/MigrateLatest.php +++ b/system/Commands/Database/MigrateLatest.php @@ -76,11 +76,15 @@ public function run(array $params=[]) $group = CLI::getOption('g'); try { - if (! is_null(CLI::getOption('all'))){ + if (! is_null(CLI::getOption('all'))){ $runner->latestAll($group); }else{ $runner->latest($namespace,$group); } + $messages = $runner->getCliMessages(); + foreach ($messages as $message) { + CLI::write($message); + } } catch (\Exception $e) diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php index 297a0c8bf62f..10f341801a21 100644 --- a/system/Commands/Database/MigrateRollback.php +++ b/system/Commands/Database/MigrateRollback.php @@ -95,6 +95,10 @@ public function run(array $params=[]) $runner->version(0,$namespace,$group); } } + $messages = $runner->getCliMessages(); + foreach ($messages as $message) { + CLI::write($message); + } } catch (\Exception $e) { diff --git a/system/Commands/Database/MigrateVersion.php b/system/Commands/Database/MigrateVersion.php index 80f1bb9968c9..5a67d3ad6076 100644 --- a/system/Commands/Database/MigrateVersion.php +++ b/system/Commands/Database/MigrateVersion.php @@ -90,6 +90,10 @@ public function run(array $params=[]) $group = CLI::getOption('g'); try { $runner->version($version, $namespace, $group); + $messages = $runner->getCliMessages(); + foreach ($messages as $message) { + CLI::write($message); + } } catch (\Exception $e) { diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 20f28e2fdd46..2f6ded100ef4 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -35,6 +35,7 @@ * @since Version 3.0.0 * @filesource */ + use CodeIgniter\Config\BaseConfig; use CodeIgniter\ConfigException; @@ -110,6 +111,13 @@ class MigrationRunner */ protected $silent = false; + /** + * used to return messages for CLI. + * + * @var bool + */ + protected $cliMessages = array(); + //-------------------------------------------------------------------- /** @@ -207,10 +215,11 @@ public function version(string $targetVersion, $namespace = null, $group = null) // Check Migration consistency $this->CheckMigrations($migrations,$method, $targetVersion); - // loop migration for each namespace (module) if(is_cli()){ - CLI::write("-) $this->namespace:"); - } + $this->cliMessages[]="-) $this->namespace:"; + } + + // loop migration for each namespace (module) foreach ($migrations as $version => $migration) { // Only include migrations within the scoop @@ -520,7 +529,6 @@ protected function getMigrationName($migration) /** * Retrieves current schema version * - * @param $group * @return string Current migration version */ protected function getVersion() @@ -536,6 +544,19 @@ protected function getVersion() return $row ? $row->version : '0'; } + //-------------------------------------------------------------------- + + /** + * Retrieves current schema version + * + * @return string Current migration version + */ + public function getCliMessages() + { + + return $this->cliMessages; + } + //-------------------------------------------------------------------- /** @@ -557,7 +578,7 @@ protected function addHistory($version) 'time' => time() ]); if(is_cli()){ - CLI::write("\t- " . lang('Migrations.migAdded') . $version); + $this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version; } } @@ -577,7 +598,7 @@ protected function removeHistory($version) ->where('namespace', $this->namespace) ->delete(); if(is_cli()){ - CLI::write("\t- " . lang('Migrations.migRemoved') . $version); + $this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version; } } From 959bfd748b338b83fa4db0aea2a4e91bccad0f3f Mon Sep 17 00:00:00 2001 From: baselbj Date: Mon, 6 Mar 2017 00:28:32 +0200 Subject: [PATCH 0528/1807] just to test --- system/Database/MigrationRunner.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 2f6ded100ef4..2fd748a8acac 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -578,7 +578,7 @@ protected function addHistory($version) 'time' => time() ]); if(is_cli()){ - $this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version; + // $this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version; } } @@ -598,7 +598,7 @@ protected function removeHistory($version) ->where('namespace', $this->namespace) ->delete(); if(is_cli()){ - $this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version; + // $this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version; } } From 437afdba432edd9ba1d075f67613deb553a7163e Mon Sep 17 00:00:00 2001 From: baselbj Date: Mon, 6 Mar 2017 01:12:07 +0200 Subject: [PATCH 0529/1807] Fix Language::requireFile() require_once always return boolean --- system/Language/Language.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index 92f4d48cb92a..16d54e689dfd 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -210,7 +210,7 @@ protected function requireFile(string $path): array continue; } - return require_once $file; + return require $file; } return []; From 47a659d239f99afac8d398e8a2c377ae2f568803 Mon Sep 17 00:00:00 2001 From: baselbj Date: Mon, 6 Mar 2017 01:24:09 +0200 Subject: [PATCH 0530/1807] return cliMessages[] --- system/Database/MigrationRunner.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 2fd748a8acac..2f6ded100ef4 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -578,7 +578,7 @@ protected function addHistory($version) 'time' => time() ]); if(is_cli()){ - // $this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version; + $this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version; } } @@ -598,7 +598,7 @@ protected function removeHistory($version) ->where('namespace', $this->namespace) ->delete(); if(is_cli()){ - // $this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version; + $this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version; } } From f6d65d37dfc3a4ebc8f65f026995fe9e53d96120 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 5 Mar 2017 22:05:46 -0600 Subject: [PATCH 0531/1807] Attempt to fix Travis tests failing in Language --- system/Language/Language.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index 92f4d48cb92a..aae121ef91cf 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -210,7 +210,12 @@ protected function requireFile(string $path): array continue; } - return require_once $file; + // On some OS's we were seeing failures + // on this command returning boolean instead + // of array during testing. + $output = require_once $file; + + return is_array($output) ? $output : []; } return []; From c263049067cf1aa0bb34765de9ba25ca8062fc0f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 5 Mar 2017 22:14:58 -0600 Subject: [PATCH 0532/1807] Another attempt to appease Travis --- system/Language/Language.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index aae121ef91cf..0e863dab28dc 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -212,10 +212,9 @@ protected function requireFile(string $path): array // On some OS's we were seeing failures // on this command returning boolean instead - // of array during testing. - $output = require_once $file; - - return is_array($output) ? $output : []; + // of array during testing, so we've removed + // the require_once for now. + return require_once $file; } return []; From 0682f7a8a13f3fee5107b84d148c3a8e0789678b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 5 Mar 2017 22:34:49 -0600 Subject: [PATCH 0533/1807] Silly me. --- system/Language/Language.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index 0e863dab28dc..eccb1f955e00 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -214,7 +214,7 @@ protected function requireFile(string $path): array // on this command returning boolean instead // of array during testing, so we've removed // the require_once for now. - return require_once $file; + return require $file; } return []; From 024b889750794c57e129ec805da2c3e0174c7dea Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 6 Mar 2017 09:39:23 +0000 Subject: [PATCH 0534/1807] Updated Code Formatting & Added Signed-off-by: Kristian Matthews --- system/Language/Language.php | 9 ++++++--- tests/system/Language/LanguageTest.php | 5 +++-- tests/system/Validation/ValidationTest.php | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/system/Language/Language.php b/system/Language/Language.php index b42d0d53e843..eeb92850673a 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -155,12 +155,15 @@ protected function parseLine(string $line): array */ protected function formatMessage($message, array $args = []) { - if (! $this->intlSupport || ! count($args)) { + if (! $this->intlSupport || ! count($args)) + { return $message; } - if (is_array($message)) { - foreach ($message as $index => $value) { + if (is_array($message)) + { + foreach ($message as $index => $value) + { $message[$index] = $this->formatMessage($value, $args); } return $message; diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index 74d490e96313..8f596cbe294f 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -67,8 +67,9 @@ public function testGetLineFormatsMessage() public function testGetLineArrayFormatsMessages() { - // No intl extension? then we can't test this - go away.... - if (! class_exists('\MessageFormatter')) { + // No intl extension? Then we can't test this - go away... + if (! class_exists('\MessageFormatter')) + { return; } diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 54aaf0be963d..fa6667d91534 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -1184,7 +1184,7 @@ public function naturalZeroProvider() ['0', false], ['12', true], ['42a', false], - ['-1', false], + ['-1', false] ]; } From 35ed5ba6c72750b6dd434f46b0e06de8ef3e36fd Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 6 Mar 2017 09:39:36 +0000 Subject: [PATCH 0535/1807] Added Documentation Signed-off-by: Kristian Matthews --- .../source/libraries/localization.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst index 5255a95136aa..a788c7ef7c11 100644 --- a/user_guide_src/source/libraries/localization.rst +++ b/user_guide_src/source/libraries/localization.rst @@ -226,3 +226,22 @@ You should be sure to read up on the MessageFormatter class and the underlying I idea on what capabilities it has, like permorming conditional replacement, pluralization, and more. Both of the links provided earlier will give you an excellent idea as to the options available. +Nested Arrays +------------- + +Language files also allow nested arrays to make working with lists, etc… easier. + + // Language/en/Fruit.php + return [ + 'list' => [ + 'Apples', + 'Bananas', + 'Grapes', + 'Lemons', + 'Oranges', + 'Strawberries' + ] + ]; + + // Displays "Apples, Bananas, Graps, Lemons, Oranges, Strawberries" + echo implode(', ', lang('Fruit.list')); \ No newline at end of file From a498eead645161f514c2f4ef279fa15351c28593 Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 6 Mar 2017 09:50:34 +0000 Subject: [PATCH 0536/1807] Fixed Formatting Signed-off-by: Kristian Matthews --- system/Validation/Validation.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index aa9d658ddc06..9cb280a2ab76 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -354,11 +354,13 @@ public function hasRule(string $field): bool */ public function getRuleGroup(string $group): array { - if (!isset($this->config->$group)) { + if (! isset($this->config->$group)) + { throw new \InvalidArgumentException(sprintf(lang('Validation.groupNotFound'), $group)); } - if (!is_array($this->config->$group)) { + if (!is_array($this->config->$group)) + { throw new \InvalidArgumentException(sprintf(lang('Validation.groupNotArray'), $group)); } @@ -366,6 +368,7 @@ public function getRuleGroup(string $group): array } //-------------------------------------------------------------------- + /** * Set rule group. * @@ -380,7 +383,8 @@ public function setRuleGroup(string $group) $this->rules = $rules; $errorName = $group . '_errors'; - if (isset($this->config->$errorName)) { + if (isset($this->config->$errorName)) + { $this->customErrors = $this->config->$errorName; } } From 8c303da62515a64f84a145358780e7196a9abd01 Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 6 Mar 2017 09:50:42 +0000 Subject: [PATCH 0537/1807] Added Documentation Signed-off-by: Kristian Matthews --- user_guide_src/source/libraries/validation.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst index c0530f4013c9..9f7d9566285e 100644 --- a/user_guide_src/source/libraries/validation.rst +++ b/user_guide_src/source/libraries/validation.rst @@ -303,6 +303,23 @@ be used for any errors when this group is used:: See below for details on the formatting of the array. +Getting & Setting Rule Groups +============================= + +Get Rule Group +-------------- + +This method gets a rule group from the validation configuration. + + $validation->getRuleGroup('signup'); + +Set Rule Group +-------------- + +This method sets a rule group from the validation configuration to the validation service. + + $validation->setRuleGroup('signup'); + ******************* Working With Errors ******************* From dd59339c3659f3701e054e245deb73a55d88b882 Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 6 Mar 2017 09:57:45 +0000 Subject: [PATCH 0538/1807] Updated Documenation Signed-off-by: Kristian Matthews --- system/API/ResponseTrait.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index f94548555f87..e6aa043bd123 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -299,11 +299,11 @@ public function failTooManyRequests(string $description, string $code=null, stri /** * Used when there is a server error. * - * @param string $description Description - * @param string|null $code Code. - * @param string $message Message. + * @param string $description The error message to show the user. + * @param string|null $code A custom, API-specific, error code. + * @param string $message A custom "reason" message to return. * - * @return Response Response. + * @return Response The value of the Response's send() method. */ public function failServerError(string $description, string $code = null, string $message = ''): Response { From 3df146d5b83af0ca0977f01428c7908851d7fb71 Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Mon, 6 Mar 2017 09:57:50 +0000 Subject: [PATCH 0539/1807] Added Documentation Signed-off-by: Kristian Matthews --- user_guide_src/source/libraries/api_responses.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/user_guide_src/source/libraries/api_responses.rst b/user_guide_src/source/libraries/api_responses.rst index 3cc1d6d7c118..ab654c60096d 100644 --- a/user_guide_src/source/libraries/api_responses.rst +++ b/user_guide_src/source/libraries/api_responses.rst @@ -278,3 +278,15 @@ Class Reference return $this->failTooManyRequests('You must wait 15 seconds before making another request.'); +.. php:method:: failServerError(string $description[, string $code = null[, string $message = '']]) + + :param string $description: The error message to show the user. + :param string $code: A custom, API-specific, error code. + :param string $message: A custom "reason" message to return. + :returns: The value of the Response object's send() method. + + Sets the appropriate status code to use when there is a server error. + + :: + + return $this->failServerError('Server error.'); From 8d0199e99fbfe014ddd41c0a9b130a5f8d0704f5 Mon Sep 17 00:00:00 2001 From: Basel Juma Date: Mon, 6 Mar 2017 19:38:03 +0200 Subject: [PATCH 0540/1807] Fix PostgreSQLPostgreSQL issue --- system/Database/MigrationRunner.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index 2f6ded100ef4..4cc14d7ef1f1 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -465,7 +465,7 @@ public function getHistory($group = 'default') { $query = $this->db->table($this->table) ->where('group', $group) - ->where('Namespace', $this->namespace) + ->where('namespace', $this->namespace) ->orderBy('version', 'ASC') ->get(); From b85298dca3db0b54f78c17df6fcd83b35b7f165b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 6 Mar 2017 23:14:22 -0600 Subject: [PATCH 0541/1807] Get CLI parameters working again. Fixes #422 --- ci.php | 2 +- system/CLI/CLI.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci.php b/ci.php index 33d05adfd6de..fdff9d47d31e 100755 --- a/ci.php +++ b/ci.php @@ -15,7 +15,7 @@ // Location to the Paths config file. $pathsPath = 'application/Config/Paths.php'; -// Path to the front controller (this file) +// Path to the front controller define('FCPATH', './public/'.DIRECTORY_SEPARATOR); /* diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 6773636afb12..883fd0d2068e 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -672,7 +672,7 @@ protected static function parseCommandLine() */ public static function getURI() { - return implode(' ', static::$segments); + return implode('/', static::$segments); } //-------------------------------------------------------------------- From 8b3152f72f60099ffbfcb6b99e439a935be4ff6f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 7 Mar 2017 00:04:13 -0600 Subject: [PATCH 0542/1807] Better handling of null values within validation rules. Fixes #412 --- application/Controllers/Checks.php | 16 +++ system/Validation/CreditCardRules.php | 4 +- system/Validation/Rules.php | 68 ++++++----- tests/system/Validation/ValidationTest.php | 136 ++++++++++++++++++++- 4 files changed, 183 insertions(+), 41 deletions(-) diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php index fcbd711289f3..ba92cd610227 100644 --- a/application/Controllers/Checks.php +++ b/application/Controllers/Checks.php @@ -2,7 +2,9 @@ use CodeIgniter\API\ResponseTrait; use CodeIgniter\Controller; +use CodeIgniter\Model; use Config\Database; +use Tests\Support\Models\JobModel; class Checks extends Controller { @@ -109,5 +111,19 @@ public function format() var_dump($this->response->getHeaderLine('content-type')); } + public function model() + { + $model = new class() extends Model { + protected $table = 'job'; + }; + + $results = $model->findAll(); + + $developer = $model->findWhere('name', 'Developer'); + + $politician = $model->find(3); + + } + } diff --git a/system/Validation/CreditCardRules.php b/system/Validation/CreditCardRules.php index 3c4db1b522db..83f92bf31ee0 100644 --- a/system/Validation/CreditCardRules.php +++ b/system/Validation/CreditCardRules.php @@ -100,7 +100,7 @@ class CreditCardRules * * @return bool */ - public function valid_cc_number(string $ccNumber, string $type, array $data): bool + public function valid_cc_number(string $ccNumber=null, string $type, array $data): bool { $type = strtolower($type); $info = null; @@ -181,7 +181,7 @@ public function valid_cc_number(string $ccNumber, string $type, array $data): bo * * @return bool */ - protected function isValidLuhn(string $number): bool + protected function isValidLuhn(string $number=null): bool { settype($number, 'string'); diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index 25b0a6f0afcc..e4649ca8ffa0 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -47,7 +47,7 @@ class Rules * * @return bool */ - public function alpha(string $str): bool + public function alpha(string $str=null): bool { return ctype_alpha($str); } @@ -61,7 +61,7 @@ public function alpha(string $str): bool * * @return bool */ - public function alpha_dash(string $str): bool + public function alpha_dash(string $str=null): bool { return (bool)preg_match('/^[a-z0-9_-]+$/i', $str); } @@ -75,7 +75,7 @@ public function alpha_dash(string $str): bool * * @return bool */ - public function alpha_numeric(string $str): bool + public function alpha_numeric(string $str=null): bool { return ctype_alnum((string)$str); } @@ -89,7 +89,7 @@ public function alpha_numeric(string $str): bool * * @return bool */ - public function alpha_numeric_spaces(string $str): bool + public function alpha_numeric_spaces(string $str=null): bool { return (bool)preg_match('/^[A-Z0-9 ]+$/i', $str); } @@ -103,7 +103,7 @@ public function alpha_numeric_spaces(string $str): bool * * @return bool */ - public function decimal(string $str): bool + public function decimal(string $str=null): bool { return (bool)preg_match('/^[\-+]?[0-9]+\.[0-9]+$/', $str); } @@ -119,9 +119,9 @@ public function decimal(string $str): bool * * @return bool */ - public function differs(string $str, string $field, array $data): bool + public function differs(string $str=null, string $field, array $data): bool { - return isset($data[$field]) + return array_key_exists($field, $data) ? ($str !== $data[$field]) : false; } @@ -137,7 +137,7 @@ public function differs(string $str, string $field, array $data): bool * * @return bool */ - public function exact_length(string $str, string $val, array $data): bool + public function exact_length(string $str=null, string $val, array $data): bool { if (! is_numeric($val)) { @@ -157,7 +157,7 @@ public function exact_length(string $str, string $val, array $data): bool * * @return bool */ - public function greater_than(string $str, string $min, array $data): bool + public function greater_than(string $str=null, string $min, array $data): bool { return is_numeric($str) ? ($str > $min) : false; } @@ -172,7 +172,7 @@ public function greater_than(string $str, string $min, array $data): bool * * @return bool */ - public function greater_than_equal_to(string $str, string $min, array $data): bool + public function greater_than_equal_to(string $str=null, string $min, array $data): bool { return is_numeric($str) ? ($str >= $min) : false; } @@ -186,9 +186,11 @@ public function greater_than_equal_to(string $str, string $min, array $data): bo * @param string * @return bool */ - public function in_list(string $value, string $list, array $data): bool + public function in_list(string $value=null, string $list, array $data): bool { - return in_array($value, explode(',', $list), TRUE); + $list = explode(',', $list); + $list = array_map(function($value) { return trim($value); }, $list); + return in_array($value, $list, TRUE); } //-------------------------------------------------------------------- @@ -200,7 +202,7 @@ public function in_list(string $value, string $list, array $data): bool * * @return bool */ - public function integer(string $str): bool + public function integer(string $str=null): bool { return (bool)preg_match('/^[\-+]?[0-9]+$/', $str); } @@ -213,7 +215,7 @@ public function integer(string $str): bool * @param string * @return bool */ - public function is_natural(string $str): bool + public function is_natural(string $str=null): bool { return ctype_digit((string) $str); } @@ -226,7 +228,7 @@ public function is_natural(string $str): bool * @param string * @return bool */ - public function is_natural_no_zero(string $str): bool + public function is_natural_no_zero(string $str=null): bool { return ($str != 0 && ctype_digit((string) $str)); } @@ -248,7 +250,7 @@ public function is_natural_no_zero(string $str): bool * * @return bool */ - public function is_unique(string $str, string $field, array $data): bool + public function is_unique(string $str=null, string $field, array $data): bool { // Grab any data for exclusion of a single row. list($field, $ignoreField, $ignoreValue) = array_pad(explode(',', $field), 3, null); @@ -279,7 +281,7 @@ public function is_unique(string $str, string $field, array $data): bool * * @return bool */ - public function less_than(string $str, string $max): bool + public function less_than(string $str=null, string $max): bool { return is_numeric($str) ? ($str < $max) : false; } @@ -294,7 +296,7 @@ public function less_than(string $str, string $max): bool * * @return bool */ - public function less_than_equal_to(string $str, string $max): bool + public function less_than_equal_to(string $str=null, string $max): bool { return is_numeric($str) ? ($str <= $max) : false; } @@ -310,9 +312,9 @@ public function less_than_equal_to(string $str, string $max): bool * * @return bool */ - public function matches(string $str, string $field, array $data): bool + public function matches(string $str=null, string $field, array $data): bool { - return isset($data[$field]) + return array_key_exists($field, $data) ? ($str === $data[$field]) : false; } @@ -328,7 +330,7 @@ public function matches(string $str, string $field, array $data): bool * * @return bool */ - public function max_length(string $str, string $val, array $data): bool + public function max_length(string $str=null, string $val, array $data): bool { if (! is_numeric($val)) { @@ -349,7 +351,7 @@ public function max_length(string $str, string $val, array $data): bool * * @return bool */ - public function min_length(string $str, string $val, array $data): bool + public function min_length(string $str=null, string $val, array $data): bool { if (! is_numeric($val)) { @@ -368,7 +370,7 @@ public function min_length(string $str, string $val, array $data): bool * * @return bool */ - public function numeric(string $str): bool + public function numeric(string $str=null): bool { return (bool)preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); @@ -385,7 +387,7 @@ public function numeric(string $str): bool * * @return bool */ - public function regex_match(string $str, string $pattern, array $data): bool + public function regex_match(string $str=null, string $pattern, array $data): bool { if (substr($pattern, 0, 1) != '/') { @@ -404,7 +406,7 @@ public function regex_match(string $str, string $pattern, array $data): bool * * @return bool */ - public function required($str): bool + public function required($str=null): bool { return is_array($str) ? (bool)count($str) : (trim($str) !== ''); } @@ -425,7 +427,7 @@ public function required($str): bool * * @return bool */ - public function required_with($str, string $fields, array $data): bool + public function required_with($str=null, string $fields, array $data): bool { $fields = explode(',', $fields); @@ -467,7 +469,7 @@ public function required_with($str, string $fields, array $data): bool * * @return bool */ - public function required_without($str, string $fields, array $data): bool + public function required_without($str=null, string $fields, array $data): bool { $fields = explode(',', $fields); @@ -506,7 +508,7 @@ public function required_without($str, string $fields, array $data): bool * * @return bool */ - public function timezone(string $str): bool + public function timezone(string $str=null): bool { return in_array($str, timezone_identifiers_list()); } @@ -522,7 +524,7 @@ public function timezone(string $str): bool * @param string * @return bool */ - public function valid_base64(string $str): bool + public function valid_base64(string $str=null): bool { return (base64_encode(base64_decode($str)) === $str); } @@ -536,7 +538,7 @@ public function valid_base64(string $str): bool * * @return bool */ - public function valid_email(string $str): bool + public function valid_email(string $str=null): bool { if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@')) { @@ -558,7 +560,7 @@ public function valid_email(string $str): bool * * @return bool */ - public function valid_emails(string $str): bool + public function valid_emails(string $str=null): bool { if (strpos($str, ',') === false) { @@ -587,7 +589,7 @@ public function valid_emails(string $str): bool * * @return bool */ - public function valid_ip(string $ip, string $which = null, array $data): bool + public function valid_ip(string $ip=null, string $which = null, array $data): bool { switch (strtolower($which)) { @@ -614,7 +616,7 @@ public function valid_ip(string $ip, string $which = null, array $data): bool * * @return bool */ - public function valid_url(string $str): bool + public function valid_url(string $str=null): bool { if (empty($str)) { diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 6e1198440b06..d3d5159da8f5 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -163,9 +163,6 @@ public function testSetErrors() //-------------------------------------------------------------------- - /** - * @group single - */ public function testRulesReturnErrors() { $this->validation->setRules([ @@ -241,6 +238,21 @@ public function testSetRuleGroupException() // Rules Tests //-------------------------------------------------------------------- + public function testRequiredNull() + { + $data = [ + 'foo' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'required', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + public function testRequiredTrueString() { $data = [ @@ -331,6 +343,22 @@ public function testRegexMatchFalse() //-------------------------------------------------------------------- + public function testMatchesNull() + { + $data = [ + 'foo' => null, + 'bar' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'matches[bar]', + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + public function testMatchesTrue() { $data = [ @@ -363,6 +391,22 @@ public function testMatchesFalse() //-------------------------------------------------------------------- + public function testDiffersNull() + { + $data = [ + 'foo' => null, + 'bar' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'differs[bar]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + public function testDiffersTrue() { $data = [ @@ -455,6 +499,21 @@ public function testIsUniqueIgnoresParams() //-------------------------------------------------------------------- + public function testMinLengthNull() + { + $data = [ + 'foo' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'min_length[3]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + public function testMinLengthReturnsFalseWithNonNumericVal() { $data = [ @@ -515,6 +574,21 @@ public function testMinLengthReturnsFalseWhenWrong() //-------------------------------------------------------------------- + public function testMaxLengthNull() + { + $data = [ + 'foo' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'max_length[1]', + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + public function testMaxLengthReturnsFalseWithNonNumericVal() { $data = [ @@ -575,6 +649,21 @@ public function testMaxLengthReturnsFalseWhenWrong() //-------------------------------------------------------------------- + public function testExactLengthNull() + { + $data = [ + 'foo' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'exact_length[3]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + public function testExactLengthReturnsTrueOnSuccess() { $data = [ @@ -623,7 +712,7 @@ public function testExactLengthReturnsFalseWhenLong() /** * @dataProvider urlProvider */ - public function testValidURL(string $url, bool $expected) + public function testValidURL(string $url=null, bool $expected) { $data = [ 'foo' => $url, @@ -651,6 +740,7 @@ public function urlProvider() ['htt://www.codeigniter.com', false], ['', false], ['code igniter', false], + [null, false], ]; } @@ -703,6 +793,7 @@ public function emailProviderSingle() return [ ['email@sample.com', true], ['valid_email', false], + [null, false], ]; } @@ -715,6 +806,7 @@ public function emailsProvider() ['1@sample.com, 2@sample.com', true], ['email@sample.com', true], ['@sample.com,2@sample.com,validemail@email.ca', false], + [null, false] ]; } @@ -753,6 +845,7 @@ public function ipProvider() ['127.0.0.1', 'ipv6', false], ['H001:0db8:85a3:0000:0000:8a2e:0370:7334', null, false], ['127.0.0.259', null, false], + [null, null, false] ]; } @@ -786,6 +879,7 @@ public function alphaProvider() ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ ', false], ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ1', false], ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ*', false], + [null, false], ]; } @@ -818,6 +912,7 @@ public function alphaNumericProvider() ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789', true], ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789\ ', false], ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789_', false], + [null, false], ]; } @@ -849,6 +944,7 @@ public function alphaNumericSpaceProvider() return [ [' abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789', true], [' abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-', false], + [null, false], ]; } @@ -880,6 +976,7 @@ public function alphaDashProvider() return [ ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-', true], ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-\ ', false], + [null, false], ]; } @@ -915,6 +1012,7 @@ public function numericProvider() ['+42', true], ['123a', false], ['--1', false], + [null, false] ]; } @@ -950,6 +1048,7 @@ public function integerProvider() ['123a', false], ['1.9', false], ['--1', false], + [null, false], ]; } @@ -985,6 +1084,7 @@ public function decimalProvider() ['1.0a', false], ['-i', false], ['--1', false], + [null, false] ]; } @@ -1019,6 +1119,7 @@ public function greaterThanProvider() ['10', '10', false], ['10', 'a', false], ['10a', '10', false], + [null, null, false] ]; } @@ -1052,6 +1153,9 @@ public function greaterThanEqualProvider() ['1', '0', true], ['-1', '0', false], ['10a', '0', false], + [null, null, false], + [1, null, true], + [null, 1, false] ]; } @@ -1085,6 +1189,9 @@ public function lessThanProvider() ['-1', '0', true], ['4', '4', false], ['10a', '5', false], + [null, null, false], + [1, null, false], + [null, 1, false] ]; } @@ -1119,6 +1226,9 @@ public function lessThanEqualProvider() ['4', '4', true], ['0', '-1', false], ['10a', '0', false], + [null, null, false], + [null, 1, false], + [1, null, false] ]; } @@ -1149,11 +1259,14 @@ public function inListProvider() { return [ ['red', 'red,Blue,123', true], + ['Blue', 'red, Blue,123', true], ['Blue', 'red,Blue,123', true], ['123', 'red,Blue,123', true], ['Red', 'red,Blue,123', false], [' red', 'red,Blue,123', false], ['1234', 'red,Blue,123', false], + [null, 'red,Blue,123', false], + ['red', null, false], ]; } @@ -1187,6 +1300,7 @@ public function naturalProvider() ['12', true], ['42a', false], ['-1', false], + [null, false] ]; } @@ -1219,7 +1333,8 @@ public function naturalZeroProvider() ['0', false], ['12', true], ['42a', false], - ['-1', false] + ['-1', false], + [null, false] ]; } @@ -1251,6 +1366,7 @@ public function base64Provider() return [ [base64_encode('string'), true], ['FA08GG', false], + [null, false] ]; } @@ -1283,6 +1399,7 @@ public function timezoneProvider() ['America/Chicago', true], ['america/chicago', false], ['foo/bar', false], + [null, false] ]; } @@ -1317,6 +1434,9 @@ public function requiredWithProvider() ['nope', 'bar', false], ['foo', 'bar', true], ['nope', 'baz', true], + [null, null, true], + [null, 'foo', true], + ['foo', null, true] ]; } @@ -1350,6 +1470,9 @@ public function requiredWithoutProvider() return [ ['nope', 'bars', false], ['foo', 'nope', true], + [null, null, false], + [null, 'foo', true], + ['foo', null, true] ]; } @@ -1391,6 +1514,7 @@ public function testValidCCNumber($type, $number, $expected = false) public function creditCardProvider() { return [ + 'null_test' => ['amex', null, false], 'random_test' => ['amex', $this->generateCardNum('37', 16), false], 'invalid_type' => ['shorty', '1111 1111 1111 1111', false], 'invalid_length' => ['amex', '', false], @@ -1714,4 +1838,4 @@ public function testUploadedFalse() } //-------------------------------------------------------------------- -} \ No newline at end of file +} From 82c1d26978a17c062e59065a786d43068d66a99f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 12 Feb 2017 08:26:38 +0700 Subject: [PATCH 0543/1807] set project name in composer.json to codeigniter/framework4 on next move, we can register at packagist under "codeigniter" package https://packagist.org/packages/codeigniter/ --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d1500a8b7c97..4b2346ccd7d7 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "description": "The CodeIgniter framework v4", - "name": "codeigniter/framework", + "name": "codeigniter/framework4", "type": "project", "homepage": "https://codeigniter.com", "license": "MIT", From 55c69ced86af1de273e1c9487c9b95adec6195dd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 7 Mar 2017 13:18:16 +0700 Subject: [PATCH 0544/1807] change to codeigniter4/framework --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4b2346ccd7d7..c3cefca6a347 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "description": "The CodeIgniter framework v4", - "name": "codeigniter/framework4", + "name": "codeigniter4/framework", "type": "project", "homepage": "https://codeigniter.com", "license": "MIT", From af6b37d916ffc6f0f01d19e7c35e102a94ec85a1 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 8 Mar 2017 22:58:06 -0600 Subject: [PATCH 0545/1807] PageNotFound errors should actually show 404 status codes. Fixes #425 --- system/CodeIgniter.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 47193d277b54..e55cebd3ef5c 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -806,7 +806,8 @@ protected function display404errors(PageNotFoundException $e) $buffer = ob_get_contents(); ob_end_clean(); - echo $buffer; + $this->response->setBody($buffer); + $this->response->send(); $this->callExit(EXIT_UNKNOWN_FILE); // Unknown file } From 5e296ca192222751e736991b310eda7e76097a5c Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Thu, 9 Mar 2017 19:05:09 +0000 Subject: [PATCH 0546/1807] Added Alpha Space Validation Rule Added `alpha_space` validation rule. Signed-off-by: Kristian Matthews --- system/Validation/Rules.php | 24 +++++++++++++ tests/system/Validation/ValidationTest.php | 36 +++++++++++++++++++ .../source/libraries/validation.rst | 1 + 3 files changed, 61 insertions(+) diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index e4649ca8ffa0..b90a6b981a2f 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -38,6 +38,11 @@ use Config\Database; +/** + * Rules. + * + * @package CodeIgniter\Validation + */ class Rules { /** @@ -54,6 +59,25 @@ public function alpha(string $str=null): bool //-------------------------------------------------------------------- + /** + * Alpha with spaces. + * + * @param string $value Value. + * + * @return bool True if alpha with spaces, else false. + */ + public function alpha_space(string $value = null): bool + { + if ($value === null) + { + return true; + } + + return (bool)preg_match('/^[A-Z ]+$/i', $value); + } + + //-------------------------------------------------------------------- + /** * Alpha-numeric with underscores and dashes * diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index d3d5159da8f5..a4b141a5f897 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -883,6 +883,42 @@ public function alphaProvider() ]; } + //-------------------------------------------------------------------- + + /** + * Test alpha with spaces. + * + * @param mixed $value Value. + * @param bool $expected Expected. + * + * @dataProvider alphaSpaceProvider + */ + public function testAlphaSpace($value, $expected) + { + $data = [ + 'foo' => $value + ]; + + $this->validation->setRules([ + 'foo' => 'alpha_space' + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function alphaSpaceProvider() + { + return [ + [null, true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ ', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ1', false], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ*', false], + ]; + } + //-------------------------------------------------------------------- /** diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst index 9f7d9566285e..6166527a02f2 100644 --- a/user_guide_src/source/libraries/validation.rst +++ b/user_guide_src/source/libraries/validation.rst @@ -563,6 +563,7 @@ The following is a list of all the native rules that are available to use: Rule Parameter Description Example ======================= =========== =============================================================================================== =================================================== alpha No Fails if field has anything other than alphabetic characters. +alpha_space No Fails if field contains anything other than alphabetic characters or spaces. alpha_dash No Fails if field contains anything other than alpha-numeric characters, underscores or dashes. alpha_numeric No Fails if field contains anything other than alpha-numeric characters or numbers. alpha_numeric_space No Fails if field contains anything other than alpha-numeric characters, numbers or space. From 57903ddae68778b8306e09193fc04f9dbc9ea2eb Mon Sep 17 00:00:00 2001 From: Ryan Wu Date: Fri, 10 Mar 2017 17:50:25 +0800 Subject: [PATCH 0547/1807] Fix issure #413: Pagination::link() cant trans string param which retrieve from $_GET to page href --- system/Pager/Pager.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php index 79b86a03f35b..1cbb5cd98003 100644 --- a/system/Pager/Pager.php +++ b/system/Pager/Pager.php @@ -309,7 +309,7 @@ public function getPageURI(int $page = null, string $group = 'default', $returnO $uri = $this->groups[$group]['uri']; - $uri->setQuery('page='.$page); + $uri->addQuery('page='.$page); return $returnObject === true ? $uri @@ -443,6 +443,11 @@ protected function ensureGroup(string $group) 'perPage' => $this->config->perPage, 'pageCount' => 1, ]; + + if($_GET) + { + $this->groups[$group]['uri'] = $this->groups[$group]['uri']->setQueryArray($_GET); + } } //-------------------------------------------------------------------- From 84675f4f4709e1eb7df709a3dc6156841a37cf28 Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Fri, 10 Mar 2017 15:57:40 +0000 Subject: [PATCH 0548/1807] Updated DB Connection Interface Table Method Changed return type from `QueryBuilder` to `BaseBuilder`. Signed-off-by: Kristian Matthews --- system/Database/ConnectionInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/ConnectionInterface.php b/system/Database/ConnectionInterface.php index 6b656f7bdf72..792b2796951a 100644 --- a/system/Database/ConnectionInterface.php +++ b/system/Database/ConnectionInterface.php @@ -175,9 +175,9 @@ public function simpleQuery(string $sql); /** * Returns an instance of the query builder for this connection. * - * @param string|array $tableName + * @param string|array $tableName Table name. * - * @return QueryBuilder + * @return BaseBuilder Builder. */ public function table($tableName); From db7cf29cf09e7c6e1a302c4d94b7f3e3c37874c6 Mon Sep 17 00:00:00 2001 From: Basel Juma Date: Fri, 10 Mar 2017 20:57:31 +0200 Subject: [PATCH 0549/1807] fix migration:create and edit documentation --- system/Commands/Database/CreateMigration.php | 5 ++--- user_guide_src/source/database/migration.rst | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php index 497b6db5b797..18c8130dcd46 100644 --- a/system/Commands/Database/CreateMigration.php +++ b/system/Commands/Database/CreateMigration.php @@ -104,9 +104,8 @@ public function run(array $params=[]) $path = $homepath.'/Database/Migrations/'.date('YmdHis_').$name.'.php'; $template =<< php ci.php migrate:create [filename] -You can use (refresh) with the following options: +You can use (create) with the following options: + - (-n) to choose namespace, otherwise (App) namespace will be used. ********************* From a8b70287b109305abbc6cfdfb8084b31bd627a1d Mon Sep 17 00:00:00 2001 From: Kristian Matthews Date: Fri, 10 Mar 2017 23:31:20 +0000 Subject: [PATCH 0550/1807] Updated Respond Created & Deleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don’t require `$data` for `respondCreated()` and `respondDeleted()` methods. Signed-off-by: Kristian Matthews --- system/API/ResponseTrait.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php index e6aa043bd123..82e8bbaa1f98 100644 --- a/system/API/ResponseTrait.php +++ b/system/API/ResponseTrait.php @@ -159,12 +159,12 @@ public function fail($messages, int $status = 400, string $code = null, string $ /** * Used after successfully creating a new resource. * - * @param $data - * @param string $message + * @param mixed $data Data. + * @param string $message Message. * * @return mixed */ - public function respondCreated($data, string $message = '') + public function respondCreated($data = null, string $message = '') { return $this->respond($data, $this->codes['created'], $message); } @@ -174,12 +174,12 @@ public function respondCreated($data, string $message = '') /** * Used after a resource has been successfully deleted. * - * @param $data - * @param string $message + * @param mixed $data Data. + * @param string $message Message. * * @return mixed */ - public function respondDeleted($data, string $message = '') + public function respondDeleted($data = null, string $message = '') { return $this->respond($data, $this->codes['deleted'], $message); } From be415bfe9a7f80e42f41d7c34805182954e33bff Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 12 Mar 2017 23:24:20 -0500 Subject: [PATCH 0551/1807] Fixes pass by reference error when setting a JSON body in CURLRequest. Fixes #434 --- application/Controllers/Checks.php | 21 +++++++++++++++++++++ system/HTTP/CURLRequest.php | 15 ++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php index ba92cd610227..5135805a8e9a 100644 --- a/application/Controllers/Checks.php +++ b/application/Controllers/Checks.php @@ -1,6 +1,7 @@ true, + 'follow_redirects' => true, + 'json' => ['foo' => 'bar'] + ]); + + echo '
';
+        $response = $client->request('PUT', 'http://ci4.dev/checks/catch');
+        echo $response->getBody();
+    }
+
+    // Simply echos back what's given in the body.
+    public function catch()
+    {
+        $body = print_r($this->request->getRawInput(), true);
+        echo $body;
+    }
+
 
 }
diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index cf6cac585bf2..7995fd38b544 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -52,14 +52,14 @@ class CURLRequest extends Request
 {
 	/**
 	 * The response object associated with this request
-	 * 
+	 *
 	 * @var ResponseInterface
 	 */
 	protected $response;
 
 	/**
 	 * The URI associated with this request
-	 * 
+	 *
 	 * @var URI
 	 */
 	protected $baseURI;
@@ -430,7 +430,7 @@ protected function applyRequestHeaders(array $curl_options = []): array
 
 	/**
 	 * Apply method
-	 * 
+	 *
 	 * @param type $method
 	 * @param array $curl_options
 	 * @return int
@@ -472,7 +472,7 @@ protected function applyMethod($method, array $curl_options): array
 
 	/**
 	 * Apply body
-	 * 
+	 *
 	 * @param array $curl_options
 	 * @return type
 	 */
@@ -526,7 +526,7 @@ protected function setResponseHeaders(array $headers = [])
 
 	/**
 	 * Set CURL options
-	 * 
+	 *
 	 * @param array $curl_options
 	 * @param array $config
 	 * @return type
@@ -590,7 +590,7 @@ protected function setCURLOptions(array $curl_options = [], array $config = [])
 		}
 
 		// Debug
-		if (isset($config['debug']))
+		if (isset($config['debug']) && $config['debug'] === true)
 		{
 			$curl_options[CURLOPT_VERBOSE] = 1;
 			$curl_options[CURLOPT_STDERR]  = is_bool($config['debug']) ? fopen('php://output', 'w+') : $config['debug'];
@@ -679,7 +679,8 @@ protected function setCURLOptions(array $curl_options = [], array $config = [])
 		if (isset($config['json']))
 		{
 			// Will be set as the body in `applyBody()`
-			$this->setBody(json_encode($config['json']));
+            $json = json_encode($config['json']);
+			$this->setBody($json);
 			$this->setHeader('Content-Type', 'application/json');
 		}
 

From c72fef408dd54ac45e6593bc6689e7da66c5bf32 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Sun, 12 Mar 2017 23:34:47 -0500
Subject: [PATCH 0552/1807] [ci skip] A little breathing room in created
 migrations.

---
 system/Commands/Database/CreateMigration.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php
index 18c8130dcd46..488c32fbca2d 100644
--- a/system/Commands/Database/CreateMigration.php
+++ b/system/Commands/Database/CreateMigration.php
@@ -69,7 +69,7 @@ class CreateMigration extends BaseCommand
      */
     public function run(array $params=[])
     {
-    
+
         $name = array_shift($params);
 
         if (empty($name))
@@ -105,6 +105,7 @@ public function run(array $params=[])
 
         $template =<<
Date: Tue, 14 Mar 2017 01:04:54 +0200
Subject: [PATCH 0553/1807] Add Help functionality

---
 system/CLI/BaseCommand.php   | 360 +++++++++++++++++++++--------------
 system/CLI/CommandRunner.php |   2 +-
 system/CLI/Console.php       |   2 +-
 system/Commands/Help.php     |  20 +-
 system/Language/en/CLI.php   |  44 +++++
 5 files changed, 282 insertions(+), 146 deletions(-)
 create mode 100644 system/Language/en/CLI.php

diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php
index 4c7ff1d0bf68..82d6522d3728 100644
--- a/system/CLI/BaseCommand.php
+++ b/system/CLI/BaseCommand.php
@@ -1,150 +1,230 @@
 logger = $logger;
-		$this->commands = $commands;
-	}
-
-	//--------------------------------------------------------------------
-
-	abstract public function run(array $params);
-
-	//--------------------------------------------------------------------
-
-	/**
-	 * Can be used by a command to run other commands.
-	 *
-	 * @param string $command
-	 * @param array  $params
-	 */
-	protected function call(string $command, array $params=[])
-	{
-		// The CommandRunner will grab the first element
-		// for the command name.
-		array_unshift($params, $command);
-
-		return $this->commands->index($params);
-	}
-
-	//--------------------------------------------------------------------
-
-	/**
-	 * A simple method to display an error with line/file,
-	 * in child commands.
-	 *
-	 * @param \Exception $e
-	 */
-	protected function showError(\Exception $e)
-	{
-		CLI::newLine();
-		CLI::error($e->getMessage());
-		CLI::write($e->getFile().' - '.$e->getLine());
-		CLI::newLine();
-	}
-
-	//--------------------------------------------------------------------
-
-	/**
-	 * Makes it simple to access our protected properties.
-	 *
-	 * @param string $key
-	 *
-	 * @return mixed
-	 */
-	public function __get(string $key)
-	{
-		if (isset($this->$key))
-		{
-			return $this->$key;
-		}
-	}
-
-	//--------------------------------------------------------------------
-}
+    * the Command's Arguments description
+    *
+    * @var string
+    */
+    protected $arguments = array();
+
+    /**
+    * @var \Psr\Log\LoggerInterface
+    */
+    protected $logger;
+    
+    /**
+    * Instance of the CommandRunner controller
+    * so commands can call other commands.
+    *
+    * @var \CodeIgniter\CLI\CommandRunner
+    */
+    protected $commands;
+    
+    //--------------------------------------------------------------------
+    
+    public function __construct(LoggerInterface $logger, CommandRunner $commands)
+    {
+        $this->logger = $logger;
+        $this->commands = $commands;
+		$this->usage = $this->name . " [arguments]" ;
+    }
+    
+    //--------------------------------------------------------------------
+    
+    abstract public function run(array $params);
+    
+    //--------------------------------------------------------------------
+    
+    /**
+    * Can be used by a command to run other commands.
+    *
+    * @param string $command
+    * @param array  $params
+    */
+    protected function call(string $command, array $params=[])
+    {
+        // The CommandRunner will grab the first element
+        // for the command name.
+        array_unshift($params, $command);
+        
+        return $this->commands->index($params);
+    }
+    
+    //--------------------------------------------------------------------
+    
+    /**
+    * A simple method to display an error with line/file,
+    * in child commands.
+    *
+    * @param \Exception $e
+    */
+    protected function showError(\Exception $e)
+    {
+        CLI::newLine();
+        CLI::error($e->getMessage());
+        CLI::write($e->getFile().' - '.$e->getLine());
+        CLI::newLine();
+    }
+    
+    //--------------------------------------------------------------------
+    
+    /**
+    * Makes it simple to access our protected properties.
+    *
+    * @param string $key
+    *
+    * @return mixed
+    */
+    public function __get(string $key)
+    {
+        if (isset($this->$key))
+        {
+            return $this->$key;
+        }
+    }
+    
+    //--------------------------------------------------------------------
+    
+    /**
+    * show Help include (usage,arguments,description,options)
+    *
+    *
+    * @return mixed
+    */
+    public function showHelp()
+    {
+		$spaces= "   ";
+        CLI::write(lang('CLI.helpDescription'), 'yellow');
+        CLI::write("$spaces" . $this->description);
+        CLI::newLine();
+        
+        CLI::write(lang('CLI.helpUsage'), 'yellow');
+        CLI::write("$spaces" . $this->usage);
+        CLI::newLine();
+        
+		$pad = max($this->getPad($this->options,6), $this->getPad($this->arguments,6));
+
+		if (!empty($this->arguments)){
+			CLI::write(lang('CLI.helpArguments'), 'yellow');			
+            foreach ($this->arguments as $argument => $description) {
+                CLI::write( "$spaces" . CLI::color(str_pad($argument, $pad),'green') . $description,'yellow');
+            }
+        }
+		
+        if (!empty($this->options)){
+			CLI::write(lang('CLI.helpOptions'), 'yellow');			
+            foreach ($this->options as $option => $description) {
+                CLI::write( "$spaces" . CLI::color(str_pad($option, $pad),'green') . $description,'yellow');
+            }
+        }		
+    }  
+
+    //--------------------------------------------------------------------
+    
+    /**
+    * Get pad for $key => $value array output
+    *
+    * @param array $array
+    * @param int   $pad
+    *
+    * @return int
+    */
+    public function getPad($array, string $pad)
+    {
+        $max = 0;
+        foreach ($array as $key => $value)
+        {            
+            $max = max($max, strlen($key));
+        }
+        return $max + $pad;
+    }
+    
+    
+    
+    //--------------------------------------------------------------------
+}
\ No newline at end of file
diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php
index 4ccc27b6a77b..fc3edef3cb48 100644
--- a/system/CLI/CommandRunner.php
+++ b/system/CLI/CommandRunner.php
@@ -73,7 +73,7 @@ public function index(array $params)
 	{
 		$command = array_shift($params);
 
-		$this->createCommandList($command);
+		$this->createCommandList();
 
 		if (is_null($command))
 		{
diff --git a/system/CLI/Console.php b/system/CLI/Console.php
index fcb5205c3a68..aaf1bbf95d50 100644
--- a/system/CLI/Console.php
+++ b/system/CLI/Console.php
@@ -60,7 +60,7 @@ public function __construct(CodeIgniter $app)
      */
     public function run()
     {
-        $path = CLI::getURI() ?: 'help';
+        $path = CLI::getURI() ?: 'list';
 
         // Set the path for the application to route to.
         $this->app->setPath("ci{$path}");
diff --git a/system/Commands/Help.php b/system/Commands/Help.php
index 02538d257154..240acbddfc24 100644
--- a/system/Commands/Help.php
+++ b/system/Commands/Help.php
@@ -65,6 +65,15 @@ class Help extends BaseCommand
      */
     protected $description = 'Displays basic usage information.';
 
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array(
+        'command_name' => 'The command name [default: "help"]'
+    );
+
     //--------------------------------------------------------------------
 
     /**
@@ -74,11 +83,14 @@ class Help extends BaseCommand
      */
     public function run(array $params)
     {
-        CLI::write('Usage:');
-        CLI::write("\tcommand [arguments]");
+        $command = array_shift($params);
+        if(is_null($command)){
+            $command = 'help';
+        }
 
-        $this->call('list');
+        $commands = $this->commands->getCommands();
+        $class=new $commands[$command]['class']($this->logger, $this->commands);
 
-        CLI::newLine();
+        $class->showHelp();
     }
 }
diff --git a/system/Language/en/CLI.php b/system/Language/en/CLI.php
new file mode 100644
index 000000000000..a68d1cb47eaa
--- /dev/null
+++ b/system/Language/en/CLI.php
@@ -0,0 +1,44 @@
+ 'Usage',
+	'helpDescription'        => 'Description',
+	'helpOptions'            => 'Options',
+	'helpArguments'          => 'Arguments',
+];
\ No newline at end of file

From 810187b8a8e4b35e514ef2eb6966d1f69911a25d Mon Sep 17 00:00:00 2001
From: Basel Juma 
Date: Tue, 14 Mar 2017 02:04:25 +0200
Subject: [PATCH 0554/1807] Add help functionality to existing commands

---
 system/CLI/BaseCommand.php                   | 12 +++++----
 system/Commands/Database/CreateMigration.php | 25 ++++++++++++++++++
 system/Commands/Database/MigrateCurrent.php  | 24 +++++++++++++++++
 system/Commands/Database/MigrateLatest.php   | 26 +++++++++++++++++++
 system/Commands/Database/MigrateRefresh.php  | 25 ++++++++++++++++++
 system/Commands/Database/MigrateRollback.php | 27 +++++++++++++++++++-
 system/Commands/Database/MigrateStatus.php   | 23 +++++++++++++++++
 system/Commands/Database/MigrateVersion.php  | 26 +++++++++++++++++++
 system/Commands/Database/Seed.php            | 23 +++++++++++++++++
 system/Commands/Help.php                     | 15 +++++++++++
 system/Commands/ListCommands.php             | 21 +++++++++++++++
 system/Commands/Sessions/CreateMigration.php | 21 +++++++++++++++
 system/Language/en/CLI.php                   |  8 +++---
 13 files changed, 266 insertions(+), 10 deletions(-)

diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php
index 82d6522d3728..0faf08122913 100644
--- a/system/CLI/BaseCommand.php
+++ b/system/CLI/BaseCommand.php
@@ -111,7 +111,6 @@ public function __construct(LoggerInterface $logger, CommandRunner $commands)
     {
         $this->logger = $logger;
         $this->commands = $commands;
-		$this->usage = $this->name . " [arguments]" ;
     }
     
     //--------------------------------------------------------------------
@@ -180,11 +179,12 @@ public function showHelp()
     {
 		$spaces= "   ";
         CLI::write(lang('CLI.helpDescription'), 'yellow');
-        CLI::write("$spaces" . $this->description);
+        CLI::write($spaces . $this->description);
         CLI::newLine();
         
         CLI::write(lang('CLI.helpUsage'), 'yellow');
-        CLI::write("$spaces" . $this->usage);
+        $usage = empty($this->usage) ? $this->name . " [arguments]" : $this->usage;
+        CLI::write($spaces . $usage );
         CLI::newLine();
         
 		$pad = max($this->getPad($this->options,6), $this->getPad($this->arguments,6));
@@ -192,15 +192,17 @@ public function showHelp()
 		if (!empty($this->arguments)){
 			CLI::write(lang('CLI.helpArguments'), 'yellow');			
             foreach ($this->arguments as $argument => $description) {
-                CLI::write( "$spaces" . CLI::color(str_pad($argument, $pad),'green') . $description,'yellow');
+                CLI::write( $spaces . CLI::color(str_pad($argument, $pad),'green') . $description,'yellow');               
             }
+             CLI::newLine();
         }
 		
         if (!empty($this->options)){
 			CLI::write(lang('CLI.helpOptions'), 'yellow');			
             foreach ($this->options as $option => $description) {
-                CLI::write( "$spaces" . CLI::color(str_pad($option, $pad),'green') . $description,'yellow');
+                CLI::write( $spaces . CLI::color(str_pad($option, $pad),'green') . $description,'yellow');                
             }
+            CLI::newLine();
         }		
     }  
 
diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php
index 488c32fbca2d..68e0a723b83d 100644
--- a/system/Commands/Database/CreateMigration.php
+++ b/system/Commands/Database/CreateMigration.php
@@ -63,6 +63,31 @@ class CreateMigration extends BaseCommand
      */
     protected $description = 'Creates a new migration file.';
 
+     /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:create [migration_name] [Options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array(
+        'migration_name' => 'The migration file name'
+    );
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-n' => 'Set migration namespace'
+    );
+
     /**
      * Creates a new migration file with the current timestamp.
      * @todo Have this check the settings and see what type of file it should create (timestamp or sequential)
diff --git a/system/Commands/Database/MigrateCurrent.php b/system/Commands/Database/MigrateCurrent.php
index 7020eaf04e78..d14fce34583c 100644
--- a/system/Commands/Database/MigrateCurrent.php
+++ b/system/Commands/Database/MigrateCurrent.php
@@ -63,6 +63,30 @@ class MigrateCurrent extends BaseCommand
      */
     protected $description = 'Migrates us up or down to the version specified as $currentVersion in the migrations config file.';
 
+     /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:current [options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-g' => 'Set database group'
+    );
+
+
     /**
      * Migrates us up or down to the version specified as $currentVersion
      * in the migrations config file.
diff --git a/system/Commands/Database/MigrateLatest.php b/system/Commands/Database/MigrateLatest.php
index 245b234e501d..accaa8561d54 100644
--- a/system/Commands/Database/MigrateLatest.php
+++ b/system/Commands/Database/MigrateLatest.php
@@ -63,6 +63,32 @@ class MigrateLatest extends BaseCommand
      */
     protected $description = 'Migrates the database to the latest schema.';
 
+     /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:latest [options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-n'   => 'Set migration namespace',
+        '-g'   => 'Set database group',
+        '-all' => 'Set latest for all namespace, will ignore (-n) option'
+    );
+
+
     /**
      * Ensures that all migrations have been run.
      */
diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php
index 5dfb33614655..2e77e03531c9 100644
--- a/system/Commands/Database/MigrateRefresh.php
+++ b/system/Commands/Database/MigrateRefresh.php
@@ -64,6 +64,31 @@ class MigrateRefresh extends BaseCommand
      */
     protected $description = 'Does a rollback followed by a latest to refresh the current state of the database.';
 
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:refresh [Options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-n'   => 'Set migration namespace',
+        '-g'   => 'Set database group',
+        '-all' => 'Set latest for all namespace, will ignore (-n) option'
+    );
+
     /**
      * Does a rollback followed by a latest to refresh the current state
      * of the database.
diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php
index 10f341801a21..7d1e368a9834 100644
--- a/system/Commands/Database/MigrateRollback.php
+++ b/system/Commands/Database/MigrateRollback.php
@@ -65,6 +65,31 @@ class MigrateRollback extends BaseCommand
     */
     protected $description = 'Runs all of the migrations in reverse order, until they have all been un-applied.';
     
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:rollback [Options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-n'   => 'Set migration namespace',
+        '-g'   => 'Set database group',
+        '-all' => 'Set latest for all namespace, will ignore (-n) option'
+    );
+
     /**
     * Runs all of the migrations in reverse order, until they have
     * all been un-applied.
@@ -95,7 +120,7 @@ public function run(array $params=[])
                     $runner->version(0,$namespace,$group);
                 }
             }
-             $messages = $runner->getCliMessages();
+            $messages = $runner->getCliMessages();
             foreach ($messages as $message) {
                 CLI::write($message); 
             }
diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php
index e2d758facbe3..91af5e5ce364 100644
--- a/system/Commands/Database/MigrateStatus.php
+++ b/system/Commands/Database/MigrateStatus.php
@@ -63,6 +63,29 @@ class MigrateStatus extends BaseCommand
     * @var string
     */
     protected $description = 'Displays a list of all migrations and whether they\'ve been run or not.';
+
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:status [Options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-g'   => 'Set database group'
+    );
     
     /**
     * Displays a list of all migrations and whether they've been run or not.
diff --git a/system/Commands/Database/MigrateVersion.php b/system/Commands/Database/MigrateVersion.php
index 5a67d3ad6076..91977b6529cf 100644
--- a/system/Commands/Database/MigrateVersion.php
+++ b/system/Commands/Database/MigrateVersion.php
@@ -63,6 +63,32 @@ class MigrateVersion extends BaseCommand
      */
     protected $description = 'Migrates the database up or down to get to the specified version.';
 
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'migrate:version [version_number] [Options]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array(
+        'version_number' => 'The version number to migrate'
+    );
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array(
+        '-n'   => 'Set migration namespace',
+        '-g'   => 'Set database group'
+    );
+
     /**
      * Migrates the database up or down to get to the specified version.
      */
diff --git a/system/Commands/Database/Seed.php b/system/Commands/Database/Seed.php
index 6197090e34a4..3fddf62fd1ca 100644
--- a/system/Commands/Database/Seed.php
+++ b/system/Commands/Database/Seed.php
@@ -65,6 +65,29 @@ class Seed extends BaseCommand
      */
     protected $description = 'Runs the specified seeder to populate known data into the database.';
 
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'db:seed [seeder_name]';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array(
+        'seeder_name' => 'The seeder name to run'
+    );
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array();
+
     /**
      * Runs all of the migrations in reverse order, until they have
      * all been un-applied.
diff --git a/system/Commands/Help.php b/system/Commands/Help.php
index 240acbddfc24..05f425be2934 100644
--- a/system/Commands/Help.php
+++ b/system/Commands/Help.php
@@ -65,6 +65,13 @@ class Help extends BaseCommand
      */
     protected $description = 'Displays basic usage information.';
 
+     /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'help command_name';
+
     /**
      * the Command's Arguments
      *
@@ -74,6 +81,14 @@ class Help extends BaseCommand
         'command_name' => 'The command name [default: "help"]'
     );
 
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array();
+
+
     //--------------------------------------------------------------------
 
     /**
diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php
index 41422d6b906f..519d42c88b2d 100644
--- a/system/Commands/ListCommands.php
+++ b/system/Commands/ListCommands.php
@@ -65,6 +65,27 @@ class ListCommands extends BaseCommand
      */
     protected $description = 'Lists the available commands.';
 
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'list';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array();
+
     /**
      * The length of the longest command name.
      * Used during display in columns.
diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php
index b6f4936d25b6..55310a0a5104 100644
--- a/system/Commands/Sessions/CreateMigration.php
+++ b/system/Commands/Sessions/CreateMigration.php
@@ -58,6 +58,27 @@ class CreateMigration extends BaseCommand
      */
     protected $description = 'Generates the migration file for database sessions.';
 
+    /**
+     * the Command's usage
+     *
+     * @var string
+     */
+    protected $usage = 'session:migration';
+
+    /**
+     * the Command's Arguments
+     *
+     * @var array
+     */
+    protected $arguments = array();
+
+    /**
+     * the Command's Options
+     *
+     * @var array
+     */
+    protected $options = array();
+
     /**
      * Creates a new migration file with the current timestamp.
      */
diff --git a/system/Language/en/CLI.php b/system/Language/en/CLI.php
index a68d1cb47eaa..ca28b7da0b5b 100644
--- a/system/Language/en/CLI.php
+++ b/system/Language/en/CLI.php
@@ -37,8 +37,8 @@
  */
 
 return [
-	'helpUsage'              => 'Usage',
-	'helpDescription'        => 'Description',
-	'helpOptions'            => 'Options',
-	'helpArguments'          => 'Arguments',
+	'helpUsage'              => 'Usage:',
+	'helpDescription'        => 'Description:',
+	'helpOptions'            => 'Options:',
+	'helpArguments'          => 'Arguments:',
 ];
\ No newline at end of file

From f40114598b852b6239bea88179c5d2aae24acb6f Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Thu, 16 Mar 2017 22:41:48 -0500
Subject: [PATCH 0555/1807] Refactoring how debug flag is handled.

---
 system/HTTP/CURLRequest.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index 7995fd38b544..06b33462ba71 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -590,9 +590,9 @@ protected function setCURLOptions(array $curl_options = [], array $config = [])
 		}
 
 		// Debug
-		if (isset($config['debug']) && $config['debug'] === true)
+		if (isset($config['debug']))
 		{
-			$curl_options[CURLOPT_VERBOSE] = 1;
+			$curl_options[CURLOPT_VERBOSE] = $config['debug'] === true ? 1 : 0;
 			$curl_options[CURLOPT_STDERR]  = is_bool($config['debug']) ? fopen('php://output', 'w+') : $config['debug'];
 		}
 

From e011457da80f6941394c09a807b2397eb7bafd9f Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Thu, 16 Mar 2017 23:11:37 -0500
Subject: [PATCH 0556/1807] [ci skip] Add editorconfig file. We will remove it
 whenever we package up zip files

---
 .editorconfig | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 .editorconfig

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000000..23a7072c587b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+; top-most EditorConfig file
+root = true
+
+; Unix-style newlines
+[*]
+end_of_line = lf
+
+[*.php]
+indent_style = tab
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true

From 0beb919c11763209315e46db9100f93b9f62af4e Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Thu, 16 Mar 2017 23:20:17 -0500
Subject: [PATCH 0557/1807] [ci skip] docblock fixes

---
 system/HTTP/CURLRequest.php | 84 ++++++++++++++++++++-----------------
 1 file changed, 45 insertions(+), 39 deletions(-)

diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index 06b33462ba71..91f5194974f5 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -134,7 +134,7 @@ public function __construct(App $config, URI $uri, ResponseInterface $response =
 	 * @param string     $url
 	 * @param array      $options
 	 *
-	 * @return Response
+	 * @return \CodeIgniter\HTTP\ResponseInterface
 	 */
 	public function request($method, string $url, array $options = []): ResponseInterface
 	{
@@ -157,7 +157,7 @@ public function request($method, string $url, array $options = []): ResponseInte
 	 * @param string $url
 	 * @param array  $options
 	 *
-	 * @return Response
+	 * @return \CodeIgniter\HTTP\ResponseInterface
 	 */
 	public function get(string $url, array $options = []): ResponseInterface
 	{
@@ -172,7 +172,7 @@ public function get(string $url, array $options = []): ResponseInterface
 	 * @param string $url
 	 * @param array  $options
 	 *
-	 * @return Response
+	 * @return \CodeIgniter\HTTP\ResponseInterface
 	 */
 	public function delete(string $url, array $options = []): ResponseInterface
 	{
@@ -211,14 +211,14 @@ public function options(string $url, array $options = []): ResponseInterface
 
 	//--------------------------------------------------------------------
 
-	/**
-	 * Convenience method for sending a PATCH request.
-	 *
-	 * @param string $url
-	 * @param array  $options
-	 *
-	 * @return Response
-	 */
+    /**
+     * Convenience method for sending a PATCH request.
+     *
+     * @param string $url
+     * @param array  $options
+     *
+     * @return \CodeIgniter\HTTP\ResponseInterface
+     */
 	public function patch(string $url, array $options = []): ResponseInterface
 	{
 		return $this->request('patch', $url, $options);
@@ -232,7 +232,7 @@ public function patch(string $url, array $options = []): ResponseInterface
 	 * @param string $url
 	 * @param array  $options
 	 *
-	 * @return Response
+	 * @return \CodeIgniter\HTTP\ResponseInterface
 	 */
 	public function post(string $url, array $options = []): ResponseInterface
 	{
@@ -247,7 +247,7 @@ public function post(string $url, array $options = []): ResponseInterface
 	 * @param string $url
 	 * @param array  $options
 	 *
-	 * @return Response
+	 * @return \CodeIgniter\HTTP\ResponseInterface
 	 */
 	public function put(string $url, array $options = []): ResponseInterface
 	{
@@ -336,12 +336,14 @@ public function getMethod($upper = false): string
 
 	//--------------------------------------------------------------------
 
-	/**
-	 * Fires the actual cURL request.
-	 *
-	 * @param string $method
-	 * @param string $url
-	 */
+    /**
+     * Fires the actual cURL request.
+     *
+     * @param string $method
+     * @param string $url
+     *
+     * @return \CodeIgniter\HTTP\ResponseInterface
+     */
 	public function send(string $method, string $url)
 	{
 		// Reset our curl options so we're on a fresh slate.
@@ -399,12 +401,14 @@ public function send(string $method, string $url)
 
 	//--------------------------------------------------------------------
 
-	/**
-	 * Takes all headers current part of this request and adds them
-	 * to the cURL request.
-	 *
-	 * @param array $curl_options
-	 */
+    /**
+     * Takes all headers current part of this request and adds them
+     * to the cURL request.
+     *
+     * @param array $curl_options
+     *
+     * @return array
+     */
 	protected function applyRequestHeaders(array $curl_options = []): array
 	{
 		$headers = $this->getHeaders();
@@ -428,13 +432,14 @@ protected function applyRequestHeaders(array $curl_options = []): array
 
 	//--------------------------------------------------------------------
 
-	/**
-	 * Apply method
-	 *
-	 * @param type $method
-	 * @param array $curl_options
-	 * @return int
-	 */
+    /**
+     * Apply method
+     *
+     * @param type  $method
+     * @param array $curl_options
+     *
+     * @return array|int
+     */
 	protected function applyMethod($method, array $curl_options): array
 	{
 		$method = strtoupper($method);
@@ -470,12 +475,13 @@ protected function applyMethod($method, array $curl_options): array
 
 	//--------------------------------------------------------------------
 
-	/**
-	 * Apply body
-	 *
-	 * @param array $curl_options
-	 * @return type
-	 */
+    /**
+     * Apply body
+     *
+     * @param array $curl_options
+     *
+     * @return array
+     */
 	protected function applyBody(array $curl_options = []): array
 	{
 		if ( ! empty($this->body))
@@ -529,7 +535,7 @@ protected function setResponseHeaders(array $headers = [])
 	 *
 	 * @param array $curl_options
 	 * @param array $config
-	 * @return type
+	 * @return array
 	 * @throws \InvalidArgumentException
 	 */
 	protected function setCURLOptions(array $curl_options = [], array $config = [])

From ce2b78641090b959e8b2eaeb4bde9374c8a2a47b Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Thu, 16 Mar 2017 23:28:24 -0500
Subject: [PATCH 0558/1807] CURLRequest fixes. Fixes #445

---
 system/HTTP/CURLRequest.php | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index 91f5194974f5..a9bc7611f4c7 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -413,7 +413,7 @@ protected function applyRequestHeaders(array $curl_options = []): array
 	{
 		$headers = $this->getHeaders();
 
-		if (empty($head))
+		if (empty($headers))
 		{
 			return $curl_options;
 		}
@@ -661,12 +661,13 @@ protected function setCURLOptions(array $curl_options = [], array $config = [])
 		// Post Data - application/x-www-form-urlencoded
 		if (! empty($config['form_params']) && is_array($config['form_params']))
 		{
-			$curl_options[CURLOPT_POSTFIELDS] = http_build_query($config['form_params']);
+		    $postFields = http_build_query($config['form_params']);
+			$curl_options[CURLOPT_POSTFIELDS] = $postFields;
 
-			if (empty($this->header('Content-Type')))
-			{
-				$this->setHeader('Content-Type', 'application/x-www-form-urlencoded');
-			}
+            // Ensure content-length is set, since CURL doesn't seem to
+            // calculate it when HTTPHEADER is set.
+            $this->setHeader('Content-Length', (string)strlen($postFields));
+            $this->setHeader('Content-Type', 'application/x-www-form-urlencoded');
 		}
 
 		// Post Data - multipart/form-data
@@ -703,6 +704,13 @@ protected function setCURLOptions(array $curl_options = [], array $config = [])
 			}
 		}
 
+        // Cookie
+        if (isset($config['cookie']))
+        {
+           $curl_options[CURLOPT_COOKIEJAR]  = $config['cookie'];
+           $curl_options[CURLOPT_COOKIEFILE] = $config['cookie'];
+        }
+
 		return $curl_options;
 	}
 

From 64afdf3c3432a448fd1eabf38038aef95587c593 Mon Sep 17 00:00:00 2001
From: Basel Juma 
Date: Sun, 19 Mar 2017 09:28:29 +0200
Subject: [PATCH 0559/1807] Fix code styling & formatting issues

---
 system/CLI/BaseCommand.php                   | 302 ++++++++++---------
 system/Commands/Database/MigrateCurrent.php  |   2 +-
 system/Commands/Database/MigrateLatest.php   |   2 +-
 system/Commands/Database/MigrateRefresh.php  |   2 +-
 system/Commands/Database/MigrateRollback.php |   2 +-
 system/Commands/Database/MigrateStatus.php   |   2 +-
 system/Commands/Database/Seed.php            |   2 +-
 7 files changed, 158 insertions(+), 156 deletions(-)

diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php
index 0faf08122913..0928f449dd42 100644
--- a/system/CLI/BaseCommand.php
+++ b/system/CLI/BaseCommand.php
@@ -1,232 +1,234 @@
 logger = $logger;
         $this->commands = $commands;
     }
-    
+
     //--------------------------------------------------------------------
-    
+
     abstract public function run(array $params);
-    
+
     //--------------------------------------------------------------------
-    
+
     /**
-    * Can be used by a command to run other commands.
-    *
-    * @param string $command
-    * @param array  $params
-    */
-    protected function call(string $command, array $params=[])
+     * Can be used by a command to run other commands.
+     *
+     * @param string $command
+     * @param array $params
+     */
+    protected function call(string $command, array $params = [])
     {
         // The CommandRunner will grab the first element
         // for the command name.
         array_unshift($params, $command);
-        
+
         return $this->commands->index($params);
     }
-    
+
     //--------------------------------------------------------------------
-    
+
     /**
-    * A simple method to display an error with line/file,
-    * in child commands.
-    *
-    * @param \Exception $e
-    */
+     * A simple method to display an error with line/file,
+     * in child commands.
+     *
+     * @param \Exception $e
+     */
     protected function showError(\Exception $e)
     {
         CLI::newLine();
         CLI::error($e->getMessage());
-        CLI::write($e->getFile().' - '.$e->getLine());
+        CLI::write($e->getFile() . ' - ' . $e->getLine());
         CLI::newLine();
     }
-    
+
     //--------------------------------------------------------------------
-    
+
     /**
-    * Makes it simple to access our protected properties.
-    *
-    * @param string $key
-    *
-    * @return mixed
-    */
+     * Makes it simple to access our protected properties.
+     *
+     * @param string $key
+     *
+     * @return mixed
+     */
     public function __get(string $key)
     {
-        if (isset($this->$key))
-        {
+        if (isset($this->$key)) {
             return $this->$key;
         }
     }
-    
+
     //--------------------------------------------------------------------
-    
+
     /**
-    * show Help include (usage,arguments,description,options)
-    *
-    *
-    * @return mixed
-    */
+     * show Help include (usage,arguments,description,options)
+     *
+     *
+     * @return mixed
+     */
     public function showHelp()
     {
-		$spaces= "   ";
+        // 4 spaces insted of tab
+        $tab = "   ";
         CLI::write(lang('CLI.helpDescription'), 'yellow');
-        CLI::write($spaces . $this->description);
+        CLI::write($tab . $this->description);
         CLI::newLine();
-        
+
         CLI::write(lang('CLI.helpUsage'), 'yellow');
         $usage = empty($this->usage) ? $this->name . " [arguments]" : $this->usage;
-        CLI::write($spaces . $usage );
+        CLI::write($tab . $usage);
         CLI::newLine();
-        
-		$pad = max($this->getPad($this->options,6), $this->getPad($this->arguments,6));
 
-		if (!empty($this->arguments)){
-			CLI::write(lang('CLI.helpArguments'), 'yellow');			
-            foreach ($this->arguments as $argument => $description) {
-                CLI::write( $spaces . CLI::color(str_pad($argument, $pad),'green') . $description,'yellow');               
+        $pad = max($this->getPad($this->options, 6), $this->getPad($this->arguments, 6));
+
+        if (!empty($this->arguments))
+        {
+            CLI::write(lang('CLI.helpArguments'), 'yellow');
+            foreach ($this->arguments as $argument => $description)
+            {
+                CLI::write($tab . CLI::color(str_pad($argument, $pad), 'green') . $description, 'yellow');
             }
-             CLI::newLine();
+            CLI::newLine();
         }
-		
-        if (!empty($this->options)){
-			CLI::write(lang('CLI.helpOptions'), 'yellow');			
-            foreach ($this->options as $option => $description) {
-                CLI::write( $spaces . CLI::color(str_pad($option, $pad),'green') . $description,'yellow');                
+
+        if (!empty($this->options))
+        {
+            CLI::write(lang('CLI.helpOptions'), 'yellow');
+            foreach ($this->options as $option => $description)
+            {
+                CLI::write($tab . CLI::color(str_pad($option, $pad), 'green') . $description, 'yellow');
             }
             CLI::newLine();
-        }		
-    }  
+        }
+    }
 
     //--------------------------------------------------------------------
-    
+
     /**
-    * Get pad for $key => $value array output
-    *
-    * @param array $array
-    * @param int   $pad
-    *
-    * @return int
-    */
+     * Get pad for $key => $value array output
+     *
+     * @param array $array
+     * @param int $pad
+     *
+     * @return int
+     */
     public function getPad($array, string $pad)
     {
         $max = 0;
-        foreach ($array as $key => $value)
-        {            
+        foreach ($array as $key => $value) {
             $max = max($max, strlen($key));
         }
         return $max + $pad;
     }
-    
-    
-    
+
+
     //--------------------------------------------------------------------
 }
\ No newline at end of file
diff --git a/system/Commands/Database/MigrateCurrent.php b/system/Commands/Database/MigrateCurrent.php
index d14fce34583c..4d04e5587fcd 100644
--- a/system/Commands/Database/MigrateCurrent.php
+++ b/system/Commands/Database/MigrateCurrent.php
@@ -75,7 +75,7 @@ class MigrateCurrent extends BaseCommand
      *
      * @var array
      */
-    protected $arguments = array();
+    protected $arguments = [];
 
     /**
      * the Command's Options
diff --git a/system/Commands/Database/MigrateLatest.php b/system/Commands/Database/MigrateLatest.php
index accaa8561d54..4428fcb4968f 100644
--- a/system/Commands/Database/MigrateLatest.php
+++ b/system/Commands/Database/MigrateLatest.php
@@ -75,7 +75,7 @@ class MigrateLatest extends BaseCommand
      *
      * @var array
      */
-    protected $arguments = array();
+    protected $arguments = [];
 
     /**
      * the Command's Options
diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php
index 2e77e03531c9..6e92901883d1 100644
--- a/system/Commands/Database/MigrateRefresh.php
+++ b/system/Commands/Database/MigrateRefresh.php
@@ -76,7 +76,7 @@ class MigrateRefresh extends BaseCommand
      *
      * @var array
      */
-    protected $arguments = array();
+    protected $arguments = [];
 
     /**
      * the Command's Options
diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php
index 7d1e368a9834..5ee07ca77630 100644
--- a/system/Commands/Database/MigrateRollback.php
+++ b/system/Commands/Database/MigrateRollback.php
@@ -77,7 +77,7 @@ class MigrateRollback extends BaseCommand
      *
      * @var array
      */
-    protected $arguments = array();
+    protected $arguments = [];
 
     /**
      * the Command's Options
diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php
index 91af5e5ce364..8b214ed8c2f3 100644
--- a/system/Commands/Database/MigrateStatus.php
+++ b/system/Commands/Database/MigrateStatus.php
@@ -76,7 +76,7 @@ class MigrateStatus extends BaseCommand
      *
      * @var array
      */
-    protected $arguments = array();
+    protected $arguments = [];
 
     /**
      * the Command's Options
diff --git a/system/Commands/Database/Seed.php b/system/Commands/Database/Seed.php
index 3fddf62fd1ca..228d2e19a053 100644
--- a/system/Commands/Database/Seed.php
+++ b/system/Commands/Database/Seed.php
@@ -86,7 +86,7 @@ class Seed extends BaseCommand
      *
      * @var array
      */
-    protected $options = array();
+    protected $options = [];
 
     /**
      * Runs all of the migrations in reverse order, until they have

From b2db387b17c7ae7b2284cb86e53d23fe80c3b6f7 Mon Sep 17 00:00:00 2001
From: Kristian Matthews 
Date: Sun, 19 Mar 2017 16:40:42 +0000
Subject: [PATCH 0560/1807] Added Validation Check

Added the ability to do a one-off validation for a single value and single set of rules.

Signed-off-by: Kristian Matthews 
---
 system/Validation/Validation.php          | 39 ++++++++++++++++++++---
 system/Validation/ValidationInterface.php | 14 ++++++++
 2 files changed, 48 insertions(+), 5 deletions(-)

diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php
index 9cb280a2ab76..9726a6a6f0b6 100644
--- a/system/Validation/Validation.php
+++ b/system/Validation/Validation.php
@@ -152,6 +152,27 @@ public function run(array $data = null, string $group = null): bool
 
 	//--------------------------------------------------------------------
 
+	/**
+	 * Check; runs the validation process, returning true or false
+	 * determining whether or not validation was successful.
+	 *
+	 * @param mixed    $value  Value to validation.
+	 * @param string   $rule   Rule.
+	 * @param string[] $errors Errors.
+	 *
+	 * @return bool True if valid, else false.
+	 */
+	public function check($value, string $rule, array $errors = []): bool
+	{
+		$this->reset();
+		$this->setRule('check', $rule, $errors);
+		return $this->run([
+			'check' => $value
+		]);
+	}
+
+	//--------------------------------------------------------------------
+
 	/**
 	 * Runs all of $rules against $field, until one fails, or
 	 * all of them have been processed. If one fails, it adds
@@ -275,7 +296,9 @@ public function withRequest(RequestInterface $request): ValidationInterface
 	public function setRule(string $field, string $rule, array $errors = [])
 	{
 		$this->rules[$field] = $rule;
-		$this->customErrors  = array_merge($this->customErrors, $errors);
+		$this->customErrors  = array_merge($this->customErrors, [
+			$field => $errors
+		]);
 
 		return $this;
 	}
@@ -517,14 +540,20 @@ public function hasError(string $field): bool
     //--------------------------------------------------------------------
 
 	/**
-	 * Returns the error(s) for a specified $field (or empty string if not set).
+	 * Returns the error(s) for a specified $field (or empty string if not
+	 * set).
 	 *
-	 * @param string $field
+	 * @param string $field Field.
 	 *
-	 * @return string
+	 * @return string Error(s).
 	 */
-	public function getError(string $field): string
+	public function getError(string $field = null): string
 	{
+		if ($field === null && count($this->rules) === 1) {
+			reset($this->rules);
+			$field = key($this->rules);
+		}
+
 		return array_key_exists($field, $this->errors)
 			? $this->errors[$field]
 			: '';
diff --git a/system/Validation/ValidationInterface.php b/system/Validation/ValidationInterface.php
index 8c959767339f..a179bbcc2013 100644
--- a/system/Validation/ValidationInterface.php
+++ b/system/Validation/ValidationInterface.php
@@ -53,6 +53,20 @@ public function run(array $data, string $group = null): bool;
 
 	//--------------------------------------------------------------------
 
+	/**
+	 * Check; runs the validation process, returning true or false
+	 * determining whether or not validation was successful.
+	 *
+	 * @param mixed    $value  Value to validation.
+	 * @param string   $rule   Rule.
+	 * @param string[] $errors Errors.
+	 *
+	 * @return bool True if valid, else false.
+	 */
+	public function check($value, string $rule, array $errors = []): bool;
+
+	//--------------------------------------------------------------------
+
 	/**
 	 * Takes a Request object and grabs the data to use from its
 	 * POST array values.

From 9f59aef4cf2a35d0f8c74130f3d342ce2e4b4afc Mon Sep 17 00:00:00 2001
From: Kristian Matthews 
Date: Sun, 19 Mar 2017 16:41:05 +0000
Subject: [PATCH 0561/1807] Updated Validation Tests

Added coverage for `Validation->check()`.

Signed-off-by: Kristian Matthews 
---
 tests/system/Validation/ValidationTest.php | 27 +++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php
index a4b141a5f897..482f851b805e 100644
--- a/tests/system/Validation/ValidationTest.php
+++ b/tests/system/Validation/ValidationTest.php
@@ -54,7 +54,7 @@ public function testSetRulesStoresRules()
 
     //--------------------------------------------------------------------
 
-    public function testRunReturnsTrueWithNOthingToDo()
+    public function testRunReturnsTrueWithNothingToDo()
     {
         $this->validation->setRules([]);
 
@@ -116,6 +116,31 @@ public function testRunWithCustomErrors()
 
     //--------------------------------------------------------------------
 
+	public function testCheck()
+	{
+		$this->assertFalse($this->validation->check('notanumber', 'is_numeric'));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testCheckLocalizedError()
+	{
+		$this->assertFalse($this->validation->check('notanumber', 'is_numeric'));
+		$this->assertEquals('is_numeric', $this->validation->getError());
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testCheckCustomError()
+	{
+		$this->validation->check('notanumber', 'is_numeric', [
+			'is_numeric' => 'Nope. Not a number.'
+		]);
+		$this->assertEquals('Nope. Not a number.', $this->validation->getError());
+	}
+
+	//--------------------------------------------------------------------
+
     public function testGetErrors()
     {
         $data = [

From c6261a4f903e4649f593de50843a1e4a1122c3b9 Mon Sep 17 00:00:00 2001
From: Kristian Matthews 
Date: Sun, 19 Mar 2017 16:41:23 +0000
Subject: [PATCH 0562/1807] Updated Docs

Signed-off-by: Kristian Matthews 
---
 user_guide_src/source/libraries/validation.rst | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst
index 6166527a02f2..bbf7f39ffe59 100644
--- a/user_guide_src/source/libraries/validation.rst
+++ b/user_guide_src/source/libraries/validation.rst
@@ -245,6 +245,13 @@ data to be validated::
     $validation->withRequest($this->request)
                ->run();
 
+****************
+Validate 1 Value
+****************
+
+Validate one value against a rule.
+
+    $validation->check($value, 'required');
 
 **************************************************
 Saving Sets of Validation Rules to the Config File

From f646b353cbac6f79abf45c5197aa46adf5033aa9 Mon Sep 17 00:00:00 2001
From: Ryan Wu 
Date: Mon, 20 Mar 2017 20:51:46 +0800
Subject: [PATCH 0563/1807] Fix issure #413: Add UnitTest

---
 system/Pager/Pager.php           |  2 +-
 tests/system/Pager/PagerTest.php | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/system/Pager/Pager.php b/system/Pager/Pager.php
index 1cbb5cd98003..5f1f559f4ef9 100644
--- a/system/Pager/Pager.php
+++ b/system/Pager/Pager.php
@@ -309,7 +309,7 @@ public function getPageURI(int $page = null, string $group = 'default', $returnO
 
 		$uri = $this->groups[$group]['uri'];
 
-		$uri->addQuery('page='.$page);
+		$uri->addQuery('page', $page);
 
 		return $returnObject === true
 			? $uri
diff --git a/tests/system/Pager/PagerTest.php b/tests/system/Pager/PagerTest.php
index 7895b7969c71..9549b6ea32c8 100644
--- a/tests/system/Pager/PagerTest.php
+++ b/tests/system/Pager/PagerTest.php
@@ -255,4 +255,34 @@ public function testGetNextURIReturnsNullOnFirstPage()
 	}
 
 	//--------------------------------------------------------------------
+
+	public function testGetNextURIWithQueryStringUsesCurrentURI()
+	{
+		$_GET['page'] = 3;
+		$_GET['status'] = 1;
+
+		$expected = current_url(true);
+		$expected = (string)$expected->setQueryArray($_GET);
+
+		$this->pager->store('foo', $_GET['page']-1, 12, 70);
+
+		$this->assertEquals((string)$expected, $this->pager->getNextPageURI('foo'));
+	}
+ 
+	//--------------------------------------------------------------------
+
+	public function testGetPreviousURIWithQueryStringUsesCurrentURI()
+	{
+		$_GET['page'] = 1;
+		$_GET['status'] = 1;
+
+		$expected = current_url(true);
+		$expected = (string)$expected->setQueryArray($_GET);
+
+		$this->pager->store('foo', $_GET['page']+1, 12, 70);
+
+		$this->assertEquals((string)$expected, $this->pager->getPreviousPageURI('foo'));
+	}
+
+	//--------------------------------------------------------------------
 }

From 54d78a224efbbe8884a8e78c9ed1da90c08a145a Mon Sep 17 00:00:00 2001
From: Basel Juma 
Date: Tue, 21 Mar 2017 11:06:06 +0200
Subject: [PATCH 0564/1807] fix documentation

---
 .../source/general/cli_commands.rst           | 60 +++++++++++++++----
 1 file changed, 50 insertions(+), 10 deletions(-)

diff --git a/user_guide_src/source/general/cli_commands.rst b/user_guide_src/source/general/cli_commands.rst
index 98d3b88db8f1..779ba1832c9e 100644
--- a/user_guide_src/source/general/cli_commands.rst
+++ b/user_guide_src/source/general/cli_commands.rst
@@ -35,6 +35,14 @@ for the information it needs to run correctly::
     > php ci.php migrate:version
     > Version?
 
+******************
+Using Help Command
+******************
+
+You can get help about any CLI command using the help command as follows::
+
+    > php ci.php help db:seed
+
 *********************
 Creating New Commands
 *********************
@@ -42,6 +50,17 @@ Creating New Commands
 You can very easily create new commands to use in your own development. Each class must be in its own file,
 and must extend ``CodeIgniter\CLI\BaseCommand``, and implement the ``run()`` method.
 
+The following properties should be used in order to get listed in CLI commands and to add help functionality to your command:
+
+* ($group): a string to describe the group the command is lumped under when listing commands. For example (Database)
+* ($name): a string to describe the command's name. For example (migrate:create)
+* ($description): a string to describe the command. For example (Creates a new migration file.)
+* ($usage): a string to describe the command usage. For example (migrate:create [migration_name] [Options])
+* ($arguments): an array of strings to describe each command argument. For example ('migration_name' => 'The migration file name')
+* ($options): an array of strings to describe each command option. For example ('-n' => 'Set migration namespace')
+
+**Help description will be automatically generated according to the above parameters.**
+
 File Location
 =============
 
@@ -49,9 +68,8 @@ Commands must be stored within a directory named **Commands**. However, that dir
 that the :doc:`Autoloader ` can locate it. This could be in **/application/Commands**, or
 a directory that you keep commands in to use in all of your project development, like **Acme/Commands**.
 
-.. note:: When the commands are executed, the full CodeIgniter cli environment has been loaded, making
-    it possible to get environment information, path information, and to use any of the tools you would
-    use when making a Controller.
+.. note:: When the commands are executed, the full CodeIgniter cli environment has been loaded, making it
+ possible to get environment information, path information, and to use any of the tools you would use when making a Controller.
 
 An Example Command
 ==================
@@ -128,7 +146,7 @@ be familiar with when creating your own commands. It also has a :doc:`Logger call('command_two', $params);
 
     .. php:method:: showError(\Exception $e)
-    
+
         :param Exception $e: The exception to use for error reporting.
 
         A convenience method to maintain a consistent and clear error output to the cli::
 
-        try {
+            try
+            {
             . . .
-        }
-        catch (\Exception $e)
-        {
+            }
+            catch (\Exception $e)
+            {
             $this->showError($e);
-        }
+            }
+
+    .. php:method:: showHelp()
+
+        A method to show command help: (usage,arguments,description,options)
+
+    .. php:method:: getPad($array, $pad)
+
+        :param Exception $array: The  $key => $value array.
+        :param Exception $pad: The pad spaces.
+
+        A method to calculate padding for $key => $value array output. The padding can be used to output a will formatted table in CLI::
+
+            $pad = $this->getPad($this->options, 6);
+            foreach ($this->options as $option => $description)
+            {
+                    CLI::write($tab . CLI::color(str_pad($option, $pad), 'green') . $description, 'yellow');
+            }
+
+            // Output will be
+            -n                  Set migration namespace
+            -r                  override file
\ No newline at end of file

From 989378eaff6e4a8b68642f186dba21439b7df253 Mon Sep 17 00:00:00 2001
From: Ryan Wu 
Date: Tue, 21 Mar 2017 21:44:58 +0800
Subject: [PATCH 0565/1807] Fix issure #413: modify $_GET

---
 tests/system/Pager/PagerTest.php | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/tests/system/Pager/PagerTest.php b/tests/system/Pager/PagerTest.php
index 9549b6ea32c8..58645c745152 100644
--- a/tests/system/Pager/PagerTest.php
+++ b/tests/system/Pager/PagerTest.php
@@ -258,8 +258,10 @@ public function testGetNextURIReturnsNullOnFirstPage()
 
 	public function testGetNextURIWithQueryStringUsesCurrentURI()
 	{
-		$_GET['page'] = 3;
-		$_GET['status'] = 1;
+		$_GET = [
+			'page' => 3,
+			'status' => 1
+		];
 
 		$expected = current_url(true);
 		$expected = (string)$expected->setQueryArray($_GET);
@@ -273,9 +275,10 @@ public function testGetNextURIWithQueryStringUsesCurrentURI()
 
 	public function testGetPreviousURIWithQueryStringUsesCurrentURI()
 	{
-		$_GET['page'] = 1;
-		$_GET['status'] = 1;
-
+		$_GET = [
+			'page' => 1,
+			'status' => 1
+		];
 		$expected = current_url(true);
 		$expected = (string)$expected->setQueryArray($_GET);
 

From cd829715093acb8707b6841333b9520e60ab69e0 Mon Sep 17 00:00:00 2001
From: Russell Bega 
Date: Fri, 24 Mar 2017 17:22:23 -0300
Subject: [PATCH 0566/1807] Update examples.rst

Removed $this reference
---
 user_guide_src/source/database/examples.rst | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/user_guide_src/source/database/examples.rst b/user_guide_src/source/database/examples.rst
index f978a942bcf0..5643dbbf591e 100644
--- a/user_guide_src/source/database/examples.rst
+++ b/user_guide_src/source/database/examples.rst
@@ -87,7 +87,7 @@ Standard Insert
 
 	$sql = "INSERT INTO mytable (title, name) VALUES (".$db->escape($title).", ".$db->escape($name).")";
 	$db->query($sql);
-	echo $this->db->getAffectedRows();
+	echo $db->getAffectedRows();
 
 Query Builder Query
 ===================
@@ -95,7 +95,7 @@ Query Builder Query
 The :doc:`Query Builder Pattern ` gives you a simplified
 means of retrieving data::
 
-	$query = $this->db->table('table_name')->get();
+	$query = $db->table('table_name')->get();
 	
 	foreach ($query->getResult() as $row)
 	{
@@ -117,5 +117,5 @@ Query Builder Insert
 		'date' => $date
 	);
 	
-	$this->db->table('mytable')->insert($data);  // Produces: INSERT INTO mytable (title, name, date) VALUES ('{$title}', '{$name}', '{$date}')
+	$db->table('mytable')->insert($data);  // Produces: INSERT INTO mytable (title, name, date) VALUES ('{$title}', '{$name}', '{$date}')
 

From d62f08f728ed7b24d5811aca3ab00bc90e77e094 Mon Sep 17 00:00:00 2001
From: Abdul Malik Ikhsan 
Date: Sat, 25 Mar 2017 09:52:20 +0700
Subject: [PATCH 0567/1807] remove 2nd parameter on parent::__construct() in
 IncomingRequest

---
 system/HTTP/IncomingRequest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php
index d7bc1812da0c..ec8a22acfa4f 100644
--- a/system/HTTP/IncomingRequest.php
+++ b/system/HTTP/IncomingRequest.php
@@ -151,7 +151,7 @@ public function __construct($config, $uri = null, $body = 'php://input')
 		$this->body   = $body;
 		$this->config = $config;
 
-		parent::__construct($config, $uri);
+		parent::__construct($config);
 
 		$this->populateHeaders();
 

From d93133d884ccde3af1581a9d0d02b57d1aa9c394 Mon Sep 17 00:00:00 2001
From: "akihiro.fukaya" 
Date: Wed, 29 Mar 2017 12:59:39 +0900
Subject: [PATCH 0568/1807] space to tab

---
 system/bootstrap.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/system/bootstrap.php b/system/bootstrap.php
index 30929afea1b1..b5418c8f3f78 100644
--- a/system/bootstrap.php
+++ b/system/bootstrap.php
@@ -97,7 +97,7 @@ class_alias('Config\Services', 'CodeIgniter\Services');
 // Now load Composer's if it's available
 if (file_exists(COMPOSER_PATH))
 {
-    require COMPOSER_PATH;
+	require COMPOSER_PATH;
 }
 
 // Always load the URL helper -

From 5afde989e49972fd32f8836d84bba533c3e483b5 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Tue, 28 Mar 2017 23:50:35 -0500
Subject: [PATCH 0569/1807] First start at making Entity a first-class citizen.
 See #427

---
 system/Entity.php           | 137 ++++++++++++++++++++++++++++++++++++
 tests/system/EntityTest.php |  86 ++++++++++++++++++++++
 2 files changed, 223 insertions(+)
 create mode 100644 system/Entity.php
 create mode 100644 tests/system/EntityTest.php

diff --git a/system/Entity.php b/system/Entity.php
new file mode 100644
index 000000000000..a5c94747c105
--- /dev/null
+++ b/system/Entity.php
@@ -0,0 +1,137 @@
+ $value)
+		{
+			$method = 'set'.str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
+
+			if (method_exists($this, $method))
+			{
+				$this->$method($value);
+			}
+			elseif (property_exists($this, $key))
+			{
+				$this->$key = $value;
+			}
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Magic method to allow retrieval of protected and private
+	 * class properties either by their name, or through a `getCamelCasedProperty()`
+	 * method.
+	 *
+	 * Examples:
+	 *
+	 *      $p = $this->my_property
+	 *      $p = $this->getMyProperty()
+	 *
+	 * @param string $key
+	 *
+	 * @return mixed
+	 */
+	public function __get(string $key)
+	{
+		// Convert to CamelCase for the method
+		$method = 'get'.str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
+
+		// if a set* method exists for this key,

+		// use that method to insert this value.

+		if (method_exists($this, $method))
+		{
+			return $this->$method();
+		}
+
+		// Otherwise return the protected property
+		// if it exists.
+	    if (property_exists($this, $key))
+	    {
+	    	return $this->$key;
+	    }
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Magic method to all protected/private class properties to be easily set,
+	 * either through a direct access or a `setCamelCasedProperty()` method.
+	 *
+	 * Examples:
+	 *
+	 *      $this->my_property = $p;
+	 *      $this->setMyProperty() = $p;
+	 *
+	 * @param string $key
+	 * @param null   $value
+	 */
+	public function __set(string $key, $value = null)
+	{
+		// if a set* method exists for this key,

+		// use that method to insert this value.

+		$method = 'set'.str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
+		if (method_exists($this, $method))
+		{
+			$this->$method($value);
+		}
+		elseif (property_exists($this, $key))
+		{
+			$this->$key = $value;
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Unsets a protected/private class property. Sets the value to null.
+	 * However, if there was a default value for the parent class, this
+	 * attribute will be reset to that default value.
+	 *
+	 * @param string $key
+	 */
+	public function __unset(string $key)
+	{
+		$this->$key = null;
+
+		// Get the class' original default value for this property
+		// so we can reset it to the original value.
+		$reflectionClass = new \ReflectionClass($this);
+		$defaultProperties = $reflectionClass->getDefaultProperties();
+
+		if (isset($defaultProperties[$key]))
+		{
+			$this->$key = $defaultProperties[$key];
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Returns true if a property exists names $key, or a getter method
+	 * exists named like for __get().
+	 *
+	 * @param string $key
+	 *
+	 * @return bool
+	 */
+	public function __isset(string $key): bool
+	{
+		$value = $this->$key;
+
+		return ! is_null($value);
+	}
+
+	//--------------------------------------------------------------------
+
+}
diff --git a/tests/system/EntityTest.php b/tests/system/EntityTest.php
new file mode 100644
index 000000000000..48df4beeb38e
--- /dev/null
+++ b/tests/system/EntityTest.php
@@ -0,0 +1,86 @@
+getEntity();
+
+	    $entity->foo = 'to wong';
+
+	    $this->assertEquals('to wong', $entity->foo);
+	}
+
+	public function testGetterSetters()
+	{
+	    $entity = $this->getEntity();
+
+	    $entity->bar = 'thanks';
+
+	    $this->assertEquals('bar:thanks:bar', $entity->bar);
+	}
+
+	public function testUnsetResetsToDefaultValue()
+	{
+	    $entity = $this->getEntity();
+
+	    $this->assertEquals('sumfin', $entity->default);
+
+	    $entity->default = 'else';
+
+		$this->assertEquals('else', $entity->default);
+
+		unset($entity->default);
+
+		$this->assertEquals('sumfin', $entity->default);
+	}
+
+	public function testIssetWorksLikeTraditionalIsset()
+	{
+	    $entity = $this->getEntity();
+
+	    $this->assertTrue(isset($entity->default));
+	    $this->assertFalse(isset($entity->foo));
+	}
+
+	public function testFill()
+	{
+	    $entity = $this->getEntity();
+
+	    $entity->fill([
+		    'foo' => 123,
+		    'bar' => 234,
+		    'baz' => 4556
+	    ]);
+
+	    $this->assertEquals(123, $entity->foo);
+	    $this->assertEquals('bar:234:bar', $entity->bar);
+	    $this->assertTrue(! isset($entity->baz));
+	}
+
+
+	protected function getEntity()
+	{
+		return new class extends Entity
+		{
+			protected $foo;
+			protected $bar;
+			protected $default = 'sumfin';
+
+			public function setBar($value)
+			{
+			    $this->bar = "bar:{$value}";
+
+			    return $this;
+			}
+
+			public function getBar()
+			{
+			    return "{$this->bar}:bar";
+			}
+
+		};
+	}
+}

From c76e6901e79dae4f50703d0671b07246ece82105 Mon Sep 17 00:00:00 2001
From: ytetsuro 
Date: Wed, 29 Mar 2017 14:12:54 +0900
Subject: [PATCH 0570/1807] Change Indent space4 to tab

Signed-off-by: ytetsuro 
---
 system/CLI/BaseCommand.php                    |  366 ++--
 system/CLI/Console.php                        |   68 +-
 system/Cache/Handlers/PredisHandler.php       |  512 ++---
 system/CodeIgniter.php                        | 1698 ++++++++---------
 system/Commands/Database/CreateMigration.php  |  218 +--
 system/Commands/Database/MigrateCurrent.php   |  110 +-
 system/Commands/Database/MigrateLatest.php    |  126 +-
 system/Commands/Database/MigrateRefresh.php   |   88 +-
 system/Commands/Database/MigrateRollback.php  |  240 +--
 system/Commands/Database/MigrateStatus.php    |  278 +--
 system/Commands/Database/MigrateVersion.php   |  140 +-
 system/Commands/Database/Seed.php             |  118 +-
 system/Commands/Help.php                      |   98 +-
 system/Commands/ListCommands.php              |  286 +--
 system/Commands/Sessions/CreateMigration.php  |  108 +-
 .../Commands/Sessions/Views/migration.tpl.php |   42 +-
 system/Common.php                             | 1282 ++++++-------
 system/Config/DotEnv.php                      |   44 +-
 system/Controller.php                         |   64 +-
 system/Database/BaseConnection.php            |  660 +++----
 system/Database/BasePreparedQuery.php         |   14 +-
 system/Database/Forge.php                     |  180 +-
 system/Database/MigrationRunner.php           | 1194 ++++++------
 system/Database/MySQLi/Connection.php         |  182 +-
 system/Database/Postgre/Connection.php        |  110 +-
 system/Database/Postgre/PreparedQuery.php     |   12 +-
 system/Database/Query.php                     |   64 +-
 system/Database/Seeder.php                    |   54 +-
 system/Debug/Toolbar/Collectors/Database.php  |  100 +-
 system/Debug/Toolbar/Collectors/Files.php     |   36 +-
 system/Debug/Toolbar/Collectors/Logs.php      |    8 +-
 system/Debug/Toolbar/Collectors/Routes.php    |   72 +-
 system/Model.php                              |  498 ++---
 33 files changed, 4535 insertions(+), 4535 deletions(-)

diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php
index 0928f449dd42..a557007c0a03 100644
--- a/system/CLI/BaseCommand.php
+++ b/system/CLI/BaseCommand.php
@@ -49,186 +49,186 @@
  */
 abstract class BaseCommand
 {
-    /**
-     * The group the command is lumped under
-     * when listing commands.
-     *
-     * @var string
-     */
-    protected $group;
-
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name;
-
-    /**
-     * the Command's usage description
-     *
-     * @var string
-     */
-    protected $usage;
-
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description;
-
-    /**
-     * the Command's options description
-     *
-     * @var string
-     */
-    protected $options = array();
-
-    /**
-     * the Command's Arguments description
-     *
-     * @var string
-     */
-    protected $arguments = array();
-
-    /**
-     * @var \Psr\Log\LoggerInterface
-     */
-    protected $logger;
-
-    /**
-     * Instance of the CommandRunner controller
-     * so commands can call other commands.
-     *
-     * @var \CodeIgniter\CLI\CommandRunner
-     */
-    protected $commands;
-
-    //--------------------------------------------------------------------
-
-    public function __construct(LoggerInterface $logger, CommandRunner $commands)
-    {
-        $this->logger = $logger;
-        $this->commands = $commands;
-    }
-
-    //--------------------------------------------------------------------
-
-    abstract public function run(array $params);
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Can be used by a command to run other commands.
-     *
-     * @param string $command
-     * @param array $params
-     */
-    protected function call(string $command, array $params = [])
-    {
-        // The CommandRunner will grab the first element
-        // for the command name.
-        array_unshift($params, $command);
-
-        return $this->commands->index($params);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * A simple method to display an error with line/file,
-     * in child commands.
-     *
-     * @param \Exception $e
-     */
-    protected function showError(\Exception $e)
-    {
-        CLI::newLine();
-        CLI::error($e->getMessage());
-        CLI::write($e->getFile() . ' - ' . $e->getLine());
-        CLI::newLine();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Makes it simple to access our protected properties.
-     *
-     * @param string $key
-     *
-     * @return mixed
-     */
-    public function __get(string $key)
-    {
-        if (isset($this->$key)) {
-            return $this->$key;
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * show Help include (usage,arguments,description,options)
-     *
-     *
-     * @return mixed
-     */
-    public function showHelp()
-    {
-        // 4 spaces insted of tab
-        $tab = "   ";
-        CLI::write(lang('CLI.helpDescription'), 'yellow');
-        CLI::write($tab . $this->description);
-        CLI::newLine();
-
-        CLI::write(lang('CLI.helpUsage'), 'yellow');
-        $usage = empty($this->usage) ? $this->name . " [arguments]" : $this->usage;
-        CLI::write($tab . $usage);
-        CLI::newLine();
-
-        $pad = max($this->getPad($this->options, 6), $this->getPad($this->arguments, 6));
-
-        if (!empty($this->arguments))
-        {
-            CLI::write(lang('CLI.helpArguments'), 'yellow');
-            foreach ($this->arguments as $argument => $description)
-            {
-                CLI::write($tab . CLI::color(str_pad($argument, $pad), 'green') . $description, 'yellow');
-            }
-            CLI::newLine();
-        }
-
-        if (!empty($this->options))
-        {
-            CLI::write(lang('CLI.helpOptions'), 'yellow');
-            foreach ($this->options as $option => $description)
-            {
-                CLI::write($tab . CLI::color(str_pad($option, $pad), 'green') . $description, 'yellow');
-            }
-            CLI::newLine();
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Get pad for $key => $value array output
-     *
-     * @param array $array
-     * @param int $pad
-     *
-     * @return int
-     */
-    public function getPad($array, string $pad)
-    {
-        $max = 0;
-        foreach ($array as $key => $value) {
-            $max = max($max, strlen($key));
-        }
-        return $max + $pad;
-    }
-
-
-    //--------------------------------------------------------------------
-}
\ No newline at end of file
+	/**
+	 * The group the command is lumped under
+	 * when listing commands.
+	 *
+	 * @var string
+	 */
+	protected $group;
+
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name;
+
+	/**
+	 * the Command's usage description
+	 *
+	 * @var string
+	 */
+	protected $usage;
+
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description;
+
+	/**
+	 * the Command's options description
+	 *
+	 * @var string
+	 */
+	protected $options = array();
+
+	/**
+	 * the Command's Arguments description
+	 *
+	 * @var string
+	 */
+	protected $arguments = array();
+
+	/**
+	 * @var \Psr\Log\LoggerInterface
+	 */
+	protected $logger;
+
+	/**
+	 * Instance of the CommandRunner controller
+	 * so commands can call other commands.
+	 *
+	 * @var \CodeIgniter\CLI\CommandRunner
+	 */
+	protected $commands;
+
+	//--------------------------------------------------------------------
+
+	public function __construct(LoggerInterface $logger, CommandRunner $commands)
+	{
+		$this->logger = $logger;
+		$this->commands = $commands;
+	}
+
+	//--------------------------------------------------------------------
+
+	abstract public function run(array $params);
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Can be used by a command to run other commands.
+	 *
+	 * @param string $command
+	 * @param array $params
+	 */
+	protected function call(string $command, array $params = [])
+	{
+		// The CommandRunner will grab the first element
+		// for the command name.
+		array_unshift($params, $command);
+
+		return $this->commands->index($params);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * A simple method to display an error with line/file,
+	 * in child commands.
+	 *
+	 * @param \Exception $e
+	 */
+	protected function showError(\Exception $e)
+	{
+		CLI::newLine();
+		CLI::error($e->getMessage());
+		CLI::write($e->getFile() . ' - ' . $e->getLine());
+		CLI::newLine();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Makes it simple to access our protected properties.
+	 *
+	 * @param string $key
+	 *
+	 * @return mixed
+	 */
+	public function __get(string $key)
+	{
+		if (isset($this->$key)) {
+			return $this->$key;
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * show Help include (usage,arguments,description,options)
+	 *
+	 *
+	 * @return mixed
+	 */
+	public function showHelp()
+	{
+		// 4 spaces insted of tab
+		$tab = "   ";
+		CLI::write(lang('CLI.helpDescription'), 'yellow');
+		CLI::write($tab . $this->description);
+		CLI::newLine();
+
+		CLI::write(lang('CLI.helpUsage'), 'yellow');
+		$usage = empty($this->usage) ? $this->name . " [arguments]" : $this->usage;
+		CLI::write($tab . $usage);
+		CLI::newLine();
+
+		$pad = max($this->getPad($this->options, 6), $this->getPad($this->arguments, 6));
+
+		if (!empty($this->arguments))
+		{
+			CLI::write(lang('CLI.helpArguments'), 'yellow');
+			foreach ($this->arguments as $argument => $description)
+			{
+				CLI::write($tab . CLI::color(str_pad($argument, $pad), 'green') . $description, 'yellow');
+			}
+			CLI::newLine();
+		}
+
+		if (!empty($this->options))
+		{
+			CLI::write(lang('CLI.helpOptions'), 'yellow');
+			foreach ($this->options as $option => $description)
+			{
+				CLI::write($tab . CLI::color(str_pad($option, $pad), 'green') . $description, 'yellow');
+			}
+			CLI::newLine();
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Get pad for $key => $value array output
+	 *
+	 * @param array $array
+	 * @param int $pad
+	 *
+	 * @return int
+	 */
+	public function getPad($array, string $pad)
+	{
+		$max = 0;
+		foreach ($array as $key => $value) {
+			$max = max($max, strlen($key));
+		}
+		return $max + $pad;
+	}
+
+
+	//--------------------------------------------------------------------
+}
diff --git a/system/CLI/Console.php b/system/CLI/Console.php
index aaf1bbf95d50..3276135a5328 100644
--- a/system/CLI/Console.php
+++ b/system/CLI/Console.php
@@ -40,50 +40,50 @@
 
 class Console
 {
-    /**
-     * Main CodeIgniter instance.
-     * @var CodeIgniter
-     */
-    protected $app;
+	/**
+	 * Main CodeIgniter instance.
+	 * @var CodeIgniter
+	 */
+	protected $app;
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    public function __construct(CodeIgniter $app)
-    {
-        $this->app = $app;
-    }
+	public function __construct(CodeIgniter $app)
+	{
+		$this->app = $app;
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * Runs the current command discovered on the CLI.
-     */
-    public function run()
-    {
-        $path = CLI::getURI() ?: 'list';
+	/**
+	 * Runs the current command discovered on the CLI.
+	 */
+	public function run()
+	{
+		$path = CLI::getURI() ?: 'list';
 
-        // Set the path for the application to route to.
-        $this->app->setPath("ci{$path}");
+		// Set the path for the application to route to.
+		$this->app->setPath("ci{$path}");
 
-        return $this->app->run();
-    }
+		return $this->app->run();
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * Displays basic information about the Console.
-     */
-    public function showHeader()
-    {
-        CLI::newLine(1);
+	/**
+	 * Displays basic information about the Console.
+	 */
+	public function showHeader()
+	{
+		CLI::newLine(1);
 
-        CLI::write(CLI::color('CodeIgniter CLI Tool', 'green')
-            . ' - Version '. CodeIgniter::CI_VERSION
-            . ' - Server-Time: '. date('Y-m-d H:i:sa'));
+		CLI::write(CLI::color('CodeIgniter CLI Tool', 'green')
+				. ' - Version '. CodeIgniter::CI_VERSION
+				. ' - Server-Time: '. date('Y-m-d H:i:sa'));
 
-        CLI::newLine(1);
-    }
+		CLI::newLine(1);
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 }
diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php
index fd415bc70de1..92e821e2fc64 100644
--- a/system/Cache/Handlers/PredisHandler.php
+++ b/system/Cache/Handlers/PredisHandler.php
@@ -41,259 +41,259 @@
 
 class PredisHandler implements CacheInterface
 {
-    /**
-     * Prefixed to all cache names.
-     *
-     * @var string
-     */
-    protected $prefix;
-
-    /**
-     * Default config
-     *
-     * @static
-     * @var    array
-     */
-    protected $config = [
-        'scheme'   => 'tcp',
-        'host'     => '127.0.0.1',
-        'password' => null,
-        'port'     => 6379,
-        'timeout'  => 0,
-    ];
-
-    /**
-     * Predis connection
-     *
-     * @var    Predis
-     */
-    protected $redis;
-
-    //--------------------------------------------------------------------
-
-    public function __construct($config)
-    {
-        $this->prefix = $config->prefix ?: '';
-
-        if (isset($config->redis))
-        {
-            $this->config = array_merge($this->config, $config->redis);
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Takes care of any handler-specific setup that must be done.
-     */
-    public function initialize()
-    {
-        try
-        {
-            // Create a new instance of Predis\Client
-            $this->redis = new \Predis\Client($this->config, ['prefix' => $this->prefix]);
-
-            // Check if the connection is valid by trying to get the time.
-            $this->redis->time();
-        }
-        catch (Exception $e)
-        {
-            // thrown if can't connect to redis server.
-            throw new CriticalError('Cache: Predis connection refused ('.$e->getMessage().')');
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Attempts to fetch an item from the cache store.
-     *
-     * @param string $key Cache item name
-     *
-     * @return mixed
-     */
-    public function get(string $key)
-    {
-        $data = array_combine(
-            ['__ci_type', '__ci_value'],
-            $this->redis->hmget($key, ['__ci_type', '__ci_value'])
-        );
-
-        if (! isset($data['__ci_type'], $data['__ci_value']) OR $data['__ci_value'] === false)
-        {
-            return false;
-        }
-
-        switch ($data['__ci_type'])
-        {
-            case 'array':
-            case 'object':
-                return unserialize($data['__ci_value']);
-            case 'boolean':
-            case 'integer':
-            case 'double': // Yes, 'double' is returned and NOT 'float'
-            case 'string':
-            case 'NULL':
-                return settype($data['__ci_value'], $data['__ci_type'])
-                    ? $data['__ci_value']
-                    : false;
-            case 'resource':
-            default:
-                return false;
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Saves an item to the cache store.
-     *
-     * The $raw parameter is only utilized by predis in order to
-     * allow usage of increment() and decrement().
-     *
-     * @param string $key    Cache item name
-     * @param        $value  the data to save
-     * @param null   $ttl    Time To Live, in seconds (default 60)
-     * @param bool   $raw    Whether to store the raw value.
-     *
-     * @return mixed
-     */
-    public function save(string $key, $value, int $ttl = 60, bool $raw = false)
-    {
-        switch ($data_type = gettype($value))
-        {
-            case 'array':
-            case 'object':
-                $value = serialize($value);
-                break;
-            case 'boolean':
-            case 'integer':
-            case 'double': // Yes, 'double' is returned and NOT 'float'
-            case 'string':
-            case 'NULL':
-                break;
-            case 'resource':
-            default:
-                return false;
-        }
-
-        if (! $this->redis->hmset($key, ['__ci_type' => $data_type, '__ci_value' => $value]))
-        {
-            return false;
-        }
-        
-        $this->redis->expireat($key, time()+$ttl);
-
-        return true;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Deletes a specific item from the cache store.
-     *
-     * @param string $key Cache item name
-     *
-     * @return mixed
-     */
-    public function delete(string $key)
-    {
-        return ($this->redis->del($key) === 1);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Performs atomic incrementation of a raw stored value.
-     *
-     * @param string $key    Cache ID
-     * @param int    $offset Step/value to increase by
-     *
-     * @return mixed
-     */
-    public function increment(string $key, int $offset = 1)
-    {
-        return $this->redis->hincrby($key, 'data', $offset);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Performs atomic decrementation of a raw stored value.
-     *
-     * @param string $key    Cache ID
-     * @param int    $offset Step/value to increase by
-     *
-     * @return mixed
-     */
-    public function decrement(string $key, int $offset = 1)
-    {
-        return $this->redis->hincrby($key, 'data', -$offset);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Will delete all items in the entire cache.
-     *
-     * @return mixed
-     */
-    public function clean()
-    {
-        return $this->redis->flushdb();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Returns information on the entire cache.
-     *
-     * The information returned and the structure of the data
-     * varies depending on the handler.
-     *
-     * @return mixed
-     */
-    public function getCacheInfo()
-    {
-        return $this->redis->info();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Returns detailed information about the specific item in the cache.
-     *
-     * @param string $key Cache item name.
-     *
-     * @return mixed
-     */
-    public function getMetaData(string $key)
-    {
-        $data = array_combine(['__ci_value'], $this->redis->hmget($key, ['__ci_value']));
-
-        if (isset($data['__ci_value']) AND $data['__ci_value'] !== false)
-        {
-            return array(
-                'expire' => time() + $this->redis->ttl($key),
-                'data' => $data['__ci_value']
-            );
-        }
-
-        return FALSE;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Determines if the driver is supported on this system.
-     *
-     * @return boolean
-     */
-    public function isSupported(): bool
-    {
-        return class_exists('\Predis\Client');
-    }
-
-    //--------------------------------------------------------------------
-
-}
\ No newline at end of file
+	/**
+	 * Prefixed to all cache names.
+	 *
+	 * @var string
+	 */
+	protected $prefix;
+
+	/**
+	 * Default config
+	 *
+	 * @static
+	 * @var    array
+	 */
+	protected $config = [
+		'scheme'   => 'tcp',
+		'host'     => '127.0.0.1',
+		'password' => null,
+		'port'     => 6379,
+		'timeout'  => 0,
+	];
+
+	/**
+	 * Predis connection
+	 *
+	 * @var    Predis
+	 */
+	protected $redis;
+
+	//--------------------------------------------------------------------
+
+	public function __construct($config)
+	{
+		$this->prefix = $config->prefix ?: '';
+
+		if (isset($config->redis))
+		{
+			$this->config = array_merge($this->config, $config->redis);
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Takes care of any handler-specific setup that must be done.
+	 */
+	public function initialize()
+	{
+		try
+		{
+			// Create a new instance of Predis\Client
+			$this->redis = new \Predis\Client($this->config, ['prefix' => $this->prefix]);
+
+			// Check if the connection is valid by trying to get the time.
+			$this->redis->time();
+		}
+		catch (Exception $e)
+		{
+			// thrown if can't connect to redis server.
+			throw new CriticalError('Cache: Predis connection refused ('.$e->getMessage().')');
+					}
+					}
+
+					//--------------------------------------------------------------------
+
+					/**
+					 * Attempts to fetch an item from the cache store.
+					 *
+					 * @param string $key Cache item name
+					 *
+					 * @return mixed
+					 */
+					public function get(string $key)
+					{
+					$data = array_combine(
+							['__ci_type', '__ci_value'],
+							$this->redis->hmget($key, ['__ci_type', '__ci_value'])
+							);
+
+					if (! isset($data['__ci_type'], $data['__ci_value']) OR $data['__ci_value'] === false)
+					{
+						return false;
+					}
+
+					switch ($data['__ci_type'])
+					{
+						case 'array':
+						case 'object':
+							return unserialize($data['__ci_value']);
+						case 'boolean':
+						case 'integer':
+						case 'double': // Yes, 'double' is returned and NOT 'float'
+						case 'string':
+						case 'NULL':
+							return settype($data['__ci_value'], $data['__ci_type'])
+								? $data['__ci_value']
+								: false;
+						case 'resource':
+						default:
+							return false;
+					}
+					}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Saves an item to the cache store.
+			 *
+			 * The $raw parameter is only utilized by predis in order to
+			 * allow usage of increment() and decrement().
+			 *
+			 * @param string $key    Cache item name
+			 * @param        $value  the data to save
+			 * @param null   $ttl    Time To Live, in seconds (default 60)
+			 * @param bool   $raw    Whether to store the raw value.
+			 *
+			 * @return mixed
+			 */
+			public function save(string $key, $value, int $ttl = 60, bool $raw = false)
+			{
+				switch ($data_type = gettype($value))
+				{
+					case 'array':
+					case 'object':
+						$value = serialize($value);
+						break;
+					case 'boolean':
+					case 'integer':
+					case 'double': // Yes, 'double' is returned and NOT 'float'
+					case 'string':
+					case 'NULL':
+						break;
+					case 'resource':
+					default:
+						return false;
+				}
+
+				if (! $this->redis->hmset($key, ['__ci_type' => $data_type, '__ci_value' => $value]))
+				{
+					return false;
+				}
+
+				$this->redis->expireat($key, time()+$ttl);
+
+				return true;
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Deletes a specific item from the cache store.
+			 *
+			 * @param string $key Cache item name
+			 *
+			 * @return mixed
+			 */
+			public function delete(string $key)
+			{
+				return ($this->redis->del($key) === 1);
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Performs atomic incrementation of a raw stored value.
+			 *
+			 * @param string $key    Cache ID
+			 * @param int    $offset Step/value to increase by
+			 *
+			 * @return mixed
+			 */
+			public function increment(string $key, int $offset = 1)
+			{
+				return $this->redis->hincrby($key, 'data', $offset);
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Performs atomic decrementation of a raw stored value.
+			 *
+			 * @param string $key    Cache ID
+			 * @param int    $offset Step/value to increase by
+			 *
+			 * @return mixed
+			 */
+			public function decrement(string $key, int $offset = 1)
+			{
+				return $this->redis->hincrby($key, 'data', -$offset);
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Will delete all items in the entire cache.
+			 *
+			 * @return mixed
+			 */
+			public function clean()
+			{
+				return $this->redis->flushdb();
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Returns information on the entire cache.
+			 *
+			 * The information returned and the structure of the data
+			 * varies depending on the handler.
+			 *
+			 * @return mixed
+			 */
+			public function getCacheInfo()
+			{
+				return $this->redis->info();
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Returns detailed information about the specific item in the cache.
+			 *
+			 * @param string $key Cache item name.
+			 *
+			 * @return mixed
+			 */
+			public function getMetaData(string $key)
+			{
+				$data = array_combine(['__ci_value'], $this->redis->hmget($key, ['__ci_value']));
+
+				if (isset($data['__ci_value']) AND $data['__ci_value'] !== false)
+				{
+					return array(
+							'expire' => time() + $this->redis->ttl($key),
+							'data' => $data['__ci_value']
+							);
+				}
+
+				return FALSE;
+			}
+
+			//--------------------------------------------------------------------
+
+			/**
+			 * Determines if the driver is supported on this system.
+			 *
+			 * @return boolean
+			 */
+			public function isSupported(): bool
+	{
+		return class_exists('\Predis\Client');
+	}
+
+			//--------------------------------------------------------------------
+
+}
diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php
index e55cebd3ef5c..fefb88e28d46 100644
--- a/system/CodeIgniter.php
+++ b/system/CodeIgniter.php
@@ -52,859 +52,859 @@
  */
 class CodeIgniter
 {
-    /**
-     * The current version of CodeIgniter Framework
-     */
-    const CI_VERSION = '4.0-dev';
-
-    /**
-     * App startup time.
-     * @var mixed
-     */
-    protected $startTime;
-
-    /**
-     * Amount of memory at app start.
-     * @var int
-     */
-    protected $startMemory;
-
-    /**
-     * Total app execution time
-     * @var float
-     */
-    protected $totalTime;
-
-    /**
-     * Main application configuration
-     * @var \Config\App
-     */
-    protected $config;
-
-    /**
-     * Timer instance.
-     * @var Timer
-     */
-    protected $benchmark;
-
-    /**
-     * Current request.
-     * @var \CodeIgniter\HTTP\Request
-     */
-    protected $request;
-
-    /**
-     * Current response.
-     * @var \CodeIgniter\HTTP\Response
-     */
-    protected $response;
-
-    /**
-     * Router to use.
-     * @var \CodeIgniter\Router\Router
-     */
-    protected $router;
-
-    /**
-     * Controller to use.
-     * @var string|\Closure
-     */
-    protected $controller;
-
-    /**
-     * Controller method to invoke.
-     * @var string
-     */
-    protected $method;
-
-    /**
-     * Output handler to use.
-     * @var string
-     */
-    protected $output;
-
-    /**
-     * Cache expiration time
-     * @var int
-     */
-    protected static $cacheTTL = 0;
-
-    /**
-     * Request path to use.
-     * @var string
-     */
-    protected $path;
-
-    //--------------------------------------------------------------------
-
-    public function __construct($config)
-    {
-        $this->startTime = microtime(true);
-        $this->startMemory = memory_get_usage(true);
-        $this->config = $config;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Handles some basic app and environment setup.
-     */
-    public function initialize()
-    {
-        // Set default timezone on the server
-        date_default_timezone_set($this->config->appTimezone ?? 'UTC');
-
-        // Setup Exception Handling
-        Config\Services::exceptions($this->config, true)
-                ->initialize();
-
-        $this->detectEnvironment();
-        $this->bootstrapEnvironment();
-        $this->loadEnvironment();
-
-        if (CI_DEBUG)
-        {
-            require_once BASEPATH.'ThirdParty/Kint/Kint.class.php';
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Launch the application!
-     *
-     * This is "the loop" if you will. The main entry point into the script
-     * that gets the required class instances, fires off the filters,
-     * tries to route the response, loads the controller and generally
-     * makes all of the pieces work together.
-     *
-     * @param \CodeIgniter\RouteCollectionInterface $routes
-     */
-    public function run(RouteCollectionInterface $routes = null)
-    {
-        $this->startBenchmark();
-
-        $this->getRequestObject();
-        $this->getResponseObject();
-
-        $this->forceSecureAccess();
-
-        // Check for a cached page. Execution will stop
-        // if the page has been cached.
-        $cacheConfig = new Cache();
-        $this->displayCache($cacheConfig);
-
-        try {
-            $this->handleRequest($routes, $cacheConfig);
-        }
-        catch (Router\RedirectException $e)
-        {
-            $logger = Config\Services::logger();
-            $logger->info('REDIRECTED ROUTE at '.$e->getMessage());
-
-            // If the route is a 'redirect' route, it throws
-            // the exception with the $to as the message
-            $this->response->redirect($e->getMessage(), 'auto', $e->getCode());
-            $this->callExit(EXIT_SUCCESS);
-        }
-            // Catch Response::redirect()
-        catch (HTTP\RedirectException $e)
-        {
-            $this->callExit(EXIT_SUCCESS);
-        }
-        catch (PageNotFoundException $e)
-        {
-            $this->display404errors($e);
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Handles the main request logic and fires the controller.
-     *
-     * @param \CodeIgniter\Router\RouteCollectionInterface $routes
-     * @param                                              $cacheConfig
-     */
-    protected function handleRequest(RouteCollectionInterface $routes = null, $cacheConfig)
-    {
-        $this->tryToRouteIt($routes);
-
-        // Run "before" filters
-        $filters = Config\Services::filters();
-        $uri = $this->request instanceof CLIRequest
-            ? $this->request->getPath()
-            : $this->request->uri->getPath();
-
-        $filters->run($uri, 'before');
-
-        $returned = $this->startController();
-
-        // Closure controller has run in startController().
-        if ( ! is_callable($this->controller))
-        {
-            $controller = $this->createController();
-
-            // Is there a "post_controller_constructor" hook?
-            Hooks::trigger('post_controller_constructor');
-
-            $returned = $this->runController($controller);
-        }
+	/**
+	 * The current version of CodeIgniter Framework
+	 */
+	const CI_VERSION = '4.0-dev';
+
+	/**
+	 * App startup time.
+	 * @var mixed
+	 */
+	protected $startTime;
+
+	/**
+	 * Amount of memory at app start.
+	 * @var int
+	 */
+	protected $startMemory;
+
+	/**
+	 * Total app execution time
+	 * @var float
+	 */
+	protected $totalTime;
+
+	/**
+	 * Main application configuration
+	 * @var \Config\App
+	 */
+	protected $config;
+
+	/**
+	 * Timer instance.
+	 * @var Timer
+	 */
+	protected $benchmark;
+
+	/**
+	 * Current request.
+	 * @var \CodeIgniter\HTTP\Request
+	 */
+	protected $request;
+
+	/**
+	 * Current response.
+	 * @var \CodeIgniter\HTTP\Response
+	 */
+	protected $response;
+
+	/**
+	 * Router to use.
+	 * @var \CodeIgniter\Router\Router
+	 */
+	protected $router;
+
+	/**
+	 * Controller to use.
+	 * @var string|\Closure
+	 */
+	protected $controller;
+
+	/**
+	 * Controller method to invoke.
+	 * @var string
+	 */
+	protected $method;
+
+	/**
+	 * Output handler to use.
+	 * @var string
+	 */
+	protected $output;
+
+	/**
+	 * Cache expiration time
+	 * @var int
+	 */
+	protected static $cacheTTL = 0;
+
+	/**
+	 * Request path to use.
+	 * @var string
+	 */
+	protected $path;
+
+	//--------------------------------------------------------------------
+
+	public function __construct($config)
+	{
+		$this->startTime = microtime(true);
+		$this->startMemory = memory_get_usage(true);
+		$this->config = $config;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Handles some basic app and environment setup.
+	 */
+	public function initialize()
+	{
+		// Set default timezone on the server
+		date_default_timezone_set($this->config->appTimezone ?? 'UTC');
+
+		// Setup Exception Handling
+		Config\Services::exceptions($this->config, true)
+			->initialize();
+
+		$this->detectEnvironment();
+		$this->bootstrapEnvironment();
+		$this->loadEnvironment();
+
+		if (CI_DEBUG)
+		{
+			require_once BASEPATH.'ThirdParty/Kint/Kint.class.php';
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Launch the application!
+	 *
+	 * This is "the loop" if you will. The main entry point into the script
+	 * that gets the required class instances, fires off the filters,
+	 * tries to route the response, loads the controller and generally
+	 * makes all of the pieces work together.
+	 *
+	 * @param \CodeIgniter\RouteCollectionInterface $routes
+	 */
+	public function run(RouteCollectionInterface $routes = null)
+	{
+		$this->startBenchmark();
+
+		$this->getRequestObject();
+		$this->getResponseObject();
+
+		$this->forceSecureAccess();
+
+		// Check for a cached page. Execution will stop
+		// if the page has been cached.
+		$cacheConfig = new Cache();
+		$this->displayCache($cacheConfig);
+
+		try {
+			$this->handleRequest($routes, $cacheConfig);
+		}
+		catch (Router\RedirectException $e)
+		{
+			$logger = Config\Services::logger();
+			$logger->info('REDIRECTED ROUTE at '.$e->getMessage());
+
+			// If the route is a 'redirect' route, it throws
+			// the exception with the $to as the message
+			$this->response->redirect($e->getMessage(), 'auto', $e->getCode());
+			$this->callExit(EXIT_SUCCESS);
+		}
+		// Catch Response::redirect()
+		catch (HTTP\RedirectException $e)
+		{
+			$this->callExit(EXIT_SUCCESS);
+		}
+		catch (PageNotFoundException $e)
+		{
+			$this->display404errors($e);
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Handles the main request logic and fires the controller.
+	 *
+	 * @param \CodeIgniter\Router\RouteCollectionInterface $routes
+	 * @param                                              $cacheConfig
+	 */
+	protected function handleRequest(RouteCollectionInterface $routes = null, $cacheConfig)
+	{
+		$this->tryToRouteIt($routes);
+
+		// Run "before" filters
+		$filters = Config\Services::filters();
+		$uri = $this->request instanceof CLIRequest
+			? $this->request->getPath()
+			: $this->request->uri->getPath();
+
+		$filters->run($uri, 'before');
+
+		$returned = $this->startController();
+
+		// Closure controller has run in startController().
+		if ( ! is_callable($this->controller))
+		{
+			$controller = $this->createController();
+
+			// Is there a "post_controller_constructor" hook?
+			Hooks::trigger('post_controller_constructor');
+
+			$returned = $this->runController($controller);
+		}
 		else
 		{
 			$this->benchmark->stop('controller_constructor');
 			$this->benchmark->stop('controller');
 		}
 
-        // If $returned is a string, then the controller output something,
-        // probably a view, instead of echoing it directly. Send it along
-        // so it can be used with the output.
-        $this->gatherOutput($cacheConfig, $returned);
-
-        // Run "after" filters
-        $response = $filters->run($uri, 'after');
-
-        if ($response instanceof Response)
-        {
-            $this->response = $response;
-        }
-
-        // Save our current URI as the previous URI in the session
-        // for safer, more accurate use with `previous_url()` helper function.
-        $this->storePreviousURL($this->request->uri ?? $uri);
-
-        unset($uri);
-
-        $this->sendResponse();
-
-        //--------------------------------------------------------------------
-        // Is there a post-system hook?
-        //--------------------------------------------------------------------
-        Hooks::trigger('post_system');
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * You can load different configurations depending on your
-     * current environment. Setting the environment also influences
-     * things like logging and error reporting.
-     *
-     * This can be set to anything, but default usage is:
-     *
-     *     development
-     *     testing
-     *     production
-     */
-    protected function detectEnvironment()
-    {
-        // running under Continuous Integration server?
-        if (getenv('CI') !== false)
-        {
-            define('ENVIRONMENT', 'testing');
-        }
-        else
-        {
-            define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'production');
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Load any custom boot files based upon the current environment.
-     *
-     * If no boot file exists, we shouldn't continue because something
-     * is wrong. At the very least, they should have error reporting setup.
-     */
-    protected function bootstrapEnvironment()
-    {
-        if (file_exists(APPPATH.'Config/Boot/'.ENVIRONMENT.'.php'))
-        {
-            require_once APPPATH.'Config/Boot/'.ENVIRONMENT.'.php';
-        }
-        else
-        {
-            header('HTTP/1.1 503 Service Unavailable.', true, 503);
-            echo 'The application environment is not set correctly.';
-            exit(1); // EXIT_ERROR
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Loads any custom server config values from the .env file.
-     */
-    protected function loadEnvironment()
-    {
-        // Load environment settings from .env files
-        // into $_SERVER and $_ENV
-        require BASEPATH.'Config/DotEnv.php';
-
-        $env = new DotEnv(ROOTPATH);
-        $env->load();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Start the Benchmark
-     *
-     * The timer is used to display total script execution both in the
-     * debug toolbar, and potentially on the displayed page.
-     */
-    protected function startBenchmark()
-    {
-        $this->startTime = microtime(true);
-
-        $this->benchmark = Config\Services::timer();
-        $this->benchmark->start('total_execution', $this->startTime);
-        $this->benchmark->start('bootstrap');
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Get our Request object, (either IncomingRequest or CLIRequest)
-     * and set the server protocol based on the information provided
-     * by the server.
-     */
-    protected function getRequestObject()
-    {
-        if (is_cli())
-        {
-            $this->request = Config\Services::clirequest($this->config);
-        }
-        else
-        {
-            $this->request = Config\Services::request($this->config);
-            $this->request->setProtocolVersion($_SERVER['SERVER_PROTOCOL']);
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Get our Response object, and set some default values, including
-     * the HTTP protocol version and a default successful response.
-     */
-    protected function getResponseObject()
-    {
-        $this->response = Config\Services::response($this->config);
-
-        if ( ! is_cli())
-        {
-            $this->response->setProtocolVersion($this->request->getProtocolVersion());
-        }
-
-        // Assume success until proven otherwise.
-        $this->response->setStatusCode(200);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Force Secure Site Access? If the config value 'forceGlobalSecureRequests'
-     * is true, will enforce that all requests to this site are made through
-     * HTTPS. Will redirect the user to the current page with HTTPS, as well
-     * as set the HTTP Strict Transport Security header for those browsers
-     * that support it.
-     *
-     * @param int $duration  How long the Strict Transport Security
-     *                       should be enforced for this URL.
-     */
-    protected function forceSecureAccess($duration = 31536000)
-    {
-        if ($this->config->forceGlobalSecureRequests !== true)
-        {
-            return;
-        }
-
-        force_https($duration, $this->request, $this->response);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Determines if a response has been cached for the given URI.
-     *
-     * @param \Config\Cache $config
-     *
-     * @return bool
-     */
-    public function displayCache($config)
-    {
-        if ($cachedResponse = cache()->get($this->generateCacheName($config)))
-        {
-            $cachedResponse = unserialize($cachedResponse);
-            if (!is_array($cachedResponse) || !isset($cachedResponse['output']) || !isset($cachedResponse['headers']))
-            {
-                throw new \Exception("Error unserializing page cache");
-            }
-
-            $headers = $cachedResponse['headers'];
-            $output  = $cachedResponse['output'];
-
-            // Clear all default headers
-            foreach($this->response->getHeaders() as $key => $val) {
-                $this->response->removeHeader($key);
-            }
-
-            // Set cached headers
-            foreach($headers as $name => $value) {
-                $this->response->setHeader($name, $value);
-            }
-
-            $output = $this->displayPerformanceMetrics($output);
-            $this->response->setBody($output)->send();
-            $this->callExit(EXIT_SUCCESS);
-        };
-    }
-
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Tells the app that the final output should be cached.
-     *
-     * @param int $time
-     *
-     * @return $this
-     */
-    public static function cache(int $time)
-    {
-        self::$cacheTTL = (int)$time;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Caches the full response from the current request. Used for
-     * full-page caching for very high performance.
-     *
-     * @param \Config\Cache $config
-     */
-    public function cachePage(Cache $config)
-    {
-        $headers = [];
-        foreach($this->response->getHeaders() as $header) {
-            $headers[$header->getName()] = $header->getValueLine();
-        }
-
-        return cache()->save(
-            $this->generateCacheName($config),
-            serialize(['headers' => $headers, 'output' => $this->output]),
-            self::$cacheTTL
-        );
-
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Returns an array with our basic performance stats collected.
-     *
-     * @return array
-     */
-    public function getPerformanceStats()
-    {
-        return [
-            'startTime'	=> $this->startTime,
-            'totalTime' => $this->totalTime,
-            'startMemory' => $this->startMemory
-        ];
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Generates the cache name to use for our full-page caching.
-     *
-     * @param $config
-     *
-     * @return string
-     */
-    protected function generateCacheName($config): string
-    {
-        if (is_cli())
-        {
-            return md5($this->request->getPath());
-        }
-
-        $uri = $this->request->uri;
-
-        if ($config->cacheQueryString)
-        {
-            $name = URI::createURIString(
-                $uri->getScheme(),
-                $uri->getAuthority(),
-                $uri->getPath(),
-                $uri->getQuery()
-            );
-        }
-        else
-        {
-            $name = URI::createURIString(
-                $uri->getScheme(),
-                $uri->getAuthority(),
-                $uri->getPath()
-            );
-        }
-
-        return md5($name);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Replaces the memory_usage and elapsed_time tags.
-     *
-     * @param string $output
-     *
-     * @return string
-     */
-    public function displayPerformanceMetrics(string $output): string
-    {
-        $this->totalTime = $this->benchmark->getElapsedTime('total_execution');
-
-        $output = str_replace('{elapsed_time}', $this->totalTime, $output);
-
-        return $output;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Try to Route It - As it sounds like, works with the router to
-     * match a route against the current URI. If the route is a
-     * "redirect route", will also handle the redirect.
-     *
-     * @param RouteCollectionInterface $routes  An collection interface to use in place
-     *                                          of the config file.
-     */
-    protected function tryToRouteIt(RouteCollectionInterface $routes = null)
-    {
-        if (empty($routes) || ! $routes instanceof RouteCollectionInterface)
-        {
-            require APPPATH.'Config/Routes.php';
-        }
-
-        // $routes is defined in Config/Routes.php
-        $this->router = Config\Services::router($routes);
-
-        $path = $this->determinePath();
-
-        $this->benchmark->stop('bootstrap');
-        $this->benchmark->start('routing');
-
-        ob_start();
-
-        $this->controller = $this->router->handle($path);
-        $this->method     = $this->router->methodName();
-
-        // If a {locale} segment was matched in the final route,
-        // then we need to set the correct locale on our Request.
-        if ($this->router->hasLocale())
-        {
-            $this->request->setLocale($this->router->getLocale());
-        }
-
-        $this->benchmark->stop('routing');
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Determines the path to use for us to try to route to, based
-     * on user input (setPath), or the CLI/IncomingRequest path.
-     */
-    protected function determinePath()
-    {
-        if (! empty($this->path))
-        {
-            return $this->path;
-        }
-
-        return is_cli()
-            ? $this->request->getPath()
-            : $this->request->uri->getPath();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Allows the request path to be set from outside the class,
-     * instead of relying on CLIRequest or IncomingRequest for the path.
-     *
-     * This is primarily used by the Console.
-     *
-     * @param string $path
-     *
-     * @return $this
-     */
-    public function setPath(string $path)
-    {
-        $this->path = $path;
-
-        return $this;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Now that everything has been setup, this method attempts to run the
-     * controller method and make the script go. If it's not able to, will
-     * show the appropriate Page Not Found error.
-     */
-    protected function startController()
-    {
-        $this->benchmark->start('controller');
-        $this->benchmark->start('controller_constructor');
-
-        // Is it routed to a Closure?
-        if (is_object($this->controller) && (get_class($this->controller) == 'Closure'))
-        {
-            $controller = $this->controller;
-            return $controller(...$this->router->params());
-        }
-        else
-        {
-            // No controller specified - we don't know what to do now.
-            if (empty($this->controller))
-            {
-                throw new PageNotFoundException('Controller is empty.');
-            }
-            else
-            {
-                // Try to autoload the class
-                if ( ! class_exists($this->controller, true) || $this->method[0] === '_')
-                {
-                    throw new PageNotFoundException('Controller or its method is not found.');
-                }
-                else if ( ! method_exists($this->controller, '_remap') &&
-                          ! is_callable([$this->controller, $this->method], false)
-                )
-                {
-                    throw new PageNotFoundException('Controller method is not found.');
-                }
-            }
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Instantiates the controller class.
-     *
-     * @return mixed
-     */
-    protected function createController()
-    {
-        $class = new $this->controller($this->request, $this->response);
-
-        $this->benchmark->stop('controller_constructor');
-
-        return $class;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Runs the controller, allowing for _remap methods to function.
-     *
-     * @param mixed $class
-     *
-     * @return mixed
-     */
-    protected function runController($class)
-    {
-        if (method_exists($class, '_remap'))
-        {
-            $output = $class->_remap($this->method, ...$this->router->params());
-        }
-        else
-        {
-            $output = $class->{$this->method}(...$this->router->params());
-        }
-
-        $this->benchmark->stop('controller');
-
-        return $output;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Displays a 404 Page Not Found error. If set, will try to
-     * call the 404Override controller/method that was set in routing config.
-     *
-     * @param PageNotFoundException $e
-     */
-    protected function display404errors(PageNotFoundException $e)
-    {
-        // Is there a 404 Override available?
-        if ($override = $this->router->get404Override())
-        {
-            if ($override instanceof \Closure)
-            {
-                echo $override();
-            }
-            else if (is_array($override))
-            {
-                $this->benchmark->start('controller');
-                $this->benchmark->start('controller_constructor');
-
-                $this->controller = $override[0];
-                $this->method     = $override[1];
-
-                unset($override);
-
-                $controller = $this->createController();
-                $this->runController($controller);
-            }
-
-            $this->gatherOutput();
-            $this->sendResponse();
-
-            return;
-        }
-
-        // Display 404 Errors
-        $this->response->setStatusCode(404);
-
-        if (ENVIRONMENT !== 'testing') {
-            if (ob_get_level() > 0) {
-                ob_end_flush();
-            }
-        }
-        else
-        {
-            // When testing, one is for phpunit, another is for test case.
-            if (ob_get_level() > 2) {
-                ob_end_flush();
-            }
-        }
-
-        ob_start();
-
-        // These might show as unused here - but don't delete!
-        // They are used within the view files.
-        $heading = 'Page Not Found';
-        $message = $e->getMessage();
-
-        // Show the 404 error page
-        if (is_cli())
-        {
-            require APPPATH.'Views/errors/cli/error_404.php';
-        }
-        else
-        {
-            require APPPATH.'Views/errors/html/error_404.php';
-        }
-
-        $buffer = ob_get_contents();
-        ob_end_clean();
-
-        $this->response->setBody($buffer);
-        $this->response->send();
-        $this->callExit(EXIT_UNKNOWN_FILE);    // Unknown file
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Gathers the script output from the buffer, replaces some execution
-     * time tag in the output and displays the debug toolbar, if required.
-     *
-     * @param null $cacheConfig
-     * @param null $returned
-     */
-    protected function gatherOutput($cacheConfig = null, $returned = null)
-    {
-        $this->output = ob_get_contents();
-        ob_end_clean();
-
-        // If the controller returned a response object,
-        // we need to grab the body from it so it can
-        // be added to anything else that might have been
-        // echoed already.
-        // We also need to save the instance locally
-        // so that any status code changes, etc, take place.
-        if ($returned instanceof Response)
-        {
-            $this->response = $returned;
-            $returned = $returned->getBody();
-        }
-
-        if (is_string($returned))
-        {
-            $this->output .= $returned;
-        }
-
-        // Cache it without the performance metrics replaced
-        // so that we can have live speed updates along the way.
-        if (self::$cacheTTL > 0)
-        {
-            $this->cachePage($cacheConfig);
-        }
-
-        $this->output = $this->displayPerformanceMetrics($this->output);
-
-        $this->response->setBody($this->output);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * If we have a session object to use, store the current URI
-     * as the previous URI. This is called just prior to sending the
-     * response to the client, and will make it available next request.
-     *
-     * This helps provider safer, more reliable previous_url() detection.
-     *
-     * @param \CodeIgniter\HTTP\URI $uri
-     */
-    public function storePreviousURL($uri)
-    {
-        // This is mainly needed during testing...
-        if (is_string($uri))
-        {
-            $uri = new URI($uri);
-        }
-
-        if (isset($_SESSION))
-        {
-            $_SESSION['_ci_previous_url'] = (string)$uri;
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Sends the output of this request back to the client.
-     * This is what they've been waiting for!
-     */
-    protected function sendResponse()
-    {
-        $this->response->send();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Exits the application, setting the exit code for CLI-based applications
-     * that might be watching.
-     *
-     * Made into a separate method so that it can be mocked during testing
-     * without actually stopping script execution.
-     *
-     * @param $code
-     */
-    protected function callExit($code)
-    {
-        exit($code);
-    }
-
-    //--------------------------------------------------------------------
+		// If $returned is a string, then the controller output something,
+		// probably a view, instead of echoing it directly. Send it along
+		// so it can be used with the output.
+		$this->gatherOutput($cacheConfig, $returned);
+
+		// Run "after" filters
+		$response = $filters->run($uri, 'after');
+
+		if ($response instanceof Response)
+		{
+			$this->response = $response;
+		}
+
+		// Save our current URI as the previous URI in the session
+		// for safer, more accurate use with `previous_url()` helper function.
+		$this->storePreviousURL($this->request->uri ?? $uri);
+
+		unset($uri);
+
+		$this->sendResponse();
+
+		//--------------------------------------------------------------------
+		// Is there a post-system hook?
+		//--------------------------------------------------------------------
+		Hooks::trigger('post_system');
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * You can load different configurations depending on your
+	 * current environment. Setting the environment also influences
+	 * things like logging and error reporting.
+	 *
+	 * This can be set to anything, but default usage is:
+	 *
+	 *     development
+	 *     testing
+	 *     production
+	 */
+	protected function detectEnvironment()
+	{
+		// running under Continuous Integration server?
+		if (getenv('CI') !== false)
+		{
+			define('ENVIRONMENT', 'testing');
+		}
+		else
+		{
+			define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'production');
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Load any custom boot files based upon the current environment.
+	 *
+	 * If no boot file exists, we shouldn't continue because something
+	 * is wrong. At the very least, they should have error reporting setup.
+	 */
+	protected function bootstrapEnvironment()
+	{
+		if (file_exists(APPPATH.'Config/Boot/'.ENVIRONMENT.'.php'))
+		{
+			require_once APPPATH.'Config/Boot/'.ENVIRONMENT.'.php';
+		}
+		else
+		{
+			header('HTTP/1.1 503 Service Unavailable.', true, 503);
+			echo 'The application environment is not set correctly.';
+			exit(1); // EXIT_ERROR
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Loads any custom server config values from the .env file.
+	 */
+	protected function loadEnvironment()
+	{
+		// Load environment settings from .env files
+		// into $_SERVER and $_ENV
+		require BASEPATH.'Config/DotEnv.php';
+
+		$env = new DotEnv(ROOTPATH);
+		$env->load();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Start the Benchmark
+	 *
+	 * The timer is used to display total script execution both in the
+	 * debug toolbar, and potentially on the displayed page.
+	 */
+	protected function startBenchmark()
+	{
+		$this->startTime = microtime(true);
+
+		$this->benchmark = Config\Services::timer();
+		$this->benchmark->start('total_execution', $this->startTime);
+		$this->benchmark->start('bootstrap');
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Get our Request object, (either IncomingRequest or CLIRequest)
+	 * and set the server protocol based on the information provided
+	 * by the server.
+	 */
+	protected function getRequestObject()
+	{
+		if (is_cli())
+		{
+			$this->request = Config\Services::clirequest($this->config);
+		}
+		else
+		{
+			$this->request = Config\Services::request($this->config);
+			$this->request->setProtocolVersion($_SERVER['SERVER_PROTOCOL']);
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Get our Response object, and set some default values, including
+	 * the HTTP protocol version and a default successful response.
+	 */
+	protected function getResponseObject()
+	{
+		$this->response = Config\Services::response($this->config);
+
+		if ( ! is_cli())
+		{
+			$this->response->setProtocolVersion($this->request->getProtocolVersion());
+		}
+
+		// Assume success until proven otherwise.
+		$this->response->setStatusCode(200);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Force Secure Site Access? If the config value 'forceGlobalSecureRequests'
+	 * is true, will enforce that all requests to this site are made through
+	 * HTTPS. Will redirect the user to the current page with HTTPS, as well
+	 * as set the HTTP Strict Transport Security header for those browsers
+	 * that support it.
+	 *
+	 * @param int $duration  How long the Strict Transport Security
+	 *                       should be enforced for this URL.
+	 */
+	protected function forceSecureAccess($duration = 31536000)
+	{
+		if ($this->config->forceGlobalSecureRequests !== true)
+		{
+			return;
+		}
+
+		force_https($duration, $this->request, $this->response);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Determines if a response has been cached for the given URI.
+	 *
+	 * @param \Config\Cache $config
+	 *
+	 * @return bool
+	 */
+	public function displayCache($config)
+	{
+		if ($cachedResponse = cache()->get($this->generateCacheName($config)))
+		{
+			$cachedResponse = unserialize($cachedResponse);
+			if (!is_array($cachedResponse) || !isset($cachedResponse['output']) || !isset($cachedResponse['headers']))
+			{
+				throw new \Exception("Error unserializing page cache");
+			}
+
+			$headers = $cachedResponse['headers'];
+			$output  = $cachedResponse['output'];
+
+			// Clear all default headers
+			foreach($this->response->getHeaders() as $key => $val) {
+				$this->response->removeHeader($key);
+			}
+
+			// Set cached headers
+			foreach($headers as $name => $value) {
+				$this->response->setHeader($name, $value);
+			}
+
+			$output = $this->displayPerformanceMetrics($output);
+			$this->response->setBody($output)->send();
+			$this->callExit(EXIT_SUCCESS);
+		};
+	}
+
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Tells the app that the final output should be cached.
+	 *
+	 * @param int $time
+	 *
+	 * @return $this
+	 */
+	public static function cache(int $time)
+	{
+		self::$cacheTTL = (int)$time;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Caches the full response from the current request. Used for
+	 * full-page caching for very high performance.
+	 *
+	 * @param \Config\Cache $config
+	 */
+	public function cachePage(Cache $config)
+	{
+		$headers = [];
+		foreach($this->response->getHeaders() as $header) {
+			$headers[$header->getName()] = $header->getValueLine();
+		}
+
+		return cache()->save(
+				$this->generateCacheName($config),
+				serialize(['headers' => $headers, 'output' => $this->output]),
+				self::$cacheTTL
+				);
+
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Returns an array with our basic performance stats collected.
+	 *
+	 * @return array
+	 */
+	public function getPerformanceStats()
+	{
+		return [
+			'startTime'	=> $this->startTime,
+			'totalTime' => $this->totalTime,
+			'startMemory' => $this->startMemory
+		];
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Generates the cache name to use for our full-page caching.
+	 *
+	 * @param $config
+	 *
+	 * @return string
+	 */
+	protected function generateCacheName($config): string
+	{
+		if (is_cli())
+		{
+			return md5($this->request->getPath());
+		}
+
+		$uri = $this->request->uri;
+
+		if ($config->cacheQueryString)
+		{
+			$name = URI::createURIString(
+					$uri->getScheme(),
+					$uri->getAuthority(),
+					$uri->getPath(),
+					$uri->getQuery()
+					);
+		}
+		else
+		{
+			$name = URI::createURIString(
+					$uri->getScheme(),
+					$uri->getAuthority(),
+					$uri->getPath()
+					);
+		}
+
+		return md5($name);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Replaces the memory_usage and elapsed_time tags.
+	 *
+	 * @param string $output
+	 *
+	 * @return string
+	 */
+	public function displayPerformanceMetrics(string $output): string
+	{
+		$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
+
+		$output = str_replace('{elapsed_time}', $this->totalTime, $output);
+
+		return $output;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Try to Route It - As it sounds like, works with the router to
+	 * match a route against the current URI. If the route is a
+	 * "redirect route", will also handle the redirect.
+	 *
+	 * @param RouteCollectionInterface $routes  An collection interface to use in place
+	 *                                          of the config file.
+	 */
+	protected function tryToRouteIt(RouteCollectionInterface $routes = null)
+	{
+		if (empty($routes) || ! $routes instanceof RouteCollectionInterface)
+		{
+			require APPPATH.'Config/Routes.php';
+		}
+
+		// $routes is defined in Config/Routes.php
+		$this->router = Config\Services::router($routes);
+
+		$path = $this->determinePath();
+
+		$this->benchmark->stop('bootstrap');
+		$this->benchmark->start('routing');
+
+		ob_start();
+
+		$this->controller = $this->router->handle($path);
+		$this->method     = $this->router->methodName();
+
+		// If a {locale} segment was matched in the final route,
+		// then we need to set the correct locale on our Request.
+		if ($this->router->hasLocale())
+		{
+			$this->request->setLocale($this->router->getLocale());
+		}
+
+		$this->benchmark->stop('routing');
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Determines the path to use for us to try to route to, based
+	 * on user input (setPath), or the CLI/IncomingRequest path.
+	 */
+	protected function determinePath()
+	{
+		if (! empty($this->path))
+		{
+			return $this->path;
+		}
+
+		return is_cli()
+			? $this->request->getPath()
+			: $this->request->uri->getPath();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Allows the request path to be set from outside the class,
+	 * instead of relying on CLIRequest or IncomingRequest for the path.
+	 *
+	 * This is primarily used by the Console.
+	 *
+	 * @param string $path
+	 *
+	 * @return $this
+	 */
+	public function setPath(string $path)
+	{
+		$this->path = $path;
+
+		return $this;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Now that everything has been setup, this method attempts to run the
+	 * controller method and make the script go. If it's not able to, will
+	 * show the appropriate Page Not Found error.
+	 */
+	protected function startController()
+	{
+		$this->benchmark->start('controller');
+		$this->benchmark->start('controller_constructor');
+
+		// Is it routed to a Closure?
+		if (is_object($this->controller) && (get_class($this->controller) == 'Closure'))
+		{
+			$controller = $this->controller;
+			return $controller(...$this->router->params());
+		}
+		else
+		{
+			// No controller specified - we don't know what to do now.
+			if (empty($this->controller))
+			{
+				throw new PageNotFoundException('Controller is empty.');
+			}
+			else
+			{
+				// Try to autoload the class
+				if ( ! class_exists($this->controller, true) || $this->method[0] === '_')
+				{
+					throw new PageNotFoundException('Controller or its method is not found.');
+				}
+				else if ( ! method_exists($this->controller, '_remap') &&
+						! is_callable([$this->controller, $this->method], false)
+						)
+				{
+					throw new PageNotFoundException('Controller method is not found.');
+				}
+			}
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Instantiates the controller class.
+	 *
+	 * @return mixed
+	 */
+	protected function createController()
+	{
+		$class = new $this->controller($this->request, $this->response);
+
+		$this->benchmark->stop('controller_constructor');
+
+		return $class;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Runs the controller, allowing for _remap methods to function.
+	 *
+	 * @param mixed $class
+	 *
+	 * @return mixed
+	 */
+	protected function runController($class)
+	{
+		if (method_exists($class, '_remap'))
+		{
+			$output = $class->_remap($this->method, ...$this->router->params());
+		}
+		else
+		{
+			$output = $class->{$this->method}(...$this->router->params());
+		}
+
+		$this->benchmark->stop('controller');
+
+		return $output;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Displays a 404 Page Not Found error. If set, will try to
+	 * call the 404Override controller/method that was set in routing config.
+	 *
+	 * @param PageNotFoundException $e
+	 */
+	protected function display404errors(PageNotFoundException $e)
+	{
+		// Is there a 404 Override available?
+		if ($override = $this->router->get404Override())
+		{
+			if ($override instanceof \Closure)
+			{
+				echo $override();
+			}
+			else if (is_array($override))
+			{
+				$this->benchmark->start('controller');
+				$this->benchmark->start('controller_constructor');
+
+				$this->controller = $override[0];
+				$this->method     = $override[1];
+
+				unset($override);
+
+				$controller = $this->createController();
+				$this->runController($controller);
+			}
+
+			$this->gatherOutput();
+			$this->sendResponse();
+
+			return;
+		}
+
+		// Display 404 Errors
+		$this->response->setStatusCode(404);
+
+		if (ENVIRONMENT !== 'testing') {
+			if (ob_get_level() > 0) {
+				ob_end_flush();
+			}
+		}
+		else
+		{
+			// When testing, one is for phpunit, another is for test case.
+			if (ob_get_level() > 2) {
+				ob_end_flush();
+			}
+		}
+
+		ob_start();
+
+		// These might show as unused here - but don't delete!
+		// They are used within the view files.
+		$heading = 'Page Not Found';
+		$message = $e->getMessage();
+
+		// Show the 404 error page
+		if (is_cli())
+		{
+			require APPPATH.'Views/errors/cli/error_404.php';
+		}
+		else
+		{
+			require APPPATH.'Views/errors/html/error_404.php';
+		}
+
+		$buffer = ob_get_contents();
+		ob_end_clean();
+
+		$this->response->setBody($buffer);
+		$this->response->send();
+		$this->callExit(EXIT_UNKNOWN_FILE);    // Unknown file
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Gathers the script output from the buffer, replaces some execution
+	 * time tag in the output and displays the debug toolbar, if required.
+	 *
+	 * @param null $cacheConfig
+	 * @param null $returned
+	 */
+	protected function gatherOutput($cacheConfig = null, $returned = null)
+	{
+		$this->output = ob_get_contents();
+		ob_end_clean();
+
+		// If the controller returned a response object,
+		// we need to grab the body from it so it can
+		// be added to anything else that might have been
+		// echoed already.
+		// We also need to save the instance locally
+		// so that any status code changes, etc, take place.
+		if ($returned instanceof Response)
+		{
+			$this->response = $returned;
+			$returned = $returned->getBody();
+		}
+
+		if (is_string($returned))
+		{
+			$this->output .= $returned;
+		}
+
+		// Cache it without the performance metrics replaced
+		// so that we can have live speed updates along the way.
+		if (self::$cacheTTL > 0)
+		{
+			$this->cachePage($cacheConfig);
+		}
+
+		$this->output = $this->displayPerformanceMetrics($this->output);
+
+		$this->response->setBody($this->output);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * If we have a session object to use, store the current URI
+	 * as the previous URI. This is called just prior to sending the
+	 * response to the client, and will make it available next request.
+	 *
+	 * This helps provider safer, more reliable previous_url() detection.
+	 *
+	 * @param \CodeIgniter\HTTP\URI $uri
+	 */
+	public function storePreviousURL($uri)
+	{
+		// This is mainly needed during testing...
+		if (is_string($uri))
+		{
+			$uri = new URI($uri);
+		}
+
+		if (isset($_SESSION))
+		{
+			$_SESSION['_ci_previous_url'] = (string)$uri;
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Sends the output of this request back to the client.
+	 * This is what they've been waiting for!
+	 */
+	protected function sendResponse()
+	{
+		$this->response->send();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Exits the application, setting the exit code for CLI-based applications
+	 * that might be watching.
+	 *
+	 * Made into a separate method so that it can be mocked during testing
+	 * without actually stopping script execution.
+	 *
+	 * @param $code
+	 */
+	protected function callExit($code)
+	{
+		exit($code);
+	}
+
+	//--------------------------------------------------------------------
 }
diff --git a/system/Commands/Database/CreateMigration.php b/system/Commands/Database/CreateMigration.php
index 68e0a723b83d..607b33106bb8 100644
--- a/system/Commands/Database/CreateMigration.php
+++ b/system/Commands/Database/CreateMigration.php
@@ -47,117 +47,117 @@
  */
 class CreateMigration extends BaseCommand
 {
-    protected $group = 'Database';
-
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'migrate:create';
-
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Creates a new migration file.';
-
-     /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'migrate:create [migration_name] [Options]';
-
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = array(
-        'migration_name' => 'The migration file name'
-    );
-
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array(
-        '-n' => 'Set migration namespace'
-    );
-
-    /**
-     * Creates a new migration file with the current timestamp.
-     * @todo Have this check the settings and see what type of file it should create (timestamp or sequential)
-     */
-    public function run(array $params=[])
-    {
-
-        $name = array_shift($params);
-
-        if (empty($name))
-        {
-            $name = CLI::prompt(lang('Migrations.migNameMigration'));
-        }
-
-        if (empty($name))
-        {
-            CLI::error(lang('Migrations.migBadCreateName'));
-            return;
-        }
-        $namespace = CLI::getOption('n');
-        $homepath = APPPATH;
-
-        if (!empty($ns))
-        {
-             // Get all namespaces form  PSR4 paths.
-            $config = new Autoload();
-            $namespaces = $config->psr4;
-
-            foreach ($namespaces as $namespace => $path) {
-
-                if ($namespace == $ns ) {
-                    $homepath =realpath($path);
-                }
-            }
-        }else {
-            $ns= "App";
-        }
-
-        $path = $homepath.'/Database/Migrations/'.date('YmdHis_').$name.'.php';
-
-        $template =<< 'The migration file name'
+			);
+
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-n' => 'Set migration namespace'
+			);
+
+	/**
+	 * Creates a new migration file with the current timestamp.
+	 * @todo Have this check the settings and see what type of file it should create (timestamp or sequential)
+	 */
+	public function run(array $params=[])
+	{
+
+		$name = array_shift($params);
+
+		if (empty($name))
+		{
+			$name = CLI::prompt(lang('Migrations.migNameMigration'));
+		}
+
+		if (empty($name))
+		{
+			CLI::error(lang('Migrations.migBadCreateName'));
+			return;
+		}
+		$namespace = CLI::getOption('n');
+		$homepath = APPPATH;
+
+		if (!empty($ns))
+		{
+			// Get all namespaces form  PSR4 paths.
+			$config = new Autoload();
+			$namespaces = $config->psr4;
+
+			foreach ($namespaces as $namespace => $path) {
+
+				if ($namespace == $ns ) {
+					$homepath =realpath($path);
+				}
+			}
+		}else {
+			$ns= "App";
+		}
+
+		$path = $homepath.'/Database/Migrations/'.date('YmdHis_').$name.'.php';
+
+		$template =<< 'Set database group'
-    );
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-g' => 'Set database group'
+			);
 
 
-    /**
-     * Migrates us up or down to the version specified as $currentVersion
-     * in the migrations config file.
-     */
-    public function run(array $params=[])
-    {
-        $runner = Services::migrations();
+	/**
+	 * Migrates us up or down to the version specified as $currentVersion
+	 * in the migrations config file.
+	 */
+	public function run(array $params=[])
+	{
+		$runner = Services::migrations();
 
-        CLI::write(lang('Migrations.migToVersion'), 'yellow');
+		CLI::write(lang('Migrations.migToVersion'), 'yellow');
 
-        $group =    CLI::getOption('g');
-        try { 
-            $runner->current($group);
-            $messages = $runner->getCliMessages();
-            foreach ($messages as $message) {
-                CLI::write($message); 
-            }
-        }
-        catch (\Exception $e)
-        {
-            $this->showError($e);
-        }
+		$group =    CLI::getOption('g');
+		try { 
+			$runner->current($group);
+			$messages = $runner->getCliMessages();
+			foreach ($messages as $message) {
+				CLI::write($message); 
+			}
+		}
+		catch (\Exception $e)
+		{
+			$this->showError($e);
+		}
 
-        CLI::write('Done');
-    }
+		CLI::write('Done');
+	}
 }
diff --git a/system/Commands/Database/MigrateLatest.php b/system/Commands/Database/MigrateLatest.php
index 4428fcb4968f..7c0bbf17a9fe 100644
--- a/system/Commands/Database/MigrateLatest.php
+++ b/system/Commands/Database/MigrateLatest.php
@@ -47,77 +47,77 @@
  */
 class MigrateLatest extends BaseCommand
 {
-    protected $group = 'Database';
+	protected $group = 'Database';
 
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'migrate:latest';
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'migrate:latest';
 
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Migrates the database to the latest schema.';
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Migrates the database to the latest schema.';
 
-     /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'migrate:latest [options]';
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'migrate:latest [options]';
 
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = [];
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = [];
 
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array(
-        '-n'   => 'Set migration namespace',
-        '-g'   => 'Set database group',
-        '-all' => 'Set latest for all namespace, will ignore (-n) option'
-    );
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-n'   => 'Set migration namespace',
+			'-g'   => 'Set database group',
+			'-all' => 'Set latest for all namespace, will ignore (-n) option'
+			);
 
 
-    /**
-     * Ensures that all migrations have been run.
-     */
-    public function run(array $params=[])
-    {
-        $runner = Services::migrations();
+	/**
+	 * Ensures that all migrations have been run.
+	 */
+	public function run(array $params=[])
+	{
+		$runner = Services::migrations();
 
-        CLI::write(lang('Migrations.migToLatest'), 'yellow');
+		CLI::write(lang('Migrations.migToLatest'), 'yellow');
 
-        $namespace = CLI::getOption('n');
-        $group =    CLI::getOption('g'); 
-        
-        try {
-            if (! is_null(CLI::getOption('all'))){        
-                $runner->latestAll($group);
-            }else{                 
-                $runner->latest($namespace,$group);
-            }
-            $messages = $runner->getCliMessages();
-            foreach ($messages as $message) {
-                CLI::write($message); 
-            }
-            
-        }
-        catch (\Exception $e)
-        {
-            $this->showError($e);
-        }
+		$namespace = CLI::getOption('n');
+		$group =    CLI::getOption('g'); 
 
-        CLI::write('Done');
-    }
+		try {
+			if (! is_null(CLI::getOption('all'))){        
+				$runner->latestAll($group);
+			}else{                 
+				$runner->latest($namespace,$group);
+			}
+			$messages = $runner->getCliMessages();
+			foreach ($messages as $message) {
+				CLI::write($message); 
+			}
+
+		}
+		catch (\Exception $e)
+		{
+			$this->showError($e);
+		}
+
+		CLI::write('Done');
+	}
 }
diff --git a/system/Commands/Database/MigrateRefresh.php b/system/Commands/Database/MigrateRefresh.php
index 6e92901883d1..53f3be246157 100644
--- a/system/Commands/Database/MigrateRefresh.php
+++ b/system/Commands/Database/MigrateRefresh.php
@@ -48,54 +48,54 @@
  */
 class MigrateRefresh extends BaseCommand
 {
-    protected $group = 'Database';
+	protected $group = 'Database';
 
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'migrate:refresh';
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'migrate:refresh';
 
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Does a rollback followed by a latest to refresh the current state of the database.';
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Does a rollback followed by a latest to refresh the current state of the database.';
 
-    /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'migrate:refresh [Options]';
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'migrate:refresh [Options]';
 
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = [];
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = [];
 
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array(
-        '-n'   => 'Set migration namespace',
-        '-g'   => 'Set database group',
-        '-all' => 'Set latest for all namespace, will ignore (-n) option'
-    );
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-n'   => 'Set migration namespace',
+			'-g'   => 'Set database group',
+			'-all' => 'Set latest for all namespace, will ignore (-n) option'
+			);
 
-    /**
-     * Does a rollback followed by a latest to refresh the current state
-     * of the database.
-     */
-    public function run(array $params=[])
-    {
-        $this->call('migrate:rollback');
-        $this->call('migrate:latest');
-    }
+	/**
+	 * Does a rollback followed by a latest to refresh the current state
+	 * of the database.
+	 */
+	public function run(array $params=[])
+	{
+		$this->call('migrate:rollback');
+		$this->call('migrate:latest');
+	}
 }
diff --git a/system/Commands/Database/MigrateRollback.php b/system/Commands/Database/MigrateRollback.php
index 5ee07ca77630..51a527a66428 100644
--- a/system/Commands/Database/MigrateRollback.php
+++ b/system/Commands/Database/MigrateRollback.php
@@ -1,40 +1,40 @@
  'Set migration namespace',
-        '-g'   => 'Set database group',
-        '-all' => 'Set latest for all namespace, will ignore (-n) option'
-    );
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Runs all of the migrations in reverse order, until they have all been un-applied.';
 
-    /**
-    * Runs all of the migrations in reverse order, until they have
-    * all been un-applied.
-    */
-    public function run(array $params=[])
-    {
-        $runner = Services::migrations();
-        
-        CLI::write(lang('Migrations.migRollingBack'), 'yellow');
-        $group = CLI::getOption('g');
-        if(!is_null($group)){
-            $runner->setGroup($group);
-        }
-        try {
-            if (is_null(CLI::getOption('all'))){   
-                $namespace =CLI::getOption('n');         
-                $runner->version(0,$namespace);
-            }else{
-                // Get all namespaces form  PSR4 paths.
-                $config = new Autoload();
-                $namespaces = $config->psr4;
-                foreach ($namespaces as $namespace => $path) {
-                    $runner->setNamespace($namespace);
-                    $migrations = $runner->findMigrations();                    
-                    if (empty($migrations)) {
-                        continue;
-                    }    
-                    $runner->version(0,$namespace,$group);
-                }
-            }
-            $messages = $runner->getCliMessages();
-            foreach ($messages as $message) {
-                CLI::write($message); 
-            }
-        }
-        catch (\Exception $e)
-        {
-            $this->showError($e);
-        }
-        
-        CLI::write('Done');
-    }
-}
\ No newline at end of file
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'migrate:rollback [Options]';
+
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = [];
+
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-n'   => 'Set migration namespace',
+			'-g'   => 'Set database group',
+			'-all' => 'Set latest for all namespace, will ignore (-n) option'
+			);
+
+	/**
+	 * Runs all of the migrations in reverse order, until they have
+	 * all been un-applied.
+	 */
+	public function run(array $params=[])
+	{
+		$runner = Services::migrations();
+
+		CLI::write(lang('Migrations.migRollingBack'), 'yellow');
+		$group = CLI::getOption('g');
+		if(!is_null($group)){
+			$runner->setGroup($group);
+		}
+		try {
+			if (is_null(CLI::getOption('all'))){   
+				$namespace =CLI::getOption('n');         
+				$runner->version(0,$namespace);
+			}else{
+				// Get all namespaces form  PSR4 paths.
+				$config = new Autoload();
+				$namespaces = $config->psr4;
+				foreach ($namespaces as $namespace => $path) {
+					$runner->setNamespace($namespace);
+					$migrations = $runner->findMigrations();                    
+					if (empty($migrations)) {
+						continue;
+					}    
+					$runner->version(0,$namespace,$group);
+				}
+			}
+			$messages = $runner->getCliMessages();
+			foreach ($messages as $message) {
+				CLI::write($message); 
+			}
+		}
+		catch (\Exception $e)
+		{
+			$this->showError($e);
+		}
+
+		CLI::write('Done');
+	}
+}
diff --git a/system/Commands/Database/MigrateStatus.php b/system/Commands/Database/MigrateStatus.php
index 8b214ed8c2f3..dccb55c7895b 100644
--- a/system/Commands/Database/MigrateStatus.php
+++ b/system/Commands/Database/MigrateStatus.php
@@ -1,40 +1,40 @@
  'Set database group'
-    );
-    
-    /**
-    * Displays a list of all migrations and whether they've been run or not.
-    */
-    public function run(array $params=[])
-    {
-        $runner = Services::migrations();
-        
-        if(! is_null(CLI::getOption('g'))){
-            $runner->setGroup(CLI::getOption('g'));
-        }
-        
-        // Get all namespaces form  PSR4 paths.
-        $config = new Autoload();
-        $namespaces = $config->psr4;
-        
-        // Loop for all $namespaces
-        foreach ($namespaces as $namespace => $path) {
-            
-            $runner->setNamespace($namespace);
-            $migrations = $runner->findMigrations();
-            $history    = $runner->getHistory();
-            
-            if (empty($migrations))
-            {
-                CLI::error("$namespace: " .lang('Migrations.migNoneFound'));
-                continue;
-            }
-            
-            ksort($migrations);
-
-            CLI::newLine(1);
-
-            CLI::write(lang('Migrations.migHistoryFor') . "$namespace: " , 'green');
-
-            CLI::newLine(1);
-
-            $max = 0;
-            foreach ($migrations as $version => $migration)
-            {
-                $file = substr($migration->name, strpos($migration->name, $version.'_'));
-                $migrations[$version]->name = $file;
-                
-                $max = max($max, strlen($file));
-            }
-            
-            CLI::write(str_pad(lang('Migrations.filename'), $max+6).lang('Migrations.migOn'),'yellow');
-            
-            
-            foreach ($migrations as $version => $migration)
-            {
-                $date = '';
-                foreach ($history as $row)
-                {
-                    if ($row['version'] != $version) continue;
-                    
-                    $date = date ("Y-m-d H:i:s", $row['time']);
-                }
-                CLI::write(str_pad($migration->name, $max+6). ($date ? $date : '---'));
-            }
-        }
-    }
-}
\ No newline at end of file
+	protected $group = 'Database';
+
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'migrate:status';
+
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Displays a list of all migrations and whether they\'ve been run or not.';
+
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'migrate:status [Options]';
+
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = [];
+
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-g'   => 'Set database group'
+			);
+
+	/**
+	 * Displays a list of all migrations and whether they've been run or not.
+	 */
+	public function run(array $params=[])
+	{
+		$runner = Services::migrations();
+
+		if(! is_null(CLI::getOption('g'))){
+			$runner->setGroup(CLI::getOption('g'));
+		}
+
+		// Get all namespaces form  PSR4 paths.
+		$config = new Autoload();
+		$namespaces = $config->psr4;
+
+		// Loop for all $namespaces
+		foreach ($namespaces as $namespace => $path) {
+
+			$runner->setNamespace($namespace);
+			$migrations = $runner->findMigrations();
+			$history    = $runner->getHistory();
+
+			if (empty($migrations))
+			{
+				CLI::error("$namespace: " .lang('Migrations.migNoneFound'));
+				continue;
+			}
+
+			ksort($migrations);
+
+			CLI::newLine(1);
+
+			CLI::write(lang('Migrations.migHistoryFor') . "$namespace: " , 'green');
+
+			CLI::newLine(1);
+
+			$max = 0;
+			foreach ($migrations as $version => $migration)
+			{
+				$file = substr($migration->name, strpos($migration->name, $version.'_'));
+				$migrations[$version]->name = $file;
+
+				$max = max($max, strlen($file));
+			}
+
+			CLI::write(str_pad(lang('Migrations.filename'), $max+6).lang('Migrations.migOn'),'yellow');
+
+
+			foreach ($migrations as $version => $migration)
+			{
+				$date = '';
+				foreach ($history as $row)
+				{
+					if ($row['version'] != $version) continue;
+
+					$date = date ("Y-m-d H:i:s", $row['time']);
+				}
+				CLI::write(str_pad($migration->name, $max+6). ($date ? $date : '---'));
+			}
+		}
+	}
+}
diff --git a/system/Commands/Database/MigrateVersion.php b/system/Commands/Database/MigrateVersion.php
index 91977b6529cf..4623ceee1bc8 100644
--- a/system/Commands/Database/MigrateVersion.php
+++ b/system/Commands/Database/MigrateVersion.php
@@ -47,85 +47,85 @@
  */
 class MigrateVersion extends BaseCommand
 {
-    protected $group = 'Database';
+	protected $group = 'Database';
 
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'migrate:version';
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'migrate:version';
 
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Migrates the database up or down to get to the specified version.';
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Migrates the database up or down to get to the specified version.';
 
-    /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'migrate:version [version_number] [Options]';
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'migrate:version [version_number] [Options]';
 
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = array(
-        'version_number' => 'The version number to migrate'
-    );
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = array(
+			'version_number' => 'The version number to migrate'
+			);
 
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array(
-        '-n'   => 'Set migration namespace',
-        '-g'   => 'Set database group'
-    );
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array(
+			'-n'   => 'Set migration namespace',
+			'-g'   => 'Set database group'
+			);
 
-    /**
-     * Migrates the database up or down to get to the specified version.
-     */
-    public function run(array $params=[])
-    {
-        $runner = Services::migrations();
+	/**
+	 * Migrates the database up or down to get to the specified version.
+	 */
+	public function run(array $params=[])
+	{
+		$runner = Services::migrations();
 
-        // Get the version number
-        $version = array_shift($params);
+		// Get the version number
+		$version = array_shift($params);
 
-        if (is_null($version))
-        {
-            $version = CLI::prompt(lang('Migrations.version'));
-        }
+		if (is_null($version))
+		{
+			$version = CLI::prompt(lang('Migrations.version'));
+		}
 
-        if (is_null($version))
-        {
-            CLI::error(lang('Migrations.invalidVersion'));
-            exit();
-        }
+		if (is_null($version))
+		{
+			CLI::error(lang('Migrations.invalidVersion'));
+			exit();
+		}
 
-        CLI::write(sprintf(lang('Migrations.migToVersionPH'), $version), 'yellow');
-        
-        $namespace = CLI::getOption('n');
-        $group =    CLI::getOption('g'); 
-        try {
-            $runner->version($version, $namespace, $group);
-            $messages = $runner->getCliMessages();
-            foreach ($messages as $message) {
-                CLI::write($message); 
-            }
-        }
-        catch (\Exception $e)
-        {
-            $this->showError($e);
-        }
+		CLI::write(sprintf(lang('Migrations.migToVersionPH'), $version), 'yellow');
 
-        CLI::write('Done');
-    }
+		$namespace = CLI::getOption('n');
+		$group =    CLI::getOption('g'); 
+		try {
+			$runner->version($version, $namespace, $group);
+			$messages = $runner->getCliMessages();
+			foreach ($messages as $message) {
+				CLI::write($message); 
+			}
+		}
+		catch (\Exception $e)
+		{
+			$this->showError($e);
+		}
+
+		CLI::write('Done');
+	}
 }
diff --git a/system/Commands/Database/Seed.php b/system/Commands/Database/Seed.php
index 228d2e19a053..f5e0ffcf2fb3 100644
--- a/system/Commands/Database/Seed.php
+++ b/system/Commands/Database/Seed.php
@@ -49,73 +49,73 @@
  */
 class Seed extends BaseCommand
 {
-    protected $group = 'Database';
+	protected $group = 'Database';
 
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'db:seed';
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'db:seed';
 
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Runs the specified seeder to populate known data into the database.';
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Runs the specified seeder to populate known data into the database.';
 
-    /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'db:seed [seeder_name]';
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'db:seed [seeder_name]';
 
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = array(
-        'seeder_name' => 'The seeder name to run'
-    );
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = array(
+			'seeder_name' => 'The seeder name to run'
+			);
 
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = [];
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = [];
 
-    /**
-     * Runs all of the migrations in reverse order, until they have
-     * all been un-applied.
-     */
-    public function run(array $params=[])
-    {
-        $seeder = new Seeder(new \Config\Database());
+	/**
+	 * Runs all of the migrations in reverse order, until they have
+	 * all been un-applied.
+	 */
+	public function run(array $params=[])
+	{
+		$seeder = new Seeder(new \Config\Database());
 
-        $seedName = array_shift($params);
+		$seedName = array_shift($params);
 
-        if (empty($seedName))
-        {
-            $seedName = CLI::prompt(lang('Migrations.migSeeder'), 'DatabaseSeeder');
-        }
+		if (empty($seedName))
+		{
+			$seedName = CLI::prompt(lang('Migrations.migSeeder'), 'DatabaseSeeder');
+		}
 
-        if (empty($seedName))
-        {
-            CLI::error(lang('Migrations.migMissingSeeder'));
-            return;
-        }
+		if (empty($seedName))
+		{
+			CLI::error(lang('Migrations.migMissingSeeder'));
+			return;
+		}
 
-        try
-        {
-            $seeder->call($seedName);
-        }
-        catch (\Exception $e)
-        {
-            $this->showError($e);
-        }
-    }
+		try
+		{
+			$seeder->call($seedName);
+		}
+		catch (\Exception $e)
+		{
+			$this->showError($e);
+		}
+	}
 }
diff --git a/system/Commands/Help.php b/system/Commands/Help.php
index 05f425be2934..2a2f21710109 100644
--- a/system/Commands/Help.php
+++ b/system/Commands/Help.php
@@ -49,63 +49,63 @@
  */
 class Help extends BaseCommand
 {
-    protected $group = 'CodeIgniter';
+	protected $group = 'CodeIgniter';
 
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'help';
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'help';
 
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Displays basic usage information.';
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Displays basic usage information.';
 
-     /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'help command_name';
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'help command_name';
 
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = array(
-        'command_name' => 'The command name [default: "help"]'
-    );
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = array(
+			'command_name' => 'The command name [default: "help"]'
+			);
 
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array();
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array();
 
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * Displays the help for the ci.php cli script itself.
-     *
-     * @param array $params
-     */
-    public function run(array $params)
-    {
-        $command = array_shift($params);
-        if(is_null($command)){
-            $command = 'help';
-        }
+	/**
+	 * Displays the help for the ci.php cli script itself.
+	 *
+	 * @param array $params
+	 */
+	public function run(array $params)
+	{
+		$command = array_shift($params);
+		if(is_null($command)){
+			$command = 'help';
+		}
 
-        $commands = $this->commands->getCommands();
-        $class=new $commands[$command]['class']($this->logger, $this->commands);
+		$commands = $this->commands->getCommands();
+		$class=new $commands[$command]['class']($this->logger, $this->commands);
 
-        $class->showHelp();
-    }
+		$class->showHelp();
+	}
 }
diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php
index 519d42c88b2d..bc0e3a09bad9 100644
--- a/system/Commands/ListCommands.php
+++ b/system/Commands/ListCommands.php
@@ -49,148 +49,148 @@
  */
 class ListCommands extends BaseCommand
 {
-    protected $group = 'CodeIgniter';
-
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'list';
-
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Lists the available commands.';
-
-    /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'list';
-
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = array();
-
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array();
-
-    /**
-     * The length of the longest command name.
-     * Used during display in columns.
-     *
-     * @var int
-     */
-    protected $maxFirstLength = 0;
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Displays the help for the ci.php cli script itself.
-     *
-     * @param array $params
-     */
-    public function run(array $params)
-    {
-        $commands = $this->commands->getCommands();
-
-        $this->describeCommands($commands);
-
-        CLI::newLine();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Displays the commands on the CLI.
-     *
-     * @param array $commands
-     */
-    protected function describeCommands(array $commands = [])
-    {
-        arsort($commands);
-
-        $names     = array_keys($commands);
-        $descs     = array_column($commands, 'description');
-        $groups    = array_column($commands, 'group');
-        $lastGroup = '';
-
-        // Pad each item to the same length
-        $names = $this->padArray($names, 2, 2);
-
-        for ($i = 0; $i < count($names); $i++)
-        {
-            $lastGroup = $this->describeGroup($groups[$i], $lastGroup);
-
-            $out = CLI::color($names[$i], 'yellow');
-
-            if (isset($descs[$i]))
-            {
-                $out .= CLI::wrap($descs[$i], 125, strlen($names[$i]));
-            }
-
-            CLI::write($out);
-        }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Outputs the description, if necessary.
-     *
-     * @param string $new
-     * @param string $old
-     *
-     * @return string
-     */
-    protected function describeGroup(string $new, string $old)
-    {
-        if ($new == $old)
-        {
-            return $old;
-        }
-
-        CLI::newLine();
-        CLI::write($new);
-
-        return $new;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Returns a new array where all of the string elements have
-     * been padding with trailing spaces to be the same length.
-     *
-     * @param array $array
-     * @param int   $extra // How many extra spaces to add at the end
-     *
-     * @return array
-     */
-    protected function padArray($array, $extra = 2, $indent=0)
-    {
-        $max = max(array_map('strlen', $array))+$extra+$indent;
-
-        foreach ($array as &$item)
-        {
-            $item = str_repeat(' ', $indent).$item;
-            $item = str_pad($item, $max);
-        }
-
-        return $array;
-    }
-
-    //--------------------------------------------------------------------
+	protected $group = 'CodeIgniter';
+
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'list';
+
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Lists the available commands.';
+
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'list';
+
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = array();
+
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array();
+
+	/**
+	 * The length of the longest command name.
+	 * Used during display in columns.
+	 *
+	 * @var int
+	 */
+	protected $maxFirstLength = 0;
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Displays the help for the ci.php cli script itself.
+	 *
+	 * @param array $params
+	 */
+	public function run(array $params)
+	{
+		$commands = $this->commands->getCommands();
+
+		$this->describeCommands($commands);
+
+		CLI::newLine();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Displays the commands on the CLI.
+	 *
+	 * @param array $commands
+	 */
+	protected function describeCommands(array $commands = [])
+	{
+		arsort($commands);
+
+		$names     = array_keys($commands);
+		$descs     = array_column($commands, 'description');
+		$groups    = array_column($commands, 'group');
+		$lastGroup = '';
+
+		// Pad each item to the same length
+		$names = $this->padArray($names, 2, 2);
+
+		for ($i = 0; $i < count($names); $i++)
+		{
+			$lastGroup = $this->describeGroup($groups[$i], $lastGroup);
+
+			$out = CLI::color($names[$i], 'yellow');
+
+			if (isset($descs[$i]))
+			{
+				$out .= CLI::wrap($descs[$i], 125, strlen($names[$i]));
+			}
+
+			CLI::write($out);
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Outputs the description, if necessary.
+	 *
+	 * @param string $new
+	 * @param string $old
+	 *
+	 * @return string
+	 */
+	protected function describeGroup(string $new, string $old)
+	{
+		if ($new == $old)
+		{
+			return $old;
+		}
+
+		CLI::newLine();
+		CLI::write($new);
+
+		return $new;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Returns a new array where all of the string elements have
+	 * been padding with trailing spaces to be the same length.
+	 *
+	 * @param array $array
+	 * @param int   $extra // How many extra spaces to add at the end
+	 *
+	 * @return array
+	 */
+	protected function padArray($array, $extra = 2, $indent=0)
+	{
+		$max = max(array_map('strlen', $array))+$extra+$indent;
+
+		foreach ($array as &$item)
+		{
+			$item = str_repeat(' ', $indent).$item;
+			$item = str_pad($item, $max);
+		}
+
+		return $array;
+	}
+
+	//--------------------------------------------------------------------
 
 }
diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php
index 55310a0a5104..e129db7ad71f 100644
--- a/system/Commands/Sessions/CreateMigration.php
+++ b/system/Commands/Sessions/CreateMigration.php
@@ -42,70 +42,70 @@
 
 class CreateMigration extends BaseCommand
 {
-    protected $group = 'CodeIgniter';
+	protected $group = 'CodeIgniter';
 
-    /**
-     * The Command's name
-     *
-     * @var string
-     */
-    protected $name = 'session:migration';
+	/**
+	 * The Command's name
+	 *
+	 * @var string
+	 */
+	protected $name = 'session:migration';
 
-    /**
-     * the Command's short description
-     *
-     * @var string
-     */
-    protected $description = 'Generates the migration file for database sessions.';
+	/**
+	 * the Command's short description
+	 *
+	 * @var string
+	 */
+	protected $description = 'Generates the migration file for database sessions.';
 
-    /**
-     * the Command's usage
-     *
-     * @var string
-     */
-    protected $usage = 'session:migration';
+	/**
+	 * the Command's usage
+	 *
+	 * @var string
+	 */
+	protected $usage = 'session:migration';
 
-    /**
-     * the Command's Arguments
-     *
-     * @var array
-     */
-    protected $arguments = array();
+	/**
+	 * the Command's Arguments
+	 *
+	 * @var array
+	 */
+	protected $arguments = array();
 
-    /**
-     * the Command's Options
-     *
-     * @var array
-     */
-    protected $options = array();
+	/**
+	 * the Command's Options
+	 *
+	 * @var array
+	 */
+	protected $options = array();
 
-    /**
-     * Creates a new migration file with the current timestamp.
-     */
-    public function run(array $params=[])
-    {
-        $name = 'create_sessions_table';
+	/**
+	 * Creates a new migration file with the current timestamp.
+	 */
+	public function run(array $params=[])
+	{
+		$name = 'create_sessions_table';
 
-        $path = APPPATH.'Database/Migrations/'.date('YmdHis_').$name.'.php';
+		$path = APPPATH.'Database/Migrations/'.date('YmdHis_').$name.'.php';
 
-        $config = new App();
+		$config = new App();
 
-        $data = [
-            'matchIP'   => $config->sessionMatchIP  ?? false,
-            'tableName' => $config->sessionSavePath ?? 'ci_sessions',
-        ];
+		$data = [
+			'matchIP'   => $config->sessionMatchIP  ?? false,
+			'tableName' => $config->sessionSavePath ?? 'ci_sessions',
+		];
 
-        $template = view('\CodeIgniter\Commands\Sessions\Views\migration.tpl', $data);
-        $template = str_replace('@php', 'forge->addField([
-            'id'         => ['type' => 'INT', 'constraint' => 9, 'unsigned' => true, 'auto_increment' => true],
-            'ip_address' => ['type' => 'VARCHAR', 'constraint' => 45, 'null' => false],
-            'timestamp'  => ['type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'null' => false, 'default' => 0],
-            'data'       => ['type' => 'text', 'null' => false, 'default' => ''],
-        ]);
+	public function up()
+	{
+		$this->forge->addField([
+				'id'         => ['type' => 'INT', 'constraint' => 9, 'unsigned' => true, 'auto_increment' => true],
+				'ip_address' => ['type' => 'VARCHAR', 'constraint' => 45, 'null' => false],
+				'timestamp'  => ['type' => 'INT', 'constraint' => 10, 'unsigned' => true, 'null' => false, 'default' => 0],
+				'data'       => ['type' => 'text', 'null' => false, 'default' => ''],
+		]);
 
-    
-        $this->forge->addKey(['id', 'ip_address'], true);
-    
-        $this->forge->addKey('id', true);
-    
-        $this->forge->addKey('timestamp');
-        $this->forge->createTable('', true);
-    }
+		
+			$this->forge->addKey(['id', 'ip_address'], true);
+		
+			$this->forge->addKey('id', true);
+		
+			$this->forge->addKey('timestamp');
+		$this->forge->createTable('', true);
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    public function down()
-    {
-        $this->forge->dropTable('', true);
-    }
+	public function down()
+	{
+		$this->forge->dropTable('', true);
+	}
 }
diff --git a/system/Common.php b/system/Common.php
index c6e7f8dbe911..c97c96cb23fe 100644
--- a/system/Common.php
+++ b/system/Common.php
@@ -55,329 +55,329 @@
 
 if (! function_exists('cache'))
 {
-    /**
-     * A convenience method that provides access to the Cache
-     * object. If no parameter is provided, will return the object,
-     * otherwise, will attempt to return the cached value.
-     *
-     * Examples:
-     *    cache()->save('foo', 'bar');
-     *    $foo = cache('bar');
-     *
-     * @param string|null $key
-     *
-     * @return mixed
-     */
-    function cache(string $key = null)
-    {
-        $cache = \Config\Services::cache();
-
-        // No params - return cache object
-        if (is_null($key))
-        {
-            return $cache;
-        }
-
-        // Still here? Retrieve the value.
-        return $cache->get($key);
-    }
+	/**
+	 * A convenience method that provides access to the Cache
+	 * object. If no parameter is provided, will return the object,
+	 * otherwise, will attempt to return the cached value.
+	 *
+	 * Examples:
+	 *    cache()->save('foo', 'bar');
+	 *    $foo = cache('bar');
+	 *
+	 * @param string|null $key
+	 *
+	 * @return mixed
+	 */
+	function cache(string $key = null)
+	{
+		$cache = \Config\Services::cache();
+
+		// No params - return cache object
+		if (is_null($key))
+		{
+			return $cache;
+		}
+
+		// Still here? Retrieve the value.
+		return $cache->get($key);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('view'))
 {
-    /**
-     * Grabs the current RendererInterface-compatible class
-     * and tells it to render the specified view. Simply provides
-     * a convenience method that can be used in Controllers,
-     * libraries, and routed closures.
-     *
-     * NOTE: Does not provide any escaping of the data, so that must
-     * all be handled manually by the developer.
-     *
-     * @param string $name
-     * @param array  $data
-     * @param array  $options Unused - reserved for third-party extensions.
-     *
-     * @return string
-     */
-    function view(string $name, array $data = [], array $options = [])
-    {
-        /**
-         * @var CodeIgniter\View\View $renderer
-         */
-        $renderer = Services::renderer();
-
-        $saveData = null;
-        if (array_key_exists('saveData', $options) && $options['saveData'] === true)
-        {
-            $saveData = (bool)$options['saveData'];
-            unset($options['saveData']);
-        }
-
-        return $renderer->setData($data, 'raw')
-                        ->render($name, $options, $saveData);
-    }
+	/**
+	 * Grabs the current RendererInterface-compatible class
+	 * and tells it to render the specified view. Simply provides
+	 * a convenience method that can be used in Controllers,
+	 * libraries, and routed closures.
+	 *
+	 * NOTE: Does not provide any escaping of the data, so that must
+	 * all be handled manually by the developer.
+	 *
+	 * @param string $name
+	 * @param array  $data
+	 * @param array  $options Unused - reserved for third-party extensions.
+	 *
+	 * @return string
+	 */
+	function view(string $name, array $data = [], array $options = [])
+	{
+		/**
+		 * @var CodeIgniter\View\View $renderer
+		 */
+		$renderer = Services::renderer();
+
+		$saveData = null;
+		if (array_key_exists('saveData', $options) && $options['saveData'] === true)
+		{
+			$saveData = (bool)$options['saveData'];
+			unset($options['saveData']);
+		}
+
+		return $renderer->setData($data, 'raw')
+			->render($name, $options, $saveData);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('view_cell'))
 {
-    /**
-     * View cells are used within views to insert HTML chunks that are managed
-     * by other classes.
-     *
-     * @param string      $library
-     * @param null        $params
-     * @param int         $ttl
-     * @param string|null $cacheName
-     *
-     * @return string
-     */
-    function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null)
-    {
-        return Services::viewcell()->render($library, $params, $ttl, $cacheName);
-    }
+	/**
+	 * View cells are used within views to insert HTML chunks that are managed
+	 * by other classes.
+	 *
+	 * @param string      $library
+	 * @param null        $params
+	 * @param int         $ttl
+	 * @param string|null $cacheName
+	 *
+	 * @return string
+	 */
+	function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null)
+	{
+		return Services::viewcell()->render($library, $params, $ttl, $cacheName);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('env'))
 {
-    /**
-     * Allows user to retrieve values from the environment
-     * variables that have been set. Especially useful for
-     * retrieving values set from the .env file for
-     * use in config files.
-     *
-     * @param string $key
-     * @param null   $default
-     *
-     * @return array|bool|false|null|string|void
-     */
-    function env(string $key, $default = null)
-    {
-        $value = getenv($key);
-        if ($value === false)
-        {
-            $value = $_ENV[$key] ?? $_SERVER[$key] ?? false;
-        }
-
-        // Not found? Return the default value
-        if ($value === false)
-        {
-            return $default;
-        }
-
-        // Handle any boolean values
-        switch (strtolower($value))
-        {
-            case 'true':
-                return true;
-            case 'false':
-                return false;
-            case 'empty':
-                return '';
-            case 'null':
-                return;
-        }
-
-        return $value;
-    }
+	/**
+	 * Allows user to retrieve values from the environment
+	 * variables that have been set. Especially useful for
+	 * retrieving values set from the .env file for
+	 * use in config files.
+	 *
+	 * @param string $key
+	 * @param null   $default
+	 *
+	 * @return array|bool|false|null|string|void
+	 */
+	function env(string $key, $default = null)
+	{
+		$value = getenv($key);
+		if ($value === false)
+		{
+			$value = $_ENV[$key] ?? $_SERVER[$key] ?? false;
+		}
+
+		// Not found? Return the default value
+		if ($value === false)
+		{
+			return $default;
+		}
+
+		// Handle any boolean values
+		switch (strtolower($value))
+		{
+			case 'true':
+				return true;
+			case 'false':
+				return false;
+			case 'empty':
+				return '';
+			case 'null':
+				return;
+		}
+
+		return $value;
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('esc'))
 {
-    /**
-     * Performs simple auto-escaping of data for security reasons.
-     * Might consider making this more complex at a later date.
-     *
-     * If $data is a string, then it simply escapes and returns it.
-     * If $data is an array, then it loops over it, escaping each
-     * 'value' of the key/value pairs.
-     *
-     * Valid context values: html, js, css, url, attr, raw, null
-     *
-     * @param string|array $data
-     * @param string       $context
-     * @param string       $encoding
-     *
-     * @return $data
-     */
-    function esc($data, $context = 'html', $encoding=null)
-    {
-        if (is_array($data))
-        {
-            foreach ($data as $key => &$value)
-            {
-                $value = esc($value, $context);
-            }
-        }
-
-        if (is_string($data))
-        {
-            $context = strtolower($context);
-
-            // Provide a way to NOT escape data since
-            // this could be called automatically by
-            // the View library.
-            if (empty($context) || $context == 'raw')
-            {
-                return $data;
-            }
-
-            if ( ! in_array($context, ['html', 'js', 'css', 'url', 'attr']))
-            {
-                throw new \InvalidArgumentException('Invalid escape context provided.');
-            }
-
-            if ($context == 'attr')
-            {
-                $method = 'escapeHtmlAttr';
-            }
-            else
-            {
-                $method = 'escape'.ucfirst($context);
-            }
-
-            // @todo Optimize this to only load a single instance during page request.
-            $escaper = new \Zend\Escaper\Escaper($encoding);
-
-            $data   = $escaper->$method($data);
-        }
-
-        return $data;
-    }
+	/**
+	 * Performs simple auto-escaping of data for security reasons.
+	 * Might consider making this more complex at a later date.
+	 *
+	 * If $data is a string, then it simply escapes and returns it.
+	 * If $data is an array, then it loops over it, escaping each
+	 * 'value' of the key/value pairs.
+	 *
+	 * Valid context values: html, js, css, url, attr, raw, null
+	 *
+	 * @param string|array $data
+	 * @param string       $context
+	 * @param string       $encoding
+	 *
+	 * @return $data
+	 */
+	function esc($data, $context = 'html', $encoding=null)
+	{
+		if (is_array($data))
+		{
+			foreach ($data as $key => &$value)
+			{
+				$value = esc($value, $context);
+			}
+		}
+
+		if (is_string($data))
+		{
+			$context = strtolower($context);
+
+			// Provide a way to NOT escape data since
+			// this could be called automatically by
+			// the View library.
+			if (empty($context) || $context == 'raw')
+			{
+				return $data;
+			}
+
+			if ( ! in_array($context, ['html', 'js', 'css', 'url', 'attr']))
+			{
+				throw new \InvalidArgumentException('Invalid escape context provided.');
+			}
+
+			if ($context == 'attr')
+			{
+				$method = 'escapeHtmlAttr';
+			}
+			else
+			{
+				$method = 'escape'.ucfirst($context);
+			}
+
+			// @todo Optimize this to only load a single instance during page request.
+			$escaper = new \Zend\Escaper\Escaper($encoding);
+
+			$data   = $escaper->$method($data);
+		}
+
+		return $data;
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('session'))
 {
-    /**
-     * A convenience method for accessing the session instance,
-     * or an item that has been set in the session.
-     *
-     * Examples:
-     *    session()->set('foo', 'bar');
-     *    $foo = session('bar');
-     *
-     * @param null $val
-     *
-     * @return \CodeIgniter\Session\Session|null|void
-     */
-    function session($val = null)
-    {
-        // Returning a single item?
-        if (is_string($val))
-        {
-            return $_SESSION[$val] ?? null;
-        }
-
-        return \Config\Services::session();
-    }
+	/**
+	 * A convenience method for accessing the session instance,
+	 * or an item that has been set in the session.
+	 *
+	 * Examples:
+	 *    session()->set('foo', 'bar');
+	 *    $foo = session('bar');
+	 *
+	 * @param null $val
+	 *
+	 * @return \CodeIgniter\Session\Session|null|void
+	 */
+	function session($val = null)
+	{
+		// Returning a single item?
+		if (is_string($val))
+		{
+			return $_SESSION[$val] ?? null;
+		}
+
+		return \Config\Services::session();
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('timer'))
 {
-    /**
-     * A convenience method for working with the timer.
-     * If no parameter is passed, it will return the timer instance,
-     * otherwise will start or stop the timer intelligently.
-     *
-     * @param string|null $name
-     *
-     * @return $this|\CodeIgniter\Debug\Timer|mixed
-     */
-    function timer(string $name = null)
-    {
-        $timer = \Config\Services::timer();
-
-        if (empty($name))
-        {
-            return $timer;
-        }
-
-        if ($timer->has($name))
-        {
-            return $timer->stop($name);
-        }
-
-        return $timer->start($name);
-    }
+	/**
+	 * A convenience method for working with the timer.
+	 * If no parameter is passed, it will return the timer instance,
+	 * otherwise will start or stop the timer intelligently.
+	 *
+	 * @param string|null $name
+	 *
+	 * @return $this|\CodeIgniter\Debug\Timer|mixed
+	 */
+	function timer(string $name = null)
+	{
+		$timer = \Config\Services::timer();
+
+		if (empty($name))
+		{
+			return $timer;
+		}
+
+		if ($timer->has($name))
+		{
+			return $timer->stop($name);
+		}
+
+		return $timer->start($name);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('service'))
 {
-    /**
-     * Allows cleaner access to the Services Config file.
-     * Always returns a SHARED instance of the class, so
-     * calling the function multiple times should always
-     * return the same instance.
-     *
-     * These are equal:
-     *  - $timer = service('timer')
-     *  - $timer = \CodeIgniter\Services::timer();
-     *
-     * @param string $name
-     * @param array  ...$params
-     *
-     * @return mixed
-     */
-    function service(string $name, ...$params)
-    {
-        // Ensure it IS a shared instance
-        array_push($params, true);
-
-        return Services::$name(...$params);
-    }
+	/**
+	 * Allows cleaner access to the Services Config file.
+	 * Always returns a SHARED instance of the class, so
+	 * calling the function multiple times should always
+	 * return the same instance.
+	 *
+	 * These are equal:
+	 *  - $timer = service('timer')
+	 *  - $timer = \CodeIgniter\Services::timer();
+	 *
+	 * @param string $name
+	 * @param array  ...$params
+	 *
+	 * @return mixed
+	 */
+	function service(string $name, ...$params)
+	{
+		// Ensure it IS a shared instance
+		array_push($params, true);
+
+		return Services::$name(...$params);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('single_service'))
 {
-    /**
-     * Allow cleaner access to a Service.
-     * Always returns a new instance of the class.
-     *
-     * @param string $name
-     * @param array|null $params
-     */
-    function single_service(string $name, ...$params)
-    {
-        // Ensure it's NOT a shared instance
-        array_push($params, false);
-
-        return Services::$name(...$params);
-    }
+	/**
+	 * Allow cleaner access to a Service.
+	 * Always returns a new instance of the class.
+	 *
+	 * @param string $name
+	 * @param array|null $params
+	 */
+	function single_service(string $name, ...$params)
+	{
+		// Ensure it's NOT a shared instance
+		array_push($params, false);
+
+		return Services::$name(...$params);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('lang'))
 {
-    /**
-     * A convenience method to translate a string and format it
-     * with the intl extension's MessageFormatter object.
-     *
-     * @param string $line
-     * @param array  $args
-     *
-     * @return string
-     */
-    function lang(string $line, array $args=[])
-    {
-        return Services::language()->getLine($line, $args);
-    }
+	/**
+	 * A convenience method to translate a string and format it
+	 * with the intl extension's MessageFormatter object.
+	 *
+	 * @param string $line
+	 * @param array  $args
+	 *
+	 * @return string
+	 */
+	function lang(string $line, array $args=[])
+	{
+		return Services::language()->getLine($line, $args);
+	}
 }
 
 //--------------------------------------------------------------------
@@ -386,40 +386,40 @@ function lang(string $line, array $args=[])
 
 if ( ! function_exists('log_message'))
 {
-    /**
-     * A convenience/compatibility method for logging events through
-     * the Log system.
-     *
-     * Allowed log levels are:
-     *  - emergency
-     *  - alert
-     *  - critical
-     *  - error
-     *  - warning
-     *  - notice
-     *  - info
-     *  - debug
-     *
-     * @param string $level
-     * @param string $message
-     * @param array|null  $context
-     *
-     * @return mixed
-     */
-    function log_message(string $level, string $message, array $context = [])
-    {
-        // When running tests, we want to always ensure that the
-        // TestLogger is running, which provides utilities for
-        // for asserting that logs were called in the test code.
-        if (ENVIRONMENT == 'testing')
-        {
-            $logger = new \CodeIgniter\Log\TestLogger(new \Config\Logger());
-            return $logger->log($level, $message, $context);
-        }
-
-        return Services::logger(true)
-                       ->log($level, $message, $context);
-    }
+	/**
+	 * A convenience/compatibility method for logging events through
+	 * the Log system.
+	 *
+	 * Allowed log levels are:
+	 *  - emergency
+	 *  - alert
+	 *  - critical
+	 *  - error
+	 *  - warning
+	 *  - notice
+	 *  - info
+	 *  - debug
+	 *
+	 * @param string $level
+	 * @param string $message
+	 * @param array|null  $context
+	 *
+	 * @return mixed
+	 */
+	function log_message(string $level, string $message, array $context = [])
+	{
+		// When running tests, we want to always ensure that the
+		// TestLogger is running, which provides utilities for
+		// for asserting that logs were called in the test code.
+		if (ENVIRONMENT == 'testing')
+		{
+			$logger = new \CodeIgniter\Log\TestLogger(new \Config\Logger());
+			return $logger->log($level, $message, $context);
+		}
+
+		return Services::logger(true)
+			->log($level, $message, $context);
+	}
 }
 
 //--------------------------------------------------------------------
@@ -427,421 +427,421 @@ function log_message(string $level, string $message, array $context = [])
 if ( ! function_exists('is_cli'))
 {
 
-    /**
-     * Is CLI?
-     *
-     * Test to see if a request was made from the command line.
-     *
-     * @return    bool
-     */
-    function is_cli()
-    {
-        return (PHP_SAPI === 'cli' || defined('STDIN'));
-    }
+	/**
+	 * Is CLI?
+	 *
+	 * Test to see if a request was made from the command line.
+	 *
+	 * @return    bool
+	 */
+	function is_cli()
+	{
+		return (PHP_SAPI === 'cli' || defined('STDIN'));
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('route_to'))
 {
-    /**
-     * Given a controller/method string and any params,
-     * will attempt to build the relative URL to the
-     * matching route.
-     *
-     * NOTE: This requires the controller/method to
-     * have a route defined in the routes Config file.
-     *
-     * @param string $method
-     * @param        ...$params
-     *
-     * @return \CodeIgniter\Router\string
-     */
-    function route_to(string $method, ...$params): string
-    {
-        $routes = Services::routes();
-
-        return $routes->reverseRoute($method, ...$params);
-    }
+	/**
+	 * Given a controller/method string and any params,
+	 * will attempt to build the relative URL to the
+	 * matching route.
+	 *
+	 * NOTE: This requires the controller/method to
+	 * have a route defined in the routes Config file.
+	 *
+	 * @param string $method
+	 * @param        ...$params
+	 *
+	 * @return \CodeIgniter\Router\string
+	 */
+	function route_to(string $method, ...$params): string
+	{
+		$routes = Services::routes();
+
+		return $routes->reverseRoute($method, ...$params);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('remove_invisible_characters'))
 {
-    /**
-     * Remove Invisible Characters
-     *
-     * This prevents sandwiching null characters
-     * between ascii characters, like Java\0script.
-     *
-     * @param   string
-     * @param   bool
-     * @return  string
-     */
-    function remove_invisible_characters($str, $url_encoded = TRUE)
-    {
-        $non_displayables = array();
-
-        // every control character except newline (dec 10),
-        // carriage return (dec 13) and horizontal tab (dec 09)
-        if ($url_encoded)
-        {
-            $non_displayables[] = '/%0[0-8bcef]/';  // url encoded 00-08, 11, 12, 14, 15
-            $non_displayables[] = '/%1[0-9a-f]/';   // url encoded 16-31
-        }
-
-        $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';   // 00-08, 11, 12, 14-31, 127
-
-        do
-        {
-            $str = preg_replace($non_displayables, '', $str, -1, $count);
-        }
-        while ($count);
-
-        return $str;
-    }
+	/**
+	 * Remove Invisible Characters
+	 *
+	 * This prevents sandwiching null characters
+	 * between ascii characters, like Java\0script.
+	 *
+	 * @param   string
+	 * @param   bool
+	 * @return  string
+	 */
+	function remove_invisible_characters($str, $url_encoded = TRUE)
+	{
+		$non_displayables = array();
+
+		// every control character except newline (dec 10),
+		// carriage return (dec 13) and horizontal tab (dec 09)
+		if ($url_encoded)
+		{
+			$non_displayables[] = '/%0[0-8bcef]/';  // url encoded 00-08, 11, 12, 14, 15
+			$non_displayables[] = '/%1[0-9a-f]/';   // url encoded 16-31
+		}
+
+		$non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';   // 00-08, 11, 12, 14-31, 127
+
+		do
+		{
+			$str = preg_replace($non_displayables, '', $str, -1, $count);
+		}
+		while ($count);
+
+		return $str;
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('helper'))
 {
-    /**
-     * Loads a helper file into memory. Supports namespaced helpers,
-     * both in and out of the 'helpers' directory of a namespaced directory.
-     *
-     * @param string|array $filenames
-     *
-     * @return string
-     */
-    function helper($filenames)//: string
-    {
-        $loader = Services::locator(true);
-
-        if (! is_array($filenames))
-        {
-            $filenames = [$filenames];
-        }
-
-        foreach ($filenames as $filename)
-        {
-            if (strpos($filename, '_helper') === false)
-            {
-                $filename .= '_helper';
-            }
-
-            $path = $loader->locateFile($filename, 'Helpers');
-
-            if (! empty($path))
-            {
-                include $path;
-            }
-        }
-    }
+	/**
+	 * Loads a helper file into memory. Supports namespaced helpers,
+	 * both in and out of the 'helpers' directory of a namespaced directory.
+	 *
+	 * @param string|array $filenames
+	 *
+	 * @return string
+	 */
+	function helper($filenames)//: string
+	{
+		$loader = Services::locator(true);
+
+		if (! is_array($filenames))
+		{
+			$filenames = [$filenames];
+		}
+
+		foreach ($filenames as $filename)
+		{
+			if (strpos($filename, '_helper') === false)
+			{
+				$filename .= '_helper';
+			}
+
+			$path = $loader->locateFile($filename, 'Helpers');
+
+			if (! empty($path))
+			{
+				include $path;
+			}
+		}
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('app_timezone'))
 {
-    /**
-     * Returns the timezone the application has been set to display
-     * dates in. This might be different than the timezone set
-     * at the server level, as you often want to stores dates in UTC
-     * and convert them on the fly for the user.
-     */
-    function app_timezone()
-    {
-        $config = new \Config\App();
-
-        return $config->appTimezone;
-    }
+	/**
+	 * Returns the timezone the application has been set to display
+	 * dates in. This might be different than the timezone set
+	 * at the server level, as you often want to stores dates in UTC
+	 * and convert them on the fly for the user.
+	 */
+	function app_timezone()
+	{
+		$config = new \Config\App();
+
+		return $config->appTimezone;
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('csrf_token'))
 {
-    /**
-     * Returns the CSRF token name.
-     * Can be used in Views when building hidden inputs manually,
-     * or used in javascript vars when using APIs.
-     *
-     * @return string
-     */
-    function csrf_token()
-    {
-        $config = new \Config\App();
-
-        return $config->CSRFTokenName;
-    }
+	/**
+	 * Returns the CSRF token name.
+	 * Can be used in Views when building hidden inputs manually,
+	 * or used in javascript vars when using APIs.
+	 *
+	 * @return string
+	 */
+	function csrf_token()
+	{
+		$config = new \Config\App();
+
+		return $config->CSRFTokenName;
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('csrf_hash'))
 {
-    /**
-     * Returns the current hash value for the CSRF protection.
-     * Can be used in Views when building hidden inputs manually,
-     * or used in javascript vars for API usage.
-     *
-     * @return string
-     */
-    function csrf_hash()
-    {
-        $security = Services::security(null, true);
-
-        return $security->getCSRFHash();
-    }
+	/**
+	 * Returns the current hash value for the CSRF protection.
+	 * Can be used in Views when building hidden inputs manually,
+	 * or used in javascript vars for API usage.
+	 *
+	 * @return string
+	 */
+	function csrf_hash()
+	{
+		$security = Services::security(null, true);
+
+		return $security->getCSRFHash();
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('csrf_field'))
 {
-    /**
-     * Generates a hidden input field for use within manually generated forms.
-     *
-     * @return string
-     */
-    function csrf_field()
-    {
-        return '';
-    }
+	/**
+	 * Generates a hidden input field for use within manually generated forms.
+	 *
+	 * @return string
+	 */
+	function csrf_field()
+	{
+		return '';
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('force_https'))
 {
-    /**
-     * Used to force a page to be accessed in via HTTPS.
-     * Uses a standard redirect, plus will set the HSTS header
-     * for modern browsers that support, which gives best
-     * protection against man-in-the-middle attacks.
-     *
-     * @see https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
-     *
-     * @param int $duration How long should the SSL header be set for? (in seconds)
-     *                      Defaults to 1 year.
-     * @param RequestInterface $request
-     * @param ResponseInterface $response
-     */
-    function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null)
-    {
-        if (is_null($request)) $request = Services::request(null, true);
-        if (is_null($response)) $response = Services::response(null, true);
-
-        if ($request->isSecure())
-        {
-            return;
-        }
-
-        // If the session library is loaded, we should regenerate
-        // the session ID for safety sake.
-        if (class_exists('Session', false))
-        {
-            Services::session(null, true)->regenerate();
-        }
-
-        $uri = $request->uri;
-        $uri->setScheme('https');
-
-        $uri = \CodeIgniter\HTTP\URI::createURIString(
-            $uri->getScheme(),
-            $uri->getAuthority(true),
-            $uri->getPath(), // Absolute URIs should use a "/" for an empty path
-            $uri->getQuery(),
-            $uri->getFragment()
-        );
-
-        // Set an HSTS header
-        $response->setHeader('Strict-Transport-Security', 'max-age='.$duration);
-        $response->redirect($uri);
-        exit();
-    }
+	/**
+	 * Used to force a page to be accessed in via HTTPS.
+	 * Uses a standard redirect, plus will set the HSTS header
+	 * for modern browsers that support, which gives best
+	 * protection against man-in-the-middle attacks.
+	 *
+	 * @see https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
+	 *
+	 * @param int $duration How long should the SSL header be set for? (in seconds)
+	 *                      Defaults to 1 year.
+	 * @param RequestInterface $request
+	 * @param ResponseInterface $response
+	 */
+	function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null)
+	{
+		if (is_null($request)) $request = Services::request(null, true);
+		if (is_null($response)) $response = Services::response(null, true);
+
+		if ($request->isSecure())
+		{
+			return;
+		}
+
+		// If the session library is loaded, we should regenerate
+		// the session ID for safety sake.
+		if (class_exists('Session', false))
+		{
+			Services::session(null, true)->regenerate();
+		}
+
+		$uri = $request->uri;
+		$uri->setScheme('https');
+
+		$uri = \CodeIgniter\HTTP\URI::createURIString(
+				$uri->getScheme(),
+				$uri->getAuthority(true),
+				$uri->getPath(), // Absolute URIs should use a "/" for an empty path
+				$uri->getQuery(),
+				$uri->getFragment()
+				);
+
+		// Set an HSTS header
+		$response->setHeader('Strict-Transport-Security', 'max-age='.$duration);
+		$response->redirect($uri);
+		exit();
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('redirect'))
 {
-    /**
-     * Convenience method that works with the current global $request and
-     * $router instances to redirect using named/reverse-routed routes
-     * to determine the URL to go to. If nothing is found, will treat
-     * as a traditional redirect and pass the string in, letting
-     * $response->redirect() determine the correct method and code.
-     *
-     * If more control is needed, you must use $response->redirect explicitly.
-     *
-     * @param string   $uri
-     * @param $params
-     */
-    function redirect(string $uri, ...$params)
-    {
-        $response = Services::response(null, true);
-        $routes   = Services::routes(true);
-
-        if ($route = $routes->reverseRoute($uri, ...$params))
-        {
-            $uri = $route;
-        }
-
-        $response->redirect($uri);
-    }
+	/**
+	 * Convenience method that works with the current global $request and
+	 * $router instances to redirect using named/reverse-routed routes
+	 * to determine the URL to go to. If nothing is found, will treat
+	 * as a traditional redirect and pass the string in, letting
+	 * $response->redirect() determine the correct method and code.
+	 *
+	 * If more control is needed, you must use $response->redirect explicitly.
+	 *
+	 * @param string   $uri
+	 * @param $params
+	 */
+	function redirect(string $uri, ...$params)
+	{
+		$response = Services::response(null, true);
+		$routes   = Services::routes(true);
+
+		if ($route = $routes->reverseRoute($uri, ...$params))
+		{
+			$uri = $route;
+		}
+
+		$response->redirect($uri);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if (! function_exists('redirect_with_input'))
 {
-    /**
-     * Identical to the redirect() method, except that this will
-     * send the current $_GET and $_POST contents in a _ci_old_input
-     * variable flashed to the session, which can then be retrieved
-     * via the old() method.
-     *
-     * @param string $uri
-     * @param array  ...$params
-     */
-    function redirect_with_input(string $uri, ...$params)
-    {
-        $session = Services::session();
-
-        // Ensure we have the session started up.
-        if (! isset($_SESSION))
-        {
-            $session->start();
-        }
-
-        $input = [
-            'get' => $_GET ?? [],
-            'post' => $_POST ?? []
-        ];
-
-        $session->setFlashdata('_ci_old_input', $input);
-
-        redirect($uri, ...$params);
-    }
+	/**
+	 * Identical to the redirect() method, except that this will
+	 * send the current $_GET and $_POST contents in a _ci_old_input
+	 * variable flashed to the session, which can then be retrieved
+	 * via the old() method.
+	 *
+	 * @param string $uri
+	 * @param array  ...$params
+	 */
+	function redirect_with_input(string $uri, ...$params)
+	{
+		$session = Services::session();
+
+		// Ensure we have the session started up.
+		if (! isset($_SESSION))
+		{
+			$session->start();
+		}
+
+		$input = [
+			'get' => $_GET ?? [],
+			'post' => $_POST ?? []
+		];
+
+			$session->setFlashdata('_ci_old_input', $input);
+
+			redirect($uri, ...$params);
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('stringify_attributes'))
 {
-    /**
-     * Stringify attributes for use in HTML tags.
-     *
-     * Helper function used to convert a string, array, or object
-     * of attributes to a string.
-     *
-     * @param   mixed   string, array, object
-     * @param   bool
-     * @return  string
-     */
-    function stringify_attributes($attributes, $js = FALSE) : string
-    {
-        $atts = '';
-
-        if (empty($attributes))
-        {
-            return $atts;
-        }
-
-        if (is_string($attributes))
-        {
-            return ' '.$attributes;
-        }
-
-        $attributes = (array) $attributes;
-
-        foreach ($attributes as $key => $val)
-        {
-            $atts .= ($js)
-                ? $key.'='.esc($val, 'js').','
-                : ' '.$key.'="'.esc($val, 'attr').'"';
-        }
-
-        return rtrim($atts, ',');
-    }
+	/**
+	 * Stringify attributes for use in HTML tags.
+	 *
+	 * Helper function used to convert a string, array, or object
+	 * of attributes to a string.
+	 *
+	 * @param   mixed   string, array, object
+	 * @param   bool
+	 * @return  string
+	 */
+	function stringify_attributes($attributes, $js = FALSE) : string
+	{
+		$atts = '';
+
+		if (empty($attributes))
+		{
+			return $atts;
+		}
+
+		if (is_string($attributes))
+		{
+			return ' '.$attributes;
+		}
+
+		$attributes = (array) $attributes;
+
+		foreach ($attributes as $key => $val)
+		{
+			$atts .= ($js)
+				? $key.'='.esc($val, 'js').','
+				: ' '.$key.'="'.esc($val, 'attr').'"';
+		}
+
+		return rtrim($atts, ',');
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('is_really_writable'))
 {
-    /**
-     * Tests for file writability
-     *
-     * is_writable() returns TRUE on Windows servers when you really can't write to
-     * the file, based on the read-only attribute. is_writable() is also unreliable
-     * on Unix servers if safe_mode is on.
-     *
-     * @link    https://bugs.php.net/bug.php?id=54709
-     * @param   string
-     * @return  bool
-     */
-    function is_really_writable($file)
-    {
-        // If we're on a Unix server with safe_mode off we call is_writable
-        if (DIRECTORY_SEPARATOR === '/' || ! ini_get('safe_mode'))
-        {
-            return is_writable($file);
-        }
-
-        /* For Windows servers and safe_mode "on" installations we'll actually
-         * write a file then read it. Bah...
-         */
-        if (is_dir($file))
-        {
-            $file = rtrim($file, '/').'/'.md5(mt_rand());
-            if (($fp = @fopen($file, 'ab')) === FALSE)
-            {
-                return FALSE;
-            }
-
-            fclose($fp);
-            @chmod($file, 0777);
-            @unlink($file);
-            return TRUE;
-        }
-        elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE)
-        {
-            return FALSE;
-        }
-
-        fclose($fp);
-        return TRUE;
-    }
+	/**
+	 * Tests for file writability
+	 *
+	 * is_writable() returns TRUE on Windows servers when you really can't write to
+	 * the file, based on the read-only attribute. is_writable() is also unreliable
+	 * on Unix servers if safe_mode is on.
+	 *
+	 * @link    https://bugs.php.net/bug.php?id=54709
+	 * @param   string
+	 * @return  bool
+	 */
+	function is_really_writable($file)
+	{
+		// If we're on a Unix server with safe_mode off we call is_writable
+		if (DIRECTORY_SEPARATOR === '/' || ! ini_get('safe_mode'))
+		{
+			return is_writable($file);
+		}
+
+		/* For Windows servers and safe_mode "on" installations we'll actually
+		 * write a file then read it. Bah...
+		 */
+		if (is_dir($file))
+		{
+			$file = rtrim($file, '/').'/'.md5(mt_rand());
+			if (($fp = @fopen($file, 'ab')) === FALSE)
+			{
+				return FALSE;
+			}
+
+			fclose($fp);
+			@chmod($file, 0777);
+			@unlink($file);
+			return TRUE;
+		}
+		elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE)
+		{
+			return FALSE;
+		}
+
+		fclose($fp);
+		return TRUE;
+	}
 }
 
 //--------------------------------------------------------------------
 
 if ( ! function_exists('slash_item'))
 {
-    //Unlike CI3, this function is placed here because
-    //it's not a config, or part of a config.
-    /**
-     * Fetch a config file item with slash appended (if not empty)
-     *
-     * @param   string      $item   Config item name
-     * @return  string|null The configuration item or NULL if
-     * the item doesn't exist
-     */
-    function slash_item($item)
-    {
-        $config     = new \Config\App();
-        $configItem = $config->{$item};
-
-        if ( ! isset($configItem) || empty(trim($configItem)))
-        {
-            return $configItem;
-        }
-
-        return rtrim($configItem, '/') . '/';
-    }
+	//Unlike CI3, this function is placed here because
+	//it's not a config, or part of a config.
+	/**
+	 * Fetch a config file item with slash appended (if not empty)
+	 *
+	 * @param   string      $item   Config item name
+	 * @return  string|null The configuration item or NULL if
+	 * the item doesn't exist
+	 */
+	function slash_item($item)
+	{
+		$config     = new \Config\App();
+		$configItem = $config->{$item};
+
+		if ( ! isset($configItem) || empty(trim($configItem)))
+		{
+			return $configItem;
+		}
+
+		return rtrim($configItem, '/') . '/';
+	}
 }
 //--------------------------------------------------------------------
diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php
index b5b89e00b174..8c89a3bbca33 100644
--- a/system/Config/DotEnv.php
+++ b/system/Config/DotEnv.php
@@ -185,20 +185,20 @@ protected function sanitizeValue(string $value): string
 			// value starts with a quote
 			$quote        = $value[0];
 			$regexPattern = sprintf(
-				'/^
-                %1$s          # match a quote at the start of the value
-                (             # capturing sub-pattern used
-                 (?:          # we do not need to capture this
-                  [^%1$s\\\\] # any character other than a quote or backslash
-                  |\\\\\\\\   # or two backslashes together
-                  |\\\\%1$s   # or an escaped quote e.g \"
-                 )*           # as many characters that match the previous rules
-                )             # end of the capturing sub-pattern
-                %1$s          # and the closing quote
-                .*$           # and discard any string after the closing quote
-                /mx',
-				$quote
-			);
+					'/^
+					%1$s          # match a quote at the start of the value
+					(             # capturing sub-pattern used
+								  (?:          # we do not need to capture this
+								   [^%1$s\\\\] # any character other than a quote or backslash
+								   |\\\\\\\\   # or two backslashes together
+								   |\\\\%1$s   # or an escaped quote e.g \"
+								  )*           # as many characters that match the previous rules
+					)             # end of the capturing sub-pattern
+					%1$s          # and the closing quote
+					.*$           # and discard any string after the closing quote
+					/mx',
+					$quote
+					);
 			$value        = preg_replace($regexPattern, '$1', $value);
 			$value        = str_replace("\\$quote", $quote, $value);
 			$value        = str_replace('\\\\', '\\', $value);
@@ -241,22 +241,22 @@ protected function resolveNestedVariables(string $value): string
 			$loader = $this;
 
 			$value = preg_replace_callback(
-				'/\${([a-zA-Z0-9_]+)}/',
-				function ($matchedPatterns) use ($loader)
-				{
+					'/\${([a-zA-Z0-9_]+)}/',
+					function ($matchedPatterns) use ($loader)
+					{
 					$nestedVariable = $loader->getVariable($matchedPatterns[1]);
 
 					if (is_null($nestedVariable))
 					{
-						return $matchedPatterns[0];
+					return $matchedPatterns[0];
 					}
 					else
 					{
-						return $nestedVariable;
+					return $nestedVariable;
 					}
-				},
-				$value
-			);
+					},
+					$value
+					);
 		}
 
 		return $value;
diff --git a/system/Controller.php b/system/Controller.php
index 072f85caec80..2181af152924 100644
--- a/system/Controller.php
+++ b/system/Controller.php
@@ -87,13 +87,13 @@ class Controller
 	 */
 	protected $forceHTTPS = 0;
 
-    /**
-     * Once validation has been run,
-     * will hold the Validation instance.
-     *
-     * @var Validation
-     */
-    protected $validator;
+	/**
+	 * Once validation has been run,
+	 * will hold the Validation instance.
+	 *
+	 * @var Validation
+	 */
+	protected $validator;
 
 	//--------------------------------------------------------------------
 
@@ -106,7 +106,7 @@ class Controller
 	 */
 	public function __construct(RequestInterface $request, ResponseInterface $response, Logger $logger = null)
 	{
-	    $this->request = $request;
+		$this->request = $request;
 
 		$this->response = $response;
 
@@ -136,7 +136,7 @@ public function __construct(RequestInterface $request, ResponseInterface $respon
 	 */
 	public function forceHTTPS(int $duration = 31536000)
 	{
-	    force_https($duration, $this->request, $this->response);
+		force_https($duration, $this->request, $this->response);
 	}
 
 	//--------------------------------------------------------------------
@@ -159,7 +159,7 @@ public function cachePage(int $time)
 	 */
 	protected function loadHelpers()
 	{
-	    if (empty($this->helpers)) return;
+		if (empty($this->helpers)) return;
 
 		foreach ($this->helpers as $helper)
 		{
@@ -169,28 +169,28 @@ protected function loadHelpers()
 
 	//--------------------------------------------------------------------
 
-    /**
-     * A shortcut to performing validation on $_POST input. If validation
-     * is not successful, a $errors property will be set on this class.
-     *
-     * @param \CodeIgniter\HTTP\RequestInterface $request
-     * @param                                    $rules
-     * @param array|null                         $messages
-     *
-     * @return bool
-     */
-    public function validate(RequestInterface $request, $rules, array $messages = null): bool
-    {
-        $this->validator = Services::validation();
-
-        $success = $this->validator->withRequest($request)
-                             ->setRules($rules, $messages)
-                             ->run();
-
-        return $success;
-    }
-
-    //--------------------------------------------------------------------
+	/**
+	 * A shortcut to performing validation on $_POST input. If validation
+	 * is not successful, a $errors property will be set on this class.
+	 *
+	 * @param \CodeIgniter\HTTP\RequestInterface $request
+	 * @param                                    $rules
+	 * @param array|null                         $messages
+	 *
+	 * @return bool
+	 */
+	public function validate(RequestInterface $request, $rules, array $messages = null): bool
+	{
+		$this->validator = Services::validation();
+
+		$success = $this->validator->withRequest($request)
+			->setRules($rules, $messages)
+			->run();
+
+		return $success;
+	}
+
+	//--------------------------------------------------------------------
 
 
 }
diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php
index 5a76e4969a51..4726f55a7fde 100644
--- a/system/Database/BaseConnection.php
+++ b/system/Database/BaseConnection.php
@@ -279,44 +279,44 @@ abstract class BaseConnection implements ConnectionInterface
 	 */
 	protected $pretend = false;
 
-    /**
-     * Transaction enabled flag
-     *
-     * @var	bool
-     */
-    public $trans_enabled = true;
-
-    /**
-     * Strict transaction mode flag
-     *
-     * @var	bool
-     */
-    public $trans_strict = true;
-
-    /**
-     * Transaction depth level
-     *
-     * @var	int
-     */
-    protected $_trans_depth = 0;
-
-    /**
-     * Transaction status flag
-     *
-     * Used with transactions to determine if a rollback should occur.
-     *
-     * @var	bool
-     */
-    protected $_trans_status = true;
-
-    /**
-     * Transaction failure flag
-     *
-     * Used with transactions to determine if a transaction has failed.
-     *
-     * @var	bool
-     */
-    protected $_trans_failure	= false;
+	/**
+	 * Transaction enabled flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_enabled = true;
+
+	/**
+	 * Strict transaction mode flag
+	 *
+	 * @var	bool
+	 */
+	public $trans_strict = true;
+
+	/**
+	 * Transaction depth level
+	 *
+	 * @var	int
+	 */
+	protected $_trans_depth = 0;
+
+	/**
+	 * Transaction status flag
+	 *
+	 * Used with transactions to determine if a rollback should occur.
+	 *
+	 * @var	bool
+	 */
+	protected $_trans_status = true;
+
+	/**
+	 * Transaction failure flag
+	 *
+	 * Used with transactions to determine if a transaction has failed.
+	 *
+	 * @var	bool
+	 */
+	protected $_trans_failure	= false;
 
 	//--------------------------------------------------------------------
 
@@ -327,21 +327,21 @@ abstract class BaseConnection implements ConnectionInterface
 	 */
 	public function __construct(array $params)
 	{
-	    foreach ($params as $key => $value)
-	    {
-		    $this->$key = $value;
-	    }
+		foreach ($params as $key => $value)
+		{
+			$this->$key = $value;
+		}
 	}
 
 	//--------------------------------------------------------------------
 
 
-    /**
-     * Initializes the database connection/settings.
-     *
-     * @return mixed
-     * @throws \CodeIgniter\DatabaseException
-     */
+	/**
+	 * Initializes the database connection/settings.
+	 *
+	 * @return mixed
+	 * @throws \CodeIgniter\DatabaseException
+	 */
 	public function initialize()
 	{
 		/* If an established connection is available, then there's
@@ -410,28 +410,28 @@ abstract public function connect($persistent = false);
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Close the database connection.
-     */
-    public function close()
-    {
-        if ($this->connID)
-        {
-            $this->_close();
-            $this->connID = FALSE;
-        }
-    }
+	/**
+	 * Close the database connection.
+	 */
+	public function close()
+	{
+		if ($this->connID)
+		{
+			$this->_close();
+			$this->connID = FALSE;
+		}
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * Platform dependent way method for closing the connection.
-     *
-     * @return mixed
-     */
-    abstract protected function _close();
+	/**
+	 * Platform dependent way method for closing the connection.
+	 *
+	 * @return mixed
+	 */
+	abstract protected function _close();
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Create a persistent database connection.
@@ -573,9 +573,9 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da
 
 		$startTime = microtime(true);
 
-        // Always save the last query so we can use
-        // the getLastQuery() method.
-        $this->lastQuery = $query;
+		// Always save the last query so we can use
+		// the getLastQuery() method.
+		$this->lastQuery = $query;
 
 		// Run the query for real
 		if (! $this->pretend && false === ($this->resultID = $this->simpleQuery($query->getQuery())))
@@ -583,41 +583,41 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da
 			$query->setDuration($startTime, $startTime);
 
 			// This will trigger a rollback if transactions are being used
-            if ($this->_trans_depth !== 0)
-            {
-                $this->_trans_status = false;
-            }
+			if ($this->_trans_depth !== 0)
+			{
+				$this->_trans_status = false;
+			}
 
 			// @todo deal with errors
 
-            if ($this->DBDebug)
-            {
-                // We call this function in order to roll-back queries
-                // if transactions are enabled. If we don't call this here
-                // the error message will trigger an exit, causing the
-                // transactions to remain in limbo.
-                while ($this->_trans_depth !== 0)
-                {
-                    $transDepth = $this->_trans_depth;
-                    $this->transComplete();
-
-                    if ($transDepth === $this->_trans_depth)
-                    {
-                        // @todo log
-                        // log_message('error', 'Database: Failure during an automated transaction commit/rollback!');
-                        break;
-                    }
-                }
-
-                // display the errors....
-                // @todo display the error...
-
-                return false;
-            }
+			if ($this->DBDebug)
+			{
+				// We call this function in order to roll-back queries
+				// if transactions are enabled. If we don't call this here
+				// the error message will trigger an exit, causing the
+				// transactions to remain in limbo.
+				while ($this->_trans_depth !== 0)
+				{
+					$transDepth = $this->_trans_depth;
+					$this->transComplete();
+
+					if ($transDepth === $this->_trans_depth)
+					{
+						// @todo log
+						// log_message('error', 'Database: Failure during an automated transaction commit/rollback!');
+						break;
+					}
+				}
+
+				// display the errors....
+				// @todo display the error...
+
+				return false;
+			}
 
 			if (! $this->pretend)
 			{
-                // Let others do something with this query.
+				// Let others do something with this query.
 				Hooks::trigger('DBQuery', $query);
 			}
 
@@ -628,8 +628,8 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da
 
 		if (! $this->pretend)
 		{
-            // Let others do somethign with this query
-            Hooks::trigger('DBQuery', $query);
+			// Let others do somethign with this query
+			Hooks::trigger('DBQuery', $query);
 		}
 
 		// If $pretend is true, then we just want to return
@@ -663,217 +663,217 @@ public function simpleQuery(string $sql)
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Disable Transactions
-     *
-     * This permits transactions to be disabled at run-time.
-     */
-    public function transOff()
-    {
-        $this->trans_enabled = FALSE;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Enable/disable Transaction Strict Mode
-     *
-     * When strict mode is enabled, if you are running multiple groups of
-     * transactions, if one group fails all subsequent groups will be
-     * rolled back.
-     *
-     * If strict mode is disabled, each group is treated autonomously,
-     * meaning a failure of one group will not affect any others
-     *
-     * @param    bool $mode = true
-     *
-     * @return $this
-     */
-    public function transStrict(bool $mode=true)
-    {
-        $this->trans_strict = $mode;
-
-        return $this;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Start Transaction
-     *
-     * @param	bool	$test_mode = FALSE
-     * @return	bool
-     */
-    public function transStart($test_mode = false)
-    {
-        if ( ! $this->trans_enabled)
-        {
-            return false;
-        }
-
-        return $this->transBegin($test_mode);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Complete Transaction
-     *
-     * @return	bool
-     */
-    public function transComplete()
-    {
-        if ( ! $this->trans_enabled)
-        {
-            return false;
-        }
-
-        // The query() function will set this flag to FALSE in the event that a query failed
-        if ($this->_trans_status === false OR $this->_trans_failure === true)
-        {
-            $this->transRollback();
-
-            // If we are NOT running in strict mode, we will reset
-            // the _trans_status flag so that subsequent groups of
-            // transactions will be permitted.
-            if ($this->trans_strict === false)
-            {
-                $this->_trans_status = true;
-            }
-
-//            log_message('debug', 'DB Transaction Failure');
-            return FALSE;
-        }
-
-        return $this->transCommit();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Lets you retrieve the transaction flag to determine if it has failed
-     *
-     * @return	bool
-     */
-    public function transStatus(): bool
-    {
-        return $this->_trans_status;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Begin Transaction
-     *
-     * @param	bool	$test_mode
-     * @return	bool
-     */
-    public function transBegin(bool $test_mode = false): bool
-    {
-        if ( ! $this->trans_enabled)
-        {
-            return false;
-        }
-        // When transactions are nested we only begin/commit/rollback the outermost ones
-        elseif ($this->_trans_depth > 0)
-        {
-            $this->_trans_depth++;
-            return true;
-        }
+	/**
+	 * Disable Transactions
+	 *
+	 * This permits transactions to be disabled at run-time.
+	 */
+	public function transOff()
+	{
+		$this->trans_enabled = FALSE;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Enable/disable Transaction Strict Mode
+	 *
+	 * When strict mode is enabled, if you are running multiple groups of
+	 * transactions, if one group fails all subsequent groups will be
+	 * rolled back.
+	 *
+	 * If strict mode is disabled, each group is treated autonomously,
+	 * meaning a failure of one group will not affect any others
+	 *
+	 * @param    bool $mode = true
+	 *
+	 * @return $this
+	 */
+	public function transStrict(bool $mode=true)
+	{
+		$this->trans_strict = $mode;
+
+		return $this;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Start Transaction
+	 *
+	 * @param	bool	$test_mode = FALSE
+	 * @return	bool
+	 */
+	public function transStart($test_mode = false)
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return false;
+		}
+
+		return $this->transBegin($test_mode);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Complete Transaction
+	 *
+	 * @return	bool
+	 */
+	public function transComplete()
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return false;
+		}
+
+		// The query() function will set this flag to FALSE in the event that a query failed
+		if ($this->_trans_status === false OR $this->_trans_failure === true)
+		{
+			$this->transRollback();
+
+			// If we are NOT running in strict mode, we will reset
+			// the _trans_status flag so that subsequent groups of
+			// transactions will be permitted.
+			if ($this->trans_strict === false)
+			{
+				$this->_trans_status = true;
+			}
+
+			//            log_message('debug', 'DB Transaction Failure');
+			return FALSE;
+		}
+
+		return $this->transCommit();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Lets you retrieve the transaction flag to determine if it has failed
+	 *
+	 * @return	bool
+	 */
+	public function transStatus(): bool
+	{
+		return $this->_trans_status;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @param	bool	$test_mode
+	 * @return	bool
+	 */
+	public function transBegin(bool $test_mode = false): bool
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return false;
+		}
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		elseif ($this->_trans_depth > 0)
+		{
+			$this->_trans_depth++;
+			return true;
+		}
 
 		if (empty($this->connID))
 		{
 			$this->initialize();
 		}
 
-        // Reset the transaction failure flag.
-        // If the $test_mode flag is set to TRUE transactions will be rolled back
-        // even if the queries produce a successful result.
-        $this->_trans_failure = ($test_mode === true);
-
-        if ($this->_transBegin())
-        {
-            $this->_trans_depth++;
-            return true;
-        }
-
-        return false;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Commit Transaction
-     *
-     * @return	bool
-     */
-    public function transCommit(): bool
-    {
-        if ( ! $this->trans_enabled || $this->_trans_depth === 0)
-        {
-            return false;
-        }
-        // When transactions are nested we only begin/commit/rollback the outermost ones
-        elseif ($this->_trans_depth > 1 || $this->_transCommit())
-        {
-            $this->_trans_depth--;
-            return true;
-        }
-
-        return false;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Rollback Transaction
-     *
-     * @return	bool
-     */
-    public function transRollback(): bool
-    {
-        if ( ! $this->trans_enabled OR $this->_trans_depth === 0)
-        {
-            return false;
-        }
-        // When transactions are nested we only begin/commit/rollback the outermost ones
-        elseif ($this->_trans_depth > 1 OR $this->_transRollback())
-        {
-            $this->_trans_depth--;
-            return true;
-        }
-
-        return false;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Begin Transaction
-     *
-     * @return	bool
-     */
-    abstract protected function _transBegin(): bool;
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Commit Transaction
-     *
-     * @return	bool
-     */
-    abstract protected function _transCommit(): bool;
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Rollback Transaction
-     *
-     * @return	bool
-     */
-    abstract protected function _transRollback(): bool;
-
-    //--------------------------------------------------------------------
+		// Reset the transaction failure flag.
+		// If the $test_mode flag is set to TRUE transactions will be rolled back
+		// even if the queries produce a successful result.
+		$this->_trans_failure = ($test_mode === true);
+
+		if ($this->_transBegin())
+		{
+			$this->_trans_depth++;
+			return true;
+		}
+
+		return false;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	public function transCommit(): bool
+	{
+		if ( ! $this->trans_enabled || $this->_trans_depth === 0)
+		{
+			return false;
+		}
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		elseif ($this->_trans_depth > 1 || $this->_transCommit())
+		{
+			$this->_trans_depth--;
+			return true;
+		}
+
+		return false;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	public function transRollback(): bool
+	{
+		if ( ! $this->trans_enabled OR $this->_trans_depth === 0)
+		{
+			return false;
+		}
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		elseif ($this->_trans_depth > 1 OR $this->_transRollback())
+		{
+			$this->_trans_depth--;
+			return true;
+		}
+
+		return false;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	abstract protected function _transBegin(): bool;
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	abstract protected function _transCommit(): bool;
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	abstract protected function _transRollback(): bool;
+
+	//--------------------------------------------------------------------
 
 	/**
 	 * Returns an instance of the query builder for this connection.
@@ -920,7 +920,7 @@ public function prepare(\Closure $func, array $options = [])
 	{
 		$this->pretend(true);
 
-	    $sql = $func($this);
+		$sql = $func($this);
 
 		$this->pretend(false);
 
@@ -956,7 +956,7 @@ public function getLastQuery()
 	 */
 	public function showLastQuery()
 	{
-	    return (string)$this->lastQuery;
+		return (string)$this->lastQuery;
 	}
 
 	//--------------------------------------------------------------------
@@ -973,7 +973,7 @@ public function showLastQuery()
 	 */
 	public function getConnectStart()
 	{
-	    return $this->connectTime;
+		return $this->connectTime;
 	}
 
 	//--------------------------------------------------------------------
@@ -990,7 +990,7 @@ public function getConnectStart()
 	 */
 	public function getConnectDuration($decimals = 6)
 	{
-	    return number_format($this->connectDuration, $decimals);
+		return number_format($this->connectDuration, $decimals);
 	}
 
 	//--------------------------------------------------------------------
@@ -1035,7 +1035,7 @@ public function protectIdentifiers($item, $prefixSingle = false, $protectIdentif
 			foreach ($item as $k => $v)
 			{
 				$escaped_array[$this->protectIdentifiers($k)] = $this->protectIdentifiers($v, $prefixSingle,
-					$protectIdentifiers, $fieldExists);
+						$protectIdentifiers, $fieldExists);
 			}
 
 			return $escaped_array;
@@ -1217,8 +1217,8 @@ public function escapeIdentifiers($item)
 		}
 		// Avoid breaking functions and literal values inside queries
 		elseif (ctype_digit($item) OR $item[0] === "'" OR ($this->escapeChar !== '"' && $item[0] === '"') OR
-		        strpos($item, '(') !== false
-		)
+				strpos($item, '(') !== false
+			   )
 		{
 			return $item;
 		}
@@ -1248,26 +1248,26 @@ public function escapeIdentifiers($item)
 			if (strpos($item, '.'.$id) !== false)
 			{
 				return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?\./i',
-					$preg_ec[2].'$1'.$preg_ec[3].'.', $item);
+						$preg_ec[2].'$1'.$preg_ec[3].'.', $item);
 			}
 		}
 
 		return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?(\.)?/i',
-			$preg_ec[2].'$1'.$preg_ec[3].'$2', $item);
+				$preg_ec[2].'$1'.$preg_ec[3].'$2', $item);
 	}
 
 	//--------------------------------------------------------------------
 
-    /**
-     * DB Prefix
-     *
-     * Prepends a database prefix if one exists in configuration
-     *
-     * @param string $table the table
-     *
-     * @return string
-     * @throws \CodeIgniter\DatabaseException
-     */
+	/**
+	 * DB Prefix
+	 *
+	 * Prepends a database prefix if one exists in configuration
+	 *
+	 * @param string $table the table
+	 *
+	 * @return string
+	 * @throws \CodeIgniter\DatabaseException
+	 */
 	public function prefixTable($table = '')
 	{
 		if ($table === '')
@@ -1366,10 +1366,10 @@ public function escapeString($str, $like = FALSE)
 		if ($like === true)
 		{
 			return str_replace(
-				[$this->likeEscapeChar, '%', '_'],
-				[$this->likeEscapeChar.$this->likeEscapeChar, $this->likeEscapeChar.'%', $this->likeEscapeChar.'_'],
-				$str
-			);
+					[$this->likeEscapeChar, '%', '_'],
+					[$this->likeEscapeChar.$this->likeEscapeChar, $this->likeEscapeChar.'%', $this->likeEscapeChar.'_'],
+					$str
+					);
 		}
 
 		return $str;
@@ -1637,7 +1637,7 @@ public function getIndexData(string $table)
 	 */
 	public function pretend(bool $pretend = true)
 	{
-	    $this->pretend = $pretend;
+		$this->pretend = $pretend;
 
 		return $this;
 	}
diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php
index 1038833cc261..a8a467267264 100644
--- a/system/Database/BasePreparedQuery.php
+++ b/system/Database/BasePreparedQuery.php
@@ -154,7 +154,7 @@ public function execute(...$data)
 		$query->setDuration($startTime);
 
 		// Let others do something with this query
-        Hooks::trigger('DBQuery', $query);
+		Hooks::trigger('DBQuery', $query);
 
 		// Return a result object
 		$resultClass = str_replace('PreparedQuery', 'Result', get_class($this));
@@ -196,7 +196,7 @@ public function close()
 			return;
 		}
 
-	    $this->statement->close();
+		$this->statement->close();
 	}
 
 	//--------------------------------------------------------------------
@@ -208,10 +208,10 @@ public function close()
 	 */
 	public function getQueryString(): string
 	{
-	    if (! $this->query instanceof QueryInterface)
-        {
-            throw new \BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.');
-        }
+		if (! $this->query instanceof QueryInterface)
+		{
+			throw new \BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.');
+		}
 
 		return $this->query->getQuery();
 	}
@@ -225,7 +225,7 @@ public function getQueryString(): string
 	 */
 	public function hasError()
 	{
-	    return ! empty($this->errorString);
+		return ! empty($this->errorString);
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Database/Forge.php b/system/Database/Forge.php
index fc31e1a4d08a..f6b139fd6eb9 100644
--- a/system/Database/Forge.php
+++ b/system/Database/Forge.php
@@ -173,7 +173,7 @@ public function __construct(ConnectionInterface $db)
 	 */
 	public function getConnection()
 	{
-	    return $this->db;
+		return $this->db;
 	}
 
 	//--------------------------------------------------------------------
@@ -198,8 +198,8 @@ public function createDatabase($db_name)
 			return false;
 		}
 		elseif ( ! $this->db->query(sprintf($this->createDatabaseStr, $db_name, $this->db->charset,
-			$this->db->DBCollat))
-		)
+						$this->db->DBCollat))
+			   )
 		{
 			if ($this->db->DBDebug)
 			{
@@ -302,11 +302,11 @@ public function addField($field)
 			if ($field === 'id')
 			{
 				$this->addField([
-					'id' => [
+						'id' => [
 						'type'           => 'INT',
 						'constraint'     => 9,
 						'auto_increment' => true,
-					],
+						],
 				]);
 				$this->addKey('id', true);
 			}
@@ -331,16 +331,16 @@ public function addField($field)
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Create Table
-     *
-     * @param    string $table         Table name
-     * @param    bool   $if_not_exists Whether to add IF NOT EXISTS condition
-     * @param    array  $attributes    Associative array of table attributes
-     *
-     * @return bool
-     * @throws \CodeIgniter\DatabaseException
-     */
+	/**
+	 * Create Table
+	 *
+	 * @param    string $table         Table name
+	 * @param    bool   $if_not_exists Whether to add IF NOT EXISTS condition
+	 * @param    array  $attributes    Associative array of table attributes
+	 *
+	 * @return bool
+	 * @throws \CodeIgniter\DatabaseException
+	 */
 	public function createTable($table, $if_not_exists = false, array $attributes = [])
 	{
 		if ($table === '')
@@ -430,7 +430,7 @@ protected function _createTable($table, $if_not_exists, $attributes)
 		}
 
 		$columns = implode(',', $columns)
-		           .$this->_processPrimaryKeys($table);
+			.$this->_processPrimaryKeys($table);
 
 		// Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL)
 		if ($this->createTableKeys === true)
@@ -440,11 +440,11 @@ protected function _createTable($table, $if_not_exists, $attributes)
 
 		// createTableStr will usually have the following format: "%s %s (%s\n)"
 		$sql = sprintf($this->createTableStr.'%s',
-			$sql,
-			$this->db->escapeIdentifiers($table),
-			$columns,
-			$this->_createTableAttributes($attributes)
-		);
+				$sql,
+				$this->db->escapeIdentifiers($table),
+				$columns,
+				$this->_createTableAttributes($attributes)
+				);
 
 		return $sql;
 	}
@@ -506,7 +506,7 @@ public function dropTable($table_name, $if_exists = false)
 		if ($query && ! empty($this->db->dataCache['table_names']))
 		{
 			$key = array_search(strtolower($this->db->DBPrefix.$table_name),
-				array_map('strtolower', $this->db->dataCache['table_names']), true);
+					array_map('strtolower', $this->db->dataCache['table_names']), true);
 			if ($key !== false)
 			{
 				unset($this->db->dataCache['table_names'][$key]);
@@ -577,14 +577,14 @@ public function renameTable($table_name, $new_table_name)
 		}
 
 		$result = $this->db->query(sprintf($this->renameTableStr,
-				$this->db->escapeIdentifiers($this->db->DBPrefix.$table_name),
-				$this->db->escapeIdentifiers($this->db->DBPrefix.$new_table_name))
-		);
+					$this->db->escapeIdentifiers($this->db->DBPrefix.$table_name),
+					$this->db->escapeIdentifiers($this->db->DBPrefix.$new_table_name))
+				);
 
 		if ($result && ! empty($this->db->dataCache['table_names']))
 		{
 			$key = array_search(strtolower($this->db->DBPrefix.$table_name),
-				array_map('strtolower', $this->db->dataCache['table_names']), true);
+					array_map('strtolower', $this->db->dataCache['table_names']), true);
 			if ($key !== false)
 			{
 				$this->db->dataCache['table_names'][$key] = $this->db->DBPrefix.$new_table_name;
@@ -741,7 +741,7 @@ protected function _alterTable($alter_type, $table, $field)
 		for ($i = 0, $c = count($field); $i < $c; $i++)
 		{
 			$sqls[] = $sql
-			          .($field[$i]['_literal'] !== false ? $field[$i]['_literal'] : $this->_processColumn($field[$i]));
+				.($field[$i]['_literal'] !== false ? $field[$i]['_literal'] : $this->_processColumn($field[$i]));
 		}
 
 		return $sqls;
@@ -867,12 +867,12 @@ protected function _processFields($create_table = false)
 	protected function _processColumn($field)
 	{
 		return $this->db->escapeIdentifiers($field['name'])
-		       .' '.$field['type'].$field['length']
-		       .$field['unsigned']
-		       .$field['default']
-		       .$field['null']
-		       .$field['auto_increment']
-		       .$field['unique'];
+			.' '.$field['type'].$field['length']
+			.$field['unsigned']
+			.$field['default']
+			.$field['null']
+			.$field['auto_increment']
+			.$field['unique'];
 	}
 
 	//--------------------------------------------------------------------
@@ -1009,8 +1009,8 @@ protected function _attributeUnique(&$attributes, &$field)
 	protected function _attributeAutoIncrement(&$attributes, &$field)
 	{
 		if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true &&
-		     stripos($field['type'], 'int') !== false
-		)
+				stripos($field['type'], 'int') !== false
+		   )
 		{
 			$field['auto_increment'] = ' AUTO_INCREMENT';
 		}
@@ -1040,62 +1040,62 @@ protected function _processPrimaryKeys($table)
 		if (count($this->primaryKeys) > 0)
 		{
 			$sql .= ",\n\tCONSTRAINT ".$this->db->escapeIdentifiers('pk_'.$table)
-			        .' PRIMARY KEY('.implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)).')';
-		}
-
-		return $sql;
-	}
-
-	//--------------------------------------------------------------------
-
-	/**
-	 * Process indexes
-	 *
-	 * @param    string $table
-	 *
-	 * @return    string
-	 */
-	protected function _processIndexes($table)
-	{
-		$sqls = [];
-
-		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
-		{
-			$this->keys[$i] = (array) $this->keys[$i];
-
-			for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
-			{
-				if ( ! isset($this->fields[$this->keys[$i][$i2]]))
-				{
-					unset($this->keys[$i][$i2]);
-				}
-			}
-			if (count($this->keys[$i]) <= 0)
+				.' PRIMARY KEY('.implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)).')';
+						}
+
+						return $sql;
+						}
+
+						//--------------------------------------------------------------------
+
+						/**
+						 * Process indexes
+						 *
+						 * @param    string $table
+						 *
+						 * @return    string
+						 */
+						protected function _processIndexes($table)
+						{
+						$sqls = [];
+
+						for ($i = 0, $c = count($this->keys); $i < $c; $i++)
+						{
+							$this->keys[$i] = (array) $this->keys[$i];
+
+							for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+							{
+								if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+								{
+									unset($this->keys[$i][$i2]);
+								}
+							}
+							if (count($this->keys[$i]) <= 0)
+							{
+								continue;
+							}
+
+							$sqls[] = 'CREATE INDEX '.$this->db->escapeIdentifiers($table.'_'.implode('_', $this->keys[$i]))
+								.' ON '.$this->db->escapeIdentifiers($table)
+								.' ('.implode(', ', $this->db->escapeIdentifiers($this->keys[$i])).');';
+						}
+
+						return $sqls;
+						}
+
+			//--------------------------------------------------------------------
+			//--------------------------------------------------------------------
+
+			/**
+			 * Reset
+			 *
+			 * Resets table creation vars
+			 *
+			 * @return    void
+			 */
+			protected function _reset()
 			{
-				continue;
+				$this->fields = $this->keys = $this->primaryKeys = [];
 			}
 
-			$sqls[] = 'CREATE INDEX '.$this->db->escapeIdentifiers($table.'_'.implode('_', $this->keys[$i]))
-			          .' ON '.$this->db->escapeIdentifiers($table)
-			          .' ('.implode(', ', $this->db->escapeIdentifiers($this->keys[$i])).');';
-		}
-
-		return $sqls;
-	}
-
-	//--------------------------------------------------------------------
-	//--------------------------------------------------------------------
-
-	/**
-	 * Reset
-	 *
-	 * Resets table creation vars
-	 *
-	 * @return    void
-	 */
-	protected function _reset()
-	{
-		$this->fields = $this->keys = $this->primaryKeys = [];
-	}
-
 }
diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php
index 4cc14d7ef1f1..df8250d0a542 100644
--- a/system/Database/MigrationRunner.php
+++ b/system/Database/MigrationRunner.php
@@ -35,7 +35,7 @@
  * @since    Version 3.0.0
  * @filesource
  */
- 
+
 
 use CodeIgniter\Config\BaseConfig;
 use CodeIgniter\ConfigException;
@@ -47,601 +47,601 @@
  */
 class MigrationRunner
 {
-    /**
-     * Whether or not migrations are allowed to run.
-     *
-     * @var bool
-     */
-    protected $enabled = false;
-
-    /**
-     * The type of migrations to use (sequential or timestamp)
-     *
-     * @var string
-     */
-    protected $type;
-
-    /**
-     * Name of table to store meta information
-     *
-     * @var string
-     */
-    protected $table;
-
-    /**
-     * The version that current() will take us to.
-     *
-     * @var int
-     */
-    protected $currentVersion = 0;
-
-    /**
-     * The Namespace  where migrations can be found.
-     *
-     * @var string
-     */
-    protected $namespace;
-
-    /**
-     * The database Group to migrate.
-     *
-     * @var string
-     */
-    protected $group;
-
-
-    /**
-     * The pattern used to locate migration file versions.
-     *
-     * @var string
-     */
-    protected $regex;
-
-    /**
-     * The main database connection. Used to store
-     * migration information in.
-     * @var ConnectionInterface
-     */
-    protected $db;
-
-    /**
-     * If true, will continue instead of throwing
-     * exceptions.
-     * @var bool
-     */
-    protected $silent = false;
-
-    /**
-     * used to return messages for CLI.
-     *
-     * @var bool
-     */
-    protected $cliMessages = array();
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Constructor.
-     *
-     * @param BaseConfig $config
-     * @param \CodeIgniter\Database\ConnectionInterface $db
-     * @throws ConfigException
-     */
-    public function __construct(BaseConfig $config, ConnectionInterface $db = null)
-    {
-        $this->enabled = $config->enabled        ?? false;
-        $this->type = $config->type           ?? 'timestamp';
-        $this->table = $config->table          ?? 'migrations';
-        $this->currentVersion = $config->currentVersion ?? 0;
-
-        // Default name space is the app namespace
-        $this->namespace = APP_NAMESPACE;
-
-        // get default database group
-        $config = new \Config\Database();
-        $this->group = $config->defaultGroup;
-        unset($config);
-
-        if (empty($this->table)) {
-            throw new ConfigException(lang('Migrations.migMissingTable'));
-        }
-
-        if (!in_array($this->type, ['sequential', 'timestamp'])) {
-            throw new ConfigException(lang('Migrations.migInvalidType') . $this->type);
-        }
-
-        // Migration basename regex
-        $this->regex = ($this->type === 'timestamp')
-            ? '/^\d{14}_(\w+)$/'
-            : '/^\d{3}_(\w+)$/';
-
-        // If no db connection passed in, use
-        // default database group.
-        $this->db = !empty($db)
-            ? $db
-            : \Config\Database::connect();
-
-        $this->ensureTable();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Migrate to a schema version
-     *
-     * Calls each migration step required to get to the schema version of
-     * choice
-     *
-     * @param    string $targetVersion Target schema version
-     * @param $group
-     * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure
-     * @throws ConfigException
-     */
-    public function version(string $targetVersion, $namespace = null, $group = null)
-    {
-        if (!$this->enabled) {
-            throw new ConfigException(lang('Migrations.migDisabled'));
-        }
-        // Set Namespace if not null
-        if (!is_null($namespace)) {
-            $this->setNamespace($namespace);
-        }
-
-        // Set database group if not null
-        if (!is_null($group)) {
-            $this->setGroup($group);
-        }
-
-        $migrations = $this->findMigrations();
-
-        if (empty($migrations)) {
-            return true;
-        }
-
-        // Get Namespace current version
-        // Note: We use strings, so that timestamp versions work on 32-bit systems
-        $currentVersion = $this->getVersion();
-        if ($targetVersion > $currentVersion) {
-            // Moving Up
-            $method = 'up';
-            ksort($migrations);
-
-        } else {
-            // Moving Down, apply in reverse order
-            $method = 'down';
-            krsort($migrations);
-        }
-
-        // Check Migration consistency
-        $this->CheckMigrations($migrations,$method, $targetVersion);
-
-         if(is_cli()){
-                $this->cliMessages[]="-) $this->namespace:";
-            }
-
-        // loop migration for each namespace (module)
-        foreach ($migrations as $version => $migration) {
-
-            // Only include migrations within the scoop
-            if (($method === 'up' && $version > $currentVersion && $version <= $targetVersion) OR
-            ($method === 'down' && $version <= $currentVersion && $version > $targetVersion)) {
-
-                include_once $migration->path;
-                // Get namespaced class name
-                $class = $this->namespace . '\Database\Migrations\Migration_' . ($migration->name);
-
-                // Validate the migration file structure
-                if (!class_exists($class, false)) {
-                    throw new \RuntimeException(sprintf(lang('Migrations.migClassNotFound'), $class));
-                }
-
-                // Forcing migration to selected database group
-                $instance = new $class(\Config\Database::forge($this->group));
-
-                if (!is_callable([$instance, $method])) {
-                    throw new \RuntimeException(sprintf(lang('Migrations.migMissingMethod'), $method));
-                }
-
-                $instance->{$method}();
-                if ($method === 'up') $this->addHistory($migration->version);
-                elseif ($method === 'down') $this->removeHistory($migration->version);            
-            }
-        }
-        return true;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Sets the schema to the latest migration
-     *
-     * @return    mixed    Current version string on success, FALSE on failure
-     */
-    public function latest($namespace = null, $group = null)
-    {
-
-        // Set Namespace if not null
-        if (!is_null($namespace)) {
-            $this->setNamespace($namespace);
-        }
-        // Set database group if not null
-        if (!is_null($group)) {
-            $this->setGroup($group);
-        }
-
-        $migrations = $this->findMigrations();       
-
-        $lastMigration = end($migrations)->version;
-
-        // Calculate the last migration step from existing migration
-        // filenames and proceed to the standard version migration       
-        return $this->version($lastMigration);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Sets the schema to the latest migration for all namespaces
-     *
-     * @return    void
-     */
-    public function latestAll($group = null)
-    {
-         // Set database group if not null
-        if (!is_null($group)) {
-            $this->setGroup($group);
-        }
-
-        // Get all namespaces form  PSR4 paths.
-        $config = new Autoload();
-        $namespaces = $config->psr4;
-
-        foreach ($namespaces as $namespace => $path) {    
-
-            $this->setNamespace($namespace);
-            $migrations = $this->findMigrations();
-
-            if (empty($migrations)) {
-                continue;
-            }
-
-            $lastMigration = end($migrations)->version;
-            // No New migrations to add
-            if($lastMigration ==  $this->getVersion()){
-                continue;
-            }
-
-            // Calculate the last migration step from existing migration
-            // filenames and proceed to the standard version migration           
-            $this->version($lastMigration);
-        }
-        return true;
-    }
-    //--------------------------------------------------------------------
-
-    /**
-     * Sets the (APP_NAMESPACE) schema to $currentVersion in migration config file
-     *      
-     *
-     * @return    mixed    TRUE if no migrations are found, current version string on success, FALSE on failure
-     */
-    public function current($group = null)
-    {
-        // Set database group if not null
-        if (!is_null($group)) {
-            $this->setGroup($group);
-        }
-
-        return $this->version($this->currentVersion);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Retrieves list of available migration scripts
-     *
-     * @return    array    list of migrations as $version for one namespace
-     */
-    public function findMigrations()
-    {
-        $migrations = [];
-        // Get namespace location form  PSR4 paths.
-        $config = new Autoload();
-
-        $location = $config->psr4[$this->namespace];
-
-        // Setting migration directories.
-        $dir = rtrim($location, '/') . '/Database/Migrations/';
-
-        // Load all *_*.php files in the migrations path
-        foreach (glob($dir . '*_*.php') as $file) {
-            $name = basename($file, '.php');
-            // Filter out non-migration files
-            if (preg_match($this->regex, $name)) {
-                // Create migration object using stdClass
-                $migration = new \stdClass();
-                // Get migration version number
-                $migration->version = $this->getMigrationNumber($name);
-                $migration->name = $this->getMigrationName($name);
-                $migration->path = $file;
-
-                // Add to migrations[version]
-                $migrations[$migration->version] = $migration;
-            }
-        }
-        return $migrations;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     *  checks if the list of available migration scripts list are consistent
-     *  if sequential check if no gaps and check if all consistent with migrations table if downgrading
-     *  if timestamp check if consistent with migrations table if downgrading
-     *
-     * @return    bool
-     */
-    protected function CheckMigrations($migrations, $method, $targetversion)
-    {
-         // Check if no migrations found 
-         if (empty($migrations)) {
-            if ($this->silent) return false;
-            throw new \RuntimeException(lang('Migrations.migEmpty') );
-        }
-
-         // Check if $targetversion file is found
-         if ($targetversion != 0 && !array_key_exists($targetversion,$migrations) ) {
-            if ($this->silent) return false;
-            throw new \RuntimeException(lang('Migrations.migNotFound'). $targetversion);
-        }
-
-        ksort($migrations);
-
-        if ($method === 'down'){
-            $history_migrations=$this->getHistory($this->group);
-            $history_size= count($history_migrations) -1;
-        }
-        // Check for sequence gaps
-        $loop = 0;
-        foreach ($migrations as  $migration) {
-            if ($this->type === 'sequential' &&  abs($migration->version - $loop) > 1) {
-                throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version);
-            }
-            // Check if all old migration files are all available to do downgrading
-            if ($method === 'down') {
-                if ($loop <= $history_size && $history_migrations[$loop]['version'] != $migration->version){
-                    throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version);
-                }
-            }
-            $loop ++;
-        }
-        return true;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Set namespace.
-     * Allows other scripts to modify on the fly as needed.
-     *
-     * @param string $namespace
-     *
-     * @return $this
-     */
-    public function setNamespace(string $namespace)
-    {
-        $this->namespace = $namespace;
-
-        return $this;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Set database Group.
-     * Allows other scripts to modify on the fly as needed.
-     *
-     * @param string $group
-     *
-     * @return $this
-     */
-    public function setGroup(string $group)
-    {
-        $this->group = $group;
-
-        return $this;
-    }
-
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Grabs the full migration history from the database.
-     *
-     * @param $group
-     * @return mixed
-     */
-    public function getHistory($group = 'default')
-    {
-        $query = $this->db->table($this->table)
-            ->where('group', $group)
-            ->where('namespace', $this->namespace)
-            ->orderBy('version', 'ASC')
-            ->get();
-
-        if (!$query) return [];
-
-        return $query->getResultArray();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * If $silent == true, then will not throw exceptions and will
-     * attempt to continue gracefully.
-     *
-     * @param bool $silent
-     *
-     * @return $this
-     */
-    public function setSilent(bool $silent)
-    {
-        $this->silent = $silent;
-
-        return $this;
-    }
-
-    //--------------------------------------------------------------------
-
-
-    /**
-     * Extracts the migration number from a filename
-     *
-     * @param    string $migration
-     *
-     * @return    string    Numeric portion of a migration filename
-     */
-    protected function getMigrationNumber($migration)
-    {
-        return sscanf($migration, '%[0-9]+', $number)
-            ? $number : '0';
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Extracts the migration class name from a filename
-     *
-     * @param    string $migration
-     *
-     * @return    string    text portion of a migration filename
-     */
-    protected function getMigrationName($migration)
-    {
-        $parts = explode('_', $migration);
-        array_shift($parts);
-
-        return implode('_', $parts);
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Retrieves current schema version
-     *
-     * @return    string    Current migration version
-     */
-    protected function getVersion()
-    {
-        $row = $this->db->table($this->table)
-            ->select('version')
-            ->where('group', $this->group)
-            ->where('namespace', $this->namespace)
-            ->orderBy('version', 'DESC')
-            ->get()
-            ->getRow();
-
-        return $row ? $row->version : '0';
-    }
-
-     //--------------------------------------------------------------------
-
-    /**
-     * Retrieves current schema version
-     *
-     * @return    string    Current migration version
-     */
-    public function getCliMessages()
-    {
-
-        return $this->cliMessages;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Stores the current schema version.
-     *
-     * @param string $version
-     * @param string $group The database group
-     *
-     * @internal param string $migration Migration reached
-     *
-     */
-    protected function addHistory($version)
-    {
-        $this->db->table($this->table)
-            ->insert([
-                'version' => $version,
-                'group' => $this->group,
-                'namespace' => $this->namespace,
-                'time' => time()
-            ]);
-            if(is_cli()){
-                $this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version;
-            }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Removes a single history
-     *
-     * @param string $version
-     * @param string $group The database group
-     */
-    protected function removeHistory($version)
-    {
-        $this->db->table($this->table)
-            ->where('version', $version)
-            ->where('group', $this->group)
-            ->where('namespace', $this->namespace)
-            ->delete();
-            if(is_cli()){
-                $this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version;
-            }
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Ensures that we have created our migrations table
-     * in the database.
-     */
-    protected function ensureTable()
-    {
-        if ($this->db->tableExists($this->table)) {
-            return;
-        }
-
-        $forge = \Config\Database::forge();
-
-        $forge->addField([
-            'version' => [
-                'type' => 'VARCHAR',
-                'constraint' => 255,
-                'null' => false
-            ],
-            'group' => [
-                'type' => 'VARCHAR',
-                'constraint' => 255,
-                'null' => false
-            ],
-            'namespace' => [
-                'type' => 'VARCHAR',
-                'constraint' => 255,
-                'null' => false
-            ],
-            'time' => [
-                'type' => 'INT',
-                'constraint' => 11,
-                'null' => false
-            ]
-        ]);
-
-        $forge->createTable($this->table, true);
-    }
-
-    //--------------------------------------------------------------------
+	/**
+	 * Whether or not migrations are allowed to run.
+	 *
+	 * @var bool
+	 */
+	protected $enabled = false;
+
+	/**
+	 * The type of migrations to use (sequential or timestamp)
+	 *
+	 * @var string
+	 */
+	protected $type;
+
+	/**
+	 * Name of table to store meta information
+	 *
+	 * @var string
+	 */
+	protected $table;
+
+	/**
+	 * The version that current() will take us to.
+	 *
+	 * @var int
+	 */
+	protected $currentVersion = 0;
+
+	/**
+	 * The Namespace  where migrations can be found.
+	 *
+	 * @var string
+	 */
+	protected $namespace;
+
+	/**
+	 * The database Group to migrate.
+	 *
+	 * @var string
+	 */
+	protected $group;
+
+
+	/**
+	 * The pattern used to locate migration file versions.
+	 *
+	 * @var string
+	 */
+	protected $regex;
+
+	/**
+	 * The main database connection. Used to store
+	 * migration information in.
+	 * @var ConnectionInterface
+	 */
+	protected $db;
+
+	/**
+	 * If true, will continue instead of throwing
+	 * exceptions.
+	 * @var bool
+	 */
+	protected $silent = false;
+
+	/**
+	 * used to return messages for CLI.
+	 *
+	 * @var bool
+	 */
+	protected $cliMessages = array();
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Constructor.
+	 *
+	 * @param BaseConfig $config
+	 * @param \CodeIgniter\Database\ConnectionInterface $db
+	 * @throws ConfigException
+	 */
+	public function __construct(BaseConfig $config, ConnectionInterface $db = null)
+	{
+		$this->enabled = $config->enabled        ?? false;
+		$this->type = $config->type           ?? 'timestamp';
+		$this->table = $config->table          ?? 'migrations';
+		$this->currentVersion = $config->currentVersion ?? 0;
+
+		// Default name space is the app namespace
+		$this->namespace = APP_NAMESPACE;
+
+		// get default database group
+		$config = new \Config\Database();
+		$this->group = $config->defaultGroup;
+		unset($config);
+
+		if (empty($this->table)) {
+			throw new ConfigException(lang('Migrations.migMissingTable'));
+		}
+
+		if (!in_array($this->type, ['sequential', 'timestamp'])) {
+			throw new ConfigException(lang('Migrations.migInvalidType') . $this->type);
+		}
+
+		// Migration basename regex
+		$this->regex = ($this->type === 'timestamp')
+			? '/^\d{14}_(\w+)$/'
+			: '/^\d{3}_(\w+)$/';
+
+		// If no db connection passed in, use
+		// default database group.
+		$this->db = !empty($db)
+			? $db
+			: \Config\Database::connect();
+
+		$this->ensureTable();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Migrate to a schema version
+	 *
+	 * Calls each migration step required to get to the schema version of
+	 * choice
+	 *
+	 * @param    string $targetVersion Target schema version
+	 * @param $group
+	 * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure
+	 * @throws ConfigException
+	 */
+	public function version(string $targetVersion, $namespace = null, $group = null)
+	{
+		if (!$this->enabled) {
+			throw new ConfigException(lang('Migrations.migDisabled'));
+		}
+		// Set Namespace if not null
+		if (!is_null($namespace)) {
+			$this->setNamespace($namespace);
+		}
+
+		// Set database group if not null
+		if (!is_null($group)) {
+			$this->setGroup($group);
+		}
+
+		$migrations = $this->findMigrations();
+
+		if (empty($migrations)) {
+			return true;
+		}
+
+		// Get Namespace current version
+		// Note: We use strings, so that timestamp versions work on 32-bit systems
+		$currentVersion = $this->getVersion();
+		if ($targetVersion > $currentVersion) {
+			// Moving Up
+			$method = 'up';
+			ksort($migrations);
+
+		} else {
+			// Moving Down, apply in reverse order
+			$method = 'down';
+			krsort($migrations);
+		}
+
+		// Check Migration consistency
+		$this->CheckMigrations($migrations,$method, $targetVersion);
+
+		if(is_cli()){
+			$this->cliMessages[]="-) $this->namespace:";
+		}
+
+		// loop migration for each namespace (module)
+		foreach ($migrations as $version => $migration) {
+
+			// Only include migrations within the scoop
+			if (($method === 'up' && $version > $currentVersion && $version <= $targetVersion) OR
+					($method === 'down' && $version <= $currentVersion && $version > $targetVersion)) {
+
+				include_once $migration->path;
+				// Get namespaced class name
+				$class = $this->namespace . '\Database\Migrations\Migration_' . ($migration->name);
+
+				// Validate the migration file structure
+				if (!class_exists($class, false)) {
+					throw new \RuntimeException(sprintf(lang('Migrations.migClassNotFound'), $class));
+				}
+
+				// Forcing migration to selected database group
+				$instance = new $class(\Config\Database::forge($this->group));
+
+				if (!is_callable([$instance, $method])) {
+					throw new \RuntimeException(sprintf(lang('Migrations.migMissingMethod'), $method));
+				}
+
+				$instance->{$method}();
+				if ($method === 'up') $this->addHistory($migration->version);
+				elseif ($method === 'down') $this->removeHistory($migration->version);            
+			}
+		}
+		return true;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Sets the schema to the latest migration
+	 *
+	 * @return    mixed    Current version string on success, FALSE on failure
+	 */
+	public function latest($namespace = null, $group = null)
+	{
+
+		// Set Namespace if not null
+		if (!is_null($namespace)) {
+			$this->setNamespace($namespace);
+		}
+		// Set database group if not null
+		if (!is_null($group)) {
+			$this->setGroup($group);
+		}
+
+		$migrations = $this->findMigrations();       
+
+		$lastMigration = end($migrations)->version;
+
+		// Calculate the last migration step from existing migration
+		// filenames and proceed to the standard version migration       
+		return $this->version($lastMigration);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Sets the schema to the latest migration for all namespaces
+	 *
+	 * @return    void
+	 */
+	public function latestAll($group = null)
+	{
+		// Set database group if not null
+		if (!is_null($group)) {
+			$this->setGroup($group);
+		}
+
+		// Get all namespaces form  PSR4 paths.
+		$config = new Autoload();
+		$namespaces = $config->psr4;
+
+		foreach ($namespaces as $namespace => $path) {    
+
+			$this->setNamespace($namespace);
+			$migrations = $this->findMigrations();
+
+			if (empty($migrations)) {
+				continue;
+			}
+
+			$lastMigration = end($migrations)->version;
+			// No New migrations to add
+			if($lastMigration ==  $this->getVersion()){
+				continue;
+			}
+
+			// Calculate the last migration step from existing migration
+			// filenames and proceed to the standard version migration           
+			$this->version($lastMigration);
+		}
+		return true;
+	}
+	//--------------------------------------------------------------------
+
+	/**
+	 * Sets the (APP_NAMESPACE) schema to $currentVersion in migration config file
+	 *      
+	 *
+	 * @return    mixed    TRUE if no migrations are found, current version string on success, FALSE on failure
+	 */
+	public function current($group = null)
+	{
+		// Set database group if not null
+		if (!is_null($group)) {
+			$this->setGroup($group);
+		}
+
+		return $this->version($this->currentVersion);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Retrieves list of available migration scripts
+	 *
+	 * @return    array    list of migrations as $version for one namespace
+	 */
+	public function findMigrations()
+	{
+		$migrations = [];
+		// Get namespace location form  PSR4 paths.
+		$config = new Autoload();
+
+		$location = $config->psr4[$this->namespace];
+
+		// Setting migration directories.
+		$dir = rtrim($location, '/') . '/Database/Migrations/';
+
+		// Load all *_*.php files in the migrations path
+		foreach (glob($dir . '*_*.php') as $file) {
+			$name = basename($file, '.php');
+			// Filter out non-migration files
+			if (preg_match($this->regex, $name)) {
+				// Create migration object using stdClass
+				$migration = new \stdClass();
+				// Get migration version number
+				$migration->version = $this->getMigrationNumber($name);
+				$migration->name = $this->getMigrationName($name);
+				$migration->path = $file;
+
+				// Add to migrations[version]
+				$migrations[$migration->version] = $migration;
+			}
+		}
+		return $migrations;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 *  checks if the list of available migration scripts list are consistent
+	 *  if sequential check if no gaps and check if all consistent with migrations table if downgrading
+	 *  if timestamp check if consistent with migrations table if downgrading
+	 *
+	 * @return    bool
+	 */
+	protected function CheckMigrations($migrations, $method, $targetversion)
+	{
+		// Check if no migrations found 
+		if (empty($migrations)) {
+			if ($this->silent) return false;
+			throw new \RuntimeException(lang('Migrations.migEmpty') );
+		}
+
+		// Check if $targetversion file is found
+		if ($targetversion != 0 && !array_key_exists($targetversion,$migrations) ) {
+			if ($this->silent) return false;
+			throw new \RuntimeException(lang('Migrations.migNotFound'). $targetversion);
+		}
+
+		ksort($migrations);
+
+		if ($method === 'down'){
+			$history_migrations=$this->getHistory($this->group);
+			$history_size= count($history_migrations) -1;
+		}
+		// Check for sequence gaps
+		$loop = 0;
+		foreach ($migrations as  $migration) {
+			if ($this->type === 'sequential' &&  abs($migration->version - $loop) > 1) {
+				throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version);
+			}
+			// Check if all old migration files are all available to do downgrading
+			if ($method === 'down') {
+				if ($loop <= $history_size && $history_migrations[$loop]['version'] != $migration->version){
+					throw new \RuntimeException(lang('Migration.migGap') . " " . $migration->version);
+				}
+			}
+			$loop ++;
+		}
+		return true;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Set namespace.
+	 * Allows other scripts to modify on the fly as needed.
+	 *
+	 * @param string $namespace
+	 *
+	 * @return $this
+	 */
+	public function setNamespace(string $namespace)
+	{
+		$this->namespace = $namespace;
+
+		return $this;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Set database Group.
+	 * Allows other scripts to modify on the fly as needed.
+	 *
+	 * @param string $group
+	 *
+	 * @return $this
+	 */
+	public function setGroup(string $group)
+	{
+		$this->group = $group;
+
+		return $this;
+	}
+
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Grabs the full migration history from the database.
+	 *
+	 * @param $group
+	 * @return mixed
+	 */
+	public function getHistory($group = 'default')
+	{
+		$query = $this->db->table($this->table)
+			->where('group', $group)
+			->where('namespace', $this->namespace)
+			->orderBy('version', 'ASC')
+			->get();
+
+		if (!$query) return [];
+
+		return $query->getResultArray();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * If $silent == true, then will not throw exceptions and will
+	 * attempt to continue gracefully.
+	 *
+	 * @param bool $silent
+	 *
+	 * @return $this
+	 */
+	public function setSilent(bool $silent)
+	{
+		$this->silent = $silent;
+
+		return $this;
+	}
+
+	//--------------------------------------------------------------------
+
+
+	/**
+	 * Extracts the migration number from a filename
+	 *
+	 * @param    string $migration
+	 *
+	 * @return    string    Numeric portion of a migration filename
+	 */
+	protected function getMigrationNumber($migration)
+	{
+		return sscanf($migration, '%[0-9]+', $number)
+			? $number : '0';
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Extracts the migration class name from a filename
+	 *
+	 * @param    string $migration
+	 *
+	 * @return    string    text portion of a migration filename
+	 */
+	protected function getMigrationName($migration)
+	{
+		$parts = explode('_', $migration);
+		array_shift($parts);
+
+		return implode('_', $parts);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Retrieves current schema version
+	 *
+	 * @return    string    Current migration version
+	 */
+	protected function getVersion()
+	{
+		$row = $this->db->table($this->table)
+			->select('version')
+			->where('group', $this->group)
+			->where('namespace', $this->namespace)
+			->orderBy('version', 'DESC')
+			->get()
+			->getRow();
+
+		return $row ? $row->version : '0';
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Retrieves current schema version
+	 *
+	 * @return    string    Current migration version
+	 */
+	public function getCliMessages()
+	{
+
+		return $this->cliMessages;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Stores the current schema version.
+	 *
+	 * @param string $version
+	 * @param string $group The database group
+	 *
+	 * @internal param string $migration Migration reached
+	 *
+	 */
+	protected function addHistory($version)
+	{
+		$this->db->table($this->table)
+			->insert([
+					'version' => $version,
+					'group' => $this->group,
+					'namespace' => $this->namespace,
+					'time' => time()
+			]);
+		if(is_cli()){
+			$this->cliMessages[]="\t- " . lang('Migrations.migAdded') . $version;
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Removes a single history
+	 *
+	 * @param string $version
+	 * @param string $group The database group
+	 */
+	protected function removeHistory($version)
+	{
+		$this->db->table($this->table)
+			->where('version', $version)
+			->where('group', $this->group)
+			->where('namespace', $this->namespace)
+			->delete();
+		if(is_cli()){
+			$this->cliMessages[]="\t- " . lang('Migrations.migRemoved') . $version;
+		}
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Ensures that we have created our migrations table
+	 * in the database.
+	 */
+	protected function ensureTable()
+	{
+		if ($this->db->tableExists($this->table)) {
+			return;
+		}
+
+		$forge = \Config\Database::forge();
+
+		$forge->addField([
+				'version' => [
+				'type' => 'VARCHAR',
+				'constraint' => 255,
+				'null' => false
+				],
+				'group' => [
+				'type' => 'VARCHAR',
+				'constraint' => 255,
+				'null' => false
+				],
+				'namespace' => [
+				'type' => 'VARCHAR',
+				'constraint' => 255,
+				'null' => false
+				],
+				'time' => [
+				'type' => 'INT',
+				'constraint' => 11,
+				'null' => false
+				]
+				]);
+
+				$forge->createTable($this->table, true);
+	}
+
+	//--------------------------------------------------------------------
 
 }
diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php
index ef71a6ef9521..2553ccf150c0 100644
--- a/system/Database/MySQLi/Connection.php
+++ b/system/Database/MySQLi/Connection.php
@@ -121,21 +121,21 @@ public function connect($persistent = false)
 			if ($this->strictOn)
 			{
 				$this->mysqli->options(MYSQLI_INIT_COMMAND,
-					'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")');
+						'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")');
 			}
 			else
 			{
 				$this->mysqli->options(MYSQLI_INIT_COMMAND,
-					'SET SESSION sql_mode =
-					REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
-					@@sql_mode,
-					"STRICT_ALL_TABLES,", ""),
-					",STRICT_ALL_TABLES", ""),
-					"STRICT_ALL_TABLES", ""),
-					"STRICT_TRANS_TABLES,", ""),
-					",STRICT_TRANS_TABLES", ""),
-					"STRICT_TRANS_TABLES", "")'
-				);
+						'SET SESSION sql_mode =
+						REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+												@@sql_mode,
+												"STRICT_ALL_TABLES,", ""),
+											",STRICT_ALL_TABLES", ""),
+										"STRICT_ALL_TABLES", ""),
+									"STRICT_TRANS_TABLES,", ""),
+								",STRICT_TRANS_TABLES", ""),
+							"STRICT_TRANS_TABLES", "")'
+						);
 			}
 		}
 
@@ -155,7 +155,7 @@ public function connect($persistent = false)
 					if ($this->encrypt['ssl_verify'])
 					{
 						defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') &&
-						$this->mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
+							$this->mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
 					}
 					// Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT
 					// to FALSE didn't do anything, so PHP 5.6.16 introduced yet another
@@ -171,26 +171,26 @@ public function connect($persistent = false)
 
 				$client_flags |= MYSQLI_CLIENT_SSL;
 				$this->mysqli->ssl_set(
-					isset($ssl['key']) ? $ssl['key'] : null,
-					isset($ssl['cert']) ? $ssl['cert'] : null,
-					isset($ssl['ca']) ? $ssl['ca'] : null,
-					isset($ssl['capath']) ? $ssl['capath'] : null,
-					isset($ssl['cipher']) ? $ssl['cipher'] : null
-				);
+						isset($ssl['key']) ? $ssl['key'] : null,
+						isset($ssl['cert']) ? $ssl['cert'] : null,
+						isset($ssl['ca']) ? $ssl['ca'] : null,
+						isset($ssl['capath']) ? $ssl['capath'] : null,
+						isset($ssl['cipher']) ? $ssl['cipher'] : null
+						);
 			}
 		}
 
 		if ($this->mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket,
-			$client_flags)
-		)
+					$client_flags)
+		   )
 		{
 			// Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails
 			if (
-				($client_flags & MYSQLI_CLIENT_SSL)
-				&& version_compare($this->mysqli->client_info, '5.7.3', '<=')
-				&& empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")
-				                      ->fetch_object()->Value)
-			)
+					($client_flags & MYSQLI_CLIENT_SSL)
+					&& version_compare($this->mysqli->client_info, '5.7.3', '<=')
+					&& empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'")
+						->fetch_object()->Value)
+			   )
 			{
 				$this->mysqli->close();
 				$message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!';
@@ -239,15 +239,15 @@ public function reconnect()
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Close the database connection.
-     */
-    protected function _close()
-    {
-        $this->connID->close();
-    }
+	/**
+	 * Close the database connection.
+	 */
+	protected function _close()
+	{
+		$this->connID->close();
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Select a specific database table to use.
@@ -288,9 +288,9 @@ public function getVersion()
 		}
 
 		if (empty($this->mysqli))
-        {
-            $this->initialize();
-        }
+		{
+			$this->initialize();
+		}
 
 		return $this->dataCache['version'] = $this->mysqli->server_info;
 	}
@@ -420,9 +420,9 @@ public function _fieldData(string $table)
 			$retval[$i]->name		= $query[$i]->Field;
 
 			sscanf($query[$i]->Type, '%[a-z](%d)',
-				$retval[$i]->type,
-				$retval[$i]->max_length
-			);
+					$retval[$i]->type,
+					$retval[$i]->max_length
+				  );
 
 			$retval[$i]->default		= $query[$i]->Default;
 			$retval[$i]->primary_key	= (int) ($query[$i]->Key === 'PRI');
@@ -498,9 +498,9 @@ public function error()
 		if ( ! empty($this->mysqli->connect_errno))
 		{
 			return array(
-				'code' => $this->mysqli->connect_errno,
-				'message' => $this->_mysqli->connect_error
-			);
+					'code' => $this->mysqli->connect_errno,
+					'message' => $this->_mysqli->connect_error
+					);
 		}
 
 		return array('code' => $this->connID->errno, 'message' => $this->connID->error);
@@ -520,53 +520,53 @@ public function insertID()
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Begin Transaction
-     *
-     * @return	bool
-     */
-    protected function _transBegin():bool
-    {
-        $this->connID->autocommit(false);
-
-        return $this->connID->begin_transaction();
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Commit Transaction
-     *
-     * @return	bool
-     */
-    protected function _transCommit(): bool
-    {
-        if ($this->connID->commit())
-        {
-            $this->connID->autocommit(true);
-            return true;
-        }
-
-        return false;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Rollback Transaction
-     *
-     * @return	bool
-     */
-    protected function _transRollback(): bool
-    {
-        if ($this->connID->rollback())
-        {
-            $this->connID->autocommit(true);
-            return true;
-        }
-
-        return false;
-    }
-
-    //--------------------------------------------------------------------
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _transBegin():bool
+	{
+		$this->connID->autocommit(false);
+
+		return $this->connID->begin_transaction();
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _transCommit(): bool
+	{
+		if ($this->connID->commit())
+		{
+			$this->connID->autocommit(true);
+			return true;
+		}
+
+		return false;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _transRollback(): bool
+	{
+		if ($this->connID->rollback())
+		{
+			$this->connID->autocommit(true);
+			return true;
+		}
+
+		return false;
+	}
+
+	//--------------------------------------------------------------------
 }
diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php
index 5b7d4a8d5206..9b203e9716d8 100644
--- a/system/Database/Postgre/Connection.php
+++ b/system/Database/Postgre/Connection.php
@@ -83,25 +83,25 @@ public function connect($persistent = false)
 			$this->buildDSN();
 		}
 
-        // Strip pgsql if exists
-        if (mb_strpos($this->DSN, 'pgsql:') === 0)
-        {
-            $this->DSN = mb_substr($this->DSN, 6);
-        }
+		// Strip pgsql if exists
+		if (mb_strpos($this->DSN, 'pgsql:') === 0)
+		{
+			$this->DSN = mb_substr($this->DSN, 6);
+		}
 
-        // Convert semicolons to spaces.
-        $this->DSN = str_replace(';', ' ', $this->DSN);
+		// Convert semicolons to spaces.
+		$this->DSN = str_replace(';', ' ', $this->DSN);
 
 		$this->connID = $persistent === true
 			? pg_pconnect($this->DSN)
-            : pg_connect($this->DSN);
+			: pg_connect($this->DSN);
 
 		if ($this->connID !== false)
 		{
 			if ($persistent === true
-				&& pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD
-				&& pg_ping($this->connID) === false
-			)
+					&& pg_connection_status($this->connID) === PGSQL_CONNECTION_BAD
+					&& pg_ping($this->connID) === false
+			   )
 			{
 				return false;
 			}
@@ -135,15 +135,15 @@ public function reconnect()
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Close the database connection.
-     */
-    protected function _close()
-    {
-        pg_close($this->connID);
-    }
+	/**
+	 * Close the database connection.
+	 */
+	protected function _close()
+	{
+		pg_close($this->connID);
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Select a specific database table to use.
@@ -331,7 +331,7 @@ public function _indexData(string $table)
 		$sql = 'SELECT "indexname", "indexdef"
 			FROM "pg_indexes"
 			WHERE LOWER("tablename") = '.$this->escape(strtolower($table)).'
-			  AND "schemaname" = '.$this->escape('public');
+			AND "schemaname" = '.$this->escape('public');
 
 		if (($query = $this->query($sql)) === false)
 		{
@@ -486,39 +486,39 @@ protected function setClientEncoding($charset)
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Begin Transaction
-     *
-     * @return	bool
-     */
-    protected function _transBegin(): bool
-    {
-        return (bool)pg_query($this->connID, 'BEGIN');
-    }
-
-    // --------------------------------------------------------------------
-
-    /**
-     * Commit Transaction
-     *
-     * @return	bool
-     */
-    protected function _transCommit(): bool
-    {
-        return (bool)pg_query($this->connID, 'COMMIT');
-    }
-
-    // --------------------------------------------------------------------
-
-    /**
-     * Rollback Transaction
-     *
-     * @return	bool
-     */
-    protected function _transRollback(): bool
-    {
-        return (bool)pg_query($this->connID, 'ROLLBACK');
-    }
-
-    // --------------------------------------------------------------------
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _transBegin(): bool
+	{
+		return (bool)pg_query($this->connID, 'BEGIN');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _transCommit(): bool
+	{
+		return (bool)pg_query($this->connID, 'COMMIT');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	protected function _transRollback(): bool
+	{
+		return (bool)pg_query($this->connID, 'ROLLBACK');
+	}
+
+	// --------------------------------------------------------------------
 }
diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php
index 412533836063..151057bc4e73 100644
--- a/system/Database/Postgre/PreparedQuery.php
+++ b/system/Database/Postgre/PreparedQuery.php
@@ -78,8 +78,8 @@ public function _prepare(string $sql, array $options = [])
 		$sql = $this->parameterize($sql);
 
 		// Update the query object since the parameters are slightly different
-        // than what was put in.
-        $this->query->setQuery($sql);
+		// than what was put in.
+		$this->query->setQuery($sql);
 
 		if (! $this->statement = pg_prepare($this->db->connID, $this->name, $sql))
 		{
@@ -140,11 +140,11 @@ public function parameterize(string $sql): string
 		$count = 0;
 
 		$sql = preg_replace_callback('/\?/', function($matches) use (&$count){
-			$count++;
-			return "\${$count}";
-		}, $sql);
+				$count++;
+				return "\${$count}";
+				}, $sql);
 
-	    return $sql;
+		return $sql;
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Database/Query.php b/system/Database/Query.php
index 54136bfe456e..4e6c40fb7b66 100644
--- a/system/Database/Query.php
+++ b/system/Database/Query.php
@@ -120,7 +120,7 @@ class Query implements QueryInterface
 	 */
 	public function __construct(&$db)
 	{
-	    $this->db = $db;
+		$this->db = $db;
 	}
 
 	//--------------------------------------------------------------------
@@ -157,7 +157,7 @@ public function setQuery(string $sql, $binds=null)
 	 */
 	public function setBinds(array $binds)
 	{
-	    $this->binds = $binds;
+		$this->binds = $binds;
 
 		return $this;
 	}
@@ -304,8 +304,8 @@ public function getErrorMessage(): string
 	public function isWriteType(): bool
 	{
 		return (bool)preg_match(
-			'/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s/i',
-			$this->originalQueryString);
+				'/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s/i',
+				$this->originalQueryString);
 	}
 
 	//--------------------------------------------------------------------
@@ -336,7 +336,7 @@ public function swapPrefix(string $orig, string $swap)
 	 */
 	public function getOriginalQuery()
 	{
-	    return $this->originalQueryString;
+		return $this->originalQueryString;
 	}
 
 	//--------------------------------------------------------------------
@@ -351,9 +351,9 @@ protected function compileBinds()
 		$hasNamedBinds  = strpos($sql, ':') !== false;
 
 		if (empty($this->binds) || empty($this->bindMarker) ||
-		    (strpos($sql, $this->bindMarker) === false &&
-		     $hasNamedBinds === false)
-		)
+				(strpos($sql, $this->bindMarker) === false &&
+				 $hasNamedBinds === false)
+		   )
 		{
 			return;
 		}
@@ -405,24 +405,24 @@ protected function matchNamedBinds(string $sql, array $binds)
 		{
 			$escapedValue = $this->db->escape($value);
 
-            // In order to correctly handle backlashes in saved strings
-            // we will need to preg_quote, so remove the wrapping escape characters
-            // otherwise it will get escaped.
-            if (is_array($value))
-            {
-                foreach ($value as &$item)
-                {
-                    $item = preg_quote($item);
-                }
-
-                $escapedValue = '('.implode(',', $escapedValue).')';
-            }
-            else
-            {
-                $escapedValue = strpos($escapedValue, '\\') !== false
-                    ? preg_quote(trim($escapedValue, $this->db->escapeChar))
-                    : $escapedValue;
-            }
+			// In order to correctly handle backlashes in saved strings
+			// we will need to preg_quote, so remove the wrapping escape characters
+			// otherwise it will get escaped.
+			if (is_array($value))
+			{
+				foreach ($value as &$item)
+				{
+					$item = preg_quote($item);
+				}
+
+				$escapedValue = '('.implode(',', $escapedValue).')';
+			}
+			else
+			{
+				$escapedValue = strpos($escapedValue, '\\') !== false
+					? preg_quote(trim($escapedValue, $this->db->escapeChar))
+					: $escapedValue;
+			}
 
 			$sql = preg_replace('/:'.$placeholder.'(?!\w)/', $escapedValue, $sql);
 		}
@@ -446,10 +446,10 @@ protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, i
 		if ($c = preg_match_all("/'[^']*'/i", $sql, $matches))
 		{
 			$c = preg_match_all('/'.preg_quote($this->bindMarker, '/').'/i',
-				str_replace($matches[0],
-					str_replace($this->bindMarker, str_repeat(' ', $ml), $matches[0]),
-					$sql, $c),
-				$matches, PREG_OFFSET_CAPTURE);
+					str_replace($matches[0],
+						str_replace($this->bindMarker, str_repeat(' ', $ml), $matches[0]),
+						$sql, $c),
+					$matches, PREG_OFFSET_CAPTURE);
 
 			// Bind values' count must match the count of markers in the query
 			if ($bindCount !== $c)
@@ -459,7 +459,7 @@ protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, i
 		}
 		// Number of binds must match bindMarkers in the string.
 		else if (($c = preg_match_all('/'.preg_quote($this->bindMarker, '/').'/i', $sql, $matches,
-				PREG_OFFSET_CAPTURE)) !== $bindCount)
+						PREG_OFFSET_CAPTURE)) !== $bindCount)
 		{
 			return $sql;
 		}
@@ -488,7 +488,7 @@ protected function matchSimpleBinds(string $sql, array $binds, int $bindCount, i
 	 */
 	public function __toString()
 	{
-	    return $this->getQuery();
+		return $this->getQuery();
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php
index e5c1deab3921..f9a07ef5eab6 100644
--- a/system/Database/Seeder.php
+++ b/system/Database/Seeder.php
@@ -90,7 +90,7 @@ class Seeder
 	 */
 	public function __construct(BaseConfig $config, BaseConnection $db = null)
 	{
-	    $this->seedPath = $config->filesPath ?? APPPATH.'Database/';
+		$this->seedPath = $config->filesPath ?? APPPATH.'Database/';
 
 		if (empty($this->seedPath))
 		{
@@ -125,35 +125,35 @@ public function __construct(BaseConfig $config, BaseConnection $db = null)
 	 */
 	public function call(string $class)
 	{
-	    if (empty($class))
-	    {
+		if (empty($class))
+		{
 			throw new \InvalidArgumentException('No Seeder was specified.');
-	    }
+		}
 
 		$path = str_replace('.php', '', $class).'.php';
 
-        // If we have namespaced class, simply try to load it.
-        if (strpos($class, '\\') !== false)
-        {
-            $seeder = new $class($this->config);
-        }
-        // Otherwise, try to load the class manually.
-        else
-        {
-            $path = $this->seedPath.$path;
-
-            if (! is_file($path))
-            {
-                throw new \InvalidArgumentException('The specified Seeder is not a valid file: '. $path);
-            }
-
-            if (! class_exists($class, false))
-            {
-                require $path;
-            }
-
-            $seeder = new $class($this->config);
-        }
+		// If we have namespaced class, simply try to load it.
+		if (strpos($class, '\\') !== false)
+		{
+			$seeder = new $class($this->config);
+		}
+		// Otherwise, try to load the class manually.
+		else
+		{
+			$path = $this->seedPath.$path;
+
+			if (! is_file($path))
+			{
+				throw new \InvalidArgumentException('The specified Seeder is not a valid file: '. $path);
+			}
+
+			if (! class_exists($class, false))
+			{
+				require $path;
+			}
+
+			$seeder = new $class($this->config);
+		}
 
 		$seeder->run();
 
@@ -192,7 +192,7 @@ public function setPath(string $path)
 	 */
 	public function setSilent(bool $silent)
 	{
-	    $this->silent = $silent;
+		$this->silent = $silent;
 
 		return $this;
 	}
diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php
index e185fcf17fcb..3fa557696732 100644
--- a/system/Debug/Toolbar/Collectors/Database.php
+++ b/system/Debug/Toolbar/Collectors/Database.php
@@ -79,13 +79,13 @@ class Database extends BaseCollector
 	 */
 	protected $connections;
 
-    /**
-     * The query instances that have been collected
-     * through the DBQuery Hook.
-     *
-     * @var array
-     */
-    protected static $queries = [];
+	/**
+	 * The query instances that have been collected
+	 * through the DBQuery Hook.
+	 *
+	 * @var array
+	 */
+	protected static $queries = [];
 
 
 	//--------------------------------------------------------------------
@@ -100,20 +100,20 @@ public function __construct()
 
 	//--------------------------------------------------------------------
 
-    /**
-     * The static method used during Hooks to collect
-     * data.
-     *
-     * @param \CodeIgniter\Database\Query $query
-     *
-     * @internal param $ array \CodeIgniter\Database\Query
-     */
-    public static function collect(Query $query)
-    {
-        static::$queries[] = $query;
-    }
-
-    //--------------------------------------------------------------------
+	/**
+	 * The static method used during Hooks to collect
+	 * data.
+	 *
+	 * @param \CodeIgniter\Database\Query $query
+	 *
+	 * @internal param $ array \CodeIgniter\Database\Query
+	 */
+	public static function collect(Query $query)
+	{
+		static::$queries[] = $query;
+	}
+
+	//--------------------------------------------------------------------
 
 	/**
 	 * Returns timeline data formatted for the toolbar.
@@ -135,15 +135,15 @@ protected function formatTimelineData(): array
 			];
 		}
 
-        foreach (static::$queries as $query)
-        {
-            $data[] = [
-                'name' => 'Query',
-                'component' => 'Database',
-                'start' => $query->getStartTime(true),
-                'duration' => $query->getDuration()
-            ];
-        }
+		foreach (static::$queries as $query)
+		{
+			$data[] = [
+				'name' => 'Query',
+				'component' => 'Database',
+				'start' => $query->getStartTime(true),
+				'duration' => $query->getDuration()
+			];
+		}
 
 		return $data;
 	}
@@ -157,35 +157,35 @@ protected function formatTimelineData(): array
 	 */
 	public function display(): string
 	{
-        // Key words we want bolded
-        $highlight = ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'AND', 'LEFT JOIN', 'ORDER BY', 'GROUP BY',
-            'LIMIT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'OR ', 'HAVING', 'OFFSET', 'NOT IN',
-            'IN', 'LIKE', 'NOT LIKE', 'COUNT', 'MAX', 'MIN', 'ON', 'AS', 'AVG', 'SUM', '(', ')'
-        ];
+		// Key words we want bolded
+		$highlight = ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'AND', 'LEFT JOIN', 'ORDER BY', 'GROUP BY',
+			'LIMIT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'OR ', 'HAVING', 'OFFSET', 'NOT IN',
+			'IN', 'LIKE', 'NOT LIKE', 'COUNT', 'MAX', 'MIN', 'ON', 'AS', 'AVG', 'SUM', '(', ')'
+		];
 
 		$parser = \Config\Services::parser(BASEPATH.'Debug/Toolbar/Views/');
 
 		$data = [
-		    'queries' => []
-        ];
+			'queries' => []
+		];
 
 		foreach (static::$queries as $query)
-        {
-            $sql = $query->getQuery();
+		{
+			$sql = $query->getQuery();
 
-            foreach ($highlight as $term)
-            {
-                $sql = str_replace($term, "{$term}", $sql);
-            }
+			foreach ($highlight as $term)
+			{
+				$sql = str_replace($term, "{$term}", $sql);
+			}
 
-            $data['queries'][] = [
-                'duration' => $query->getDuration(5) * 1000,
-                'sql' => $sql
-            ];
-        }
+			$data['queries'][] = [
+				'duration' => $query->getDuration(5) * 1000,
+				'sql' => $sql
+			];
+		}
 
 		$output = $parser->setData($data)
-                         ->render('_database.tpl');
+			->render('_database.tpl');
 
 		return $output;
 	}
@@ -200,7 +200,7 @@ public function display(): string
 	public function getTitleDetails(): string
 	{
 		return '('.count(static::$queries).' Queries across '.count($this->connections).' Connection'.
-		       (count($this->connections) > 1 ? 's' : '').')';
+			(count($this->connections) > 1 ? 's' : '').')';
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Debug/Toolbar/Collectors/Files.php b/system/Debug/Toolbar/Collectors/Files.php
index e3d384c60b64..6eb64f922c75 100644
--- a/system/Debug/Toolbar/Collectors/Files.php
+++ b/system/Debug/Toolbar/Collectors/Files.php
@@ -85,13 +85,13 @@ public function getTitleDetails(): string
 	 *
 	 * @return string
 	 */
-	 public function display(): string
-	 {
-        $parser = \Config\Services::parser(BASEPATH.'Debug/Toolbar/Views/');
+	public function display(): string
+	{
+		$parser = \Config\Services::parser(BASEPATH.'Debug/Toolbar/Views/');
 
-        $rawFiles = get_included_files();
-        $coreFiles = [];
-        $userFiles = [];
+		$rawFiles = get_included_files();
+		$coreFiles = [];
+		$userFiles = [];
 
 		foreach ($rawFiles as $file)
 		{
@@ -100,16 +100,16 @@ public function display(): string
 			if (strpos($path, 'BASEPATH') !== false)
 			{
 				$coreFiles[] = [
-				    'name' => basename($file),
-                    'path' => $path
-                ];
+					'name' => basename($file),
+					'path' => $path
+				];
 			}
 			else
 			{
-                $userFiles[] = [
-                    'name' => basename($file),
-                    'path' => $path
-                ];
+				$userFiles[] = [
+					'name' => basename($file),
+					'path' => $path
+				];
 			}
 		}
 
@@ -117,11 +117,11 @@ public function display(): string
 		sort($coreFiles);
 
 		return $parser->setData([
-                'coreFiles' => $coreFiles,
-                'userFiles' => $userFiles,
-            ])
-            ->render('_files.tpl');
-	 }
+				'coreFiles' => $coreFiles,
+				'userFiles' => $userFiles,
+		])
+			->render('_files.tpl');
+	}
 
 	//--------------------------------------------------------------------
 }
diff --git a/system/Debug/Toolbar/Collectors/Logs.php b/system/Debug/Toolbar/Collectors/Logs.php
index ea3116afca36..e76f2a7f238d 100644
--- a/system/Debug/Toolbar/Collectors/Logs.php
+++ b/system/Debug/Toolbar/Collectors/Logs.php
@@ -77,7 +77,7 @@ class Logs extends BaseCollector
 	 */
 	public function display(): string
 	{
-        $parser = \Config\Services::parser(BASEPATH.'Debug/Toolbar/Views/');
+		$parser = \Config\Services::parser(BASEPATH.'Debug/Toolbar/Views/');
 
 		$logger = Services::logger(true);
 		$logs = $logger->logCache;
@@ -88,9 +88,9 @@ public function display(): string
 		}
 
 		return $parser->setData([
-                'logs' => $logs
-            ])
-            ->render('_logs.tpl');
+				'logs' => $logs
+		])
+			->render('_logs.tpl');
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php
index 8c76a9d1e6f9..02b6d16f600b 100644
--- a/system/Debug/Toolbar/Collectors/Routes.php
+++ b/system/Debug/Toolbar/Collectors/Routes.php
@@ -70,14 +70,14 @@ class Routes extends BaseCollector
 	//--------------------------------------------------------------------
 
 	/**
-	* Builds and returns the HTML needed to fill a tab to display
-	* within the Debug Bar
-	*
-	* @return string
-	*/
+	 * Builds and returns the HTML needed to fill a tab to display
+	 * within the Debug Bar
+	 *
+	 * @return string
+	 */
 	public function display(): string
 	{
-        $parser = \Config\Services::parser();
+		$parser = \Config\Services::parser();
 
 		$rawRoutes = Services::routes(true);
 		$router = Services::router(null, true);
@@ -91,46 +91,46 @@ public function display(): string
 		$method = is_callable($router->controllerName()) ? new \ReflectionFunction($router->controllerName()) : new \ReflectionMethod($router->controllerName(), $router->methodName());
 		$rawParams = $method->getParameters();
 
-        $params = [];
-        foreach ($rawParams as $key => $param)
-        {
-            $params[] = [
-                'name'  => $param->getName(),
-                'value' => $router->params()[$key] ?:
-                    "<empty> | default: ". var_export($param->getDefaultValue(), true)
-            ];
-        }
+		$params = [];
+		foreach ($rawParams as $key => $param)
+		{
+			$params[] = [
+				'name'  => $param->getName(),
+				'value' => $router->params()[$key] ?:
+					"<empty> | default: ". var_export($param->getDefaultValue(), true)
+			];
+		}
 
 		$matchedRoute = [
-		    [
-                'directory'  => $router->directory(),
-                'controller' => $router->controllerName(),
-                'method'     => $router->methodName(),
-                'paramCount' => count($router->params()),
-                'truePCount' => count($params),
-                'params'     => $params ?? []
-            ]
-        ];
-
-        /*
-         * Defined Routes
-         */
+			[
+				'directory'  => $router->directory(),
+			'controller' => $router->controllerName(),
+			'method'     => $router->methodName(),
+			'paramCount' => count($router->params()),
+			'truePCount' => count($params),
+			'params'     => $params ?? []
+			]
+		];
+
+		/*
+		 * Defined Routes
+		 */
 		$rawRoutes = $rawRoutes->getRoutes();
 		$routes    = [];
 
 		foreach ($rawRoutes as $from => $to)
 		{
-		    $routes[] = [
-		        'from' => $from,
-                'to'   => $to
-            ];
+			$routes[] = [
+				'from' => $from,
+				'to'   => $to
+			];
 		}
 
 		return $parser->setData([
-            'matchedRoute' => $matchedRoute,
-            'routes' => $routes
-        ])
-            ->render('CodeIgniter\Debug\Toolbar\Views\_routes.tpl');
+				'matchedRoute' => $matchedRoute,
+				'routes' => $routes
+		])
+			->render('CodeIgniter\Debug\Toolbar\Views\_routes.tpl');
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Model.php b/system/Model.php
index 37e1d75f189f..87d453099381 100644
--- a/system/Model.php
+++ b/system/Model.php
@@ -191,37 +191,37 @@ class Model
 	 */
 	protected $builder;
 
-    /**
-     * Rules used to validate data in insert, update, and save methods.
-     * The array must match the format of data passed to the Validation
-     * library.
-     *
-     * @var array
-     */
-    protected $validationRules = [];
-
-    /**
-     * Contains any custom error messages to be
-     * used during data validation.
-     *
-     * @var array|null
-     */
-    protected $validationMessages = null;
-
-    /**
-     * Skip the model's validation. Used in conjunction with skipValidation()
-     * to skip data validation for any future calls.
-     *
-     * @var bool
-     */
-    protected $skipValidation = false;
-
-    /**
-     * Our validator instance.
-     *
-     * @var \CodeIgniter\Validation\ValidationInterface
-     */
-    protected $validation;
+	/**
+	 * Rules used to validate data in insert, update, and save methods.
+	 * The array must match the format of data passed to the Validation
+	 * library.
+	 *
+	 * @var array
+	 */
+	protected $validationRules = [];
+
+	/**
+	 * Contains any custom error messages to be
+	 * used during data validation.
+	 *
+	 * @var array|null
+	 */
+	protected $validationMessages = null;
+
+	/**
+	 * Skip the model's validation. Used in conjunction with skipValidation()
+	 * to skip data validation for any future calls.
+	 *
+	 * @var bool
+	 */
+	protected $skipValidation = false;
+
+	/**
+	 * Our validator instance.
+	 *
+	 * @var \CodeIgniter\Validation\ValidationInterface
+	 */
+	protected $validation;
 
 	//--------------------------------------------------------------------
 
@@ -253,11 +253,11 @@ public function __construct(ConnectionInterface &$db = null, BaseConfig $config
 		$this->tempReturnType     = $this->returnType;
 		$this->tempUseSoftDeletes = $this->useSoftDeletes;
 
-        if (is_null($validation))
-        {
-            $validation = \Config\Services::validation();
-        }
-        $this->validation = $validation;
+		if (is_null($validation))
+		{
+			$validation = \Config\Services::validation();
+		}
+		$this->validation = $validation;
 	}
 
 	//--------------------------------------------------------------------
@@ -286,13 +286,13 @@ public function find($id)
 		if (is_array($id))
 		{
 			$row = $builder->whereIn($this->primaryKey, $id)
-			               ->get();
+				->get();
 			$row = $row->getResult($this->tempReturnType);
 		}
 		else
 		{
 			$row = $builder->where($this->primaryKey, $id)
-			               ->get();
+				->get();
 
 			$row = $row->getFirstRow($this->tempReturnType);
 		}
@@ -323,7 +323,7 @@ public function findWhere($key, $value = null)
 		}
 
 		$rows = $builder->where($key, $value)
-		                ->get();
+			->get();
 
 		$rows = $rows->getResult($this->tempReturnType);
 
@@ -354,7 +354,7 @@ public function findAll(int $limit = 0, int $offset = 0)
 		}
 
 		$row = $builder->limit($limit, $offset)
-		               ->get();
+			->get();
 
 		$row = $row->getResult($this->tempReturnType);
 
@@ -389,7 +389,7 @@ public function first()
 		}
 
 		$row = $builder->limit(1, 0)
-		               ->get();
+			->get();
 
 		$row = $row->getFirstRow($this->tempReturnType);
 
@@ -441,7 +441,7 @@ public function encodeID($id)
 		// method, so simple base64 encoding will work for now.
 		if (! is_numeric($id))
 		{
-	        return '=_'.base64_encode($id);
+			return '=_'.base64_encode($id);
 		}
 
 		$id = (int)$id;
@@ -546,15 +546,15 @@ protected function getHash($str, $len)
 	 */
 	public function save($data)
 	{
-	    $saveData = $data;
+		$saveData = $data;
 
-	    // If $data is using a custom class with public or protected
-        // properties representing the table elements, we need to grab
-        // them as an array.
-        if (is_object($data) && ! $data instanceof \stdClass)
-        {
-            $data = $this->classToArray($data);
-        }
+		// If $data is using a custom class with public or protected
+		// properties representing the table elements, we need to grab
+		// them as an array.
+		if (is_object($data) && ! $data instanceof \stdClass)
+		{
+			$data = $this->classToArray($data);
+		}
 
 		if (is_object($data) && isset($data->{$this->primaryKey}))
 		{
@@ -564,53 +564,53 @@ public function save($data)
 		{
 			$response = $this->update($data[$this->primaryKey], $data);
 		}
-        else
-        {
-            $response = $this->insert($data);
-        }
-
-        // If it was an Entity class, check it for an onSave method.
-        if (is_object($saveData) && ! $saveData instanceof \stdClass)
-        {
-            if (method_exists($saveData, 'onSave'))
-            {
-                $saveData->onSave();
-            }
-        }
-
-        return $response;
+		else
+		{
+			$response = $this->insert($data);
+		}
+
+		// If it was an Entity class, check it for an onSave method.
+		if (is_object($saveData) && ! $saveData instanceof \stdClass)
+		{
+			if (method_exists($saveData, 'onSave'))
+			{
+				$saveData->onSave();
+			}
+		}
+
+		return $response;
 	}
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Takes a class an returns an array of it's public and protected
-     * properties as an array suitable for use in creates and updates.
-     *
-     * @param $data
-     *
-     * @return array
-     */
-    protected function classToArray($data): array
-    {
-        $mirror = new \ReflectionClass($data);
-        $props  = $mirror->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);
-
-        $properties = [];
-
-        // Loop over each property,
-        // saving the name/value in a new array we can return.
-        foreach ($props as $prop)
-        {
-            // Must make protected values accessible.
-            $prop->setAccessible(true);
-            $properties[$prop->getName()] = $prop->getValue($data);
-        }
-
-        return $properties;
-    }
-
-    //--------------------------------------------------------------------
+	/**
+	 * Takes a class an returns an array of it's public and protected
+	 * properties as an array suitable for use in creates and updates.
+	 *
+	 * @param $data
+	 *
+	 * @return array
+	 */
+	protected function classToArray($data): array
+	{
+		$mirror = new \ReflectionClass($data);
+		$props  = $mirror->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);
+
+		$properties = [];
+
+		// Loop over each property,
+		// saving the name/value in a new array we can return.
+		foreach ($props as $prop)
+		{
+			// Must make protected values accessible.
+			$prop->setAccessible(true);
+			$properties[$prop->getName()] = $prop->getValue($data);
+		}
+
+		return $properties;
+	}
+
+	//--------------------------------------------------------------------
 
 	/**
 	 * Inserts data into the current table. If an object is provided,
@@ -622,30 +622,30 @@ protected function classToArray($data): array
 	 */
 	public function insert($data)
 	{
-        // If $data is using a custom class with public or protected
-        // properties representing the table elements, we need to grab
-        // them as an array.
-        if (is_object($data) && ! $data instanceof \stdClass)
-        {
-            $data = $this->classToArray($data);
-        }
-
-        // If it's still a stdClass, go ahead and convert to
-        // an array so doProtectFields and other model methods
-        // don't have to do special checks.
-        if (is_object($data))
-        {
-            $data = (array)$data;
-        }
-
-	    // Validate data before saving.
-	    if ($this->skipValidation === false)
-        {
-            if ($this->validate($data) === false)
-            {
-                return false;
-            }
-        }
+		// If $data is using a custom class with public or protected
+		// properties representing the table elements, we need to grab
+		// them as an array.
+		if (is_object($data) && ! $data instanceof \stdClass)
+		{
+			$data = $this->classToArray($data);
+		}
+
+		// If it's still a stdClass, go ahead and convert to
+		// an array so doProtectFields and other model methods
+		// don't have to do special checks.
+		if (is_object($data))
+		{
+			$data = (array)$data;
+		}
+
+		// Validate data before saving.
+		if ($this->skipValidation === false)
+		{
+			if ($this->validate($data) === false)
+			{
+				return false;
+			}
+		}
 
 		// Must be called first so we don't
 		// strip out created_at values.
@@ -663,8 +663,8 @@ public function insert($data)
 
 		// Must use the set() method to ensure objects get converted to arrays
 		$return = $this->builder()
-		            ->set($data)
-		            ->insert();
+			->set($data)
+			->insert();
 
 		if (! $return) return $return;
 
@@ -684,30 +684,30 @@ public function insert($data)
 	 */
 	public function update($id, $data)
 	{
-        // If $data is using a custom class with public or protected
-        // properties representing the table elements, we need to grab
-        // them as an array.
-        if (is_object($data) && ! $data instanceof \stdClass)
-        {
-            $data = $this->classToArray($data);
-        }
-
-        // If it's still a stdClass, go ahead and convert to
-        // an array so doProtectFields and other model methods
-        // don't have to do special checks.
-        if (is_object($data))
-        {
-            $data = (array)$data;
-        }
-
-	    // Validate data before saving.
-        if ($this->skipValidation === false)
-        {
-            if ($this->validate($data) === false)
-            {
-                return false;
-            }
-        }
+		// If $data is using a custom class with public or protected
+		// properties representing the table elements, we need to grab
+		// them as an array.
+		if (is_object($data) && ! $data instanceof \stdClass)
+		{
+			$data = $this->classToArray($data);
+		}
+
+		// If it's still a stdClass, go ahead and convert to
+		// an array so doProtectFields and other model methods
+		// don't have to do special checks.
+		if (is_object($data))
+		{
+			$data = (array)$data;
+		}
+
+		// Validate data before saving.
+		if ($this->skipValidation === false)
+		{
+			if ($this->validate($data) === false)
+			{
+				return false;
+			}
+		}
 
 		// Must be called first so we don't
 		// strip out updated_at values.
@@ -725,9 +725,9 @@ public function update($id, $data)
 
 		// Must use the set() method to ensure objects get converted to arrays
 		return $this->builder()
-		            ->where($this->primaryKey, $id)
-		            ->set($data)
-		            ->update();
+			->where($this->primaryKey, $id)
+			->set($data)
+			->update();
 	}
 
 	//--------------------------------------------------------------------
@@ -747,13 +747,13 @@ public function delete($id, $purge = false)
 		if ($this->useSoftDeletes && ! $purge)
 		{
 			return $this->builder()
-			            ->where($this->primaryKey, $id)
-			            ->update(['deleted' => 1]);
+				->where($this->primaryKey, $id)
+				->update(['deleted' => 1]);
 		}
 
 		return $this->builder()
-		            ->where($this->primaryKey, $id)
-		            ->delete();
+			->where($this->primaryKey, $id)
+			->delete();
 	}
 
 	//--------------------------------------------------------------------
@@ -780,13 +780,13 @@ public function deleteWhere($key, $value = null, $purge = false)
 		if ($this->useSoftDeletes && ! $purge)
 		{
 			return $this->builder()
-			            ->where($key, $value)
-			            ->update(['deleted' => 1]);
+				->where($key, $value)
+				->update(['deleted' => 1]);
 		}
 
 		return $this->builder()
-		            ->where($key, $value)
-		            ->delete();
+			->where($key, $value)
+			->delete();
 	}
 
 	//--------------------------------------------------------------------
@@ -806,8 +806,8 @@ public function purgeDeleted()
 		}
 
 		return $this->builder()
-		            ->where('deleted', 1)
-		            ->delete();
+			->where('deleted', 1)
+			->delete();
 	}
 
 	//--------------------------------------------------------------------
@@ -840,7 +840,7 @@ public function onlyDeleted()
 		$this->tempUseSoftDeletes = false;
 
 		$this->builder()
-		     ->where('deleted', 1);
+			->where('deleted', 1);
 
 		return $this;
 	}
@@ -895,7 +895,7 @@ public function asObject(string $class = 'object')
 	public function chunk($size = 100, \Closure $userFunc)
 	{
 		$total = $this->builder()
-		              ->countAllResults(false);
+			->countAllResults(false);
 
 		$offset = 0;
 
@@ -971,7 +971,7 @@ public function paginate(int $perPage = 20, string $group = 'default')
 	 */
 	public function protect(bool $protect = true)
 	{
-	    $this->protectFields = $protect;
+		$this->protectFields = $protect;
 
 		return $this;
 	}
@@ -1022,7 +1022,7 @@ protected function builder(string $table = null)
 	 */
 	protected function doProtectFields($data)
 	{
-        if ($this->protectFields === false) return $data;
+		if ($this->protectFields === false) return $data;
 
 		if (empty($this->allowedFields))
 		{
@@ -1086,101 +1086,101 @@ protected function setDate($userData = null)
 	 */
 	public function setTable(string $table)
 	{
-	    $this->table = $table;
+		$this->table = $table;
 
 		return $this;
 	}
 
 	//--------------------------------------------------------------------
 
-    /**
-     * Grabs the last error(s) that occurred. If data was validated,
-     * it will first check for errors there, otherwise will try to
-     * grab the last error from the Database connection.
-     *
-     * @param bool $forceDB   Always grab the db error, not validation
-     *
-     * @return array|null
-     */
-    public function errors(bool $forceDB = false)
-    {
-        // Do we have validation errors?
-        if ($forceDB === false && $this->skipValidation === false)
-        {
-            $errors = $this->validation->getErrors();
-
-            if (! empty($errors))
-            {
-                return $errors;
-            }
-        }
-
-        // Still here? Grab the database-specific error, if any.
-        $error = $this->db->getError();
-
-        return $error['message'] ?? null;
-    }
-
-    //--------------------------------------------------------------------
-
-    //--------------------------------------------------------------------
-    // Validation
-    //--------------------------------------------------------------------
-
-    /**
-     * Set the value of the skipValidation flag.
-     *
-     * @param bool $skip
-     *
-     * @return $this
-     */
-    public function skipValidation(bool $skip = true)
-    {
-        $this->skipValidation = $skip;
-
-        return $this;
-    }
-
-    //--------------------------------------------------------------------
-
-    /**
-     * Validate the data against the validation rules (or the validation group)
-     * specified in the class property, $validationRules.
-     *
-     * @param array $data
-     *
-     * @return bool
-     */
-    public function validate($data): bool
-    {
-        if ($this->skipValidation === true || empty($this->validationRules))
-        {
-            return true;
-        }
-
-        // Query Builder works with objects as well as arrays,
-        // but validation requires array, so cast away.
-        if (is_object($data))
-        {
-            $data = (array)$data;
-        }
-
-        // ValidationRules can be either a string, which is the group name,
-        // or an array of rules.
-        if (is_string($this->validationRules))
-        {
-            $valid = $this->validation->run($data, $this->validationRules);
-        }
-        else
-        {
-            $this->validation->setRules($this->validationRules, $this->validationMessages);
-            $valid = $this->validation->run($data);
-        }
-
-        return (bool)$valid;
-    }
-
-    //--------------------------------------------------------------------
+	/**
+	 * Grabs the last error(s) that occurred. If data was validated,
+	 * it will first check for errors there, otherwise will try to
+	 * grab the last error from the Database connection.
+	 *
+	 * @param bool $forceDB   Always grab the db error, not validation
+	 *
+	 * @return array|null
+	 */
+	public function errors(bool $forceDB = false)
+	{
+		// Do we have validation errors?
+		if ($forceDB === false && $this->skipValidation === false)
+		{
+			$errors = $this->validation->getErrors();
+
+			if (! empty($errors))
+			{
+				return $errors;
+			}
+		}
+
+		// Still here? Grab the database-specific error, if any.
+		$error = $this->db->getError();
+
+		return $error['message'] ?? null;
+	}
+
+	//--------------------------------------------------------------------
+
+	//--------------------------------------------------------------------
+	// Validation
+	//--------------------------------------------------------------------
+
+	/**
+	 * Set the value of the skipValidation flag.
+	 *
+	 * @param bool $skip
+	 *
+	 * @return $this
+	 */
+	public function skipValidation(bool $skip = true)
+	{
+		$this->skipValidation = $skip;
+
+		return $this;
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Validate the data against the validation rules (or the validation group)
+	 * specified in the class property, $validationRules.
+	 *
+	 * @param array $data
+	 *
+	 * @return bool
+	 */
+	public function validate($data): bool
+	{
+		if ($this->skipValidation === true || empty($this->validationRules))
+		{
+			return true;
+		}
+
+		// Query Builder works with objects as well as arrays,
+		// but validation requires array, so cast away.
+		if (is_object($data))
+		{
+			$data = (array)$data;
+		}
+
+		// ValidationRules can be either a string, which is the group name,
+		// or an array of rules.
+		if (is_string($this->validationRules))
+		{
+			$valid = $this->validation->run($data, $this->validationRules);
+		}
+		else
+		{
+			$this->validation->setRules($this->validationRules, $this->validationMessages);
+			$valid = $this->validation->run($data);
+		}
+
+		return (bool)$valid;
+	}
+
+	//--------------------------------------------------------------------
 
 
 	//--------------------------------------------------------------------

From e05c384fa57c6210a92127b0d0af028caad1783e Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Wed, 29 Mar 2017 22:24:01 -0500
Subject: [PATCH 0571/1807] [ci skip] Initial Entity documentation

---
 system/Entity.php                           |   6 +
 user_guide_src/source/database/entities.rst | 203 ++++++++++++++++++++
 2 files changed, 209 insertions(+)
 create mode 100644 user_guide_src/source/database/entities.rst

diff --git a/system/Entity.php b/system/Entity.php
index a5c94747c105..46247f16041f 100644
--- a/system/Entity.php
+++ b/system/Entity.php
@@ -75,6 +75,8 @@ public function __get(string $key)
 	 *
 	 * @param string $key
 	 * @param null   $value
+	 *
+	 * @return $this
 	 */
 	public function __set(string $key, $value = null)
 	{
@@ -84,10 +86,14 @@ public function __set(string $key, $value = null)
 		if (method_exists($this, $method))
 		{
 			$this->$method($value);
+
+			return $this;
 		}
 		elseif (property_exists($this, $key))
 		{
 			$this->$key = $value;
+
+			return $this;
 		}
 	}
 
diff --git a/user_guide_src/source/database/entities.rst b/user_guide_src/source/database/entities.rst
new file mode 100644
index 000000000000..a4a870c92f0b
--- /dev/null
+++ b/user_guide_src/source/database/entities.rst
@@ -0,0 +1,203 @@
+#####################
+Working With Entities
+#####################
+
+CodeIgniter supports Entity classes as a first-class citizen in it's database layer, while keeping
+them completely optional to use. They are commonly used as part of the Repository pattern, but can
+be used directly with the :doc:`Model ` if that fits your needs better.
+
+Entity Usage
+============
+
+At its core, an Entity class is simply a class that represents a single database row. It has class properties
+to represent the database columns, and provides any additional methods to implement the business logic for
+that row.  The core feature, though, is that it doesn't know anything about how to persist itself. That's the
+responsibility of the model or the repository class. That way, if anything changes on how you need to save the
+object, you don't have to change how that object is used throughout the application. This makes it possible to
+use JSON or XML files to store the objects during a rapid prototyping stage, and then easily switch to a
+database when you've proven the concept works.
+
+Lets walk through a very simple User Entity and how we'd work with it to help make things clear.
+
+Assume you have a database table named ``users`` that has the following schema::
+
+    id          - integer
+    username    - string
+    email       - string
+    password    - string
+    created_at  - datetime
+
+Create the Entity Class
+-----------------------
+
+Now create a new Entity class. Since there's no default location to store these classes, and it doesn't fit
+in with the existing directory structure, create a new directory at **application/Entities**. Create the
+Entity itself at **application/Entities/User.php**.
+
+::
+
+    find($id);
+
+    // Display
+    echo $user->username;
+    echo $user->email;
+
+    // Updating
+    unset($user->username);
+    if (! isset($user->username)
+    {
+        $user->username = 'something new';
+    }
+    $userModel->save($user);
+
+    // Create
+    $user = new App\Entities\User();
+    $user->username = 'foo';
+    $user->email = 'foo@example.com';
+    $userModel->save($user);
+
+You may have noticed that the User class has all of the properties as **protected** not **public**, but you can still
+access them as if they were public properties. The base class, **CodeIgniter\Entity**, takes care of this for you, as
+well as providing the ability to check the properties with **isset()**, or **unset()** the property.
+
+When the User is passed to the model's **save()** method, it automatically takes care of reading the protected properties
+and saving any changes to columns listed in the model's **$allowedFields** property. It also knows whether to create
+a new row, or update an existing one.
+
+Filling Properties Quickly
+--------------------------
+
+The Entity class also provides a method, ``fill()`` that allows you to shove an array of key/value pairs into the class
+and populate the class properties. Only properties that already exist on the class can be populated in this way.
+
+::
+
+    $data = $this->request->getPost();
+
+    $user = new App\Entities\User();
+    $user->fill($data);
+    $userModel->save($user);
+
+Handling Business Logic
+=======================
+
+While the examples above are convenient, they don't help enforce any business logic. The base Entity class implements
+some smart ``__get()`` and ``__set()`` methods that will check for special methods and use those instead of using
+the class properties directly, allowing you to enforce any business logic or data conversion that you need.
+
+Here's an updated User entity to provide some examples of how this could be used::
+
+    password = password_hash($pass, PASSWORD_BCRYPT);
+
+            return $this;
+        }
+
+        public function setCreatedOn(string $dateString)
+        {
+            $this->created_at = new \DateTime($datetime, new \DateTimeZone('UTC'));
+
+            return
+        }
+
+        public function getCreatedOn(string $format = 'Y-m-d H:i:s')
+        {
+            $timezone = isset($this->timezone)
+            ? $this->timezone
+            : app_timezone();
+
+            $this->created_at->setTimezone($timezone);
+
+            return $format === true
+                ? $this->created_at
+                : $this->created_at->format($format);
+        }
+    }
+
+The first thing to notice is the name of the methods we've added. For each one, the class expects the snake_case
+column name to be converted into PascalCase, and prefixed with either ``set`` or ``get``. These methods will then
+be automatically called whenever you set or retrieve the class property using the direct syntax (i.e. $user->email).
+The methods do not need to be public unless you want them accessed from other classes. For example, the ``created_at``
+class property will be access through the ``setCreatedAt()`` and ``getCreatedAt()`` methods.
+
+In the ``setPassword()`` method we ensure that the password is always hashed.
+
+In ``setCreatedOn()`` we convert the string we receive from the model into a DateTime object, ensuring that our timezone
+is UTC so we can easily convert the the viewer's current timezone. In ``getCreatedAt()``, it converts the time to
+a formatted string in the application's current timezone.
+
+While fairly simple, these examples show that using Entity classes can provide a very flexible way to enforce
+business logic and create objects that are pleasant to use.
+
+::
+
+    // Auto-hash the password - both do the same thing
+    $user->password = 'my great password';
+    $user->setPassword('my great password');
+
+
+DataMapping
+===========
+
+Coming soon.

From 4bdf64ce847c63c9da2e415335406e4fa2a32c6d Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Wed, 29 Mar 2017 23:10:35 -0500
Subject: [PATCH 0572/1807] Added datamapping to Entity class. Fixes #427

---
 system/Entity.php                           | 47 ++++++++++-
 tests/system/EntityTest.php                 | 87 +++++++++++++++++++++
 user_guide_src/source/database/entities.rst | 62 ++++++++++++++-
 3 files changed, 192 insertions(+), 4 deletions(-)

diff --git a/system/Entity.php b/system/Entity.php
index 46247f16041f..79573dda03fc 100644
--- a/system/Entity.php
+++ b/system/Entity.php
@@ -2,6 +2,20 @@
 
 class Entity
 {
+	/**
+	 * Maps names used in sets and gets against unique
+	 * names within the class, allowing independence from
+	 * database column names.
+	 *
+	 * Example:
+	 *  $datamap = [
+	 *      'db_name' => 'class_name'
+	 *  ];
+	 *
+	 * @var array
+	 */
+	protected $datamap = [];
+
 	/**
 	 * Takes an array of key/value pairs and sets them as
 	 * class properties, using any `setCamelCasedProperty()` methods
@@ -44,6 +58,8 @@ public function fill(array $data)
 	 */
 	public function __get(string $key)
 	{
+		$key = $this->mapProperty($key);
+
 		// Convert to CamelCase for the method
 		$method = 'get'.str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
 
@@ -80,6 +96,8 @@ public function __get(string $key)
 	 */
 	public function __set(string $key, $value = null)
 	{
+		$key = $this->mapProperty($key);
+
 		// if a set* method exists for this key,

 		// use that method to insert this value.

 		$method = 'set'.str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
@@ -108,6 +126,10 @@ public function __set(string $key, $value = null)
 	 */
 	public function __unset(string $key)
 	{
+		// If not actual property exists, get out
+		// before we confuse our data mapping.
+		if (! property_exists($this, $key)) return;
+
 		$this->$key = null;
 
 		// Get the class' original default value for this property
@@ -133,11 +155,34 @@ public function __unset(string $key)
 	 */
 	public function __isset(string $key): bool
 	{
-		$value = $this->$key;
+		// Ensure an actual property exists, otherwise
+		// we confuse the data mapping.
+		$value = property_exists($this, $key)
+			? $this->$key
+			: null;
 
 		return ! is_null($value);
 	}
 
 	//--------------------------------------------------------------------
 
+	/**
+	 * Checks the datamap to see if this column name is being mapped,
+	 * and returns the mapped name, if any, or the original name.
+	 *
+	 * @param string $key
+	 *
+	 * @return mixed|string
+	 */
+	protected function mapProperty(string $key)
+	{
+		if (array_key_exists($key, $this->datamap))
+		{
+			return $this->datamap[$key];
+		}
+
+		return $key;
+	}
+
+	//--------------------------------------------------------------------
 }
diff --git a/tests/system/EntityTest.php b/tests/system/EntityTest.php
index 48df4beeb38e..a25a16148a21 100644
--- a/tests/system/EntityTest.php
+++ b/tests/system/EntityTest.php
@@ -60,6 +60,68 @@ public function testFill()
 	    $this->assertTrue(! isset($entity->baz));
 	}
 
+	public function testDataMappingConvertsOriginalName()
+	{
+	    $entity = $this->getMappedEntity();
+
+	    $entity->bar = 'made it';
+
+	    // Check mapped field
+	    $this->assertEquals('made it', $entity->foo);
+
+	    // Should also get from original name
+		// since Model's would be looking for the original name
+	    $this->assertEquals('made it', $entity->bar);
+
+	    // But it shouldn't actually set a class property for the original name...
+		$this->expectException(\ReflectionException::class);
+		$this->getPrivateProperty($entity, 'bar');
+	}
+
+	public function testDataMappingWorksWithCustomSettersAndGetters()
+	{
+	    $entity = $this->getMappedEntity();
+
+	    // Will map to "simple"
+		$entity->orig = 'first';
+
+		$this->assertEquals('oo:first:oo', $entity->simple);
+
+		$entity->simple = 'second';
+
+		$this->assertEquals('oo:second:oo', $entity->simple);
+	}
+
+	public function testIssetWorksWithMapping()
+	{
+		$entity = $this->getMappedEntity();
+
+		// maps to 'foo'
+		$entity->bar = 'here';
+
+		$this->assertTrue(isset($entity->foo));
+		$this->assertFalse(isset($entity->bar));
+	}
+
+	public function testUnsetWorksWithMapping()
+	{
+		$entity = $this->getMappedEntity();
+
+		// maps to 'foo'
+		$entity->bar = 'here';
+
+		// doesn't work on original name
+		unset($entity->bar);
+		$this->assertEquals('here', $entity->bar);
+		$this->assertEquals('here', $entity->foo);
+
+		// does work on mapped field
+		unset($entity->foo);
+		$this->assertNull($entity->foo);
+		$this->assertNull($entity->bar);
+	}
+
+
 
 	protected function getEntity()
 	{
@@ -83,4 +145,29 @@ public function getBar()
 
 		};
 	}
+
+	protected function getMappedEntity()
+	{
+		return new class extends Entity
+		{
+			protected $foo;
+			protected $simple;
+
+			// 'bar' is db column, 'foo' is internal representation
+			protected $datamap = [
+				'bar' => 'foo',
+				'orig' => 'simple'
+			];
+
+			protected function setSimple(string $val)
+			{
+				$this->simple = 'oo:'.$val;
+			}
+
+			protected function getSimple()
+			{
+				return $this->simple.':oo';
+			}
+		};
+	}
 }
diff --git a/user_guide_src/source/database/entities.rst b/user_guide_src/source/database/entities.rst
index a4a870c92f0b..e6c81be86564 100644
--- a/user_guide_src/source/database/entities.rst
+++ b/user_guide_src/source/database/entities.rst
@@ -197,7 +197,63 @@ business logic and create objects that are pleasant to use.
     $user->setPassword('my great password');
 
 
-DataMapping
-===========
+Data Mapping
+============
+
+At many points in your career, you will run into situations where the use of an application has changed and the
+original column names in the database no longer make sense. Or you find that your coding style prefers camelCase
+class properties, but your database schema required snake_case names. These situations can be easily handled
+with the Entity class' data mapping features.
+
+As an example, imagine your have the simplified User Entity that is used throughout your application::
+
+     'name'
+        ];
+    }
+
+By adding our new database name to the ``$datamap`` array, we can tell the class what class property the database column
+should be accessible through. The key of the array is the name of the column in the database, where the value in the array
+is class property to map it to.
 
-Coming soon.
+In this example, when the model sets the ``full_name`` field on the User class, it actually assigns that value to the
+class' ``$name`` property, so it can be set and retrieved through ``$user->name``. The value will still be accessible
+through the original ``$user->full_name``, also, as this is needed for the model to get the data back out and save it
+to the database. However, ``unset`` and ``isset`` only work on the mapped property, ``$name``, not on the original name,
+``full_name``.

From 6cd62f09e4e0fad7fd286739c3f401f4d2a7560e Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Fri, 31 Mar 2017 22:45:10 -0500
Subject: [PATCH 0573/1807] Fix toolbar error when post has array.

---
 system/Debug/Toolbar/Views/toolbar.tpl.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/system/Debug/Toolbar/Views/toolbar.tpl.php b/system/Debug/Toolbar/Views/toolbar.tpl.php
index e7ef4e1c055c..2f42bb80a785 100644
--- a/system/Debug/Toolbar/Views/toolbar.tpl.php
+++ b/system/Debug/Toolbar/Views/toolbar.tpl.php
@@ -173,7 +173,7 @@
 				 $value) : ?>
 					
 						
-						
+						
 					
 				
 				

From e4f579c53a3c54d8536d173bd56243e61bc5ecc0 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Fri, 31 Mar 2017 22:46:49 -0500
Subject: [PATCH 0574/1807] [ci skip] Moved env.example to root folder where it
 should be.

---
 application/env.example => env.example | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename application/env.example => env.example (100%)

diff --git a/application/env.example b/env.example
similarity index 100%
rename from application/env.example
rename to env.example

From 08753c011ff3b8818b5e5b3c3a34b156e933e2d0 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Fri, 31 Mar 2017 23:24:58 -0500
Subject: [PATCH 0575/1807] Allow specifying locale with lang helper function.
 Fixes #448

---
 system/Common.php                             |  4 ++--
 system/Language/Language.php                  |  8 ++++++-
 tests/system/Language/LanguageTest.php        |  6 ++----
 .../source/libraries/localization.rst         | 21 ++++++++++++++++---
 4 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/system/Common.php b/system/Common.php
index c97c96cb23fe..321a7dce1fd9 100644
--- a/system/Common.php
+++ b/system/Common.php
@@ -374,9 +374,9 @@ function single_service(string $name, ...$params)
 	 *
 	 * @return string
 	 */
-	function lang(string $line, array $args=[])
+	function lang(string $line, array $args=[], string $locale=null)
 	{
-		return Services::language()->getLine($line, $args);
+		return Services::language($locale)->getLine($line, $args);
 	}
 }
 
diff --git a/system/Language/Language.php b/system/Language/Language.php
index 918a987f280b..6feb647f4db1 100644
--- a/system/Language/Language.php
+++ b/system/Language/Language.php
@@ -124,9 +124,15 @@ public function getLine(string $line, array $args = [])
 	 */
 	protected function parseLine(string $line): array
 	{
+		// If there's no possibility of a filename being in the string
+		// simply return the string, and they can parse the replacement
+		// without it being in a file.
 		if (strpos($line, '.') === false)
 		{
-			throw new \InvalidArgumentException('No language file specified in line: '.$line);
+			return [
+				null,
+				$line
+			];
 		}
 
 		$file = substr($line, 0, strpos($line, '.'));
diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php
index 8f596cbe294f..21e532741451 100644
--- a/tests/system/Language/LanguageTest.php
+++ b/tests/system/Language/LanguageTest.php
@@ -2,13 +2,11 @@
 
 class LanguageTest extends \CIUnitTestCase
 {
-	public function testThrowsWithNoFileInMessage()
+	public function testReturnsStringWithNoFileInMessage()
 	{
 	    $lang = new MockLanguage('en');
 
-		$this->expectException('\InvalidArgumentException');
-
-		$lang->getLine('something');
+		$this->assertEquals('something', $lang->getLine('something'));
 	}
 
 	//--------------------------------------------------------------------
diff --git a/user_guide_src/source/libraries/localization.rst b/user_guide_src/source/libraries/localization.rst
index a788c7ef7c11..ea360073f03f 100644
--- a/user_guide_src/source/libraries/localization.rst
+++ b/user_guide_src/source/libraries/localization.rst
@@ -170,6 +170,7 @@ You can also use named keys to make it easier to keep things straight, if you'd
     // Displays "I have 3 apples."
     echo lang("Tests.namedApples", ['number_apples' => 3]);
 
+
 Obviously, you can do more than just number replacement. According to the
 `official ICU docs `_ for the underlying
 library, the following types of data can be replaced:
@@ -226,10 +227,24 @@ You should be sure to read up on the MessageFormatter class and the underlying I
 idea on what capabilities it has, like permorming conditional replacement, pluralization, and more. Both of the links provided
 earlier will give you an excellent idea as to the options available.
 
+Specifying Locale
+-----------------
+
+To specify a different locale to be used when replacing parameters, you can pass the locale in as the
+third parameter to the ``lang()`` method.
+
+    // Displays "The time is now 23:21:28 GMT-5"
+    echo lang('Test.longTime', [time()], 'ru_RU');
+
+    // Displays "£7.41"
+    echo lang('{price, number, currency}', ['price' => 7.41], 'en_GB');
+    // Displays "$7.41"
+    echo lang('{price, number, currency}', ['price' => 7.41], 'en_US');
+
 Nested Arrays
 -------------
 
-Language files also allow nested arrays to make working with lists, etc… easier.
+Language files also allow nested arrays to make working with lists, etc... easier.
 
     // Language/en/Fruit.php
     return [
@@ -243,5 +258,5 @@ Language files also allow nested arrays to make working with lists, etc… easie
         ]
     ];
 
-    // Displays "Apples, Bananas, Graps, Lemons, Oranges, Strawberries"
-    echo implode(', ', lang('Fruit.list'));
\ No newline at end of file
+    // Displays "Apples, Bananas, Grapes, Lemons, Oranges, Strawberries"
+    echo implode(', ', lang('Fruit.list'));

From dbf171652a410929480fa7a720a5fd8002777ac3 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Fri, 31 Mar 2017 23:44:42 -0500
Subject: [PATCH 0576/1807] Allow method spoofing for POSt requests. Fixes #432

---
 system/CodeIgniter.php  | 23 +++++++++++++++++++++++
 system/HTTP/Request.php | 29 +++++++++++++++++++++++++++--
 2 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php
index fefb88e28d46..af05ee54d6c0 100644
--- a/system/CodeIgniter.php
+++ b/system/CodeIgniter.php
@@ -194,6 +194,8 @@ public function run(RouteCollectionInterface $routes = null)
 		$cacheConfig = new Cache();
 		$this->displayCache($cacheConfig);
 
+		$this->spoofRequestMethod();
+
 		try {
 			$this->handleRequest($routes, $cacheConfig);
 		}
@@ -881,6 +883,27 @@ public function storePreviousURL($uri)
 
 	//--------------------------------------------------------------------
 
+	/**
+	 * Modifies the Request Object to use a different method if a POST
+	 * variable called _method is found.
+	 *
+	 * Does not work on CLI commands.
+	 */
+	public function spoofRequestMethod()
+	{
+	    if (is_cli()) return;
+
+	    // Only works with POSTED forms
+	    if ($this->request->getMethod() !== 'post') return;
+
+	    $method = $this->request->getPost('_method');
+
+	    if (empty($method)) return;
+
+		$this->request = $this->request->setMethod($method);
+	}
+
+
 	/**
 	 * Sends the output of this request back to the client.
 	 * This is what they've been waiting for!
diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php
index 72f032d0880d..457d3b911b80 100644
--- a/system/HTTP/Request.php
+++ b/system/HTTP/Request.php
@@ -57,6 +57,13 @@ class Request extends Message implements RequestInterface
 	 */
 	protected $proxyIPs;
 
+	/**
+	 * Request method.
+	 *
+	 * @var string
+	 */
+	protected $method;
+
 	//--------------------------------------------------------------------
 
 	/**
@@ -67,6 +74,8 @@ class Request extends Message implements RequestInterface
 	public function __construct($config)
 	{
 	    $this->proxyIPs = $config->proxyIPs;
+
+	    $this->method = $this->getServer('REQUEST_METHOD');
 	}
 
 	//--------------------------------------------------------------------
@@ -245,8 +254,24 @@ public function isValidIP(string $ip, string $which = null): bool
 	public function getMethod($upper = false): string
 	{
 		return ($upper)
-			? strtoupper($this->getServer('REQUEST_METHOD'))
-			: strtolower($this->getServer('REQUEST_METHOD'));
+			? strtoupper($this->method)
+			: strtolower($this->method);
+	}
+
+	//--------------------------------------------------------------------
+
+	/**
+	 * Sets the request method. Used when spoofing the request.
+	 *
+	 * @param string $method
+	 *
+	 * @return $this
+	 */
+	public function setMethod(string $method)
+	{
+	    $this->method = $method;
+
+	    return $this;
 	}
 
 	//--------------------------------------------------------------------

From b3687c6f895802b31a4c59b7d0758165bcc0fc2d Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Fri, 31 Mar 2017 23:53:31 -0500
Subject: [PATCH 0577/1807] Fixing Request tests by default request method to
 GET

---
 system/HTTP/Request.php           | 2 +-
 tests/system/HTTP/RequestTest.php | 5 +----
 2 files changed, 2 insertions(+), 5 deletions(-)

diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php
index 457d3b911b80..aa1b675b518b 100644
--- a/system/HTTP/Request.php
+++ b/system/HTTP/Request.php
@@ -75,7 +75,7 @@ public function __construct($config)
 	{
 	    $this->proxyIPs = $config->proxyIPs;
 
-	    $this->method = $this->getServer('REQUEST_METHOD');
+	    $this->method = $this->getServer('REQUEST_METHOD') ?? 'GET';
 	}
 
 	//--------------------------------------------------------------------
diff --git a/tests/system/HTTP/RequestTest.php b/tests/system/HTTP/RequestTest.php
index e286c2b37062..22aa15c78ad1 100644
--- a/tests/system/HTTP/RequestTest.php
+++ b/tests/system/HTTP/RequestTest.php
@@ -48,10 +48,7 @@ public function testValidIPAddress($expected, $address, $type=null)
 
 	public function testMethodReturnsRightStuff()
 	{
-		$this->assertEquals('', $this->request->getMethod());
-
-		$_SERVER['REQUEST_METHOD'] = 'GET';
-
+		// Defaults method to GET now.
 		$this->assertEquals('get', $this->request->getMethod());
 		$this->assertEquals('GET', $this->request->getMethod(true));
 	}

From 78962ab8b05e7e280724d2d20a04a95dadcda802 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Sun, 2 Apr 2017 21:57:04 -0500
Subject: [PATCH 0578/1807] Move Formatting functionality from API namespace to
 its own home now. Fixes #451

---
 application/Config/{API.php => Format.php}    | 6 +++---
 system/API/ResponseTrait.php                  | 3 ++-
 system/{API => Format}/FormatterInterface.php | 2 +-
 system/{API => Format}/JSONFormatter.php      | 2 +-
 system/{API => Format}/XMLFormatter.php       | 2 +-
 5 files changed, 8 insertions(+), 7 deletions(-)
 rename application/Config/{API.php => Format.php} (91%)
 rename system/{API => Format}/FormatterInterface.php (97%)
 rename system/{API => Format}/JSONFormatter.php (98%)
 rename system/{API => Format}/XMLFormatter.php (98%)

diff --git a/application/Config/API.php b/application/Config/Format.php
similarity index 91%
rename from application/Config/API.php
rename to application/Config/Format.php
index e559d6824833..93ef78a46dc6 100644
--- a/application/Config/API.php
+++ b/application/Config/Format.php
@@ -2,7 +2,7 @@
 
 use CodeIgniter\Config\BaseConfig;
 
-class API extends BaseConfig
+class Format extends BaseConfig
 {
 	/*
 	|--------------------------------------------------------------------------
@@ -34,8 +34,8 @@ class API extends BaseConfig
 	|
 	*/
 	public $formatters = [
-		'application/json' => \CodeIgniter\API\JSONFormatter::class,
-		'application/xml'  => \CodeIgniter\API\XMLFormatter::class
+		'application/json' => \CodeIgniter\Format\JSONFormatter::class,
+		'application/xml'  => \CodeIgniter\Format\XMLFormatter::class
 	];
 
 	//--------------------------------------------------------------------
diff --git a/system/API/ResponseTrait.php b/system/API/ResponseTrait.php
index 82e8bbaa1f98..e278d75e923d 100644
--- a/system/API/ResponseTrait.php
+++ b/system/API/ResponseTrait.php
@@ -36,6 +36,7 @@
  * @filesource
  */
 
+use Config\Format;
 use CodeIgniter\HTTP\Response;
 
 /**
@@ -332,7 +333,7 @@ protected function format($data = null)
 			return $data;
 		}
 
-		$config = new \Config\API();
+		$config = new Format();
 
 		// Determine correct response type through content negotiation
 		$format = $this->request->negotiate('media', $config->supportedResponseFormats);
diff --git a/system/API/FormatterInterface.php b/system/Format/FormatterInterface.php
similarity index 97%
rename from system/API/FormatterInterface.php
rename to system/Format/FormatterInterface.php
index 38bd1f507f3d..7f00b467d8e9 100644
--- a/system/API/FormatterInterface.php
+++ b/system/Format/FormatterInterface.php
@@ -1,4 +1,4 @@
-
Date: Sun, 2 Apr 2017 22:35:11 -0500
Subject: [PATCH 0579/1807] Rename Hooks to Events because they are more than
 what CI3 hooks were and this is more in line with what people expect.

---
 application/Config/{Hooks.php => Events.php}  |  4 +-
 system/CodeIgniter.php                        |  6 +-
 system/Config/AutoloadConfig.php              | 28 +++---
 system/Database/BaseConnection.php            |  6 +-
 system/Database/BasePreparedQuery.php         |  4 +-
 system/{Hooks/Hooks.php => Events/Events.php} | 12 +--
 system/Log/Handlers/ChromeLoggerHandler.php   |  4 +-
 .../HooksTest.php => Events/EventsTest.php}   | 90 +++++++++----------
 .../source/general/{hooks.rst => events.rst}  | 73 ++++++++-------
 user_guide_src/source/general/index.rst       |  2 +-
 10 files changed, 114 insertions(+), 115 deletions(-)
 rename application/Config/{Hooks.php => Events.php} (89%)
 rename system/{Hooks/Hooks.php => Events/Events.php} (97%)
 rename tests/system/{Hooks/HooksTest.php => Events/EventsTest.php} (59%)
 rename user_guide_src/source/general/{hooks.rst => events.rst} (52%)

diff --git a/application/Config/Hooks.php b/application/Config/Events.php
similarity index 89%
rename from application/Config/Hooks.php
rename to application/Config/Events.php
index 50d8da8ce666..97be681b6fa1 100644
--- a/application/Config/Hooks.php
+++ b/application/Config/Events.php
@@ -1,6 +1,6 @@
 createController();
 
 			// Is there a "post_controller_constructor" hook?
-			Hooks::trigger('post_controller_constructor');
+			Events::trigger('post_controller_constructor');
 
 			$returned = $this->runController($controller);
 		}
@@ -282,7 +282,7 @@ protected function handleRequest(RouteCollectionInterface $routes = null, $cache
 		//--------------------------------------------------------------------
 		// Is there a post-system hook?
 		//--------------------------------------------------------------------
-		Hooks::trigger('post_system');
+		Events::trigger('post_system');
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php
index 4ee1d8110ec3..cb0f6d150e76 100644
--- a/system/Config/AutoloadConfig.php
+++ b/system/Config/AutoloadConfig.php
@@ -137,19 +137,19 @@ public function __construct()
 			'CodeIgniter\Database\Database'                 => BASEPATH.'Database/Database.php',
 			'CodeIgniter\Database\Query'                    => BASEPATH.'Database/Query.php',
 			'CodeIgniter\Database\QueryInterface'           => BASEPATH.'Database/QueryInterface.php',
-			'CodeIgniter\Database\ResultInterface'          => BASEPATH.'Database/ResultInterface.php',
-			'CodeIgniter\Database\Migration'                => BASEPATH.'Database/Migration.php',
-			'CodeIgniter\Database\MigrationRunner'          => BASEPATH.'Database/MigrationRunner.php',
-			'CodeIgniter\Debug\Exceptions'                  => BASEPATH.'Debug/Exceptions.php',
-			'CodeIgniter\Debug\Timer'                       => BASEPATH.'Debug/Timer.php',
-			'CodeIgniter\Debug\Iterator'                    => BASEPATH.'Debug/Iterator.php',
-			'CodeIgniter\Hooks\Hooks'                       => BASEPATH.'Hooks/Hooks.php',
-			'CodeIgniter\HTTP\CLIRequest'                   => BASEPATH.'HTTP/CLIRequest.php',
-			'CodeIgniter\HTTP\ContentSecurityPolicy'        => BASEPATH.'HTTP/ContentSecurityPolicy.php',
-			'CodeIgniter\HTTP\CURLRequest'                  => BASEPATH.'HTTP/CURLRequest.php',
-			'CodeIgniter\HTTP\IncomingRequest'              => BASEPATH.'HTTP/IncomingRequest.php',
-			'CodeIgniter\HTTP\Message'                      => BASEPATH.'HTTP/Message.php',
-			'CodeIgniter\HTTP\Negotiate'                    => BASEPATH.'HTTP/Negotiate.php',
+			'CodeIgniter\Database\ResultInterface'   => BASEPATH.'Database/ResultInterface.php',
+			'CodeIgniter\Database\Migration'         => BASEPATH.'Database/Migration.php',
+			'CodeIgniter\Database\MigrationRunner'   => BASEPATH.'Database/MigrationRunner.php',
+			'CodeIgniter\Debug\Exceptions'           => BASEPATH.'Debug/Exceptions.php',
+			'CodeIgniter\Debug\Timer'                => BASEPATH.'Debug/Timer.php',
+			'CodeIgniter\Debug\Iterator'             => BASEPATH.'Debug/Iterator.php',
+			'CodeIgniter\Hooks\Events'               => BASEPATH.'Hooks/Hooks.php',
+			'CodeIgniter\HTTP\CLIRequest'            => BASEPATH.'HTTP/CLIRequest.php',
+			'CodeIgniter\HTTP\ContentSecurityPolicy' => BASEPATH.'HTTP/ContentSecurityPolicy.php',
+			'CodeIgniter\HTTP\CURLRequest'           => BASEPATH.'HTTP/CURLRequest.php',
+			'CodeIgniter\HTTP\IncomingRequest'       => BASEPATH.'HTTP/IncomingRequest.php',
+			'CodeIgniter\HTTP\Message'               => BASEPATH.'HTTP/Message.php',
+			'CodeIgniter\HTTP\Negotiate'             => BASEPATH.'HTTP/Negotiate.php',
 			'CodeIgniter\HTTP\Request'                      => BASEPATH.'HTTP/Request.php',
 			'CodeIgniter\HTTP\RequestInterface'             => BASEPATH.'HTTP/RequestInterface.php',
 			'CodeIgniter\HTTP\Response'                     => BASEPATH.'HTTP/Response.php',
@@ -181,7 +181,7 @@ public function __construct()
 			'CodeIgniter\View\Cell'                         => BASEPATH.'View/Cell.php',
 			'Zend\Escaper\Escaper'                          => BASEPATH.'ThirdParty/ZendEscaper/Escaper.php',
 			'CodeIgniter\Log\TestLogger'                    => BASEPATH.'../tests/_support/Log/TestLogger.php',
-		    'CIDatabaseTestCase'                            => BASEPATH.'../tests/_support/CIDatabaseTestCase.php'
+			'CIDatabaseTestCase'                            => BASEPATH.'../tests/_support/CIDatabaseTestCase.php'
 		];
 	}
 
diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php
index 4726f55a7fde..468d666e8b01 100644
--- a/system/Database/BaseConnection.php
+++ b/system/Database/BaseConnection.php
@@ -37,7 +37,7 @@
  */
 
 use CodeIgniter\DatabaseException;
-use CodeIgniter\Hooks\Hooks;
+use CodeIgniter\Hooks\Events;
 
 /**
  * Class BaseConnection
@@ -618,7 +618,7 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da
 			if (! $this->pretend)
 			{
 				// Let others do something with this query.
-				Hooks::trigger('DBQuery', $query);
+				Events::trigger('DBQuery', $query);
 			}
 
 			return new $resultClass($this->connID, $this->resultID);
@@ -629,7 +629,7 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da
 		if (! $this->pretend)
 		{
 			// Let others do somethign with this query
-			Hooks::trigger('DBQuery', $query);
+			Events::trigger('DBQuery', $query);
 		}
 
 		// If $pretend is true, then we just want to return
diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php
index a8a467267264..d8c00871c830 100644
--- a/system/Database/BasePreparedQuery.php
+++ b/system/Database/BasePreparedQuery.php
@@ -36,7 +36,7 @@
  * @filesource
  */
 
-use CodeIgniter\Hooks\Hooks;
+use CodeIgniter\Hooks\Events;
 
 abstract class BasePreparedQuery implements PreparedQueryInterface
 {
@@ -154,7 +154,7 @@ public function execute(...$data)
 		$query->setDuration($startTime);
 
 		// Let others do something with this query
-		Hooks::trigger('DBQuery', $query);
+		Events::trigger('DBQuery', $query);
 
 		// Return a result object
 		$resultClass = str_replace('PreparedQuery', 'Result', get_class($this));
diff --git a/system/Hooks/Hooks.php b/system/Events/Events.php
similarity index 97%
rename from system/Hooks/Hooks.php
rename to system/Events/Events.php
index 0a13c9d5ecbd..f528c6364db7 100644
--- a/system/Hooks/Hooks.php
+++ b/system/Events/Events.php
@@ -1,4 +1,4 @@
-json['request_uri'] = (string)$request->uri;
 
-		Hooks::on('post_controller', [$this, 'sendLogs'], HOOKS_PRIORITY_HIGH);
+		Events::on('post_controller', [$this, 'sendLogs'], HOOKS_PRIORITY_HIGH);
 	}
 
 	//--------------------------------------------------------------------
diff --git a/tests/system/Hooks/HooksTest.php b/tests/system/Events/EventsTest.php
similarity index 59%
rename from tests/system/Hooks/HooksTest.php
rename to tests/system/Events/EventsTest.php
index 2c66001a11b8..4c1f6c4c489c 100644
--- a/tests/system/Hooks/HooksTest.php
+++ b/tests/system/Events/EventsTest.php
@@ -1,6 +1,6 @@
-assertEquals([$callback2, $callback1], Hooks::listeners('foo'));
+		$this->assertEquals([$callback2, $callback1], Events::listeners('foo'));
 	}
 
 	//--------------------------------------------------------------------
@@ -31,11 +31,11 @@ public function testHandleEvent()
 	{
 		$result = null;
 
-		Hooks::on('foo', function($arg) use(&$result) {
+		Events::on('foo', function($arg) use(&$result) {
 			$result = $arg;
 		});
 
-		$this->assertTrue(Hooks::trigger('foo', 'bar') );
+		$this->assertTrue(Events::trigger('foo', 'bar') );
 
 		$this->assertEquals('bar', $result);
 	}
@@ -48,15 +48,15 @@ public function testCancelEvent()
 
 		// This should cancel the flow of events, and leave
 		// $result = 1.
-		Hooks::on('foo', function($arg) use (&$result) {
+		Events::on('foo', function($arg) use (&$result) {
 			$result = 1;
 			return false;
 		});
-		Hooks::on('foo', function($arg) use (&$result) {
+		Events::on('foo', function($arg) use (&$result) {
 			$result = 2;
 		});
 
-		$this->assertFalse(Hooks::trigger('foo', 'bar'));
+		$this->assertFalse(Events::trigger('foo', 'bar'));
 		$this->assertEquals(1, $result);
 	}
 
@@ -66,18 +66,18 @@ public function testPriority()
 	{
 		$result = 0;
 
-		Hooks::on('foo', function() use (&$result) {
+		Events::on('foo', function() use (&$result) {
 			$result = 1;
 			return false;
-		}, HOOKS_PRIORITY_NORMAL);
+		}, EVENT_PRIORITY_NORMAL);
 		// Since this has a higher priority, it will
 		// run first.
-		Hooks::on('foo', function() use (&$result) {
+		Events::on('foo', function() use (&$result) {
 			$result = 2;
 			return false;
-		}, HOOKS_PRIORITY_HIGH);
+		}, EVENT_PRIORITY_HIGH);
 
-		$this->assertFalse(Hooks::trigger('foo', 'bar'));
+		$this->assertFalse(Events::trigger('foo', 'bar'));
 		$this->assertEquals(2, $result);
 	}
 
@@ -87,23 +87,23 @@ public function testPriorityWithMultiple()
 	{
 		$result = [];
 
-		Hooks::on('foo', function() use (&$result) {
+		Events::on('foo', function() use (&$result) {
 			$result[] = 'a';
-		}, HOOKS_PRIORITY_NORMAL);
+		}, EVENT_PRIORITY_NORMAL);
 
-		Hooks::on('foo', function() use (&$result) {
+		Events::on('foo', function() use (&$result) {
 			$result[] = 'b';
-		}, HOOKS_PRIORITY_LOW);
+		}, EVENT_PRIORITY_LOW);
 
-		Hooks::on('foo', function() use (&$result) {
+		Events::on('foo', function() use (&$result) {
 			$result[] = 'c';
-		}, HOOKS_PRIORITY_HIGH);
+		}, EVENT_PRIORITY_HIGH);
 
-		Hooks::on('foo', function() use (&$result) {
+		Events::on('foo', function() use (&$result) {
 			$result[] = 'd';
 		}, 75);
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertEquals(['c', 'd', 'a', 'b'], $result);
 	}
 
@@ -118,15 +118,15 @@ public function testRemoveListener()
 			$result = true;
 		};
 
-		Hooks::on('foo', $callback);
+		Events::on('foo', $callback);
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertTrue($result);
 
 		$result = false;
-		$this->assertTrue( Hooks::removeListener('foo', $callback) );
+		$this->assertTrue( Events::removeListener('foo', $callback) );
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertFalse($result);
 	}
 
@@ -141,16 +141,16 @@ public function testRemoveListenerTwice()
 			$result = true;
 		};
 
-		Hooks::on('foo', $callback);
+		Events::on('foo', $callback);
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertTrue($result);
 
 		$result = false;
-		$this->assertTrue( Hooks::removeListener('foo', $callback) );
-		$this->assertFalse( Hooks::removeListener('foo', $callback) );
+		$this->assertTrue( Events::removeListener('foo', $callback) );
+		$this->assertFalse( Events::removeListener('foo', $callback) );
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertFalse($result);
 	}
 
@@ -165,15 +165,15 @@ public function testRemoveUnknownListener()
 			$result = true;
 		};
 
-		Hooks::on('foo', $callback);
+		Events::on('foo', $callback);
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertTrue($result);
 
 		$result = false;
-		$this->assertFalse( Hooks::removeListener('bar', $callback) );
+		$this->assertFalse( Events::removeListener('bar', $callback) );
 
-		Hooks::trigger('foo');
+		Events::trigger('foo');
 		$this->assertTrue($result);
 	}
 
@@ -188,11 +188,11 @@ public function testRemoveAllListenersWithSingleEvent()
 			$result = true;
 		};
 
-		Hooks::on('foo', $callback);
+		Events::on('foo', $callback);
 
-		Hooks::removeAllListeners('foo');
+		Events::removeAllListeners('foo');
 
-		$listeners = Hooks::listeners('foo');
+		$listeners = Events::listeners('foo');
 
 		$this->assertEquals([], $listeners);
 	}
@@ -209,13 +209,13 @@ public function testRemoveAllListenersWithMultipleEvents()
 			$result = true;
 		};
 
-		Hooks::on('foo', $callback);
-		Hooks::on('bar', $callback);
+		Events::on('foo', $callback);
+		Events::on('bar', $callback);
 
-		Hooks::removeAllListeners();
+		Events::removeAllListeners();
 
-		$this->assertEquals([], Hooks::listeners('foo'));
-		$this->assertEquals([], Hooks::listeners('bar'));
+		$this->assertEquals([], Events::listeners('foo'));
+		$this->assertEquals([], Events::listeners('bar'));
 	}
 
 	//--------------------------------------------------------------------
diff --git a/user_guide_src/source/general/hooks.rst b/user_guide_src/source/general/events.rst
similarity index 52%
rename from user_guide_src/source/general/hooks.rst
rename to user_guide_src/source/general/events.rst
index fd86516d6413..7999a665ee9f 100644
--- a/user_guide_src/source/general/hooks.rst
+++ b/user_guide_src/source/general/events.rst
@@ -1,49 +1,49 @@
-####################################
-Hooks - Extending the Framework Core
-####################################
+#####################################
+Events - Extending the Framework Core
+#####################################
 
-CodeIgniter's Hooks feature provides a means to tap into and modify the inner workings of the framework without hacking
+CodeIgniter's Events feature provides a means to tap into and modify the inner workings of the framework without hacking
 core files. When CodeIgniter runs it follows a specific execution process. There may be instances, however, when you'd
 like to cause some action to take place at a particular stage in the execution process. For example, you might want to run
 a script right before your controllers get loaded, or right after, or you might want to trigger one of your own scripts
 in some other location.
 
-Hooks work on a *publish/subscribe* pattern, where a hook, or event, is triggered at some point during the script execution.
-Other scripts can "subscribe" to that event by registering with the Hooks class to let it know they want to perform an
-action when that hook is triggered.
+Events work on a *publish/subscribe* pattern, where an event, is triggered at some point during the script execution.
+Other scripts can "subscribe" to that event by registering with the Events class to let it know they want to perform an
+action when that event is triggered.
 
-Enabling Hooks
-==============
+Enabling Events
+===============
 
-Hooks are always enabled, and are available globally.
+Events are always enabled, and are available globally.
 
-Defining a Hook
-===============
+Defining an Event
+=================
 
-Most hooks are defined within the **application/Config/Hooks.php** file. You can subscribe an action to a hook with
-the Hooks class' ``on()`` method. The first parameter is the name of the hook to subscribe to. The second parameter is
+Most events are defined within the **application/Config/Events.php** file. You can subscribe an action to an event with
+the Events class' ``on()`` method. The first parameter is the name of the event to subscribe to. The second parameter is
 a callable that will be run when that event is triggered::
 
-	use CodeIgniter\Hooks\Hooks;
+	use CodeIgniter\Events\Events;
 
-	Hooks::on('pre_system', ['MyClass', 'MyFunction']);
+	Events::on('pre_system', ['MyClass', 'MyFunction']);
 
-In this example, whenever the **pre_controller** hook is executed, an instance of ``MyClass`` is created and the
+In this example, whenever the **pre_controller** event is executed, an instance of ``MyClass`` is created and the
 ``MyFunction`` method is ran. Note that the second parameter can be *any* form of
 `callable `_ that PHP recognizes::
 
 	// Call a standalone function
-	Hooks::on('pre_system', 'some_function');
+	Events::on('pre_system', 'some_function');
 
 	// Call on an instance method
 	$user = new User();
-	Hooks::on('pre_system', [$user, 'some_method']);
+	Events::on('pre_system', [$user, 'some_method']);
 
 	// Call on a static method
-	Hooks::on('pre_system', 'SomeClass::someMethod');
+	Events::on('pre_system', 'SomeClass::someMethod');
 
 	// Use a Closure
-	Hooks::on('pre_system', function(...$params)
+	Events::on('pre_system', function(...$params)
 	{
 		. . .
 	});
@@ -55,44 +55,43 @@ Since multiple methods can be subscribed to a single event, you will need a way
 are called. You can do this by passing a priority value as the third parameter of the ``on()`` method. Lower values
 are executed first, with a value of 1 having the highest priority, and there being no limit on the lower values::
 
-    Hooks::on('post_controller_constructor', 'some_function', 25);
+    Events::on('post_controller_constructor', 'some_function', 25);
 
 Any subscribers with the same priority will be executed in the order they were defined.
 
 Three constants are defined for your use, that set some helpful ranges on the values. You are not required to use these
 but you might find they aid readability::
 
-	define('HOOKS_PRIORITY_LOW', 200);
-	define('HOOKS_PRIORITY_NORMAL', 100);
-	define('HOOKS_PRIORITY_HIGH', 10);
+	define('EVENT_PRIORITY_LOW', 200);
+	define('EVENT_PRIORITY_NORMAL', 100);
+	define('EVENT_PRIORITY_HIGH', 10);
 
 Once sorted, all subscribers are executed in order. If any subscriber returns a boolean false value, then execution of
 the subscribers will stop.
 
-Publishing your own Hooks
-=========================
+Publishing your own Events
+==========================
 
-The Hooks library makes it simple for you to create hooks into your own code, also. To use this feature, you would simply
-need to call the ``trigger()`` method on the **Hooks** class with the name of the hook::
+The Events library makes it simple for you to create events in your own code, also. To use this feature, you would simply
+need to call the ``trigger()`` method on the **Events** class with the name of the event::
 
-	\CodeIgniter\Hooks\Hooks::trigger('some_hook');
+	\CodeIgniter\Events\Events::trigger('some_event');
 
 You can pass any number of arguments to the subscribers by adding them as additional parameters. Subscribers will be
 given the arguments in the same order as defined::
 
-	\CodeIgniter\Hooks\Hooks::trigger('some_hook', $foo, $bar, $baz);
+	\CodeIgniter\Events\Events::trigger('some_events', $foo, $bar, $baz);
 
-	Hooks::on('some_hook', function($foo, $bar, $baz) {
+	Events::on('some_event', function($foo, $bar, $baz) {
 		...
 	});
 
-Hook Points
-===========
+Event Points
+============
 
-The following is a list of available hook points:
+The following is a list of available event points within the CodeIgniter core code:
 
-* **pre_system** Called very early during system execution. Only the benchmark and hooks class have been loaded at this point. No routing or other processes have happened.
+* **pre_system** Called very early during system execution. Only the benchmark and events class have been loaded at this point. No routing or other processes have happened.
 * **post_controller_constructor** Called immediately after your controller is instantiated, but prior to any method calls happening.
 * **post_system** Called after the final rendered page is sent to the browser, at the end of system execution after the finalized data is sent to the browser.
 
-Hooks are closely related to :doc:`Filters `, and you should be sure to read up on them.
diff --git a/user_guide_src/source/general/index.rst b/user_guide_src/source/general/index.rst
index a06d11d33548..88c5e19accd9 100644
--- a/user_guide_src/source/general/index.rst
+++ b/user_guide_src/source/general/index.rst
@@ -14,7 +14,7 @@ General Topics
     view_parser
     helpers
     core_classes
-    hooks
+    events
     common_functions
     routing
     filters

From 7d6ac700886b4eaeb47dea6bc931c3323328ed7d Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Sun, 2 Apr 2017 22:57:35 -0500
Subject: [PATCH 0580/1807] Fixing missed Hooks rename issues

---
 system/Config/AutoloadConfig.php               | 2 +-
 system/Database/BaseConnection.php             | 2 +-
 system/Database/BasePreparedQuery.php          | 2 +-
 system/Log/Handlers/ChromeLoggerHandler.php    | 6 +++---
 user_guide_src/source/database/hooks.rst       | 2 +-
 user_guide_src/source/general/core_classes.rst | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php
index cb0f6d150e76..cd26fb49ce47 100644
--- a/system/Config/AutoloadConfig.php
+++ b/system/Config/AutoloadConfig.php
@@ -143,7 +143,7 @@ public function __construct()
 			'CodeIgniter\Debug\Exceptions'           => BASEPATH.'Debug/Exceptions.php',
 			'CodeIgniter\Debug\Timer'                => BASEPATH.'Debug/Timer.php',
 			'CodeIgniter\Debug\Iterator'             => BASEPATH.'Debug/Iterator.php',
-			'CodeIgniter\Hooks\Events'               => BASEPATH.'Hooks/Hooks.php',
+			'CodeIgniter\Events\Events'               => BASEPATH.'Hooks/Hooks.php',
 			'CodeIgniter\HTTP\CLIRequest'            => BASEPATH.'HTTP/CLIRequest.php',
 			'CodeIgniter\HTTP\ContentSecurityPolicy' => BASEPATH.'HTTP/ContentSecurityPolicy.php',
 			'CodeIgniter\HTTP\CURLRequest'           => BASEPATH.'HTTP/CURLRequest.php',
diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php
index 468d666e8b01..37ed2bca8fb0 100644
--- a/system/Database/BaseConnection.php
+++ b/system/Database/BaseConnection.php
@@ -37,7 +37,7 @@
  */
 
 use CodeIgniter\DatabaseException;
-use CodeIgniter\Hooks\Events;
+use CodeIgniter\Events\Events;
 
 /**
  * Class BaseConnection
diff --git a/system/Database/BasePreparedQuery.php b/system/Database/BasePreparedQuery.php
index d8c00871c830..8556ae484f73 100644
--- a/system/Database/BasePreparedQuery.php
+++ b/system/Database/BasePreparedQuery.php
@@ -36,7 +36,7 @@
  * @filesource
  */
 
-use CodeIgniter\Hooks\Events;
+use CodeIgniter\Events\Events;
 
 abstract class BasePreparedQuery implements PreparedQueryInterface
 {
diff --git a/system/Log/Handlers/ChromeLoggerHandler.php b/system/Log/Handlers/ChromeLoggerHandler.php
index 97d0ff0c01e1..ef38f150766e 100644
--- a/system/Log/Handlers/ChromeLoggerHandler.php
+++ b/system/Log/Handlers/ChromeLoggerHandler.php
@@ -36,7 +36,7 @@
  * @filesource
  */
 
-use CodeIgniter\Hooks\Events;
+use CodeIgniter\Events\Events;
 use CodeIgniter\HTTP\ResponseInterface;
 use CodeIgniter\Services;
 
@@ -104,7 +104,7 @@ class ChromeLoggerHandler extends BaseHandler implements HandlerInterface
 
 	/**
 	 * Constructor
-	 * 
+	 *
 	 * @param array $config
 	 */
 	public function __construct(array $config = [])
@@ -185,7 +185,7 @@ protected function format($object)
 
 	/**
 	 * Attaches the header and the content to the passed in request object.
-	 * 
+	 *
 	 * @param ResponseInterface response
 	 */
 	public function sendLogs(ResponseInterface &$response=null)
diff --git a/user_guide_src/source/database/hooks.rst b/user_guide_src/source/database/hooks.rst
index 76962472e192..039b1de23173 100644
--- a/user_guide_src/source/database/hooks.rst
+++ b/user_guide_src/source/database/hooks.rst
@@ -18,7 +18,7 @@ a :doc:`Query ` instance of the current query. You could use th
 in STDOUT, or logging to a file, or even creating tools to do automatic query analysis to help you spot
 potentially missing indexes, slow queries, etc. An example usage might be::
 
-    // In Config\Hooks.php
+    // In Config\Events.php
     Hooks::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
 
     // Collect the queries so something can be done with them later.
diff --git a/user_guide_src/source/general/core_classes.rst b/user_guide_src/source/general/core_classes.rst
index 5565fd334dd9..8829bfd50145 100644
--- a/user_guide_src/source/general/core_classes.rst
+++ b/user_guide_src/source/general/core_classes.rst
@@ -23,7 +23,7 @@ The following is a list of the core system files that are invoked every time Cod
 * CodeIgniter\\Controller
 * CodeIgniter\\Debug\\Exceptions
 * CodeIgniter\\Debug\\Timer
-* CodeIgniter\\Hooks\\Hooks
+* CodeIgniter\\Events\\Events
 * CodeIgniter\\HTTP\\CLIRequest (if launched from command line only)
 * CodeIgniter\\HTTP\\IncomingRequest (if launched over HTTP)
 * CodeIgniter\\HTTP\\Request

From 5c770b9469d3a78415dd59fecfdd6c892760d016 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Sun, 2 Apr 2017 23:02:09 -0500
Subject: [PATCH 0581/1807] Fixing a migration issue during testing

---
 system/Database/MigrationRunner.php | 19 +++++++++----------
 system/Test/CIDatabaseTestCase.php  |  2 +-
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php
index df8250d0a542..c29d28061de3 100644
--- a/system/Database/MigrationRunner.php
+++ b/system/Database/MigrationRunner.php
@@ -244,7 +244,7 @@ public function version(string $targetVersion, $namespace = null, $group = null)
 
 				$instance->{$method}();
 				if ($method === 'up') $this->addHistory($migration->version);
-				elseif ($method === 'down') $this->removeHistory($migration->version);            
+				elseif ($method === 'down') $this->removeHistory($migration->version);
 			}
 		}
 		return true;
@@ -269,12 +269,12 @@ public function latest($namespace = null, $group = null)
 			$this->setGroup($group);
 		}
 
-		$migrations = $this->findMigrations();       
+		$migrations = $this->findMigrations();
 
 		$lastMigration = end($migrations)->version;
 
 		// Calculate the last migration step from existing migration
-		// filenames and proceed to the standard version migration       
+		// filenames and proceed to the standard version migration
 		return $this->version($lastMigration);
 	}
 
@@ -296,7 +296,7 @@ public function latestAll($group = null)
 		$config = new Autoload();
 		$namespaces = $config->psr4;
 
-		foreach ($namespaces as $namespace => $path) {    
+		foreach ($namespaces as $namespace => $path) {
 
 			$this->setNamespace($namespace);
 			$migrations = $this->findMigrations();
@@ -312,7 +312,7 @@ public function latestAll($group = null)
 			}
 
 			// Calculate the last migration step from existing migration
-			// filenames and proceed to the standard version migration           
+			// filenames and proceed to the standard version migration
 			$this->version($lastMigration);
 		}
 		return true;
@@ -321,7 +321,7 @@ public function latestAll($group = null)
 
 	/**
 	 * Sets the (APP_NAMESPACE) schema to $currentVersion in migration config file
-	 *      
+	 *
 	 *
 	 * @return    mixed    TRUE if no migrations are found, current version string on success, FALSE on failure
 	 */
@@ -383,7 +383,7 @@ public function findMigrations()
 	 */
 	protected function CheckMigrations($migrations, $method, $targetversion)
 	{
-		// Check if no migrations found 
+		// Check if no migrations found
 		if (empty($migrations)) {
 			if ($this->silent) return false;
 			throw new \RuntimeException(lang('Migrations.migEmpty') );
@@ -538,10 +538,9 @@ protected function getVersion()
 			->where('group', $this->group)
 			->where('namespace', $this->namespace)
 			->orderBy('version', 'DESC')
-			->get()
-			->getRow();
+			->get();
 
-		return $row ? $row->version : '0';
+		return $row ? $row->getRow()->version : '0';
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Test/CIDatabaseTestCase.php b/system/Test/CIDatabaseTestCase.php
index cfc32333157e..68f2a7de0d5a 100644
--- a/system/Test/CIDatabaseTestCase.php
+++ b/system/Test/CIDatabaseTestCase.php
@@ -69,7 +69,7 @@ class CIDatabaseTestCase extends CIUnitTestCase
 	 *
 	 * @var string
 	 */
-	protected $basePath = APPPATH.'../tests/_support/Database';
+	protected $basePath = TESTPATH.'_support/Database';
 
 	/**
 	 * The name of the database group to connect to.

From 5be1b6033c2651aa6c90314820ecf535593e8875 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Sun, 2 Apr 2017 23:13:18 -0500
Subject: [PATCH 0582/1807] More Hooks rename fixes

---
 system/Config/AutoloadConfig.php |  2 +-
 system/Events/Events.php         | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php
index cd26fb49ce47..89633948eb59 100644
--- a/system/Config/AutoloadConfig.php
+++ b/system/Config/AutoloadConfig.php
@@ -143,7 +143,7 @@ public function __construct()
 			'CodeIgniter\Debug\Exceptions'           => BASEPATH.'Debug/Exceptions.php',
 			'CodeIgniter\Debug\Timer'                => BASEPATH.'Debug/Timer.php',
 			'CodeIgniter\Debug\Iterator'             => BASEPATH.'Debug/Iterator.php',
-			'CodeIgniter\Events\Events'               => BASEPATH.'Hooks/Hooks.php',
+			'CodeIgniter\Events\Events'               => BASEPATH.'Events/Events.php',
 			'CodeIgniter\HTTP\CLIRequest'            => BASEPATH.'HTTP/CLIRequest.php',
 			'CodeIgniter\HTTP\ContentSecurityPolicy' => BASEPATH.'HTTP/ContentSecurityPolicy.php',
 			'CodeIgniter\HTTP\CURLRequest'           => BASEPATH.'HTTP/CURLRequest.php',
diff --git a/system/Events/Events.php b/system/Events/Events.php
index f528c6364db7..7730ea75f216 100644
--- a/system/Events/Events.php
+++ b/system/Events/Events.php
@@ -62,11 +62,11 @@ class Events
 	protected static $haveReadFromFile = false;
 
 	/**
-	 * The path to the file containing the hooks to load in.
+	 * The path to the file containing the events to load in.
 	 *
 	 * @var string
 	 */
-	protected static $hooksFile;
+	protected static $eventsFile;
 
 	//--------------------------------------------------------------------
 
@@ -78,7 +78,7 @@ class Events
 	public static function initialize(string $file=null)
 	{
 		// Don't overwrite anything....
-		if (! empty(self::$hooksFile))
+		if (! empty(self::$eventsFile))
 		{
 			return;
 		}
@@ -86,10 +86,10 @@ public static function initialize(string $file=null)
 		// Default value
 	    if (empty($file))
 		{
-			$file = APPPATH.'Config/Hooks.php';
+			$file = APPPATH.'Config/Events.php';
 		}
 
-		self::$hooksFile = $file;
+		self::$eventsFile = $file;
 	}
 
 	//--------------------------------------------------------------------
@@ -145,9 +145,9 @@ public static function trigger($event_name, ...$arguments): bool
 		{
 			self::initialize();
 
-			if (is_file(self::$hooksFile))
+			if (is_file(self::$eventsFile))
 			{
-				include self::$hooksFile;
+				include self::$eventsFile;
 			}
 			self::$haveReadFromFile = true;
 		}
@@ -265,7 +265,7 @@ public static function removeAllListeners($event_name = null)
 	 */
 	public function setFile(string $path)
 	{
-		self::$hooksFile = $path;
+		self::$eventsFile = $path;
 	}
 
 	//--------------------------------------------------------------------

From 355333a41d70f35d2614f3bc4a72ed87c73bfe77 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell 
Date: Sun, 2 Apr 2017 23:34:05 -0500
Subject: [PATCH 0583/1807] Adding comments to the Parser

---
 system/View/Parser.php                        | 35 +++++++++---
 tests/system/View/ParserTest.php              | 20 +++++++
 user_guide_src/source/general/view_parser.rst | 54 ++++++++++++-------
 3 files changed, 83 insertions(+), 26 deletions(-)

diff --git a/system/View/Parser.php b/system/View/Parser.php
index 148981590ea8..9f2b13ee9b27 100644
--- a/system/View/Parser.php
+++ b/system/View/Parser.php
@@ -68,7 +68,7 @@ class Parser extends View {
 	 */
 	public $rightDelimiter = '}';
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Constructor
@@ -84,7 +84,7 @@ public function __construct($config, string $viewPath = null, $loader = null, bo
 		parent::__construct($config, $viewPath, $loader, $debug, $logger);
 	}
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Parse a template
@@ -150,7 +150,7 @@ public function render(string $view, array $options = null, $saveData = null): s
 		return $output;
 	}
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Parse a String
@@ -183,7 +183,7 @@ public function renderString(string $template, array $options = null, $saveData
 		return $output;
 	}
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Parse a template
@@ -203,6 +203,8 @@ protected function parse(string $template, array $data = [], array $options = nu
 			return '';
 		}
 
+		$template = $this->parseComments($template);
+
 		// build the variable substitution list
 		$replace = array();
 		foreach ($data as $key => $val)
@@ -219,7 +221,7 @@ protected function parse(string $template, array $data = [], array $options = nu
 		return $template;
 	}
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	protected function is_assoc($arr)
 	{
@@ -238,7 +240,7 @@ function strpos_all($haystack, $needle)
 		return $allpos;
 	}
 
-// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Parse a single key/value
@@ -253,7 +255,7 @@ protected function parseSingle(string $key, string $val, string $template): arra
 		return array($this->leftDelimiter . $key . $this->rightDelimiter => (string) $val);
 	}
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * Parse a tag pair
@@ -313,6 +315,24 @@ protected function parsePair(string $variable, array $data, string $template): a
 		return $replace;
 	}
 
+	//--------------------------------------------------------------------
+
+	/**
+	 * Removes any comments from the file. Comments are wrapped in {# #} symbols:
+	 *
+	 *      {# This is a comment #}
+	 *
+	 * @param string $template
+	 *
+	 * @return string
+	 */
+	protected function parseComments(string $template): string
+	{
+		return preg_replace('/\{#.*?#\}/s', '', $template);
+	}
+
+	//--------------------------------------------------------------------
+
 	/**
 	 * Over-ride the substitution field delimiters.
 	 *
@@ -327,4 +347,5 @@ public function setDelimiters($leftDelimiter = '{', $rightDelimiter = '}'): Rend
 		return $this;
 	}
 
+	//--------------------------------------------------------------------
 }
diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php
index 2e2b87d9ea3e..b235f0921740 100644
--- a/tests/system/View/ParserTest.php
+++ b/tests/system/View/ParserTest.php
@@ -197,4 +197,24 @@ public function testEscHandling($value, $expected = null)
 
 	// ------------------------------------------------------------------------
 
+	/**
+	 * @group single
+	 */
+	public function testIgnoresComments()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = array (
+			'title'	 => 'Super Heroes',
+			'powers' => array (
+				array ('invisibility' => 'yes', 'flying' => 'no')
+			)
+		);
+
+		$template = "{# Comments #}{title}\n{powers}{invisibility}\n{flying}";
+		$result = "Super Heroes\n{powers}{invisibility}\n{flying}";
+
+		$parser->setData($data);
+		$this->assertEquals($result, $parser->renderString($template));
+	}
+
 }
diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst
index cb1702d5a9b1..2692dc0d14b4 100644
--- a/user_guide_src/source/general/view_parser.rst
+++ b/user_guide_src/source/general/view_parser.rst
@@ -2,9 +2,9 @@
 View Parser
 ###########
 
-The View Parser can perform simple text substitution for 
-pseudo-variables contained within your view files. 
-It can parse simple variables or variable tag pairs. 
+The View Parser can perform simple text substitution for
+pseudo-variables contained within your view files.
+It can parse simple variables or variable tag pairs.
 
 Pseudo-variable names or control constructs are enclosed in braces, like this::
 
@@ -28,7 +28,7 @@ representations that allow you to eliminate PHP from your templates
 (view files).
 
 .. note:: CodeIgniter does **not** require you to use this class since
-	using pure PHP in your view pages (for instance using the 
+	using pure PHP in your view pages (for instance using the
 	:doc:`View renderer ` )
 	lets them run a little faster.
 	However, some developers prefer to use some form of template engine if
@@ -40,7 +40,7 @@ Using the View Parser Class
 ***************************
 
 If you have the ``Parser`` as your default renderer, then the parsing will occur
-transparently. If you want to work with it more directly, you can access the 
+transparently. If you want to work with it more directly, you can access the
 Parser service directly::
 
 	$parser = \Config\Services::renderer();
@@ -50,9 +50,9 @@ can instantiate it directly::
 
 	$parser = new \CodeIgniter\View\Parser();
 
-Then you can use any of the three standard rendering methods that it provides: 
-**render(viewpath, options, save)**, **setVar(name, value, context)** and 
-**setData(data, context)**. You will also be able to specify delimiters directly, 
+Then you can use any of the three standard rendering methods that it provides:
+**render(viewpath, options, save)**, **setVar(name, value, context)** and
+**setData(data, context)**. You will also be able to specify delimiters directly,
 through the **setDelimiters(left,right)** method.
 
 Using the ``Parser``, your view templates are processed only by the Parser
@@ -67,7 +67,7 @@ What It Does
 The ``Parser`` class processes "PHP/HTML scripts" stored in the application's view path.
 These scripts have a ``.php`` extension, but should not contain any PHP.
 
-Each view parameter (which we refer to as a pseudo-variable) triggers a substitution, 
+Each view parameter (which we refer to as a pseudo-variable) triggers a substitution,
 based on the type of value you provided for it. Pseudo-variables are not
 extracted into PHP variables; instead their value is accessed through the pseudo-variable
 syntax, where its name is referenced inside braces.
@@ -100,7 +100,7 @@ array of data to be replaced in the template. In the above example, the
 template would contain two variables: {blog_title} and {blog_heading}
 The first parameter to ``render()`` contains the name of the :doc:`view
 file <../general/views>` (in this example the file would be called
-blog_template.php), 
+blog_template.php),
 
 
 Parser Configuration Options
@@ -237,7 +237,7 @@ array. The key/value pairs defined inside it will be exposed inside
 the variable pair loop for that variable.
 
 A ``blog_template`` that might work for the above::
-	
+
 	

{blog_title} - {blog_heading}

{blog_entry}
@@ -249,17 +249,33 @@ A ``blog_template`` that might work for the above:: If you would like the other pseudo-variables accessible inside the "blog_entry" scope, then make sure that the "cascadeData" option is set to true. +Comments +======== + +You can place comments in your templates that will be ignored and removed during parsing by wrapping the +comments in a ``{# ... #}`` symbols. + +:: + + {# This comment is removed during parsing. #} + {blog_entry} +
+

{title}

+

{body}{/p} +

+ {/blog_entry} + Cascading Data ============== With both a nested and a loop substitution, you have the option of cascading -data pairs into the inner substitution. +data pairs into the inner substitution. The following example is not impacted by cascading:: $template = '{name} lives in {location}{city} on {planet}{/location}.'; - $data = ['name' => 'George', + $data = ['name' => 'George', 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; echo $parser->setData($data)->renderString($template); @@ -269,7 +285,7 @@ This example gives different results, depending on cascading:: $template = '{location}{name} lives in {city} on {planet}{/location}.'; - $data = ['name' => 'George', + $data = ['name' => 'George', 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; echo $parser->setData($data)->renderString($template, ['cascadeData'=>false]); @@ -361,7 +377,7 @@ Result::
  • Second Link
  • -An example with the iteration controlled in the controller, +An example with the iteration controlled in the controller, using a view fragment:: $temp = ''; @@ -398,7 +414,7 @@ Class Reference .. php:method:: render($view[, $options[, $saveData=false]]]) - :param string $view: File name of the view source + :param string $view: File name of the view source :param array $options: Array of options, as key/value pairs :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. :returns: The rendered text for the chosen view @@ -418,7 +434,7 @@ Class Reference - ``rightDelimiter`` - the right delimiter to use in pseudo-variable syntax Any conditional substitutions are performed first, then remaining - substitutions are performed for each data pair. + substitutions are performed for each data pair. .. php:method:: renderString($template[, $options[, $saveData=false]]]) @@ -437,7 +453,7 @@ Class Reference .. php:method:: setData([$data[, $context=null]]) :param array $data: Array of view data strings, as key/value pairs - :param string $context: The context to use for data escaping. + :param string $context: The context to use for data escaping. :returns: The Renderer, for method chaining :rtype: CodeIgniter\\View\\RendererInterface. @@ -452,7 +468,7 @@ Class Reference :param string $name: Name of the view data variable :param mixed $value: The value of this view data - :param string $context: The context to use for data escaping. + :param string $context: The context to use for data escaping. :returns: The Renderer, for method chaining :rtype: CodeIgniter\\View\\RendererInterface. From 68ac15cfc3544bbc44bc3e7c56b77f0cd6eff0c4 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 2 Apr 2017 23:56:49 -0500 Subject: [PATCH 0584/1807] Adding noparse option to parser. --- system/View/Parser.php | 66 ++++++++++++++++++- tests/system/View/ParserTest.php | 23 ++++++- user_guide_src/source/general/view_parser.rst | 11 ++++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 9f2b13ee9b27..9f4849a1fbf1 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -68,6 +68,13 @@ class Parser extends View { */ public $rightDelimiter = '}'; + /** + * Stores extracted noparse blocks. + * + * @var array + */ + protected $noparseBlocks = []; + //-------------------------------------------------------------------- /** @@ -204,13 +211,16 @@ protected function parse(string $template, array $data = [], array $options = nu } $template = $this->parseComments($template); + $template = $this->extractNoparse($template); // build the variable substitution list $replace = array(); foreach ($data as $key => $val) { $replace = array_merge( - $replace, is_array($val) ? $this->parsePair($key, $val, $template) : $this->parseSingle($key, (string) $val, $template) + $replace, is_array($val) + ? $this->parsePair($key, $val, $template) + : $this->parseSingle($key, (string) $val, $template) ); } @@ -218,6 +228,8 @@ protected function parse(string $template, array $data = [], array $options = nu // do the substitutions $template = strtr($template, $replace); + $template = $this->insertNoparse($template); + return $template; } @@ -333,6 +345,58 @@ protected function parseComments(string $template): string //-------------------------------------------------------------------- + /** + * Extracts noparse blocks, inserting a hash in its place so that + * those blocks of the page are not touched by parsing. + * + * @param string $template + * + * @return string + */ + protected function extractNoparse(string $template): string + { + $pattern = '/\{\s*noparse\s*\}(.*?)\{\s*\/noparse\s*\}/ms'; + + /** + * $matches[][0] is the raw match + * $matches[][1] is the contents + */ + if (preg_match_all($pattern, $template, $matches, PREG_SET_ORDER)) + { + foreach ($matches as $match) + { + // Create a hash of the contents to insert in its place. + $hash = md5($match[1]); + $this->noparseBlocks[$hash] = $match[1]; + $template = str_replace($match[0], "noparse_{$hash}", $template); + } + } + + return $template; + } + + //-------------------------------------------------------------------- + + /** + * Re-inserts the noparsed contents back into the template. + * + * @param string $template + * + * @return string + */ + public function insertNoparse(string $template): string + { + foreach ($this->noparseBlocks as $hash => $replace) + { + $template = str_replace("noparse_{$hash}", $replace, $template); + unset($this->noparseBlocks[$hash]); + } + + return $template; + } + + //-------------------------------------------------------------------- + /** * Over-ride the substitution field delimiters. * diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index b235f0921740..77411dc4a6dd 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -197,9 +197,6 @@ public function testEscHandling($value, $expected = null) // ------------------------------------------------------------------------ - /** - * @group single - */ public function testIgnoresComments() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -217,4 +214,24 @@ public function testIgnoresComments() $this->assertEquals($result, $parser->renderString($template)); } + /** + * @group single + */ + public function testNoParse() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = array ( + 'title' => 'Super Heroes', + 'powers' => array ( + array ('invisibility' => 'yes', 'flying' => 'no') + ) + ); + + $template = "{noparse}{title}\n{powers}{invisibility}\n{flying}{/noparse}"; + $result = "{title}\n{powers}{invisibility}\n{flying}"; + + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); + } + } diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index 2692dc0d14b4..5a754c9106c6 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -265,6 +265,17 @@ comments in a ``{# ... #}`` symbols.
    {/blog_entry} +Preventing Parsing +================== + +You can specify portions of the page to not be parsed with the ``{noparse}{/noparse}`` tag pair. + +:: + + {noparse} +

    Untouched Code

    + {/noparse} + Cascading Data ============== From c425591661d1e8486e388cad4bd44ec59df4917d Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 3 Apr 2017 00:37:20 -0500 Subject: [PATCH 0585/1807] Early success at adding conditional logic to the parser. --- system/View/Parser.php | 57 ++++++++++++ tests/system/View/ParserTest.php | 150 ++++++++++++++++++------------- 2 files changed, 143 insertions(+), 64 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 9f4849a1fbf1..a69171e4f43b 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -213,6 +213,9 @@ protected function parse(string $template, array $data = [], array $options = nu $template = $this->parseComments($template); $template = $this->extractNoparse($template); + // Replace any conditional code here so we don't have to parse as much + $template = $this->parseConditionals($template); + // build the variable substitution list $replace = array(); foreach ($data as $key => $val) @@ -397,6 +400,60 @@ public function insertNoparse(string $template): string //-------------------------------------------------------------------- + /** + * Parses any conditionals in the code, removing blocks that don't + * pass so we don't try to parse it later. + * + * Valid conditionals: + * - if + * - elseif + * - else + * + * @param string $template + * + * @return string + */ + protected function parseConditionals(string $template): string + { + $pattern = '/\{\s*(if|elseif)\s*((?:\()?(.*?)(?:\))?)\s*\}/ms'; + + /** + * For each match: + * [0] = raw match `{if var}` + * [1] = conditional `if` + * [2] = condition `do === true` + * [3] = same as [2] + */ + preg_match_all($pattern, $template, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) + { + // Build the string to replace the `if` statement with. + $condition = $match[2]; + + $statement = ''; + $template = str_replace($match[0], $statement, $template); + } + + $template = preg_replace('/\{\s*else\s*\}/ms', '', $template); + $template = preg_replace('/\{\s*endif\s*\}/ms', '', $template); + + // Parse the PHP itself, or insert an error so they can debug + ob_start(); + extract($this->data); + $result = eval('?>'.$template.'', 'loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); + $this->loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); $this->viewsDir = __DIR__.'/Views'; - $this->config = new Config\View(); + $this->config = new Config\View(); } // -------------------------------------------------------------------- @@ -53,10 +53,10 @@ public function testParseSimple() public function testParseString() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Page Title', - 'body' => 'Lorem ipsum dolor sit amet.' - ); + $data = [ + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.', + ]; $template = "{title}\n{body}"; @@ -71,10 +71,10 @@ public function testParseString() public function testParseStringMissingData() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Page Title', - 'body' => 'Lorem ipsum dolor sit amet.' - ); + $data = [ + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.', + ]; $template = "{title}\n{body}\n{name}"; @@ -89,11 +89,11 @@ public function testParseStringMissingData() public function testParseStringUnusedData() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Page Title', - 'body' => 'Lorem ipsum dolor sit amet.', - 'name' => 'Someone' - ); + $data = [ + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.', + 'name' => 'Someone', + ]; $template = "{title}\n{body}"; @@ -116,12 +116,12 @@ public function testParseNoTemplate() public function testParseNested() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Super Heroes', - 'powers' => array ( - array ('invisibility' => 'yes', 'flying' => 'no') - ) - ); + $data = [ + 'title' => 'Super Heroes', + 'powers' => [ + ['invisibility' => 'yes', 'flying' => 'no'], + ], + ]; $template = "{title}\n{powers}{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{/powers}"; @@ -134,14 +134,14 @@ public function testParseNested() public function testParseLoop() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Super Heroes', - 'powers' => array ( - array ('name' => 'Tom'), - array ('name' => 'Dick'), - array ('name' => 'Henry') - ) - ); + $data = [ + 'title' => 'Super Heroes', + 'powers' => [ + ['name' => 'Tom'], + ['name' => 'Dick'], + ['name' => 'Henry'], + ], + ]; $template = "{title}\n{powers}{name} {/powers}"; @@ -154,35 +154,40 @@ public function testParseLoop() public function testMismatchedVarPair() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Super Heroes', - 'powers' => array ( - array ('invisibility' => 'yes', 'flying' => 'no') - ) - ); + $data = [ + 'title' => 'Super Heroes', + 'powers' => [ + ['invisibility' => 'yes', 'flying' => 'no'], + ], + ]; $template = "{title}\n{powers}{invisibility}\n{flying}"; - $result = "Super Heroes\n{powers}{invisibility}\n{flying}"; + $result = "Super Heroes\n{powers}{invisibility}\n{flying}"; $parser->setData($data); $this->assertEquals($result, $parser->renderString($template)); } - // Test anchor + // Test anchor public function escValueTypes() { return [ - 'scalar' => [42], - 'string' => ['George'], - 'scalarlist' => [ [1, 2, 17, -4]], - 'stringlist' => [ ['George', 'Paul', 'John', 'Ringo']], - 'associative' => [ ['name' => 'George', 'role' => 'guitar']], - 'compound' => [ ['name' => 'George', 'address' => ['line1' => '123 Some St', 'planet' => 'Naboo']]], - 'pseudo' => [ ['name' => 'George', 'emails' => [ + 'scalar' => [42], + 'string' => ['George'], + 'scalarlist' => [[1, 2, 17, -4]], + 'stringlist' => [['George', 'Paul', 'John', 'Ringo']], + 'associative' => [['name' => 'George', 'role' => 'guitar']], + 'compound' => [['name' => 'George', 'address' => ['line1' => '123 Some St', 'planet' => 'Naboo']]], + 'pseudo' => [ + [ + 'name' => 'George', + 'emails' => [ ['email' => 'me@here.com', 'type' => 'home'], - ['email' => 'me@there.com', 'type' => 'work'] - ]]], + ['email' => 'me@there.com', 'type' => 'work'], + ], + ], + ], ]; } @@ -191,7 +196,10 @@ public function escValueTypes() */ public function testEscHandling($value, $expected = null) { - if ($expected == null) $expected = $value; + if ($expected == null) + { + $expected = $value; + } $this->assertEquals($expected, \esc($value, 'html')); } @@ -200,38 +208,52 @@ public function testEscHandling($value, $expected = null) public function testIgnoresComments() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Super Heroes', - 'powers' => array ( - array ('invisibility' => 'yes', 'flying' => 'no') - ) - ); + $data = [ + 'title' => 'Super Heroes', + 'powers' => [ + ['invisibility' => 'yes', 'flying' => 'no'], + ], + ]; $template = "{# Comments #}{title}\n{powers}{invisibility}\n{flying}"; - $result = "Super Heroes\n{powers}{invisibility}\n{flying}"; + $result = "Super Heroes\n{powers}{invisibility}\n{flying}"; $parser->setData($data); $this->assertEquals($result, $parser->renderString($template)); } - /** - * @group single - */ public function testNoParse() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $data = array ( - 'title' => 'Super Heroes', - 'powers' => array ( - array ('invisibility' => 'yes', 'flying' => 'no') - ) - ); + $data = [ + 'title' => 'Super Heroes', + 'powers' => [ + ['invisibility' => 'yes', 'flying' => 'no'], + ], + ]; $template = "{noparse}{title}\n{powers}{invisibility}\n{flying}{/noparse}"; - $result = "{title}\n{powers}{invisibility}\n{flying}"; + $result = "{title}\n{powers}{invisibility}\n{flying}"; $parser->setData($data); $this->assertEquals($result, $parser->renderString($template)); } + /** + * @group single + */ + public function testIfConditionalTrue() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'doit' => true, + 'dontdoit' => false + ]; + + $template = "{if doit}Howdy{endif}{ if dontdoit === false}Welcome{ endif }"; + $parser->setData($data); + + $this->assertEquals('HowdyWelcome', $parser->renderString($template)); + } + } From ea385583a9429e52c18d9de899c350ed891ffcad Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Thu, 30 Mar 2017 02:19:39 +0900 Subject: [PATCH 0586/1807] Split Validation Rules Signed-off-by: ytetsuro --- application/Config/Validation.php | 13 +- system/Validation/Rules.php | 670 ------------------ system/Validation/Rules/AlphaFormat.php | 118 +++ system/Validation/Rules/Comparison.php | 140 ++++ .../CreditCard.php} | 4 +- .../Validation/Rules/DatabaseDependency.php | 83 +++ system/Validation/Rules/DateTimeFormat.php | 60 ++ .../{FileRules.php => Rules/File.php} | 4 +- system/Validation/Rules/Format.php | 211 ++++++ system/Validation/Rules/Length.php | 106 +++ system/Validation/Rules/NumberFormat.php | 111 +++ system/Validation/Rules/Required.php | 143 ++++ tests/system/Validation/ValidationTest.php | 13 +- 13 files changed, 996 insertions(+), 680 deletions(-) delete mode 100644 system/Validation/Rules.php create mode 100644 system/Validation/Rules/AlphaFormat.php create mode 100644 system/Validation/Rules/Comparison.php rename system/Validation/{CreditCardRules.php => Rules/CreditCard.php} (99%) create mode 100644 system/Validation/Rules/DatabaseDependency.php create mode 100644 system/Validation/Rules/DateTimeFormat.php rename system/Validation/{FileRules.php => Rules/File.php} (99%) create mode 100644 system/Validation/Rules/Format.php create mode 100644 system/Validation/Rules/Length.php create mode 100644 system/Validation/Rules/NumberFormat.php create mode 100644 system/Validation/Rules/Required.php diff --git a/application/Config/Validation.php b/application/Config/Validation.php index e9f6e29e33ee..12ed5624fb24 100644 --- a/application/Config/Validation.php +++ b/application/Config/Validation.php @@ -13,9 +13,16 @@ class Validation * @var array */ public $ruleSets = [ - \CodeIgniter\Validation\Rules::class, - \CodeIgniter\Validation\FileRules::class, - \CodeIgniter\Validation\CreditCardRules::class, + \CodeIgniter\Validation\Rules\Required::class, + \CodeIgniter\Validation\Rules\AlphaFormat::class, + \CodeIgniter\Validation\Rules\DateTimeFormat::class, + \CodeIgniter\Validation\Rules\NumberFormat::class, + \CodeIgniter\Validation\Rules\Format::class, + \CodeIgniter\Validation\Rules\Comparison::class, + \CodeIgniter\Validation\Rules\DatabaseDependency::class, + \CodeIgniter\Validation\Rules\Length::class, + \CodeIgniter\Validation\Rules\File::class, + \CodeIgniter\Validation\Rules\CreditCard::class, ]; /** diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php deleted file mode 100644 index b90a6b981a2f..000000000000 --- a/system/Validation/Rules.php +++ /dev/null @@ -1,670 +0,0 @@ - $min) : false; - } - - //-------------------------------------------------------------------- - - /** - * Equal to or Greater than - * - * @param string - * @param int - * - * @return bool - */ - public function greater_than_equal_to(string $str=null, string $min, array $data): bool - { - return is_numeric($str) ? ($str >= $min) : false; - } - - //-------------------------------------------------------------------- - - /** - * Value should be within an array of values - * - * @param string - * @param string - * @return bool - */ - public function in_list(string $value=null, string $list, array $data): bool - { - $list = explode(',', $list); - $list = array_map(function($value) { return trim($value); }, $list); - return in_array($value, $list, TRUE); - } - - //-------------------------------------------------------------------- - - /** - * Integer - * - * @param string - * - * @return bool - */ - public function integer(string $str=null): bool - { - return (bool)preg_match('/^[\-+]?[0-9]+$/', $str); - } - - //-------------------------------------------------------------------- - - /** - * Is a Natural number (0,1,2,3, etc.) - * - * @param string - * @return bool - */ - public function is_natural(string $str=null): bool - { - return ctype_digit((string) $str); - } - - //-------------------------------------------------------------------- - - /** - * Is a Natural number, but not a zero (1,2,3, etc.) - * - * @param string - * @return bool - */ - public function is_natural_no_zero(string $str=null): bool - { - return ($str != 0 && ctype_digit((string) $str)); - } - - //-------------------------------------------------------------------- - - /** - * Checks the database to see if the given value is unique. Can - * ignore a single record by field/value to make it useful during - * record updates. - * - * Example: - * is_unique[table.field,ignore_field,ignore_value] - * is_unique[users.email,id,5] - * - * @param string $str - * @param string $field - * @param array $data - * - * @return bool - */ - public function is_unique(string $str=null, string $field, array $data): bool - { - // Grab any data for exclusion of a single row. - list($field, $ignoreField, $ignoreValue) = array_pad(explode(',', $field), 3, null); - - // Break the table and field apart - sscanf($field, '%[^.].%[^.]', $table, $field); - - $db = Database::connect(); - $row = $db->table($table) - ->where($field, $str); - - if (! empty($ignoreField) && ! empty($ignoreValue)) - { - $row = $row->where("{$ignoreField} !=", $ignoreValue); - } - - return (bool)($row->get() - ->getRow() === null); - } - - //-------------------------------------------------------------------- - - /** - * Less than - * - * @param string - * @param int - * - * @return bool - */ - public function less_than(string $str=null, string $max): bool - { - return is_numeric($str) ? ($str < $max) : false; - } - - //-------------------------------------------------------------------- - - /** - * Equal to or Less than - * - * @param string - * @param int - * - * @return bool - */ - public function less_than_equal_to(string $str=null, string $max): bool - { - return is_numeric($str) ? ($str <= $max) : false; - } - - //-------------------------------------------------------------------- - - /** - * Matches the value of another field in $data. - * - * @param string $str - * @param string $field - * @param array $data Other field/value pairs - * - * @return bool - */ - public function matches(string $str=null, string $field, array $data): bool - { - return array_key_exists($field, $data) - ? ($str === $data[$field]) - : false; - } - - //-------------------------------------------------------------------- - - /** - * Returns true if $str is $val or fewer characters in length. - * - * @param string $str - * @param string $val - * @param array $data - * - * @return bool - */ - public function max_length(string $str=null, string $val, array $data): bool - { - if (! is_numeric($val)) - { - return false; - } - - return ($val >= mb_strlen($str)); - } - - //-------------------------------------------------------------------- - - /** - * Returns true if $str is at least $val length. - * - * @param string $str - * @param string $val - * @param array $data - * - * @return bool - */ - public function min_length(string $str=null, string $val, array $data): bool - { - if (! is_numeric($val)) - { - return false; - } - - return ($val <= mb_strlen($str)); - } - - //-------------------------------------------------------------------- - - /** - * Numeric - * - * @param string - * - * @return bool - */ - public function numeric(string $str=null): bool - { - return (bool)preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); - - } - - //-------------------------------------------------------------------- - - /** - * Compares value against a regular expression pattern. - * - * @param string $str - * @param string $pattern - * @param array $data Other field/value pairs - * - * @return bool - */ - public function regex_match(string $str=null, string $pattern, array $data): bool - { - if (substr($pattern, 0, 1) != '/') - { - $pattern = "/{$pattern}/"; - } - - return (bool)preg_match($pattern, $str); - } - - //-------------------------------------------------------------------- - - /** - * Required - * - * @param string - * - * @return bool - */ - public function required($str=null): bool - { - return is_array($str) ? (bool)count($str) : (trim($str) !== ''); - } - - //-------------------------------------------------------------------- - - /** - * The field is required when any of the other fields are present - * in the data. - * - * Example (field is required when the password field is present): - * - * required_with[password] - * - * @param $str - * @param string $fields - * @param array $data - * - * @return bool - */ - public function required_with($str=null, string $fields, array $data): bool - { - $fields = explode(',', $fields); - - // If the field is present we can safely assume that - // the field is here, no matter whether the corresponding - // search field is present or not. - $present = $this->required($data[$str] ?? null); - - if ($present === true) - { - return true; - } - - // Still here? Then we fail this test if - // any of the fields are present in $data - $requiredFields = array_intersect($fields, $data); - - $requiredFields = array_filter($requiredFields, function($item) - { - return ! empty($item); - }); - - return ! (bool)count($requiredFields); - } - - //-------------------------------------------------------------------- - - /** - * The field is required when all of the other fields are not present - * in the data. - * - * Example (field is required when the id or email field is missing): - * - * required_without[id,email] - * - * @param $str - * @param string $fields - * @param array $data - * - * @return bool - */ - public function required_without($str=null, string $fields, array $data): bool - { - $fields = explode(',', $fields); - - // If the field is present we can safely assume that - // the field is here, no matter whether the corresponding - // search field is present or not. - $present = $this->required($data[$str] ?? null); - - if ($present === true) - { - return true; - } - - // Still here? Then we fail this test if - // any of the fields are not present in $data - foreach ($fields as $field) - { - if (! array_key_exists($field, $data)) - { - return false; - } - } - - return true; - } - - //-------------------------------------------------------------------- - - /** - * Validates that the string is a valid timezone as per the - * timezone_identifiers_list function. - * - * @see http://php.net/manual/en/datetimezone.listidentifiers.php - * - * @param string $str - * - * @return bool - */ - public function timezone(string $str=null): bool - { - return in_array($str, timezone_identifiers_list()); - } - - //-------------------------------------------------------------------- - - /** - * Valid Base64 - * - * Tests a string for characters outside of the Base64 alphabet - * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045 - * - * @param string - * @return bool - */ - public function valid_base64(string $str=null): bool - { - return (base64_encode(base64_decode($str)) === $str); - } - - //-------------------------------------------------------------------- - - /** - * Checks for a correctly formatted email address - * - * @param string - * - * @return bool - */ - public function valid_email(string $str=null): bool - { - if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@')) - { - $str = substr($str, 0, ++$atpos).idn_to_ascii(substr($str, $atpos)); - } - - return (bool)filter_var($str, FILTER_VALIDATE_EMAIL); - } - - //-------------------------------------------------------------------- - - /** - * Validate a comma-separated list of email addresses. - * - * Example: - * valid_emails[one@example.com,two@example.com] - * - * @param string - * - * @return bool - */ - public function valid_emails(string $str=null): bool - { - if (strpos($str, ',') === false) - { - return $this->valid_email(trim($str)); - } - - foreach (explode(',', $str) as $email) - { - if (trim($email) !== '' && $this->valid_email(trim($email)) === false) - { - return false; - } - } - - return true; - } - - //-------------------------------------------------------------------- - - /** - * Validate an IP address - * - * @param $ip IP Address - * @param string $which IP protocol: 'ipv4' or 'ipv6' - * @param array $data - * - * @return bool - */ - public function valid_ip(string $ip=null, string $which = null, array $data): bool - { - switch (strtolower($which)) - { - case 'ipv4': - $which = FILTER_FLAG_IPV4; - break; - case 'ipv6': - $which = FILTER_FLAG_IPV6; - break; - default: - $which = null; - break; - } - - return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which); - } - - //-------------------------------------------------------------------- - - /** - * Checks a URL to ensure it's formed correctly. - * - * @param string $str - * - * @return bool - */ - public function valid_url(string $str=null): bool - { - if (empty($str)) - { - return false; - } - elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) - { - if (empty($matches[2])) - { - return false; - } - elseif (! in_array($matches[1], ['http', 'https'], true)) - { - return false; - } - - $str = $matches[2]; - } - - $str = 'http://'.$str; - - return (filter_var($str, FILTER_VALIDATE_URL) !== false); - } - - //-------------------------------------------------------------------- - -} diff --git a/system/Validation/Rules/AlphaFormat.php b/system/Validation/Rules/AlphaFormat.php new file mode 100644 index 000000000000..5d6bc036da5a --- /dev/null +++ b/system/Validation/Rules/AlphaFormat.php @@ -0,0 +1,118 @@ + $min) : false; + } + + //-------------------------------------------------------------------- + + /** + * Equal to or Greater than + * + * @param string + * @param int + * + * @return bool + */ + public function greater_than_equal_to(string $str=null, string $min, array $data): bool + { + return is_numeric($str) ? ($str >= $min) : false; + } + + //-------------------------------------------------------------------- + + /** + * Less than + * + * @param string + * @param int + * + * @return bool + */ + public function less_than(string $str=null, string $max): bool + { + return is_numeric($str) ? ($str < $max) : false; + } + + //-------------------------------------------------------------------- + + /** + * Equal to or Less than + * + * @param string + * @param int + * + * @return bool + */ + public function less_than_equal_to(string $str=null, string $max): bool + { + return is_numeric($str) ? ($str <= $max) : false; + } + + //-------------------------------------------------------------------- + + /** + * Matches the value of another field in $data. + * + * @param string $str + * @param string $field + * @param array $data Other field/value pairs + * + * @return bool + */ + public function matches(string $str=null, string $field, array $data): bool + { + return array_key_exists($field, $data) + ? ($str === $data[$field]) + : false; + } + +} diff --git a/system/Validation/CreditCardRules.php b/system/Validation/Rules/CreditCard.php similarity index 99% rename from system/Validation/CreditCardRules.php rename to system/Validation/Rules/CreditCard.php index 83f92bf31ee0..0165971c4b9d 100644 --- a/system/Validation/CreditCardRules.php +++ b/system/Validation/Rules/CreditCard.php @@ -1,4 +1,4 @@ -table($table) + ->where($field, $str); + + if (! empty($ignoreField) && ! empty($ignoreValue)) + { + $row = $row->where("{$ignoreField} !=", $ignoreValue); + } + + return (bool)($row->get() + ->getRow() === null); + } +} diff --git a/system/Validation/Rules/DateTimeFormat.php b/system/Validation/Rules/DateTimeFormat.php new file mode 100644 index 000000000000..ab55d0210555 --- /dev/null +++ b/system/Validation/Rules/DateTimeFormat.php @@ -0,0 +1,60 @@ +valid_email(trim($str)); + } + + foreach (explode(',', $str) as $email) + { + if (trim($email) !== '' && $this->valid_email(trim($email)) === false) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Validate an IP address + * + * @param $ip IP Address + * @param string $which IP protocol: 'ipv4' or 'ipv6' + * @param array $data + * + * @return bool + */ + public function valid_ip(string $ip=null, string $which = null, array $data): bool + { + switch (strtolower($which)) + { + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = null; + break; + } + + return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which); + } + + //-------------------------------------------------------------------- + + /** + * Checks a URL to ensure it's formed correctly. + * + * @param string $str + * + * @return bool + */ + public function valid_url(string $str=null): bool + { + if (empty($str)) + { + return false; + } + elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) + { + if (empty($matches[2])) + { + return false; + } + elseif (! in_array($matches[1], ['http', 'https'], true)) + { + return false; + } + + $str = $matches[2]; + } + + $str = 'http://'.$str; + + return (filter_var($str, FILTER_VALIDATE_URL) !== false); + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Validation/Rules/Length.php b/system/Validation/Rules/Length.php new file mode 100644 index 000000000000..3855492a1e62 --- /dev/null +++ b/system/Validation/Rules/Length.php @@ -0,0 +1,106 @@ += mb_strlen($str)); + } + + //-------------------------------------------------------------------- + + /** + * Returns true if $str is at least $val length. + * + * @param string $str + * @param string $val + * @param array $data + * + * @return bool + */ + public function min_length(string $str=null, string $val, array $data): bool + { + if (! is_numeric($val)) + { + return false; + } + + return ($val <= mb_strlen($str)); + } +} diff --git a/system/Validation/Rules/NumberFormat.php b/system/Validation/Rules/NumberFormat.php new file mode 100644 index 000000000000..8fadecbedfed --- /dev/null +++ b/system/Validation/Rules/NumberFormat.php @@ -0,0 +1,111 @@ +required($data[$str] ?? null); + + if ($present === true) + { + return true; + } + + // Still here? Then we fail this test if + // any of the fields are present in $data + $requiredFields = array_intersect($fields, $data); + + $requiredFields = array_filter($requiredFields, function($item) + { + return ! empty($item); + }); + + return ! (bool)count($requiredFields); + } + + //-------------------------------------------------------------------- + + /** + * The field is required when all of the other fields are not present + * in the data. + * + * Example (field is required when the id or email field is missing): + * + * required_without[id,email] + * + * @param $str + * @param string $fields + * @param array $data + * + * @return bool + */ + public function required_without($str=null, string $fields, array $data): bool + { + $fields = explode(',', $fields); + + // If the field is present we can safely assume that + // the field is here, no matter whether the corresponding + // search field is present or not. + $present = $this->required($data[$str] ?? null); + + if ($present === true) + { + return true; + } + + // Still here? Then we fail this test if + // any of the fields are not present in $data + foreach ($fields as $field) + { + if (! array_key_exists($field, $data)) + { + return false; + } + } + + return true; + } + +} diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 482f851b805e..a8f348f733db 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -12,9 +12,16 @@ class ValidationTest extends \CIUnitTestCase protected $config = [ 'ruleSets' => [ - \CodeIgniter\Validation\Rules::class, - \CodeIgniter\Validation\FileRules::class, - \CodeIgniter\Validation\CreditCardRules::class, + \CodeIgniter\Validation\Rules\Required::class, + \CodeIgniter\Validation\Rules\AlphaFormat::class, + \CodeIgniter\Validation\Rules\DateTimeFormat::class, + \CodeIgniter\Validation\Rules\NumberFormat::class, + \CodeIgniter\Validation\Rules\Format::class, + \CodeIgniter\Validation\Rules\Comparison::class, + \CodeIgniter\Validation\Rules\DatabaseDependency::class, + \CodeIgniter\Validation\Rules\Length::class, + \CodeIgniter\Validation\Rules\File::class, + \CodeIgniter\Validation\Rules\CreditCard::class, \CodeIgniter\Validation\TestRules::class, ], 'groupA' => [ From 06a67a4c64c7bbd753fa575cfcc55f10a6858a28 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Thu, 30 Mar 2017 02:22:32 +0900 Subject: [PATCH 0587/1807] space to tab --- tests/system/Validation/ValidationTest.php | 3362 ++++++++++---------- 1 file changed, 1681 insertions(+), 1681 deletions(-) diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index a8f348f733db..4270e3051726 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -5,13 +5,13 @@ class ValidationTest extends \CIUnitTestCase { - /** - * @var Validation - */ - protected $validation; + /** + * @var Validation + */ + protected $validation; - protected $config = [ - 'ruleSets' => [ + protected $config = [ + 'ruleSets' => [ \CodeIgniter\Validation\Rules\Required::class, \CodeIgniter\Validation\Rules\AlphaFormat::class, \CodeIgniter\Validation\Rules\DateTimeFormat::class, @@ -22,106 +22,106 @@ class ValidationTest extends \CIUnitTestCase \CodeIgniter\Validation\Rules\Length::class, \CodeIgniter\Validation\Rules\File::class, \CodeIgniter\Validation\Rules\CreditCard::class, - \CodeIgniter\Validation\TestRules::class, - ], - 'groupA' => [ - 'foo' => 'required|min_length[5]', - ], - 'groupA_errors' => [ - 'foo' => [ - 'min_length' => 'Shame, shame. Too short.', - ], - ], - ]; + \CodeIgniter\Validation\TestRules::class, + ], + 'groupA' => [ + 'foo' => 'required|min_length[5]', + ], + 'groupA_errors' => [ + 'foo' => [ + 'min_length' => 'Shame, shame. Too short.', + ], + ], + ]; - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function setUp() - { - parent::setUp(); - $this->validation = new Validation((object)$this->config, \Config\Services::renderer()); - $this->validation->reset(); + public function setUp() + { + parent::setUp(); + $this->validation = new Validation((object)$this->config, \Config\Services::renderer()); + $this->validation->reset(); - $_FILES = []; - } + $_FILES = []; + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testSetRulesStoresRules() - { - $rules = [ - 'foo' => 'bar|baz', - 'bar' => 'baz|belch', - ]; + public function testSetRulesStoresRules() + { + $rules = [ + 'foo' => 'bar|baz', + 'bar' => 'baz|belch', + ]; - $this->validation->setRules($rules); + $this->validation->setRules($rules); - $this->assertEquals($rules, $this->validation->getRules()); - } + $this->assertEquals($rules, $this->validation->getRules()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRunReturnsTrueWithNothingToDo() - { - $this->validation->setRules([]); + public function testRunReturnsTrueWithNothingToDo() + { + $this->validation->setRules([]); - $this->assertTrue($this->validation->run([])); - } + $this->assertTrue($this->validation->run([])); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRunDoesTheBasics() - { - $data = [ - 'foo' => 'notanumber', - ]; + public function testRunDoesTheBasics() + { + $data = [ + 'foo' => 'notanumber', + ]; - $this->validation->setRules([ - 'foo' => 'is_numeric', - ]); + $this->validation->setRules([ + 'foo' => 'is_numeric', + ]); - $this->assertFalse($this->validation->run($data)); - } + $this->assertFalse($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRunReturnsLocalizedErrors() - { - $data = [ - 'foo' => 'notanumber', - ]; + public function testRunReturnsLocalizedErrors() + { + $data = [ + 'foo' => 'notanumber', + ]; - $this->validation->setRules([ - 'foo' => 'is_numeric', - ]); + $this->validation->setRules([ + 'foo' => 'is_numeric', + ]); - $this->assertFalse($this->validation->run($data)); - $this->assertEquals('is_numeric', $this->validation->getError('foo')); - } + $this->assertFalse($this->validation->run($data)); + $this->assertEquals('is_numeric', $this->validation->getError('foo')); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRunWithCustomErrors() - { - $data = [ - 'foo' => 'notanumber', - ]; + public function testRunWithCustomErrors() + { + $data = [ + 'foo' => 'notanumber', + ]; - $messages = [ - 'foo' => [ - 'is_numeric' => 'Nope. Not a number.', - ], - ]; + $messages = [ + 'foo' => [ + 'is_numeric' => 'Nope. Not a number.', + ], + ]; - $this->validation->setRules([ - 'foo' => 'is_numeric', - ], $messages); + $this->validation->setRules([ + 'foo' => 'is_numeric', + ], $messages); - $this->validation->run($data); - $this->assertEquals('Nope. Not a number.', $this->validation->getError('foo')); - } + $this->validation->run($data); + $this->assertEquals('Nope. Not a number.', $this->validation->getError('foo')); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- public function testCheck() { @@ -141,100 +141,100 @@ public function testCheckLocalizedError() public function testCheckCustomError() { $this->validation->check('notanumber', 'is_numeric', [ - 'is_numeric' => 'Nope. Not a number.' + 'is_numeric' => 'Nope. Not a number.' ]); $this->assertEquals('Nope. Not a number.', $this->validation->getError()); } //-------------------------------------------------------------------- - public function testGetErrors() - { - $data = [ - 'foo' => 'notanumber', - ]; + public function testGetErrors() + { + $data = [ + 'foo' => 'notanumber', + ]; - $this->validation->setRules([ - 'foo' => 'is_numeric', - ]); + $this->validation->setRules([ + 'foo' => 'is_numeric', + ]); - $this->validation->run($data); + $this->validation->run($data); - $this->assertEquals(['foo' => 'is_numeric'], $this->validation->getErrors()); - } + $this->assertEquals(['foo' => 'is_numeric'], $this->validation->getErrors()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testGetErrorsWhenNone() - { - $data = [ - 'foo' => 123, - ]; + public function testGetErrorsWhenNone() + { + $data = [ + 'foo' => 123, + ]; - $this->validation->setRules([ - 'foo' => 'is_numeric', - ]); + $this->validation->setRules([ + 'foo' => 'is_numeric', + ]); - $this->validation->run($data); + $this->validation->run($data); - $this->assertEquals([], $this->validation->getErrors()); - } + $this->assertEquals([], $this->validation->getErrors()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testSetErrors() - { - $this->validation->setRules([ - 'foo' => 'is_numeric', - ]); + public function testSetErrors() + { + $this->validation->setRules([ + 'foo' => 'is_numeric', + ]); - $this->validation->setError('foo', 'Nadda'); + $this->validation->setError('foo', 'Nadda'); - $this->assertEquals(['foo' => 'Nadda'], $this->validation->getErrors()); - } + $this->assertEquals(['foo' => 'Nadda'], $this->validation->getErrors()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRulesReturnErrors() - { - $this->validation->setRules([ - 'foo' => 'customError' - ]); + public function testRulesReturnErrors() + { + $this->validation->setRules([ + 'foo' => 'customError' + ]); - $this->validation->run(['foo' => 'bar']); + $this->validation->run(['foo' => 'bar']); - $this->assertEquals(['foo' => 'My lovely error'], $this->validation->getErrors()); - } + $this->assertEquals(['foo' => 'My lovely error'], $this->validation->getErrors()); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testGroupsReadFromConfig() - { - $data = [ - 'foo' => 'bar', - ]; + public function testGroupsReadFromConfig() + { + $data = [ + 'foo' => 'bar', + ]; - $this->assertFalse($this->validation->run($data, 'groupA')); - $this->assertEquals('Shame, shame. Too short.', $this->validation->getError('foo')); - } + $this->assertFalse($this->validation->run($data, 'groupA')); + $this->assertEquals('Shame, shame. Too short.', $this->validation->getError('foo')); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testGroupsReadFromConfigValid() - { - $data = [ - 'foo' => 'barsteps', - ]; + public function testGroupsReadFromConfigValid() + { + $data = [ + 'foo' => 'barsteps', + ]; - $this->assertTrue($this->validation->run($data, 'groupA')); - } + $this->assertTrue($this->validation->run($data, 'groupA')); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- public function testGetRuleGroup() { $this->assertEquals([ - 'foo' => 'required|min_length[5]', + 'foo' => 'required|min_length[5]', ], $this->validation->getRuleGroup('groupA')); } @@ -253,7 +253,7 @@ public function testSetRuleGroup() $this->validation->setRuleGroup('groupA'); $this->assertEquals([ - 'foo' => 'required|min_length[5]', + 'foo' => 'required|min_length[5]', ], $this->validation->getRules()); } @@ -267,1643 +267,1643 @@ public function testSetRuleGroupException() } //-------------------------------------------------------------------- - // Rules Tests - //-------------------------------------------------------------------- + // Rules Tests + //-------------------------------------------------------------------- + + public function testRequiredNull() + { + $data = [ + 'foo' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'required', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- - public function testRequiredNull() - { - $data = [ - 'foo' => null, - ]; + public function testRequiredTrueString() + { + $data = [ + 'foo' => 123, + ]; - $this->validation->setRules([ - 'foo' => 'required', - ]); + $this->validation->setRules([ + 'foo' => 'required', + ]); - $this->assertFalse($this->validation->run($data)); - } + $this->assertTrue($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRequiredTrueString() - { - $data = [ - 'foo' => 123, - ]; + public function testRequiredFalseString() + { + $data = [ + 'bar' => 123, + ]; - $this->validation->setRules([ - 'foo' => 'required', - ]); + $this->validation->setRules([ + 'foo' => 'required', + ]); - $this->assertTrue($this->validation->run($data)); - } + $this->assertFalse($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRequiredFalseString() - { - $data = [ - 'bar' => 123, - ]; + public function testRequiredTrueArray() + { + $data = [ + 'foo' => [123], + ]; - $this->validation->setRules([ - 'foo' => 'required', - ]); + $this->validation->setRules([ + 'foo' => 'required', + ]); - $this->assertFalse($this->validation->run($data)); - } + $this->assertTrue($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRequiredTrueArray() - { - $data = [ - 'foo' => [123], - ]; + public function testRequiredFalseArray() + { + $data = [ + 'foo' => [], + ]; - $this->validation->setRules([ - 'foo' => 'required', - ]); + $this->validation->setRules([ + 'foo' => 'required', + ]); - $this->assertTrue($this->validation->run($data)); - } + $this->assertFalse($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRequiredFalseArray() - { - $data = [ - 'foo' => [], - ]; + public function testRegexMatch() + { + $data = [ + 'foo' => 'abcde', + ]; - $this->validation->setRules([ - 'foo' => 'required', - ]); + $this->validation->setRules([ + 'foo' => 'regex_match[/[a-z]/]', + ]); - $this->assertFalse($this->validation->run($data)); - } + $this->assertTrue($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRegexMatch() - { - $data = [ - 'foo' => 'abcde', - ]; + public function testRegexMatchFalse() + { + $data = [ + 'foo' => 'abcde', + ]; - $this->validation->setRules([ - 'foo' => 'regex_match[/[a-z]/]', - ]); + $this->validation->setRules([ + 'foo' => 'regex_match[\d]', + ]); - $this->assertTrue($this->validation->run($data)); - } + $this->assertFalse($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testRegexMatchFalse() - { - $data = [ - 'foo' => 'abcde', - ]; + public function testMatchesNull() + { + $data = [ + 'foo' => null, + 'bar' => null, + ]; - $this->validation->setRules([ - 'foo' => 'regex_match[\d]', - ]); + $this->validation->setRules([ + 'foo' => 'matches[bar]', + ]); - $this->assertFalse($this->validation->run($data)); - } + $this->assertTrue($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testMatchesNull() - { - $data = [ - 'foo' => null, - 'bar' => null, - ]; + public function testMatchesTrue() + { + $data = [ + 'foo' => 'match', + 'bar' => 'match', + ]; - $this->validation->setRules([ - 'foo' => 'matches[bar]', - ]); + $this->validation->setRules([ + 'foo' => 'matches[bar]', + ]); - $this->assertTrue($this->validation->run($data)); - } + $this->assertTrue($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testMatchesTrue() - { - $data = [ - 'foo' => 'match', - 'bar' => 'match', - ]; + public function testMatchesFalse() + { + $data = [ + 'foo' => 'match', + 'bar' => 'nope', + ]; - $this->validation->setRules([ - 'foo' => 'matches[bar]', - ]); + $this->validation->setRules([ + 'foo' => 'matches[bar]', + ]); - $this->assertTrue($this->validation->run($data)); - } + $this->assertFalse($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testMatchesFalse() - { - $data = [ - 'foo' => 'match', - 'bar' => 'nope', - ]; - - $this->validation->setRules([ - 'foo' => 'matches[bar]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function testDiffersNull() - { - $data = [ - 'foo' => null, - 'bar' => null, - ]; - - $this->validation->setRules([ - 'foo' => 'differs[bar]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function testDiffersTrue() - { - $data = [ - 'foo' => 'match', - 'bar' => 'nope', - ]; - - $this->validation->setRules([ - 'foo' => 'differs[bar]', - ]); - - $this->assertTrue($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function testDiffersFalse() - { - $data = [ - 'foo' => 'match', - 'bar' => 'match', - ]; - - $this->validation->setRules([ - 'foo' => 'differs[bar]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - /** - * @group DatabaseLive - */ - public function testIsUniqueFalse() - { - $data = [ - 'email' => 'derek@world.com', - ]; - - $this->validation->setRules([ - 'email' => 'is_unique[user.email]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- + public function testDiffersNull() + { + $data = [ + 'foo' => null, + 'bar' => null, + ]; - /** - * @group DatabaseLive - */ - public function testIsUniqueTrue() - { - $data = [ - 'email' => 'derek@world.co.uk', - ]; + $this->validation->setRules([ + 'foo' => 'differs[bar]', + ]); - $this->validation->setRules([ - 'email' => 'is_unique[user.email]', - ]); + $this->assertFalse($this->validation->run($data)); + } - $this->assertTrue($this->validation->run($data)); - } + //-------------------------------------------------------------------- - //-------------------------------------------------------------------- + public function testDiffersTrue() + { + $data = [ + 'foo' => 'match', + 'bar' => 'nope', + ]; - /** - * @group DatabaseLive - */ - public function testIsUniqueIgnoresParams() - { - $db = Database::connect(); - $row = $db->table('user') - ->limit(1) - ->get() - ->getRow(); + $this->validation->setRules([ + 'foo' => 'differs[bar]', + ]); - $data = [ - 'email' => 'derek@world.co.uk', - ]; + $this->assertTrue($this->validation->run($data)); + } - $this->validation->setRules([ - 'email' => "is_unique[user.email,id,{$row->id}]", - ]); - - $this->assertTrue($this->validation->run($data)); - } + //-------------------------------------------------------------------- - //-------------------------------------------------------------------- + public function testDiffersFalse() + { + $data = [ + 'foo' => 'match', + 'bar' => 'match', + ]; - public function testMinLengthNull() - { - $data = [ - 'foo' => null, - ]; + $this->validation->setRules([ + 'foo' => 'differs[bar]', + ]); - $this->validation->setRules([ - 'foo' => 'min_length[3]', - ]); - - $this->assertFalse($this->validation->run($data)); - } + $this->assertFalse($this->validation->run($data)); + } - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- - public function testMinLengthReturnsFalseWithNonNumericVal() - { - $data = [ - 'foo' => 'bar', - ]; + /** + * @group DatabaseLive + */ + public function testIsUniqueFalse() + { + $data = [ + 'email' => 'derek@world.com', + ]; - $this->validation->setRules([ - 'foo' => 'min_length[bar]', - ]); - - $this->assertFalse($this->validation->run($data)); - } + $this->validation->setRules([ + 'email' => 'is_unique[user.email]', + ]); - //-------------------------------------------------------------------- + $this->assertFalse($this->validation->run($data)); + } - public function testMinLengthReturnsTrueWithSuccess() - { - $data = [ - 'foo' => 'bar', - ]; + //-------------------------------------------------------------------- - $this->validation->setRules([ - 'foo' => 'min_length[2]', - ]); + /** + * @group DatabaseLive + */ + public function testIsUniqueTrue() + { + $data = [ + 'email' => 'derek@world.co.uk', + ]; - $this->assertTrue($this->validation->run($data)); - } + $this->validation->setRules([ + 'email' => 'is_unique[user.email]', + ]); - //-------------------------------------------------------------------- + $this->assertTrue($this->validation->run($data)); + } - public function testMinLengthReturnsTrueWithExactLength() - { - $data = [ - 'foo' => 'bar', - ]; + //-------------------------------------------------------------------- - $this->validation->setRules([ - 'foo' => 'min_length[3]', - ]); + /** + * @group DatabaseLive + */ + public function testIsUniqueIgnoresParams() + { + $db = Database::connect(); + $row = $db->table('user') + ->limit(1) + ->get() + ->getRow(); - $this->assertTrue($this->validation->run($data)); - } + $data = [ + 'email' => 'derek@world.co.uk', + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'email' => "is_unique[user.email,id,{$row->id}]", + ]); - public function testMinLengthReturnsFalseWhenWrong() - { - $data = [ - 'foo' => 'bar', - ]; + $this->assertTrue($this->validation->run($data)); + } - $this->validation->setRules([ - 'foo' => 'min_length[4]', - ]); + //-------------------------------------------------------------------- - $this->assertFalse($this->validation->run($data)); - } + public function testMinLengthNull() + { + $data = [ + 'foo' => null, + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'foo' => 'min_length[3]', + ]); - public function testMaxLengthNull() - { - $data = [ - 'foo' => null, - ]; + $this->assertFalse($this->validation->run($data)); + } - $this->validation->setRules([ - 'foo' => 'max_length[1]', - ]); + //-------------------------------------------------------------------- - $this->assertTrue($this->validation->run($data)); - } + public function testMinLengthReturnsFalseWithNonNumericVal() + { + $data = [ + 'foo' => 'bar', + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'foo' => 'min_length[bar]', + ]); - public function testMaxLengthReturnsFalseWithNonNumericVal() - { - $data = [ - 'foo' => 'bar', - ]; + $this->assertFalse($this->validation->run($data)); + } - $this->validation->setRules([ - 'foo' => 'max_length[bar]', - ]); + //-------------------------------------------------------------------- - $this->assertFalse($this->validation->run($data)); - } + public function testMinLengthReturnsTrueWithSuccess() + { + $data = [ + 'foo' => 'bar', + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'foo' => 'min_length[2]', + ]); - public function testMaxLengthReturnsTrueWithSuccess() - { - $data = [ - 'foo' => 'bar', - ]; + $this->assertTrue($this->validation->run($data)); + } - $this->validation->setRules([ - 'foo' => 'max_length[4]', - ]); + //-------------------------------------------------------------------- - $this->assertTrue($this->validation->run($data)); - } + public function testMinLengthReturnsTrueWithExactLength() + { + $data = [ + 'foo' => 'bar', + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'foo' => 'min_length[3]', + ]); - public function testMaxLengthReturnsTrueWithExactLength() - { - $data = [ - 'foo' => 'bar', - ]; + $this->assertTrue($this->validation->run($data)); + } - $this->validation->setRules([ - 'foo' => 'max_length[3]', - ]); + //-------------------------------------------------------------------- - $this->assertTrue($this->validation->run($data)); - } + public function testMinLengthReturnsFalseWhenWrong() + { + $data = [ + 'foo' => 'bar', + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'foo' => 'min_length[4]', + ]); - public function testMaxLengthReturnsFalseWhenWrong() - { - $data = [ - 'foo' => 'bar', - ]; + $this->assertFalse($this->validation->run($data)); + } - $this->validation->setRules([ - 'foo' => 'max_length[2]', - ]); + //-------------------------------------------------------------------- - $this->assertFalse($this->validation->run($data)); - } + public function testMaxLengthNull() + { + $data = [ + 'foo' => null, + ]; - //-------------------------------------------------------------------- + $this->validation->setRules([ + 'foo' => 'max_length[1]', + ]); - public function testExactLengthNull() - { - $data = [ - 'foo' => null, - ]; - - $this->validation->setRules([ - 'foo' => 'exact_length[3]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function testExactLengthReturnsTrueOnSuccess() - { - $data = [ - 'foo' => 'bar', - ]; - - $this->validation->setRules([ - 'foo' => 'exact_length[3]', - ]); - - $this->assertTrue($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function testExactLengthReturnsFalseWhenShort() - { - $data = [ - 'foo' => 'bar', - ]; - - $this->validation->setRules([ - 'foo' => 'exact_length[2]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function testExactLengthReturnsFalseWhenLong() - { - $data = [ - 'foo' => 'bar', - ]; - - $this->validation->setRules([ - 'foo' => 'exact_length[4]', - ]); - - $this->assertFalse($this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider urlProvider - */ - public function testValidURL(string $url=null, bool $expected) - { - $data = [ - 'foo' => $url, - ]; - - $this->validation->setRules([ - 'foo' => 'valid_url', - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function urlProvider() - { - return [ - ['www.codeigniter.com', true], - ['http://codeigniter.com', true], - //https://bugs.php.net/bug.php?id=51192 - ['http://accept-dashes.tld', true], - ['http://reject_underscores', false], - // https://github.com/bcit-ci/CodeIgniter/issues/4415 - ['http://[::1]/ipv6', true], - ['htt://www.codeigniter.com', false], - ['', false], - ['code igniter', false], - [null, false], - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider emailProviderSingle - * - * @param $email - * @param $expected - */ - public function testValidEmail($email, $expected) - { - $data = [ - 'foo' => $email, - ]; - - $this->validation->setRules([ - 'foo' => 'valid_email', - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider emailProviderSingle - * - * @param $email - * @param $expected - */ - public function testValidEmails($email, $expected) - { - $data = [ - 'foo' => $email, - ]; - - $this->validation->setRules([ - 'foo' => 'valid_emails', - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function emailProviderSingle() - { - return [ - ['email@sample.com', true], - ['valid_email', false], - [null, false], - ]; - } - - //-------------------------------------------------------------------- - - public function emailsProvider() - { - return [ - ['1@sample.com,2@sample.com', true], - ['1@sample.com, 2@sample.com', true], - ['email@sample.com', true], - ['@sample.com,2@sample.com,validemail@email.ca', false], - [null, false] - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider ipProvider - * - * @param $ip - * @param $which - * @param $expected - */ - public function testValidIP($ip, $which, $expected) - { - $data = [ - 'foo' => $ip, - ]; - - $this->validation->setRules([ - 'foo' => "valid_ip[{$which}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function ipProvider() - { - return [ - ['127.0.0.1', null, true], - ['127.0.0.1', 'ipv4', true], - ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', null, true], - ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 'ipv6', true], - ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 'ipv4', false], - ['127.0.0.1', 'ipv6', false], - ['H001:0db8:85a3:0000:0000:8a2e:0370:7334', null, false], - ['127.0.0.259', null, false], - [null, null, false] - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider alphaProvider - * - * @param $str - * @param $expected - */ - public function testAlpha($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "alpha", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function alphaProvider() - { - return [ - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ', true], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ ', false], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ1', false], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ*', false], - [null, false], - ]; - } + $this->assertTrue($this->validation->run($data)); + } //-------------------------------------------------------------------- - /** - * Test alpha with spaces. - * - * @param mixed $value Value. - * @param bool $expected Expected. - * - * @dataProvider alphaSpaceProvider - */ - public function testAlphaSpace($value, $expected) + public function testMaxLengthReturnsFalseWithNonNumericVal() { $data = [ - 'foo' => $value + 'foo' => 'bar', ]; $this->validation->setRules([ - 'foo' => 'alpha_space' + 'foo' => 'max_length[bar]', ]); - $this->assertEquals($expected, $this->validation->run($data)); + $this->assertFalse($this->validation->run($data)); } //-------------------------------------------------------------------- - public function alphaSpaceProvider() + public function testMaxLengthReturnsTrueWithSuccess() { - return [ - [null, true], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ', true], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ ', true], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ1', false], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ*', false], + $data = [ + 'foo' => 'bar', ]; + + $this->validation->setRules([ + 'foo' => 'max_length[4]', + ]); + + $this->assertTrue($this->validation->run($data)); } - //-------------------------------------------------------------------- - - /** - * @dataProvider alphaNumericProvider - * - * @param $str - * @param $expected - */ - public function testAlphaNumeric($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "alpha_numeric", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function alphaNumericProvider() - { - return [ - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789', true], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789\ ', false], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789_', false], - [null, false], - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider alphaNumericProvider - * - * @param $str - * @param $expected - */ - public function testAlphaNumericSpace($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "alpha_numeric_spaces", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function alphaNumericSpaceProvider() - { - return [ - [' abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789', true], - [' abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-', false], - [null, false], - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider alphaDashProvider - * - * @param $str - * @param $expected - */ - public function testAlphaDash($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "alpha_dash", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function alphaDashProvider() - { - return [ - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-', true], - ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-\ ', false], - [null, false], - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider numericProvider - * - * @param $str - * @param $expected - */ - public function testNumeric($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "numeric", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function numericProvider() - { - return [ - ['0', true], - ['12314', true], - ['-42', true], - ['+42', true], - ['123a', false], - ['--1', false], - [null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider integerProvider - * - * @param $str - * @param $expected - */ - public function testInteger($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "integer", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function integerProvider() - { - return [ - ['0', true], - ['42', true], - ['-1', true], - ['123a', false], - ['1.9', false], - ['--1', false], - [null, false], - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider decimalProvider - * - * @param $str - * @param $expected - */ - public function testDecimal($str, $expected) - { - $data = [ - 'foo' => $str, - ]; - - $this->validation->setRules([ - 'foo' => "decimal", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function decimalProvider() - { - return [ - ['1.0', true], - ['-0.98', true], - ['0', false], - ['1.0a', false], - ['-i', false], - ['--1', false], - [null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider greaterThanProvider - * - * @param $str - * @param $expected - */ - public function testGreaterThan($first, $second, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "greater_than[{$second}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function greaterThanProvider() - { - return [ - ['-10', '-11', true], - ['10', '9', true], - ['10', '10', false], - ['10', 'a', false], - ['10a', '10', false], - [null, null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider greaterThanEqualProvider - * - * @param $str - * @param $expected - */ - public function testGreaterThanEqual($first, $second, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "greater_than_equal_to[{$second}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function greaterThanEqualProvider() - { - return [ - ['0', '0', true], - ['1', '0', true], - ['-1', '0', false], - ['10a', '0', false], - [null, null, false], - [1, null, true], - [null, 1, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider lessThanProvider - * - * @param $str - * @param $expected - */ - public function testLessThan($first, $second, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "less_than[{$second}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function lessThanProvider() - { - return [ - ['4', '5', true], - ['-1', '0', true], - ['4', '4', false], - ['10a', '5', false], - [null, null, false], - [1, null, false], - [null, 1, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider lessThanEqualProvider - * - * @param $str - * @param $expected - */ - public function testLessEqualThan($first, $second, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "less_than_equal_to[{$second}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function lessThanEqualProvider() - { - return [ - ['-1', '0', true], - ['-1', '-1', true], - ['4', '4', true], - ['0', '-1', false], - ['10a', '0', false], - [null, null, false], - [null, 1, false], - [1, null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider inListProvider - * - * @param $str - * @param $expected - */ - public function testInList($first, $second, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "in_list[{$second}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function inListProvider() - { - return [ - ['red', 'red,Blue,123', true], - ['Blue', 'red, Blue,123', true], - ['Blue', 'red,Blue,123', true], - ['123', 'red,Blue,123', true], - ['Red', 'red,Blue,123', false], - [' red', 'red,Blue,123', false], - ['1234', 'red,Blue,123', false], - [null, 'red,Blue,123', false], - ['red', null, false], - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider naturalProvider - * - * @param $str - * @param $expected - */ - public function testNatural($first, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "is_natural", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function naturalProvider() - { - return [ - ['0', true], - ['12', true], - ['42a', false], - ['-1', false], - [null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider naturalZeroProvider - * - * @param $str - * @param $expected - */ - public function testNaturalNoZero($first, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "is_natural_no_zero", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function naturalZeroProvider() - { - return [ - ['0', false], - ['12', true], - ['42a', false], - ['-1', false], - [null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider base64Provider - * - * @param $str - * @param $expected - */ - public function testBase64($first, $expected) - { - $data = [ - 'foo' => $first, - ]; - - $this->validation->setRules([ - 'foo' => "valid_base64", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function base64Provider() - { - return [ - [base64_encode('string'), true], - ['FA08GG', false], - [null, false] - ]; - } - - //------------------------------------------------------------------- - - /** - * @dataProvider timezoneProvider - * - * @param $value - * @param $expected - */ - public function testTimeZone($value, $expected) - { - $data = [ - 'foo' => $value, - ]; - - $this->validation->setRules([ - 'foo' => "timezone", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function timezoneProvider() - { - return [ - ['America/Chicago', true], - ['america/chicago', false], - ['foo/bar', false], - [null, false] - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider requiredWithProvider - * - * @param $check - * @param $expected - */ - public function testRequiredWith($field, $check, $expected = false) - { - $data = [ - 'foo' => 'bar', - 'bar' => 'something', - 'baz' => null, - ]; - - $this->validation->setRules([ - $field => "required_with[{$check}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function requiredWithProvider() - { - return [ - ['nope', 'bar', false], - ['foo', 'bar', true], - ['nope', 'baz', true], - [null, null, true], - [null, 'foo', true], - ['foo', null, true] - ]; - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider requiredWithoutProvider - * - * @param $check - * @param $expected - */ - public function testRequiredWithout($field, $check, $expected = false) - { - $data = [ - 'foo' => 'bar', - 'bar' => 'something', - 'baz' => null, - ]; - - $this->validation->setRules([ - $field => "required_without[{$check}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - public function requiredWithoutProvider() - { - return [ - ['nope', 'bars', false], - ['foo', 'nope', true], - [null, null, false], - [null, 'foo', true], - ['foo', null, true] - ]; - } - - //-------------------------------------------------------------------- - - //-------------------------------------------------------------------- - // Credit Card Rules - //-------------------------------------------------------------------- - - /** - * @dataProvider creditCardProvider - * - * @param $type - * @param $number - * @param bool $expected - */ - public function testValidCCNumber($type, $number, $expected = false) - { - $data = [ - 'cc' => $number, - ]; - - $this->validation->setRules([ - 'cc' => "valid_cc_number[{$type}]", - ]); - - $this->assertEquals($expected, $this->validation->run($data)); - } - - //-------------------------------------------------------------------- - - /** - * Cards shown are test cards found around the web. - * - * @see https://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm - * - * @return array - */ - public function creditCardProvider() - { - return [ - 'null_test' => ['amex', null, false], - 'random_test' => ['amex', $this->generateCardNum('37', 16), false], - 'invalid_type' => ['shorty', '1111 1111 1111 1111', false], - 'invalid_length' => ['amex', '', false], - 'not_numeric' => ['amex', 'abcd efgh ijkl mnop', false], - 'bad_length' => ['amex', '3782 8224 6310 0051', false], - 'bad_prefix' => ['amex', '3582 8224 6310 0051', false], - 'amex1' => ['amex', '3782 8224 6310 005', true], - 'amex2' => ['amex', '3714 4963 5398 431', true], - 'dinersclub1' => ['dinersclub', '3056 9309 0259 04', true], - 'dinersculb2' => ['dinersclub', '3852 0000 0232 37', true], - 'discover1' => ['discover', '6011 1111 1111 1117', true], - 'discover2' => ['discover', '6011 0009 9013 9424', true], - 'jcb1' => ['jcb', '3530 1113 3330 0000', true], - 'jcb2' => ['jcb', '3566 0020 2036 0505', true], - 'mastercard1' => ['mastercard', '5555 5555 5555 4444', true], - 'mastercard2' => ['mastercard', '5105 1051 0510 5100', true], - 'visa1' => ['visa', '4111 1111 1111 1111', true], - 'visa2' => ['visa', '4012 8888 8888 1881', true], - 'visa3' => ['visa', '4222 2222 2222 2', true], - 'dankort1' => ['dankort', '5019 7170 1010 3742', true], - 'unionpay1' => ['unionpay', $this->generateCardNum(62, 16), true], - 'unionpay2' => ['unionpay', $this->generateCardNum(62, 17), true], - 'unionpay3' => ['unionpay', $this->generateCardNum(62, 18), true], - 'unionpay4' => ['unionpay', $this->generateCardNum(62, 19), true], - 'unionpay5' => ['unionpay', $this->generateCardNum(63, 19), false], - 'carteblanche1' => ['carteblanche', $this->generateCardNum(300, 14), true], - 'carteblanche2' => ['carteblanche', $this->generateCardNum(301, 14), true], - 'carteblanche3' => ['carteblanche', $this->generateCardNum(302, 14), true], - 'carteblanche4' => ['carteblanche', $this->generateCardNum(303, 14), true], - 'carteblanche5' => ['carteblanche', $this->generateCardNum(304, 14), true], - 'carteblanche6' => ['carteblanche', $this->generateCardNum(305, 14), true], - 'carteblanche7' => ['carteblanche', $this->generateCardNum(306, 14), false], - 'dinersclub3' => ['dinersclub', $this->generateCardNum(300, 14), true], - 'dinersclub4' => ['dinersclub', $this->generateCardNum(301, 14), true], - 'dinersclub5' => ['dinersclub', $this->generateCardNum(302, 14), true], - 'dinersclub6' => ['dinersclub', $this->generateCardNum(303, 14), true], - 'dinersclub7' => ['dinersclub', $this->generateCardNum(304, 14), true], - 'dinersclub8' => ['dinersclub', $this->generateCardNum(305, 14), true], - 'dinersclub9' => ['dinersclub', $this->generateCardNum(309, 14), true], - 'dinersclub10' => ['dinersclub', $this->generateCardNum(36, 14), true], - 'dinersclub11' => ['dinersclub', $this->generateCardNum(38, 14), true], - 'dinersclub12' => ['dinersclub', $this->generateCardNum(39, 14), true], - 'dinersclub13' => ['dinersclub', $this->generateCardNum(54, 14), true], - 'dinersclub14' => ['dinersclub', $this->generateCardNum(55, 14), true], - 'dinersclub15' => ['dinersclub', $this->generateCardNum(300, 16), true], - 'dinersclub16' => ['dinersclub', $this->generateCardNum(301, 16), true], - 'dinersclub17' => ['dinersclub', $this->generateCardNum(302, 16), true], - 'dinersclub18' => ['dinersclub', $this->generateCardNum(303, 16), true], - 'dinersclub19' => ['dinersclub', $this->generateCardNum(304, 16), true], - 'dinersclub20' => ['dinersclub', $this->generateCardNum(305, 16), true], - 'dinersclub21' => ['dinersclub', $this->generateCardNum(309, 16), true], - 'dinersclub22' => ['dinersclub', $this->generateCardNum(36, 16), true], - 'dinersclub23' => ['dinersclub', $this->generateCardNum(38, 16), true], - 'dinersclub24' => ['dinersclub', $this->generateCardNum(39, 16), true], - 'dinersclub25' => ['dinersclub', $this->generateCardNum(54, 16), true], - 'dinersclub26' => ['dinersclub', $this->generateCardNum(55, 16), true], - 'discover3' => ['discover', $this->generateCardNum(6011, 16), true], - 'discover4' => ['discover', $this->generateCardNum(622, 16), true], - 'discover5' => ['discover', $this->generateCardNum(644, 16), true], - 'discover6' => ['discover', $this->generateCardNum(645, 16), true], - 'discover7' => ['discover', $this->generateCardNum(656, 16), true], - 'discover8' => ['discover', $this->generateCardNum(647, 16), true], - 'discover9' => ['discover', $this->generateCardNum(648, 16), true], - 'discover10' => ['discover', $this->generateCardNum(649, 16), true], - 'discover11' => ['discover', $this->generateCardNum(65, 16), true], - 'discover12' => ['discover', $this->generateCardNum(6011, 19), true], - 'discover13' => ['discover', $this->generateCardNum(622, 19), true], - 'discover14' => ['discover', $this->generateCardNum(644, 19), true], - 'discover15' => ['discover', $this->generateCardNum(645, 19), true], - 'discover16' => ['discover', $this->generateCardNum(656, 19), true], - 'discover17' => ['discover', $this->generateCardNum(647, 19), true], - 'discover18' => ['discover', $this->generateCardNum(648, 19), true], - 'discover19' => ['discover', $this->generateCardNum(649, 19), true], - 'discover20' => ['discover', $this->generateCardNum(65, 19), true], - 'interpayment1' => ['interpayment', $this->generateCardNum(4, 16), true], - 'interpayment2' => ['interpayment', $this->generateCardNum(4, 17), true], - 'interpayment3' => ['interpayment', $this->generateCardNum(4, 18), true], - 'interpayment4' => ['interpayment', $this->generateCardNum(4, 19), true], - 'jcb1' => ['jcb', $this->generateCardNum(352, 16), true], - 'jcb2' => ['jcb', $this->generateCardNum(353, 16), true], - 'jcb3' => ['jcb', $this->generateCardNum(354, 16), true], - 'jcb4' => ['jcb', $this->generateCardNum(355, 16), true], - 'jcb5' => ['jcb', $this->generateCardNum(356, 16), true], - 'jcb6' => ['jcb', $this->generateCardNum(357, 16), true], - 'jcb7' => ['jcb', $this->generateCardNum(358, 16), true], - 'maestro1' => ['maestro', $this->generateCardNum(50, 12), true], - 'maestro2' => ['maestro', $this->generateCardNum(56, 12), true], - 'maestro3' => ['maestro', $this->generateCardNum(57, 12), true], - 'maestro4' => ['maestro', $this->generateCardNum(58, 12), true], - 'maestro5' => ['maestro', $this->generateCardNum(59, 12), true], - 'maestro6' => ['maestro', $this->generateCardNum(60, 12), true], - 'maestro7' => ['maestro', $this->generateCardNum(61, 12), true], - 'maestro8' => ['maestro', $this->generateCardNum(62, 12), true], - 'maestro9' => ['maestro', $this->generateCardNum(63, 12), true], - 'maestro10' => ['maestro', $this->generateCardNum(64, 12), true], - 'maestro11' => ['maestro', $this->generateCardNum(65, 12), true], - 'maestro12' => ['maestro', $this->generateCardNum(66, 12), true], - 'maestro13' => ['maestro', $this->generateCardNum(67, 12), true], - 'maestro14' => ['maestro', $this->generateCardNum(68, 12), true], - 'maestro15' => ['maestro', $this->generateCardNum(69, 12), true], - 'maestro16' => ['maestro', $this->generateCardNum(50, 13), true], - 'maestro17' => ['maestro', $this->generateCardNum(56, 13), true], - 'maestro18' => ['maestro', $this->generateCardNum(57, 13), true], - 'maestro19' => ['maestro', $this->generateCardNum(58, 13), true], - 'maestro20' => ['maestro', $this->generateCardNum(59, 13), true], - 'maestro21' => ['maestro', $this->generateCardNum(60, 13), true], - 'maestro22' => ['maestro', $this->generateCardNum(61, 13), true], - 'maestro23' => ['maestro', $this->generateCardNum(62, 13), true], - 'maestro24' => ['maestro', $this->generateCardNum(63, 13), true], - 'maestro25' => ['maestro', $this->generateCardNum(64, 13), true], - 'maestro26' => ['maestro', $this->generateCardNum(65, 13), true], - 'maestro27' => ['maestro', $this->generateCardNum(66, 13), true], - 'maestro28' => ['maestro', $this->generateCardNum(67, 13), true], - 'maestro29' => ['maestro', $this->generateCardNum(68, 13), true], - 'maestro30' => ['maestro', $this->generateCardNum(69, 13), true], - 'maestro31' => ['maestro', $this->generateCardNum(50, 14), true], - 'maestro32' => ['maestro', $this->generateCardNum(56, 14), true], - 'maestro33' => ['maestro', $this->generateCardNum(57, 14), true], - 'maestro34' => ['maestro', $this->generateCardNum(58, 14), true], - 'maestro35' => ['maestro', $this->generateCardNum(59, 14), true], - 'maestro36' => ['maestro', $this->generateCardNum(60, 14), true], - 'maestro37' => ['maestro', $this->generateCardNum(61, 14), true], - 'maestro38' => ['maestro', $this->generateCardNum(62, 14), true], - 'maestro39' => ['maestro', $this->generateCardNum(63, 14), true], - 'maestro40' => ['maestro', $this->generateCardNum(64, 14), true], - 'maestro41' => ['maestro', $this->generateCardNum(65, 14), true], - 'maestro42' => ['maestro', $this->generateCardNum(66, 14), true], - 'maestro43' => ['maestro', $this->generateCardNum(67, 14), true], - 'maestro44' => ['maestro', $this->generateCardNum(68, 14), true], - 'maestro45' => ['maestro', $this->generateCardNum(69, 14), true], - 'maestro46' => ['maestro', $this->generateCardNum(50, 15), true], - 'maestro47' => ['maestro', $this->generateCardNum(56, 15), true], - 'maestro48' => ['maestro', $this->generateCardNum(57, 15), true], - 'maestro49' => ['maestro', $this->generateCardNum(58, 15), true], - 'maestro50' => ['maestro', $this->generateCardNum(59, 15), true], - 'maestro51' => ['maestro', $this->generateCardNum(60, 15), true], - 'maestro52' => ['maestro', $this->generateCardNum(61, 15), true], - 'maestro53' => ['maestro', $this->generateCardNum(62, 15), true], - 'maestro54' => ['maestro', $this->generateCardNum(63, 15), true], - 'maestro55' => ['maestro', $this->generateCardNum(64, 15), true], - 'maestro56' => ['maestro', $this->generateCardNum(65, 15), true], - 'maestro57' => ['maestro', $this->generateCardNum(66, 15), true], - 'maestro58' => ['maestro', $this->generateCardNum(67, 15), true], - 'maestro59' => ['maestro', $this->generateCardNum(68, 15), true], - 'maestro60' => ['maestro', $this->generateCardNum(69, 15), true], - 'maestro61' => ['maestro', $this->generateCardNum(50, 16), true], - 'maestro62' => ['maestro', $this->generateCardNum(56, 16), true], - 'maestro63' => ['maestro', $this->generateCardNum(57, 16), true], - 'maestro64' => ['maestro', $this->generateCardNum(58, 16), true], - 'maestro65' => ['maestro', $this->generateCardNum(59, 16), true], - 'maestro66' => ['maestro', $this->generateCardNum(60, 16), true], - 'maestro67' => ['maestro', $this->generateCardNum(61, 16), true], - 'maestro68' => ['maestro', $this->generateCardNum(62, 16), true], - 'maestro69' => ['maestro', $this->generateCardNum(63, 16), true], - 'maestro70' => ['maestro', $this->generateCardNum(64, 16), true], - 'maestro71' => ['maestro', $this->generateCardNum(65, 16), true], - 'maestro72' => ['maestro', $this->generateCardNum(66, 16), true], - 'maestro73' => ['maestro', $this->generateCardNum(67, 16), true], - 'maestro74' => ['maestro', $this->generateCardNum(68, 16), true], - 'maestro75' => ['maestro', $this->generateCardNum(69, 16), true], - 'maestro91' => ['maestro', $this->generateCardNum(50, 18), true], - 'maestro92' => ['maestro', $this->generateCardNum(56, 18), true], - 'maestro93' => ['maestro', $this->generateCardNum(57, 18), true], - 'maestro94' => ['maestro', $this->generateCardNum(58, 18), true], - 'maestro95' => ['maestro', $this->generateCardNum(59, 18), true], - 'maestro96' => ['maestro', $this->generateCardNum(60, 18), true], - 'maestro97' => ['maestro', $this->generateCardNum(61, 18), true], - 'maestro98' => ['maestro', $this->generateCardNum(62, 18), true], - 'maestro99' => ['maestro', $this->generateCardNum(63, 18), true], - 'maestro100' => ['maestro', $this->generateCardNum(64, 18), true], - 'maestro101' => ['maestro', $this->generateCardNum(65, 18), true], - 'maestro102' => ['maestro', $this->generateCardNum(66, 18), true], - 'maestro103' => ['maestro', $this->generateCardNum(67, 18), true], - 'maestro104' => ['maestro', $this->generateCardNum(68, 18), true], - 'maestro105' => ['maestro', $this->generateCardNum(69, 18), true], - 'maestro106' => ['maestro', $this->generateCardNum(50, 19), true], - 'maestro107' => ['maestro', $this->generateCardNum(56, 19), true], - 'maestro108' => ['maestro', $this->generateCardNum(57, 19), true], - 'maestro109' => ['maestro', $this->generateCardNum(58, 19), true], - 'maestro110' => ['maestro', $this->generateCardNum(59, 19), true], - 'maestro111' => ['maestro', $this->generateCardNum(60, 19), true], - 'maestro112' => ['maestro', $this->generateCardNum(61, 19), true], - 'maestro113' => ['maestro', $this->generateCardNum(62, 19), true], - 'maestro114' => ['maestro', $this->generateCardNum(63, 19), true], - 'maestro115' => ['maestro', $this->generateCardNum(64, 19), true], - 'maestro116' => ['maestro', $this->generateCardNum(65, 19), true], - 'maestro117' => ['maestro', $this->generateCardNum(66, 19), true], - 'maestro118' => ['maestro', $this->generateCardNum(67, 19), true], - 'maestro119' => ['maestro', $this->generateCardNum(68, 19), true], - 'maestro120' => ['maestro', $this->generateCardNum(69, 19), true], - 'dankort1' => ['dankort', $this->generateCardNum(5019, 16), true], - 'dankort2' => ['dankort', $this->generateCardNum(4175, 16), true], - 'dankort3' => ['dankort', $this->generateCardNum(4571, 16), true], - 'dankort4' => ['dankort', $this->generateCardNum(4, 16), true], - 'mir1' => ['mir', $this->generateCardNum(2200, 16), true], - 'mir2' => ['mir', $this->generateCardNum(2201, 16), true], - 'mir3' => ['mir', $this->generateCardNum(2202, 16), true], - 'mir4' => ['mir', $this->generateCardNum(2203, 16), true], - 'mir5' => ['mir', $this->generateCardNum(2204, 16), true], - 'mastercard1' => ['mastercard', $this->generateCardNum(51, 16), true], - 'mastercard2' => ['mastercard', $this->generateCardNum(52, 16), true], - 'mastercard3' => ['mastercard', $this->generateCardNum(53, 16), true], - 'mastercard4' => ['mastercard', $this->generateCardNum(54, 16), true], - 'mastercard5' => ['mastercard', $this->generateCardNum(55, 16), true], - 'mastercard6' => ['mastercard', $this->generateCardNum(22, 16), true], - 'mastercard7' => ['mastercard', $this->generateCardNum(23, 16), true], - 'mastercard8' => ['mastercard', $this->generateCardNum(24, 16), true], - 'mastercard9' => ['mastercard', $this->generateCardNum(25, 16), true], - 'mastercard10' => ['mastercard', $this->generateCardNum(26, 16), true], - 'mastercard11' => ['mastercard', $this->generateCardNum(27, 16), true], - 'visa1' => ['visa', $this->generateCardNum(4, 13), true], - 'visa2' => ['visa', $this->generateCardNum(4, 16), true], - 'visa3' => ['visa', $this->generateCardNum(4, 19), true], - 'uatp' => ['uatp', $this->generateCardNum(1, 15), true], - 'verve1' => ['verve', $this->generateCardNum(506, 16), true], - 'verve2' => ['verve', $this->generateCardNum(650, 16), true], - 'verve3' => ['verve', $this->generateCardNum(506, 19), true], - 'verve4' => ['verve', $this->generateCardNum(650, 19), true], - 'cibc1' => ['cibc', $this->generateCardNum(4506, 16), true], - 'rbc1' => ['rbc', $this->generateCardNum(45, 16), true], - 'tdtrust' => ['tdtrust', $this->generateCardNum(589297, 16), true], - 'scotia1' => ['scotia', $this->generateCardNum(4536, 16), true], - 'bmoabm1' => ['bmoabm', $this->generateCardNum(500, 16), true], - 'hsbc' => ['hsbc', $this->generateCardNum(56, 16), true], - 'hsbc' => ['hsbc', $this->generateCardNum(57, 16), false], - ]; - } - - //-------------------------------------------------------------------- - - /** - * Used to generate fake credit card numbers that will still pass the Luhn - * check used to validate the card so we can be sure the cards are recognized correctly. - * - * @param int $prefix - * @param int $length - * - * @return string - */ - protected function generateCardNum(int $prefix, int $length) - { - $pos = mb_strlen($prefix); - $finalDigit = 0; - $sum = 0; - - // Fill in the first values of the string based on $prefix - $string = str_split($prefix); - - // Pad out the array to the appropriate length - $string = array_pad($string, $length, 0); - - // Fill all of the remaining values with random numbers, except the last one. - while ($pos < $length-1) { - $string[$pos++] = random_int(0, 9); - } - - // Calculate the Luhn checksum of the current values. - $lenOffset = ($length+1)%2; - for ($pos = 0; $pos < $length-1; $pos++) { - if (($pos+$lenOffset)%2) { - $temp = $string[$pos]*2; - if ($temp > 9) { - $temp -= 9; - } - - $sum += $temp; - } else { - $sum += $string[$pos]; - } - } - - // Make the last number whatever would cause the entire number to pass the checksum - $finalDigit = (10-($sum%10))%10; - $string[$length-1] = $finalDigit; - - return implode('', $string); - } - - //-------------------------------------------------------------------- - - public function testUploadedTrue() - { - $_FILES = [ - 'avatar' => [ - 'tmp_name' => 'phpUxcOty', - 'name' => 'my-avatar.png', - 'size' => 90996, - 'type' => 'image/png', - 'error' => 0, - ] - ]; - - $this->validation->setRules([ - 'avatar' => "uploaded[avatar]", - ]); - - $this->assertTrue($this->validation->run([])); - - } - - //-------------------------------------------------------------------- - - public function testUploadedFalse() - { - $_FILES = [ - 'avatar' => [ - 'tmp_name' => 'phpUxcOty', - 'name' => 'my-avatar.png', - 'size' => 90996, - 'type' => 'image/png', - 'error' => 0, - ] - ]; - - $this->validation->setRules([ - 'avatar' => "uploaded[userfile]", - ]); - - $this->assertFalse($this->validation->run([])); - - } - - //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + + public function testMaxLengthReturnsTrueWithExactLength() + { + $data = [ + 'foo' => 'bar', + ]; + + $this->validation->setRules([ + 'foo' => 'max_length[3]', + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testMaxLengthReturnsFalseWhenWrong() + { + $data = [ + 'foo' => 'bar', + ]; + + $this->validation->setRules([ + 'foo' => 'max_length[2]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testExactLengthNull() + { + $data = [ + 'foo' => null, + ]; + + $this->validation->setRules([ + 'foo' => 'exact_length[3]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testExactLengthReturnsTrueOnSuccess() + { + $data = [ + 'foo' => 'bar', + ]; + + $this->validation->setRules([ + 'foo' => 'exact_length[3]', + ]); + + $this->assertTrue($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testExactLengthReturnsFalseWhenShort() + { + $data = [ + 'foo' => 'bar', + ]; + + $this->validation->setRules([ + 'foo' => 'exact_length[2]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function testExactLengthReturnsFalseWhenLong() + { + $data = [ + 'foo' => 'bar', + ]; + + $this->validation->setRules([ + 'foo' => 'exact_length[4]', + ]); + + $this->assertFalse($this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider urlProvider + */ + public function testValidURL(string $url=null, bool $expected) + { + $data = [ + 'foo' => $url, + ]; + + $this->validation->setRules([ + 'foo' => 'valid_url', + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function urlProvider() + { + return [ + ['www.codeigniter.com', true], + ['http://codeigniter.com', true], + //https://bugs.php.net/bug.php?id=51192 + ['http://accept-dashes.tld', true], + ['http://reject_underscores', false], + // https://github.com/bcit-ci/CodeIgniter/issues/4415 + ['http://[::1]/ipv6', true], + ['htt://www.codeigniter.com', false], + ['', false], + ['code igniter', false], + [null, false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider emailProviderSingle + * + * @param $email + * @param $expected + */ + public function testValidEmail($email, $expected) + { + $data = [ + 'foo' => $email, + ]; + + $this->validation->setRules([ + 'foo' => 'valid_email', + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider emailProviderSingle + * + * @param $email + * @param $expected + */ + public function testValidEmails($email, $expected) + { + $data = [ + 'foo' => $email, + ]; + + $this->validation->setRules([ + 'foo' => 'valid_emails', + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function emailProviderSingle() + { + return [ + ['email@sample.com', true], + ['valid_email', false], + [null, false], + ]; + } + + //-------------------------------------------------------------------- + + public function emailsProvider() + { + return [ + ['1@sample.com,2@sample.com', true], + ['1@sample.com, 2@sample.com', true], + ['email@sample.com', true], + ['@sample.com,2@sample.com,validemail@email.ca', false], + [null, false] + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider ipProvider + * + * @param $ip + * @param $which + * @param $expected + */ + public function testValidIP($ip, $which, $expected) + { + $data = [ + 'foo' => $ip, + ]; + + $this->validation->setRules([ + 'foo' => "valid_ip[{$which}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function ipProvider() + { + return [ + ['127.0.0.1', null, true], + ['127.0.0.1', 'ipv4', true], + ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', null, true], + ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 'ipv6', true], + ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 'ipv4', false], + ['127.0.0.1', 'ipv6', false], + ['H001:0db8:85a3:0000:0000:8a2e:0370:7334', null, false], + ['127.0.0.259', null, false], + [null, null, false] + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider alphaProvider + * + * @param $str + * @param $expected + */ + public function testAlpha($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "alpha", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function alphaProvider() + { + return [ + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ ', false], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ1', false], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ*', false], + [null, false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * Test alpha with spaces. + * + * @param mixed $value Value. + * @param bool $expected Expected. + * + * @dataProvider alphaSpaceProvider + */ + public function testAlphaSpace($value, $expected) + { + $data = [ + 'foo' => $value + ]; + + $this->validation->setRules([ + 'foo' => 'alpha_space' + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function alphaSpaceProvider() + { + return [ + [null, true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ ', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ1', false], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ*', false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider alphaNumericProvider + * + * @param $str + * @param $expected + */ + public function testAlphaNumeric($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "alpha_numeric", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function alphaNumericProvider() + { + return [ + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789\ ', false], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789_', false], + [null, false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider alphaNumericProvider + * + * @param $str + * @param $expected + */ + public function testAlphaNumericSpace($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "alpha_numeric_spaces", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function alphaNumericSpaceProvider() + { + return [ + [' abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789', true], + [' abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-', false], + [null, false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider alphaDashProvider + * + * @param $str + * @param $expected + */ + public function testAlphaDash($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "alpha_dash", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function alphaDashProvider() + { + return [ + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-', true], + ['abcdefghijklmnopqrstuvwxyzABCDEFGHLIJKLMNOPQRSTUVWXYZ0123456789-\ ', false], + [null, false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider numericProvider + * + * @param $str + * @param $expected + */ + public function testNumeric($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "numeric", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function numericProvider() + { + return [ + ['0', true], + ['12314', true], + ['-42', true], + ['+42', true], + ['123a', false], + ['--1', false], + [null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider integerProvider + * + * @param $str + * @param $expected + */ + public function testInteger($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "integer", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function integerProvider() + { + return [ + ['0', true], + ['42', true], + ['-1', true], + ['123a', false], + ['1.9', false], + ['--1', false], + [null, false], + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider decimalProvider + * + * @param $str + * @param $expected + */ + public function testDecimal($str, $expected) + { + $data = [ + 'foo' => $str, + ]; + + $this->validation->setRules([ + 'foo' => "decimal", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function decimalProvider() + { + return [ + ['1.0', true], + ['-0.98', true], + ['0', false], + ['1.0a', false], + ['-i', false], + ['--1', false], + [null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider greaterThanProvider + * + * @param $str + * @param $expected + */ + public function testGreaterThan($first, $second, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "greater_than[{$second}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function greaterThanProvider() + { + return [ + ['-10', '-11', true], + ['10', '9', true], + ['10', '10', false], + ['10', 'a', false], + ['10a', '10', false], + [null, null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider greaterThanEqualProvider + * + * @param $str + * @param $expected + */ + public function testGreaterThanEqual($first, $second, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "greater_than_equal_to[{$second}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function greaterThanEqualProvider() + { + return [ + ['0', '0', true], + ['1', '0', true], + ['-1', '0', false], + ['10a', '0', false], + [null, null, false], + [1, null, true], + [null, 1, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider lessThanProvider + * + * @param $str + * @param $expected + */ + public function testLessThan($first, $second, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "less_than[{$second}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function lessThanProvider() + { + return [ + ['4', '5', true], + ['-1', '0', true], + ['4', '4', false], + ['10a', '5', false], + [null, null, false], + [1, null, false], + [null, 1, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider lessThanEqualProvider + * + * @param $str + * @param $expected + */ + public function testLessEqualThan($first, $second, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "less_than_equal_to[{$second}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function lessThanEqualProvider() + { + return [ + ['-1', '0', true], + ['-1', '-1', true], + ['4', '4', true], + ['0', '-1', false], + ['10a', '0', false], + [null, null, false], + [null, 1, false], + [1, null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider inListProvider + * + * @param $str + * @param $expected + */ + public function testInList($first, $second, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "in_list[{$second}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function inListProvider() + { + return [ + ['red', 'red,Blue,123', true], + ['Blue', 'red, Blue,123', true], + ['Blue', 'red,Blue,123', true], + ['123', 'red,Blue,123', true], + ['Red', 'red,Blue,123', false], + [' red', 'red,Blue,123', false], + ['1234', 'red,Blue,123', false], + [null, 'red,Blue,123', false], + ['red', null, false], + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider naturalProvider + * + * @param $str + * @param $expected + */ + public function testNatural($first, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "is_natural", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function naturalProvider() + { + return [ + ['0', true], + ['12', true], + ['42a', false], + ['-1', false], + [null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider naturalZeroProvider + * + * @param $str + * @param $expected + */ + public function testNaturalNoZero($first, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "is_natural_no_zero", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function naturalZeroProvider() + { + return [ + ['0', false], + ['12', true], + ['42a', false], + ['-1', false], + [null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider base64Provider + * + * @param $str + * @param $expected + */ + public function testBase64($first, $expected) + { + $data = [ + 'foo' => $first, + ]; + + $this->validation->setRules([ + 'foo' => "valid_base64", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function base64Provider() + { + return [ + [base64_encode('string'), true], + ['FA08GG', false], + [null, false] + ]; + } + + //------------------------------------------------------------------- + + /** + * @dataProvider timezoneProvider + * + * @param $value + * @param $expected + */ + public function testTimeZone($value, $expected) + { + $data = [ + 'foo' => $value, + ]; + + $this->validation->setRules([ + 'foo' => "timezone", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function timezoneProvider() + { + return [ + ['America/Chicago', true], + ['america/chicago', false], + ['foo/bar', false], + [null, false] + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider requiredWithProvider + * + * @param $check + * @param $expected + */ + public function testRequiredWith($field, $check, $expected = false) + { + $data = [ + 'foo' => 'bar', + 'bar' => 'something', + 'baz' => null, + ]; + + $this->validation->setRules([ + $field => "required_with[{$check}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function requiredWithProvider() + { + return [ + ['nope', 'bar', false], + ['foo', 'bar', true], + ['nope', 'baz', true], + [null, null, true], + [null, 'foo', true], + ['foo', null, true] + ]; + } + + //-------------------------------------------------------------------- + + /** + * @dataProvider requiredWithoutProvider + * + * @param $check + * @param $expected + */ + public function testRequiredWithout($field, $check, $expected = false) + { + $data = [ + 'foo' => 'bar', + 'bar' => 'something', + 'baz' => null, + ]; + + $this->validation->setRules([ + $field => "required_without[{$check}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + public function requiredWithoutProvider() + { + return [ + ['nope', 'bars', false], + ['foo', 'nope', true], + [null, null, false], + [null, 'foo', true], + ['foo', null, true] + ]; + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // Credit Card Rules + //-------------------------------------------------------------------- + + /** + * @dataProvider creditCardProvider + * + * @param $type + * @param $number + * @param bool $expected + */ + public function testValidCCNumber($type, $number, $expected = false) + { + $data = [ + 'cc' => $number, + ]; + + $this->validation->setRules([ + 'cc' => "valid_cc_number[{$type}]", + ]); + + $this->assertEquals($expected, $this->validation->run($data)); + } + + //-------------------------------------------------------------------- + + /** + * Cards shown are test cards found around the web. + * + * @see https://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm + * + * @return array + */ + public function creditCardProvider() + { + return [ + 'null_test' => ['amex', null, false], + 'random_test' => ['amex', $this->generateCardNum('37', 16), false], + 'invalid_type' => ['shorty', '1111 1111 1111 1111', false], + 'invalid_length' => ['amex', '', false], + 'not_numeric' => ['amex', 'abcd efgh ijkl mnop', false], + 'bad_length' => ['amex', '3782 8224 6310 0051', false], + 'bad_prefix' => ['amex', '3582 8224 6310 0051', false], + 'amex1' => ['amex', '3782 8224 6310 005', true], + 'amex2' => ['amex', '3714 4963 5398 431', true], + 'dinersclub1' => ['dinersclub', '3056 9309 0259 04', true], + 'dinersculb2' => ['dinersclub', '3852 0000 0232 37', true], + 'discover1' => ['discover', '6011 1111 1111 1117', true], + 'discover2' => ['discover', '6011 0009 9013 9424', true], + 'jcb1' => ['jcb', '3530 1113 3330 0000', true], + 'jcb2' => ['jcb', '3566 0020 2036 0505', true], + 'mastercard1' => ['mastercard', '5555 5555 5555 4444', true], + 'mastercard2' => ['mastercard', '5105 1051 0510 5100', true], + 'visa1' => ['visa', '4111 1111 1111 1111', true], + 'visa2' => ['visa', '4012 8888 8888 1881', true], + 'visa3' => ['visa', '4222 2222 2222 2', true], + 'dankort1' => ['dankort', '5019 7170 1010 3742', true], + 'unionpay1' => ['unionpay', $this->generateCardNum(62, 16), true], + 'unionpay2' => ['unionpay', $this->generateCardNum(62, 17), true], + 'unionpay3' => ['unionpay', $this->generateCardNum(62, 18), true], + 'unionpay4' => ['unionpay', $this->generateCardNum(62, 19), true], + 'unionpay5' => ['unionpay', $this->generateCardNum(63, 19), false], + 'carteblanche1' => ['carteblanche', $this->generateCardNum(300, 14), true], + 'carteblanche2' => ['carteblanche', $this->generateCardNum(301, 14), true], + 'carteblanche3' => ['carteblanche', $this->generateCardNum(302, 14), true], + 'carteblanche4' => ['carteblanche', $this->generateCardNum(303, 14), true], + 'carteblanche5' => ['carteblanche', $this->generateCardNum(304, 14), true], + 'carteblanche6' => ['carteblanche', $this->generateCardNum(305, 14), true], + 'carteblanche7' => ['carteblanche', $this->generateCardNum(306, 14), false], + 'dinersclub3' => ['dinersclub', $this->generateCardNum(300, 14), true], + 'dinersclub4' => ['dinersclub', $this->generateCardNum(301, 14), true], + 'dinersclub5' => ['dinersclub', $this->generateCardNum(302, 14), true], + 'dinersclub6' => ['dinersclub', $this->generateCardNum(303, 14), true], + 'dinersclub7' => ['dinersclub', $this->generateCardNum(304, 14), true], + 'dinersclub8' => ['dinersclub', $this->generateCardNum(305, 14), true], + 'dinersclub9' => ['dinersclub', $this->generateCardNum(309, 14), true], + 'dinersclub10' => ['dinersclub', $this->generateCardNum(36, 14), true], + 'dinersclub11' => ['dinersclub', $this->generateCardNum(38, 14), true], + 'dinersclub12' => ['dinersclub', $this->generateCardNum(39, 14), true], + 'dinersclub13' => ['dinersclub', $this->generateCardNum(54, 14), true], + 'dinersclub14' => ['dinersclub', $this->generateCardNum(55, 14), true], + 'dinersclub15' => ['dinersclub', $this->generateCardNum(300, 16), true], + 'dinersclub16' => ['dinersclub', $this->generateCardNum(301, 16), true], + 'dinersclub17' => ['dinersclub', $this->generateCardNum(302, 16), true], + 'dinersclub18' => ['dinersclub', $this->generateCardNum(303, 16), true], + 'dinersclub19' => ['dinersclub', $this->generateCardNum(304, 16), true], + 'dinersclub20' => ['dinersclub', $this->generateCardNum(305, 16), true], + 'dinersclub21' => ['dinersclub', $this->generateCardNum(309, 16), true], + 'dinersclub22' => ['dinersclub', $this->generateCardNum(36, 16), true], + 'dinersclub23' => ['dinersclub', $this->generateCardNum(38, 16), true], + 'dinersclub24' => ['dinersclub', $this->generateCardNum(39, 16), true], + 'dinersclub25' => ['dinersclub', $this->generateCardNum(54, 16), true], + 'dinersclub26' => ['dinersclub', $this->generateCardNum(55, 16), true], + 'discover3' => ['discover', $this->generateCardNum(6011, 16), true], + 'discover4' => ['discover', $this->generateCardNum(622, 16), true], + 'discover5' => ['discover', $this->generateCardNum(644, 16), true], + 'discover6' => ['discover', $this->generateCardNum(645, 16), true], + 'discover7' => ['discover', $this->generateCardNum(656, 16), true], + 'discover8' => ['discover', $this->generateCardNum(647, 16), true], + 'discover9' => ['discover', $this->generateCardNum(648, 16), true], + 'discover10' => ['discover', $this->generateCardNum(649, 16), true], + 'discover11' => ['discover', $this->generateCardNum(65, 16), true], + 'discover12' => ['discover', $this->generateCardNum(6011, 19), true], + 'discover13' => ['discover', $this->generateCardNum(622, 19), true], + 'discover14' => ['discover', $this->generateCardNum(644, 19), true], + 'discover15' => ['discover', $this->generateCardNum(645, 19), true], + 'discover16' => ['discover', $this->generateCardNum(656, 19), true], + 'discover17' => ['discover', $this->generateCardNum(647, 19), true], + 'discover18' => ['discover', $this->generateCardNum(648, 19), true], + 'discover19' => ['discover', $this->generateCardNum(649, 19), true], + 'discover20' => ['discover', $this->generateCardNum(65, 19), true], + 'interpayment1' => ['interpayment', $this->generateCardNum(4, 16), true], + 'interpayment2' => ['interpayment', $this->generateCardNum(4, 17), true], + 'interpayment3' => ['interpayment', $this->generateCardNum(4, 18), true], + 'interpayment4' => ['interpayment', $this->generateCardNum(4, 19), true], + 'jcb1' => ['jcb', $this->generateCardNum(352, 16), true], + 'jcb2' => ['jcb', $this->generateCardNum(353, 16), true], + 'jcb3' => ['jcb', $this->generateCardNum(354, 16), true], + 'jcb4' => ['jcb', $this->generateCardNum(355, 16), true], + 'jcb5' => ['jcb', $this->generateCardNum(356, 16), true], + 'jcb6' => ['jcb', $this->generateCardNum(357, 16), true], + 'jcb7' => ['jcb', $this->generateCardNum(358, 16), true], + 'maestro1' => ['maestro', $this->generateCardNum(50, 12), true], + 'maestro2' => ['maestro', $this->generateCardNum(56, 12), true], + 'maestro3' => ['maestro', $this->generateCardNum(57, 12), true], + 'maestro4' => ['maestro', $this->generateCardNum(58, 12), true], + 'maestro5' => ['maestro', $this->generateCardNum(59, 12), true], + 'maestro6' => ['maestro', $this->generateCardNum(60, 12), true], + 'maestro7' => ['maestro', $this->generateCardNum(61, 12), true], + 'maestro8' => ['maestro', $this->generateCardNum(62, 12), true], + 'maestro9' => ['maestro', $this->generateCardNum(63, 12), true], + 'maestro10' => ['maestro', $this->generateCardNum(64, 12), true], + 'maestro11' => ['maestro', $this->generateCardNum(65, 12), true], + 'maestro12' => ['maestro', $this->generateCardNum(66, 12), true], + 'maestro13' => ['maestro', $this->generateCardNum(67, 12), true], + 'maestro14' => ['maestro', $this->generateCardNum(68, 12), true], + 'maestro15' => ['maestro', $this->generateCardNum(69, 12), true], + 'maestro16' => ['maestro', $this->generateCardNum(50, 13), true], + 'maestro17' => ['maestro', $this->generateCardNum(56, 13), true], + 'maestro18' => ['maestro', $this->generateCardNum(57, 13), true], + 'maestro19' => ['maestro', $this->generateCardNum(58, 13), true], + 'maestro20' => ['maestro', $this->generateCardNum(59, 13), true], + 'maestro21' => ['maestro', $this->generateCardNum(60, 13), true], + 'maestro22' => ['maestro', $this->generateCardNum(61, 13), true], + 'maestro23' => ['maestro', $this->generateCardNum(62, 13), true], + 'maestro24' => ['maestro', $this->generateCardNum(63, 13), true], + 'maestro25' => ['maestro', $this->generateCardNum(64, 13), true], + 'maestro26' => ['maestro', $this->generateCardNum(65, 13), true], + 'maestro27' => ['maestro', $this->generateCardNum(66, 13), true], + 'maestro28' => ['maestro', $this->generateCardNum(67, 13), true], + 'maestro29' => ['maestro', $this->generateCardNum(68, 13), true], + 'maestro30' => ['maestro', $this->generateCardNum(69, 13), true], + 'maestro31' => ['maestro', $this->generateCardNum(50, 14), true], + 'maestro32' => ['maestro', $this->generateCardNum(56, 14), true], + 'maestro33' => ['maestro', $this->generateCardNum(57, 14), true], + 'maestro34' => ['maestro', $this->generateCardNum(58, 14), true], + 'maestro35' => ['maestro', $this->generateCardNum(59, 14), true], + 'maestro36' => ['maestro', $this->generateCardNum(60, 14), true], + 'maestro37' => ['maestro', $this->generateCardNum(61, 14), true], + 'maestro38' => ['maestro', $this->generateCardNum(62, 14), true], + 'maestro39' => ['maestro', $this->generateCardNum(63, 14), true], + 'maestro40' => ['maestro', $this->generateCardNum(64, 14), true], + 'maestro41' => ['maestro', $this->generateCardNum(65, 14), true], + 'maestro42' => ['maestro', $this->generateCardNum(66, 14), true], + 'maestro43' => ['maestro', $this->generateCardNum(67, 14), true], + 'maestro44' => ['maestro', $this->generateCardNum(68, 14), true], + 'maestro45' => ['maestro', $this->generateCardNum(69, 14), true], + 'maestro46' => ['maestro', $this->generateCardNum(50, 15), true], + 'maestro47' => ['maestro', $this->generateCardNum(56, 15), true], + 'maestro48' => ['maestro', $this->generateCardNum(57, 15), true], + 'maestro49' => ['maestro', $this->generateCardNum(58, 15), true], + 'maestro50' => ['maestro', $this->generateCardNum(59, 15), true], + 'maestro51' => ['maestro', $this->generateCardNum(60, 15), true], + 'maestro52' => ['maestro', $this->generateCardNum(61, 15), true], + 'maestro53' => ['maestro', $this->generateCardNum(62, 15), true], + 'maestro54' => ['maestro', $this->generateCardNum(63, 15), true], + 'maestro55' => ['maestro', $this->generateCardNum(64, 15), true], + 'maestro56' => ['maestro', $this->generateCardNum(65, 15), true], + 'maestro57' => ['maestro', $this->generateCardNum(66, 15), true], + 'maestro58' => ['maestro', $this->generateCardNum(67, 15), true], + 'maestro59' => ['maestro', $this->generateCardNum(68, 15), true], + 'maestro60' => ['maestro', $this->generateCardNum(69, 15), true], + 'maestro61' => ['maestro', $this->generateCardNum(50, 16), true], + 'maestro62' => ['maestro', $this->generateCardNum(56, 16), true], + 'maestro63' => ['maestro', $this->generateCardNum(57, 16), true], + 'maestro64' => ['maestro', $this->generateCardNum(58, 16), true], + 'maestro65' => ['maestro', $this->generateCardNum(59, 16), true], + 'maestro66' => ['maestro', $this->generateCardNum(60, 16), true], + 'maestro67' => ['maestro', $this->generateCardNum(61, 16), true], + 'maestro68' => ['maestro', $this->generateCardNum(62, 16), true], + 'maestro69' => ['maestro', $this->generateCardNum(63, 16), true], + 'maestro70' => ['maestro', $this->generateCardNum(64, 16), true], + 'maestro71' => ['maestro', $this->generateCardNum(65, 16), true], + 'maestro72' => ['maestro', $this->generateCardNum(66, 16), true], + 'maestro73' => ['maestro', $this->generateCardNum(67, 16), true], + 'maestro74' => ['maestro', $this->generateCardNum(68, 16), true], + 'maestro75' => ['maestro', $this->generateCardNum(69, 16), true], + 'maestro91' => ['maestro', $this->generateCardNum(50, 18), true], + 'maestro92' => ['maestro', $this->generateCardNum(56, 18), true], + 'maestro93' => ['maestro', $this->generateCardNum(57, 18), true], + 'maestro94' => ['maestro', $this->generateCardNum(58, 18), true], + 'maestro95' => ['maestro', $this->generateCardNum(59, 18), true], + 'maestro96' => ['maestro', $this->generateCardNum(60, 18), true], + 'maestro97' => ['maestro', $this->generateCardNum(61, 18), true], + 'maestro98' => ['maestro', $this->generateCardNum(62, 18), true], + 'maestro99' => ['maestro', $this->generateCardNum(63, 18), true], + 'maestro100' => ['maestro', $this->generateCardNum(64, 18), true], + 'maestro101' => ['maestro', $this->generateCardNum(65, 18), true], + 'maestro102' => ['maestro', $this->generateCardNum(66, 18), true], + 'maestro103' => ['maestro', $this->generateCardNum(67, 18), true], + 'maestro104' => ['maestro', $this->generateCardNum(68, 18), true], + 'maestro105' => ['maestro', $this->generateCardNum(69, 18), true], + 'maestro106' => ['maestro', $this->generateCardNum(50, 19), true], + 'maestro107' => ['maestro', $this->generateCardNum(56, 19), true], + 'maestro108' => ['maestro', $this->generateCardNum(57, 19), true], + 'maestro109' => ['maestro', $this->generateCardNum(58, 19), true], + 'maestro110' => ['maestro', $this->generateCardNum(59, 19), true], + 'maestro111' => ['maestro', $this->generateCardNum(60, 19), true], + 'maestro112' => ['maestro', $this->generateCardNum(61, 19), true], + 'maestro113' => ['maestro', $this->generateCardNum(62, 19), true], + 'maestro114' => ['maestro', $this->generateCardNum(63, 19), true], + 'maestro115' => ['maestro', $this->generateCardNum(64, 19), true], + 'maestro116' => ['maestro', $this->generateCardNum(65, 19), true], + 'maestro117' => ['maestro', $this->generateCardNum(66, 19), true], + 'maestro118' => ['maestro', $this->generateCardNum(67, 19), true], + 'maestro119' => ['maestro', $this->generateCardNum(68, 19), true], + 'maestro120' => ['maestro', $this->generateCardNum(69, 19), true], + 'dankort1' => ['dankort', $this->generateCardNum(5019, 16), true], + 'dankort2' => ['dankort', $this->generateCardNum(4175, 16), true], + 'dankort3' => ['dankort', $this->generateCardNum(4571, 16), true], + 'dankort4' => ['dankort', $this->generateCardNum(4, 16), true], + 'mir1' => ['mir', $this->generateCardNum(2200, 16), true], + 'mir2' => ['mir', $this->generateCardNum(2201, 16), true], + 'mir3' => ['mir', $this->generateCardNum(2202, 16), true], + 'mir4' => ['mir', $this->generateCardNum(2203, 16), true], + 'mir5' => ['mir', $this->generateCardNum(2204, 16), true], + 'mastercard1' => ['mastercard', $this->generateCardNum(51, 16), true], + 'mastercard2' => ['mastercard', $this->generateCardNum(52, 16), true], + 'mastercard3' => ['mastercard', $this->generateCardNum(53, 16), true], + 'mastercard4' => ['mastercard', $this->generateCardNum(54, 16), true], + 'mastercard5' => ['mastercard', $this->generateCardNum(55, 16), true], + 'mastercard6' => ['mastercard', $this->generateCardNum(22, 16), true], + 'mastercard7' => ['mastercard', $this->generateCardNum(23, 16), true], + 'mastercard8' => ['mastercard', $this->generateCardNum(24, 16), true], + 'mastercard9' => ['mastercard', $this->generateCardNum(25, 16), true], + 'mastercard10' => ['mastercard', $this->generateCardNum(26, 16), true], + 'mastercard11' => ['mastercard', $this->generateCardNum(27, 16), true], + 'visa1' => ['visa', $this->generateCardNum(4, 13), true], + 'visa2' => ['visa', $this->generateCardNum(4, 16), true], + 'visa3' => ['visa', $this->generateCardNum(4, 19), true], + 'uatp' => ['uatp', $this->generateCardNum(1, 15), true], + 'verve1' => ['verve', $this->generateCardNum(506, 16), true], + 'verve2' => ['verve', $this->generateCardNum(650, 16), true], + 'verve3' => ['verve', $this->generateCardNum(506, 19), true], + 'verve4' => ['verve', $this->generateCardNum(650, 19), true], + 'cibc1' => ['cibc', $this->generateCardNum(4506, 16), true], + 'rbc1' => ['rbc', $this->generateCardNum(45, 16), true], + 'tdtrust' => ['tdtrust', $this->generateCardNum(589297, 16), true], + 'scotia1' => ['scotia', $this->generateCardNum(4536, 16), true], + 'bmoabm1' => ['bmoabm', $this->generateCardNum(500, 16), true], + 'hsbc' => ['hsbc', $this->generateCardNum(56, 16), true], + 'hsbc' => ['hsbc', $this->generateCardNum(57, 16), false], + ]; + } + + //-------------------------------------------------------------------- + + /** + * Used to generate fake credit card numbers that will still pass the Luhn + * check used to validate the card so we can be sure the cards are recognized correctly. + * + * @param int $prefix + * @param int $length + * + * @return string + */ + protected function generateCardNum(int $prefix, int $length) + { + $pos = mb_strlen($prefix); + $finalDigit = 0; + $sum = 0; + + // Fill in the first values of the string based on $prefix + $string = str_split($prefix); + + // Pad out the array to the appropriate length + $string = array_pad($string, $length, 0); + + // Fill all of the remaining values with random numbers, except the last one. + while ($pos < $length-1) { + $string[$pos++] = random_int(0, 9); + } + + // Calculate the Luhn checksum of the current values. + $lenOffset = ($length+1)%2; + for ($pos = 0; $pos < $length-1; $pos++) { + if (($pos+$lenOffset)%2) { + $temp = $string[$pos]*2; + if ($temp > 9) { + $temp -= 9; + } + + $sum += $temp; + } else { + $sum += $string[$pos]; + } + } + + // Make the last number whatever would cause the entire number to pass the checksum + $finalDigit = (10-($sum%10))%10; + $string[$length-1] = $finalDigit; + + return implode('', $string); + } + + //-------------------------------------------------------------------- + + public function testUploadedTrue() + { + $_FILES = [ + 'avatar' => [ + 'tmp_name' => 'phpUxcOty', + 'name' => 'my-avatar.png', + 'size' => 90996, + 'type' => 'image/png', + 'error' => 0, + ] + ]; + + $this->validation->setRules([ + 'avatar' => "uploaded[avatar]", + ]); + + $this->assertTrue($this->validation->run([])); + + } + + //-------------------------------------------------------------------- + + public function testUploadedFalse() + { + $_FILES = [ + 'avatar' => [ + 'tmp_name' => 'phpUxcOty', + 'name' => 'my-avatar.png', + 'size' => 90996, + 'type' => 'image/png', + 'error' => 0, + ] + ]; + + $this->validation->setRules([ + 'avatar' => "uploaded[userfile]", + ]); + + $this->assertFalse($this->validation->run([])); + + } + + //-------------------------------------------------------------------- } From 8fd9f0eb412b56a1e8e77971a098cf4fb1e667ed Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Mon, 3 Apr 2017 18:22:55 +0900 Subject: [PATCH 0588/1807] Revert "Split Validation Rules" This reverts commit fc7e18ceab2d543e566fb3e24e1fb12e690506bf. --- application/Config/Validation.php | 13 +- .../CreditCard.php => CreditCardRules.php} | 4 +- .../{Rules/File.php => FileRules.php} | 4 +- system/Validation/Rules.php | 670 ++++++++++++++++++ system/Validation/Rules/AlphaFormat.php | 118 --- system/Validation/Rules/Comparison.php | 140 ---- .../Validation/Rules/DatabaseDependency.php | 83 --- system/Validation/Rules/DateTimeFormat.php | 60 -- system/Validation/Rules/Format.php | 211 ------ system/Validation/Rules/Length.php | 106 --- system/Validation/Rules/NumberFormat.php | 111 --- system/Validation/Rules/Required.php | 143 ---- tests/system/Validation/ValidationTest.php | 39 +- 13 files changed, 693 insertions(+), 1009 deletions(-) rename system/Validation/{Rules/CreditCard.php => CreditCardRules.php} (99%) rename system/Validation/{Rules/File.php => FileRules.php} (99%) create mode 100644 system/Validation/Rules.php delete mode 100644 system/Validation/Rules/AlphaFormat.php delete mode 100644 system/Validation/Rules/Comparison.php delete mode 100644 system/Validation/Rules/DatabaseDependency.php delete mode 100644 system/Validation/Rules/DateTimeFormat.php delete mode 100644 system/Validation/Rules/Format.php delete mode 100644 system/Validation/Rules/Length.php delete mode 100644 system/Validation/Rules/NumberFormat.php delete mode 100644 system/Validation/Rules/Required.php diff --git a/application/Config/Validation.php b/application/Config/Validation.php index 12ed5624fb24..e9f6e29e33ee 100644 --- a/application/Config/Validation.php +++ b/application/Config/Validation.php @@ -13,16 +13,9 @@ class Validation * @var array */ public $ruleSets = [ - \CodeIgniter\Validation\Rules\Required::class, - \CodeIgniter\Validation\Rules\AlphaFormat::class, - \CodeIgniter\Validation\Rules\DateTimeFormat::class, - \CodeIgniter\Validation\Rules\NumberFormat::class, - \CodeIgniter\Validation\Rules\Format::class, - \CodeIgniter\Validation\Rules\Comparison::class, - \CodeIgniter\Validation\Rules\DatabaseDependency::class, - \CodeIgniter\Validation\Rules\Length::class, - \CodeIgniter\Validation\Rules\File::class, - \CodeIgniter\Validation\Rules\CreditCard::class, + \CodeIgniter\Validation\Rules::class, + \CodeIgniter\Validation\FileRules::class, + \CodeIgniter\Validation\CreditCardRules::class, ]; /** diff --git a/system/Validation/Rules/CreditCard.php b/system/Validation/CreditCardRules.php similarity index 99% rename from system/Validation/Rules/CreditCard.php rename to system/Validation/CreditCardRules.php index 0165971c4b9d..83f92bf31ee0 100644 --- a/system/Validation/Rules/CreditCard.php +++ b/system/Validation/CreditCardRules.php @@ -1,4 +1,4 @@ - $min) : false; + } + + //-------------------------------------------------------------------- + + /** + * Equal to or Greater than + * + * @param string + * @param int + * + * @return bool + */ + public function greater_than_equal_to(string $str=null, string $min, array $data): bool + { + return is_numeric($str) ? ($str >= $min) : false; + } + + //-------------------------------------------------------------------- + + /** + * Value should be within an array of values + * + * @param string + * @param string + * @return bool + */ + public function in_list(string $value=null, string $list, array $data): bool + { + $list = explode(',', $list); + $list = array_map(function($value) { return trim($value); }, $list); + return in_array($value, $list, TRUE); + } + + //-------------------------------------------------------------------- + + /** + * Integer + * + * @param string + * + * @return bool + */ + public function integer(string $str=null): bool + { + return (bool)preg_match('/^[\-+]?[0-9]+$/', $str); + } + + //-------------------------------------------------------------------- + + /** + * Is a Natural number (0,1,2,3, etc.) + * + * @param string + * @return bool + */ + public function is_natural(string $str=null): bool + { + return ctype_digit((string) $str); + } + + //-------------------------------------------------------------------- + + /** + * Is a Natural number, but not a zero (1,2,3, etc.) + * + * @param string + * @return bool + */ + public function is_natural_no_zero(string $str=null): bool + { + return ($str != 0 && ctype_digit((string) $str)); + } + + //-------------------------------------------------------------------- + + /** + * Checks the database to see if the given value is unique. Can + * ignore a single record by field/value to make it useful during + * record updates. + * + * Example: + * is_unique[table.field,ignore_field,ignore_value] + * is_unique[users.email,id,5] + * + * @param string $str + * @param string $field + * @param array $data + * + * @return bool + */ + public function is_unique(string $str=null, string $field, array $data): bool + { + // Grab any data for exclusion of a single row. + list($field, $ignoreField, $ignoreValue) = array_pad(explode(',', $field), 3, null); + + // Break the table and field apart + sscanf($field, '%[^.].%[^.]', $table, $field); + + $db = Database::connect(); + $row = $db->table($table) + ->where($field, $str); + + if (! empty($ignoreField) && ! empty($ignoreValue)) + { + $row = $row->where("{$ignoreField} !=", $ignoreValue); + } + + return (bool)($row->get() + ->getRow() === null); + } + + //-------------------------------------------------------------------- + + /** + * Less than + * + * @param string + * @param int + * + * @return bool + */ + public function less_than(string $str=null, string $max): bool + { + return is_numeric($str) ? ($str < $max) : false; + } + + //-------------------------------------------------------------------- + + /** + * Equal to or Less than + * + * @param string + * @param int + * + * @return bool + */ + public function less_than_equal_to(string $str=null, string $max): bool + { + return is_numeric($str) ? ($str <= $max) : false; + } + + //-------------------------------------------------------------------- + + /** + * Matches the value of another field in $data. + * + * @param string $str + * @param string $field + * @param array $data Other field/value pairs + * + * @return bool + */ + public function matches(string $str=null, string $field, array $data): bool + { + return array_key_exists($field, $data) + ? ($str === $data[$field]) + : false; + } + + //-------------------------------------------------------------------- + + /** + * Returns true if $str is $val or fewer characters in length. + * + * @param string $str + * @param string $val + * @param array $data + * + * @return bool + */ + public function max_length(string $str=null, string $val, array $data): bool + { + if (! is_numeric($val)) + { + return false; + } + + return ($val >= mb_strlen($str)); + } + + //-------------------------------------------------------------------- + + /** + * Returns true if $str is at least $val length. + * + * @param string $str + * @param string $val + * @param array $data + * + * @return bool + */ + public function min_length(string $str=null, string $val, array $data): bool + { + if (! is_numeric($val)) + { + return false; + } + + return ($val <= mb_strlen($str)); + } + + //-------------------------------------------------------------------- + + /** + * Numeric + * + * @param string + * + * @return bool + */ + public function numeric(string $str=null): bool + { + return (bool)preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); + + } + + //-------------------------------------------------------------------- + + /** + * Compares value against a regular expression pattern. + * + * @param string $str + * @param string $pattern + * @param array $data Other field/value pairs + * + * @return bool + */ + public function regex_match(string $str=null, string $pattern, array $data): bool + { + if (substr($pattern, 0, 1) != '/') + { + $pattern = "/{$pattern}/"; + } + + return (bool)preg_match($pattern, $str); + } + + //-------------------------------------------------------------------- + + /** + * Required + * + * @param string + * + * @return bool + */ + public function required($str=null): bool + { + return is_array($str) ? (bool)count($str) : (trim($str) !== ''); + } + + //-------------------------------------------------------------------- + + /** + * The field is required when any of the other fields are present + * in the data. + * + * Example (field is required when the password field is present): + * + * required_with[password] + * + * @param $str + * @param string $fields + * @param array $data + * + * @return bool + */ + public function required_with($str=null, string $fields, array $data): bool + { + $fields = explode(',', $fields); + + // If the field is present we can safely assume that + // the field is here, no matter whether the corresponding + // search field is present or not. + $present = $this->required($data[$str] ?? null); + + if ($present === true) + { + return true; + } + + // Still here? Then we fail this test if + // any of the fields are present in $data + $requiredFields = array_intersect($fields, $data); + + $requiredFields = array_filter($requiredFields, function($item) + { + return ! empty($item); + }); + + return ! (bool)count($requiredFields); + } + + //-------------------------------------------------------------------- + + /** + * The field is required when all of the other fields are not present + * in the data. + * + * Example (field is required when the id or email field is missing): + * + * required_without[id,email] + * + * @param $str + * @param string $fields + * @param array $data + * + * @return bool + */ + public function required_without($str=null, string $fields, array $data): bool + { + $fields = explode(',', $fields); + + // If the field is present we can safely assume that + // the field is here, no matter whether the corresponding + // search field is present or not. + $present = $this->required($data[$str] ?? null); + + if ($present === true) + { + return true; + } + + // Still here? Then we fail this test if + // any of the fields are not present in $data + foreach ($fields as $field) + { + if (! array_key_exists($field, $data)) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Validates that the string is a valid timezone as per the + * timezone_identifiers_list function. + * + * @see http://php.net/manual/en/datetimezone.listidentifiers.php + * + * @param string $str + * + * @return bool + */ + public function timezone(string $str=null): bool + { + return in_array($str, timezone_identifiers_list()); + } + + //-------------------------------------------------------------------- + + /** + * Valid Base64 + * + * Tests a string for characters outside of the Base64 alphabet + * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045 + * + * @param string + * @return bool + */ + public function valid_base64(string $str=null): bool + { + return (base64_encode(base64_decode($str)) === $str); + } + + //-------------------------------------------------------------------- + + /** + * Checks for a correctly formatted email address + * + * @param string + * + * @return bool + */ + public function valid_email(string $str=null): bool + { + if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@')) + { + $str = substr($str, 0, ++$atpos).idn_to_ascii(substr($str, $atpos)); + } + + return (bool)filter_var($str, FILTER_VALIDATE_EMAIL); + } + + //-------------------------------------------------------------------- + + /** + * Validate a comma-separated list of email addresses. + * + * Example: + * valid_emails[one@example.com,two@example.com] + * + * @param string + * + * @return bool + */ + public function valid_emails(string $str=null): bool + { + if (strpos($str, ',') === false) + { + return $this->valid_email(trim($str)); + } + + foreach (explode(',', $str) as $email) + { + if (trim($email) !== '' && $this->valid_email(trim($email)) === false) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Validate an IP address + * + * @param $ip IP Address + * @param string $which IP protocol: 'ipv4' or 'ipv6' + * @param array $data + * + * @return bool + */ + public function valid_ip(string $ip=null, string $which = null, array $data): bool + { + switch (strtolower($which)) + { + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = null; + break; + } + + return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which); + } + + //-------------------------------------------------------------------- + + /** + * Checks a URL to ensure it's formed correctly. + * + * @param string $str + * + * @return bool + */ + public function valid_url(string $str=null): bool + { + if (empty($str)) + { + return false; + } + elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) + { + if (empty($matches[2])) + { + return false; + } + elseif (! in_array($matches[1], ['http', 'https'], true)) + { + return false; + } + + $str = $matches[2]; + } + + $str = 'http://'.$str; + + return (filter_var($str, FILTER_VALIDATE_URL) !== false); + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Validation/Rules/AlphaFormat.php b/system/Validation/Rules/AlphaFormat.php deleted file mode 100644 index 5d6bc036da5a..000000000000 --- a/system/Validation/Rules/AlphaFormat.php +++ /dev/null @@ -1,118 +0,0 @@ - $min) : false; - } - - //-------------------------------------------------------------------- - - /** - * Equal to or Greater than - * - * @param string - * @param int - * - * @return bool - */ - public function greater_than_equal_to(string $str=null, string $min, array $data): bool - { - return is_numeric($str) ? ($str >= $min) : false; - } - - //-------------------------------------------------------------------- - - /** - * Less than - * - * @param string - * @param int - * - * @return bool - */ - public function less_than(string $str=null, string $max): bool - { - return is_numeric($str) ? ($str < $max) : false; - } - - //-------------------------------------------------------------------- - - /** - * Equal to or Less than - * - * @param string - * @param int - * - * @return bool - */ - public function less_than_equal_to(string $str=null, string $max): bool - { - return is_numeric($str) ? ($str <= $max) : false; - } - - //-------------------------------------------------------------------- - - /** - * Matches the value of another field in $data. - * - * @param string $str - * @param string $field - * @param array $data Other field/value pairs - * - * @return bool - */ - public function matches(string $str=null, string $field, array $data): bool - { - return array_key_exists($field, $data) - ? ($str === $data[$field]) - : false; - } - -} diff --git a/system/Validation/Rules/DatabaseDependency.php b/system/Validation/Rules/DatabaseDependency.php deleted file mode 100644 index 0a520c1bde49..000000000000 --- a/system/Validation/Rules/DatabaseDependency.php +++ /dev/null @@ -1,83 +0,0 @@ -table($table) - ->where($field, $str); - - if (! empty($ignoreField) && ! empty($ignoreValue)) - { - $row = $row->where("{$ignoreField} !=", $ignoreValue); - } - - return (bool)($row->get() - ->getRow() === null); - } -} diff --git a/system/Validation/Rules/DateTimeFormat.php b/system/Validation/Rules/DateTimeFormat.php deleted file mode 100644 index ab55d0210555..000000000000 --- a/system/Validation/Rules/DateTimeFormat.php +++ /dev/null @@ -1,60 +0,0 @@ -valid_email(trim($str)); - } - - foreach (explode(',', $str) as $email) - { - if (trim($email) !== '' && $this->valid_email(trim($email)) === false) - { - return false; - } - } - - return true; - } - - //-------------------------------------------------------------------- - - /** - * Validate an IP address - * - * @param $ip IP Address - * @param string $which IP protocol: 'ipv4' or 'ipv6' - * @param array $data - * - * @return bool - */ - public function valid_ip(string $ip=null, string $which = null, array $data): bool - { - switch (strtolower($which)) - { - case 'ipv4': - $which = FILTER_FLAG_IPV4; - break; - case 'ipv6': - $which = FILTER_FLAG_IPV6; - break; - default: - $which = null; - break; - } - - return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which); - } - - //-------------------------------------------------------------------- - - /** - * Checks a URL to ensure it's formed correctly. - * - * @param string $str - * - * @return bool - */ - public function valid_url(string $str=null): bool - { - if (empty($str)) - { - return false; - } - elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) - { - if (empty($matches[2])) - { - return false; - } - elseif (! in_array($matches[1], ['http', 'https'], true)) - { - return false; - } - - $str = $matches[2]; - } - - $str = 'http://'.$str; - - return (filter_var($str, FILTER_VALIDATE_URL) !== false); - } - - //-------------------------------------------------------------------- - -} diff --git a/system/Validation/Rules/Length.php b/system/Validation/Rules/Length.php deleted file mode 100644 index 3855492a1e62..000000000000 --- a/system/Validation/Rules/Length.php +++ /dev/null @@ -1,106 +0,0 @@ -= mb_strlen($str)); - } - - //-------------------------------------------------------------------- - - /** - * Returns true if $str is at least $val length. - * - * @param string $str - * @param string $val - * @param array $data - * - * @return bool - */ - public function min_length(string $str=null, string $val, array $data): bool - { - if (! is_numeric($val)) - { - return false; - } - - return ($val <= mb_strlen($str)); - } -} diff --git a/system/Validation/Rules/NumberFormat.php b/system/Validation/Rules/NumberFormat.php deleted file mode 100644 index 8fadecbedfed..000000000000 --- a/system/Validation/Rules/NumberFormat.php +++ /dev/null @@ -1,111 +0,0 @@ -required($data[$str] ?? null); - - if ($present === true) - { - return true; - } - - // Still here? Then we fail this test if - // any of the fields are present in $data - $requiredFields = array_intersect($fields, $data); - - $requiredFields = array_filter($requiredFields, function($item) - { - return ! empty($item); - }); - - return ! (bool)count($requiredFields); - } - - //-------------------------------------------------------------------- - - /** - * The field is required when all of the other fields are not present - * in the data. - * - * Example (field is required when the id or email field is missing): - * - * required_without[id,email] - * - * @param $str - * @param string $fields - * @param array $data - * - * @return bool - */ - public function required_without($str=null, string $fields, array $data): bool - { - $fields = explode(',', $fields); - - // If the field is present we can safely assume that - // the field is here, no matter whether the corresponding - // search field is present or not. - $present = $this->required($data[$str] ?? null); - - if ($present === true) - { - return true; - } - - // Still here? Then we fail this test if - // any of the fields are not present in $data - foreach ($fields as $field) - { - if (! array_key_exists($field, $data)) - { - return false; - } - } - - return true; - } - -} diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 4270e3051726..ee340edc789c 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -10,29 +10,22 @@ class ValidationTest extends \CIUnitTestCase */ protected $validation; - protected $config = [ - 'ruleSets' => [ - \CodeIgniter\Validation\Rules\Required::class, - \CodeIgniter\Validation\Rules\AlphaFormat::class, - \CodeIgniter\Validation\Rules\DateTimeFormat::class, - \CodeIgniter\Validation\Rules\NumberFormat::class, - \CodeIgniter\Validation\Rules\Format::class, - \CodeIgniter\Validation\Rules\Comparison::class, - \CodeIgniter\Validation\Rules\DatabaseDependency::class, - \CodeIgniter\Validation\Rules\Length::class, - \CodeIgniter\Validation\Rules\File::class, - \CodeIgniter\Validation\Rules\CreditCard::class, - \CodeIgniter\Validation\TestRules::class, - ], - 'groupA' => [ - 'foo' => 'required|min_length[5]', - ], - 'groupA_errors' => [ - 'foo' => [ - 'min_length' => 'Shame, shame. Too short.', - ], - ], - ]; + protected $config = [ + 'ruleSets' => [ + \CodeIgniter\Validation\Rules::class, + \CodeIgniter\Validation\FileRules::class, + \CodeIgniter\Validation\CreditCardRules::class, + \CodeIgniter\Validation\TestRules::class, + ], + 'groupA' => [ + 'foo' => 'required|min_length[5]', + ], + 'groupA_errors' => [ + 'foo' => [ + 'min_length' => 'Shame, shame. Too short.', + ], + ], + ]; //-------------------------------------------------------------------- From 39a64cb67c5ef01cbb818044a5125fc7553472be Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Mon, 3 Apr 2017 18:35:48 +0900 Subject: [PATCH 0589/1807] Split CodeIgniter\\Validation\\Rules:class --- application/Config/Validation.php | 1 + system/Validation/FormatRules.php | 356 +++++++++++++++++++++ system/Validation/Rules.php | 307 ------------------ tests/system/Validation/ValidationTest.php | 1 + 4 files changed, 358 insertions(+), 307 deletions(-) create mode 100644 system/Validation/FormatRules.php diff --git a/application/Config/Validation.php b/application/Config/Validation.php index e9f6e29e33ee..1b45f4e90624 100644 --- a/application/Config/Validation.php +++ b/application/Config/Validation.php @@ -14,6 +14,7 @@ class Validation */ public $ruleSets = [ \CodeIgniter\Validation\Rules::class, + \CodeIgniter\Validation\FormatRules::class, \CodeIgniter\Validation\FileRules::class, \CodeIgniter\Validation\CreditCardRules::class, ]; diff --git a/system/Validation/FormatRules.php b/system/Validation/FormatRules.php new file mode 100644 index 000000000000..711aaa455c53 --- /dev/null +++ b/system/Validation/FormatRules.php @@ -0,0 +1,356 @@ +valid_email(trim($str)); + } + + foreach (explode(',', $str) as $email) + { + if (trim($email) !== '' && $this->valid_email(trim($email)) === false) + { + return false; + } + } + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Validate an IP address + * + * @param $ip IP Address + * @param string $which IP protocol: 'ipv4' or 'ipv6' + * @param array $data + * + * @return bool + */ + public function valid_ip(string $ip=null, string $which = null, array $data): bool + { + switch (strtolower($which)) + { + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = null; + break; + } + + return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which); + } + + //-------------------------------------------------------------------- + + /** + * Checks a URL to ensure it's formed correctly. + * + * @param string $str + * + * @return bool + */ + public function valid_url(string $str=null): bool + { + if (empty($str)) + { + return false; + } + elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) + { + if (empty($matches[2])) + { + return false; + } + elseif (! in_array($matches[1], ['http', 'https'], true)) + { + return false; + } + + $str = $matches[2]; + } + + $str = 'http://'.$str; + + return (filter_var($str, FILTER_VALIDATE_URL) !== false); + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index b90a6b981a2f..b6b5e369a3fa 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -45,92 +45,6 @@ */ class Rules { - /** - * Alpha - * - * @param string - * - * @return bool - */ - public function alpha(string $str=null): bool - { - return ctype_alpha($str); - } - - //-------------------------------------------------------------------- - - /** - * Alpha with spaces. - * - * @param string $value Value. - * - * @return bool True if alpha with spaces, else false. - */ - public function alpha_space(string $value = null): bool - { - if ($value === null) - { - return true; - } - - return (bool)preg_match('/^[A-Z ]+$/i', $value); - } - - //-------------------------------------------------------------------- - - /** - * Alpha-numeric with underscores and dashes - * - * @param string - * - * @return bool - */ - public function alpha_dash(string $str=null): bool - { - return (bool)preg_match('/^[a-z0-9_-]+$/i', $str); - } - - //-------------------------------------------------------------------- - - /** - * Alpha-numeric - * - * @param string - * - * @return bool - */ - public function alpha_numeric(string $str=null): bool - { - return ctype_alnum((string)$str); - } - - //-------------------------------------------------------------------- - - /** - * Alpha-numeric w/ spaces - * - * @param string - * - * @return bool - */ - public function alpha_numeric_spaces(string $str=null): bool - { - return (bool)preg_match('/^[A-Z0-9 ]+$/i', $str); - } - - //-------------------------------------------------------------------- - - /** - * Decimal number - * - * @param string - * - * @return bool - */ - public function decimal(string $str=null): bool - { - return (bool)preg_match('/^[\-+]?[0-9]+\.[0-9]+$/', $str); - } //-------------------------------------------------------------------- @@ -219,46 +133,6 @@ public function in_list(string $value=null, string $list, array $data): bool //-------------------------------------------------------------------- - /** - * Integer - * - * @param string - * - * @return bool - */ - public function integer(string $str=null): bool - { - return (bool)preg_match('/^[\-+]?[0-9]+$/', $str); - } - - //-------------------------------------------------------------------- - - /** - * Is a Natural number (0,1,2,3, etc.) - * - * @param string - * @return bool - */ - public function is_natural(string $str=null): bool - { - return ctype_digit((string) $str); - } - - //-------------------------------------------------------------------- - - /** - * Is a Natural number, but not a zero (1,2,3, etc.) - * - * @param string - * @return bool - */ - public function is_natural_no_zero(string $str=null): bool - { - return ($str != 0 && ctype_digit((string) $str)); - } - - //-------------------------------------------------------------------- - /** * Checks the database to see if the given value is unique. Can * ignore a single record by field/value to make it useful during @@ -387,42 +261,6 @@ public function min_length(string $str=null, string $val, array $data): bool //-------------------------------------------------------------------- - /** - * Numeric - * - * @param string - * - * @return bool - */ - public function numeric(string $str=null): bool - { - return (bool)preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); - - } - - //-------------------------------------------------------------------- - - /** - * Compares value against a regular expression pattern. - * - * @param string $str - * @param string $pattern - * @param array $data Other field/value pairs - * - * @return bool - */ - public function regex_match(string $str=null, string $pattern, array $data): bool - { - if (substr($pattern, 0, 1) != '/') - { - $pattern = "/{$pattern}/"; - } - - return (bool)preg_match($pattern, $str); - } - - //-------------------------------------------------------------------- - /** * Required * @@ -522,149 +360,4 @@ public function required_without($str=null, string $fields, array $data): bool //-------------------------------------------------------------------- - /** - * Validates that the string is a valid timezone as per the - * timezone_identifiers_list function. - * - * @see http://php.net/manual/en/datetimezone.listidentifiers.php - * - * @param string $str - * - * @return bool - */ - public function timezone(string $str=null): bool - { - return in_array($str, timezone_identifiers_list()); - } - - //-------------------------------------------------------------------- - - /** - * Valid Base64 - * - * Tests a string for characters outside of the Base64 alphabet - * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045 - * - * @param string - * @return bool - */ - public function valid_base64(string $str=null): bool - { - return (base64_encode(base64_decode($str)) === $str); - } - - //-------------------------------------------------------------------- - - /** - * Checks for a correctly formatted email address - * - * @param string - * - * @return bool - */ - public function valid_email(string $str=null): bool - { - if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@')) - { - $str = substr($str, 0, ++$atpos).idn_to_ascii(substr($str, $atpos)); - } - - return (bool)filter_var($str, FILTER_VALIDATE_EMAIL); - } - - //-------------------------------------------------------------------- - - /** - * Validate a comma-separated list of email addresses. - * - * Example: - * valid_emails[one@example.com,two@example.com] - * - * @param string - * - * @return bool - */ - public function valid_emails(string $str=null): bool - { - if (strpos($str, ',') === false) - { - return $this->valid_email(trim($str)); - } - - foreach (explode(',', $str) as $email) - { - if (trim($email) !== '' && $this->valid_email(trim($email)) === false) - { - return false; - } - } - - return true; - } - - //-------------------------------------------------------------------- - - /** - * Validate an IP address - * - * @param $ip IP Address - * @param string $which IP protocol: 'ipv4' or 'ipv6' - * @param array $data - * - * @return bool - */ - public function valid_ip(string $ip=null, string $which = null, array $data): bool - { - switch (strtolower($which)) - { - case 'ipv4': - $which = FILTER_FLAG_IPV4; - break; - case 'ipv6': - $which = FILTER_FLAG_IPV6; - break; - default: - $which = null; - break; - } - - return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which); - } - - //-------------------------------------------------------------------- - - /** - * Checks a URL to ensure it's formed correctly. - * - * @param string $str - * - * @return bool - */ - public function valid_url(string $str=null): bool - { - if (empty($str)) - { - return false; - } - elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) - { - if (empty($matches[2])) - { - return false; - } - elseif (! in_array($matches[1], ['http', 'https'], true)) - { - return false; - } - - $str = $matches[2]; - } - - $str = 'http://'.$str; - - return (filter_var($str, FILTER_VALIDATE_URL) !== false); - } - - //-------------------------------------------------------------------- - } diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index ee340edc789c..0ce7f2077c00 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -13,6 +13,7 @@ class ValidationTest extends \CIUnitTestCase protected $config = [ 'ruleSets' => [ \CodeIgniter\Validation\Rules::class, + \CodeIgniter\Validation\FormatRules::class, \CodeIgniter\Validation\FileRules::class, \CodeIgniter\Validation\CreditCardRules::class, \CodeIgniter\Validation\TestRules::class, From dc3e2a1a6ed877af251b40d37018923e6f938758 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Mon, 3 Apr 2017 18:38:19 +0900 Subject: [PATCH 0590/1807] space to tab --- tests/system/Validation/ValidationTest.php | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 0ce7f2077c00..4bc228439ca0 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -10,23 +10,23 @@ class ValidationTest extends \CIUnitTestCase */ protected $validation; - protected $config = [ - 'ruleSets' => [ - \CodeIgniter\Validation\Rules::class, - \CodeIgniter\Validation\FormatRules::class, - \CodeIgniter\Validation\FileRules::class, - \CodeIgniter\Validation\CreditCardRules::class, - \CodeIgniter\Validation\TestRules::class, - ], - 'groupA' => [ - 'foo' => 'required|min_length[5]', - ], - 'groupA_errors' => [ - 'foo' => [ - 'min_length' => 'Shame, shame. Too short.', - ], - ], - ]; + protected $config = [ + 'ruleSets' => [ + \CodeIgniter\Validation\Rules::class, + \CodeIgniter\Validation\FormatRules::class, + \CodeIgniter\Validation\FileRules::class, + \CodeIgniter\Validation\CreditCardRules::class, + \CodeIgniter\Validation\TestRules::class, + ], + 'groupA' => [ + 'foo' => 'required|min_length[5]', + ], + 'groupA_errors' => [ + 'foo' => [ + 'min_length' => 'Shame, shame. Too short.', + ], + ], + ]; //-------------------------------------------------------------------- From 659fb7250abc2f73d7ea17eb1c7728c9faa7b836 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 3 Apr 2017 12:45:08 -0500 Subject: [PATCH 0591/1807] Ensure there's no PHP tags to execute when doing an eval in parseConditionals --- system/View/Parser.php | 8 +++++++- tests/system/View/ParserTest.php | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index a69171e4f43b..c9093e91ba72 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -210,6 +210,10 @@ protected function parse(string $template, array $data = [], array $options = nu return ''; } + // Remove any possible PHP tags since we don't support it + // and parseConditionals needs it clean anyway... + $template = str_replace([''], ['<?', '?>'], $template); + $template = $this->parseComments($template); $template = $this->extractNoparse($template); @@ -243,6 +247,8 @@ protected function is_assoc($arr) return array_keys($arr) !== range(0, count($arr) - 1); } + //-------------------------------------------------------------------- + function strpos_all($haystack, $needle) { $offset = 0; @@ -446,7 +452,7 @@ protected function parseConditionals(string $template): string if ($result === false) { $output = 'You have a syntax error in your Parser tags: '; - throw new \RuntimeException($output.str_replace(array('?>', '', 'assertEquals($result, $parser->renderString($template)); } - /** - * @group single - */ public function testIfConditionalTrue() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -256,4 +253,13 @@ public function testIfConditionalTrue() $this->assertEquals('HowdyWelcome', $parser->renderString($template)); } + public function testWontParsePHP() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $template = " - "; + $this->assertEquals('<?php echo \'Foo\' ?> - <?= \'Bar\' ?>', $parser->renderString($template)); + } + + } From c8aba99b39f86419b9622e8ae0743d9e17df2240 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 3 Apr 2017 21:47:08 -0500 Subject: [PATCH 0592/1807] Finishing up simple conditionals in parser --- system/View/Parser.php | 4 +- tests/system/View/ParserTest.php | 40 +++++++++++++++++++ user_guide_src/source/general/view_parser.rst | 34 ++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index c9093e91ba72..75331f930a8d 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -437,7 +437,9 @@ protected function parseConditionals(string $template): string // Build the string to replace the `if` statement with. $condition = $match[2]; - $statement = ''; + $statement = $match[1] == 'elseif' + ? '' + : ''; $template = str_replace($match[0], $statement, $template); } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 2ba50f7dc890..ac8451eb29c2 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -253,6 +253,46 @@ public function testIfConditionalTrue() $this->assertEquals('HowdyWelcome', $parser->renderString($template)); } + public function testElseConditionalFalse() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'doit' => true, + ]; + + $template = "{if doit}Howdy{else}Welcome{ endif }"; + $parser->setData($data); + + $this->assertEquals('Howdy', $parser->renderString($template)); + } + + public function testElseConditionalTrue() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'doit' => false, + ]; + + $template = "{if doit}Howdy{else}Welcome{ endif }"; + $parser->setData($data); + + $this->assertEquals('Welcome', $parser->renderString($template)); + } + + public function testElseifConditionalTrue() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'doit' => false, + 'dontdoit' => true + ]; + + $template = "{if doit}Howdy{elseif dontdoit}Welcome{ endif }"; + $parser->setData($data); + + $this->assertEquals('Welcome', $parser->renderString($template)); + } + public function testWontParsePHP() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index 5a754c9106c6..d66178d515f3 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -276,6 +276,40 @@ You can specify portions of the page to not be parsed with the ``{noparse}{/nopa

    Untouched Code

    {/noparse} +Conditional Logic +================= + +The Parser class supports some basic conditionals to handle ``if``, ``else``, and ``elseif`` syntax. All ``if`` +blocks must be closed with an ``endif`` tag:: + + {if role=='admin'} +

    Welcome, Admin!

    + {endif} + +This simple block is converted to the following during parsing:: + + +

    Welcome, Admin!

    + + +All variables used within if statement must have been previously set with the same name. Other than that, it is +treated exactly like a standard PHP conditional, and all standard PHP rules would apply here. You can use any +of the comparison operators you would normally, like ``==``, ``===``, ``!==``, ``<``, ``>``, etc. + +:: + + {if role=='admin'} +

    Welcome, Admin

    + {elseif role=='moderator'} +

    Welcome, Moderator

    + {else} +

    Welcome, User

    + {endif} + + +.. note:: In the background, conditionals are parsed using an **eval()**, so you must ensure that you take + care with the user data that is used within conditionals, or you could open your application up to security risks. + Cascading Data ============== From a6f87d8b5bd700c45bdd7ecb859660ae274b53db Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 3 Apr 2017 23:02:08 -0500 Subject: [PATCH 0593/1807] Allowing white space in tags --- system/View/Parser.php | 48 ++++++++++++++++++++++++-------- tests/system/View/ParserTest.php | 19 ++++++++++++- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 75331f930a8d..c44b6e84a2ca 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -233,7 +233,10 @@ protected function parse(string $template, array $data = [], array $options = nu unset($data); // do the substitutions - $template = strtr($template, $replace); + foreach ($replace as $pattern => $content) + { + $template = preg_replace($pattern, $content, $template); + } $template = $this->insertNoparse($template); @@ -273,7 +276,9 @@ function strpos_all($haystack, $needle) */ protected function parseSingle(string $key, string $val, string $template): array { - return array($this->leftDelimiter . $key . $this->rightDelimiter => (string) $val); + $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'.$this->rightDelimiter.'#ms'; + + return [$pattern => (string) $val]; } //-------------------------------------------------------------------- @@ -290,21 +295,36 @@ protected function parseSingle(string $key, string $val, string $template): arra */ protected function parsePair(string $variable, array $data, string $template): array { - $replace = array(); + // Holds the replacement patterns and contents + // that will be used within a preg_replace in parse() + $replace = []; + + // Find all matches of space-flexible versions of {tag}{/tag} so we + // have something to loop over. preg_match_all( - '#' . preg_quote($this->leftDelimiter . $variable . $this->rightDelimiter) . '(.+?)' . - preg_quote($this->leftDelimiter . '/' . $variable . $this->rightDelimiter) . '#s', + '#'.$this->leftDelimiter.'\s*'.preg_quote($variable).'\s*'.$this->rightDelimiter.'(.+?)' . + $this->leftDelimiter.'\s*'.'/'.preg_quote($variable).'\s*'.$this->rightDelimiter.'#s', $template, $matches, PREG_SET_ORDER ); + /* + * Each match looks like: + * + * $match[0] {tag}...{/tag} + * $match[1] Contents inside the tag + */ foreach ($matches as $match) { - $str = ''; + // Loop over each piece of $data, replacing + // it's contents so that we know what to replace in parse() + $str = ''; // holds the new contents for this tag pair. foreach ($data as $row) { - $temp = array(); + $temp = []; + $out = $match[1]; foreach ($row as $key => $val) { + // For nested data, send us back through this method... if (is_array($val)) { $pair = $this->parsePair($key, $val, $match[1]); @@ -324,13 +344,19 @@ protected function parsePair(string $variable, array $data, string $template): a $val = 'Resource'; } - $temp[$this->leftDelimiter . $key . $this->rightDelimiter] = $val; + $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'. $this->rightDelimiter.'#s'] = $val; } - $str .= strtr($match[1], $temp); + // Now replace our placeholders with the new content. + foreach ($temp as $pattern => $content) + { + $out = preg_replace($pattern, $content, $out); + } + + $str .= $out; } - $replace[$match[0]] = $str; + $replace['#'.$match[0].'#s'] = $str; } return $replace; @@ -366,7 +392,7 @@ protected function extractNoparse(string $template): string { $pattern = '/\{\s*noparse\s*\}(.*?)\{\s*\/noparse\s*\}/ms'; - /** + /* * $matches[][0] is the raw match * $matches[][1] is the contents */ diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index ac8451eb29c2..ac9ff6846862 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -123,7 +123,7 @@ public function testParseNested() ], ]; - $template = "{title}\n{powers}{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{/powers}"; + $template = "{ title }\n{ powers }{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{ /powers}"; $parser->setData($data); $this->assertEquals("Super Heroes\nyes\nno\nsecond: yes no", $parser->renderString($template)); @@ -301,5 +301,22 @@ public function testWontParsePHP() $this->assertEquals('<?php echo \'Foo\' ?> - <?= \'Bar\' ?>', $parser->renderString($template)); } + public function testParseHandlesSpaces() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.', + ]; + + $template = "{ title}\n{ body }"; + + $result = implode("\n", $data); + + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); + } + + // -------------------------------------------------------------------- } From 1feee6a9727524b97df3adb25a986802c66ef96b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 3 Apr 2017 23:19:48 -0500 Subject: [PATCH 0594/1807] Escape all parser output as html by default. --- system/View/Parser.php | 4 +++- tests/system/View/ParserTest.php | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index c44b6e84a2ca..3ede9ec5703f 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -276,6 +276,8 @@ function strpos_all($haystack, $needle) */ protected function parseSingle(string $key, string $val, string $template): array { + $val = esc($val, 'html'); + $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'.$this->rightDelimiter.'#ms'; return [$pattern => (string) $val]; @@ -344,7 +346,7 @@ protected function parsePair(string $variable, array $data, string $template): a $val = 'Resource'; } - $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'. $this->rightDelimiter.'#s'] = $val; + $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'. $this->rightDelimiter.'#s'] = esc($val, 'html'); } // Now replace our placeholders with the new content. diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index ac9ff6846862..3ca31ee95cb1 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -205,6 +205,24 @@ public function testEscHandling($value, $expected = null) // ------------------------------------------------------------------------ + public function testParserEscapesDataDefaultsToHTML() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'title' => '', + 'powers' => [ + ['link' => "Link"], + ], + ]; + + $template = "{title} {powers}{link}{/powers}"; + $parser->setData($data); + $this->assertEquals('<script>Heroes</script> <a href='test'>Link</a>', $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + public function testIgnoresComments() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -222,6 +240,8 @@ public function testIgnoresComments() $this->assertEquals($result, $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testNoParse() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -239,6 +259,8 @@ public function testNoParse() $this->assertEquals($result, $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testIfConditionalTrue() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -253,6 +275,8 @@ public function testIfConditionalTrue() $this->assertEquals('HowdyWelcome', $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testElseConditionalFalse() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -266,6 +290,8 @@ public function testElseConditionalFalse() $this->assertEquals('Howdy', $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testElseConditionalTrue() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -279,6 +305,8 @@ public function testElseConditionalTrue() $this->assertEquals('Welcome', $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testElseifConditionalTrue() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -293,6 +321,8 @@ public function testElseifConditionalTrue() $this->assertEquals('Welcome', $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testWontParsePHP() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -301,6 +331,8 @@ public function testWontParsePHP() $this->assertEquals('<?php echo \'Foo\' ?> - <?= \'Bar\' ?>', $parser->renderString($template)); } + //-------------------------------------------------------------------- + public function testParseHandlesSpaces() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); From 7ee30656fd23a93c5c852bec515f906252bd93b1 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 5 Apr 2017 00:25:13 -0500 Subject: [PATCH 0595/1807] Good progress on adding filters to the Parser --- application/Config/View.php | 15 +- system/Config/View.php | 31 +++ system/View/Filters.php | 337 +++++++++++++++++++++++++++++++ system/View/Parser.php | 52 ++++- tests/system/View/ParserTest.php | 49 +++++ 5 files changed, 478 insertions(+), 6 deletions(-) create mode 100644 system/Config/View.php create mode 100644 system/View/Filters.php diff --git a/application/Config/View.php b/application/Config/View.php index 41da2946138d..3c7be64c8db8 100644 --- a/application/Config/View.php +++ b/application/Config/View.php @@ -1,6 +1,6 @@ '\CodeIgniter\View\Filters::abs', + 'capitalize' => '\CodeIgniter\View\Filters::capitalize', + 'date' => '\CodeIgniter\View\Filters::date', + 'date_modify' => '\CodeIgniter\View\Filters::date_modify', + 'default' => '\CodeIgniter\View\Filters::default', + 'esc' => '\CodeIgniter\View\Filters::esc', + 'excerpt' => '\CodeIgniter\View\Filters::excerpt', + 'highlight' => '\CodeIgniter\View\Filters::highlight', + 'highlight_code' => '\CodeIgniter\View\Filters::highlight_code', + 'limit_words' => '\CodeIgniter\View\Filters::limit_words', + 'limit_chars' => '\CodeIgniter\View\Filters::limit_chars', + 'lower' => '\CodeIgniter\View\Filters::lower', + 'nl2br' => '\CodeIgniter\View\Filters::nl2br', + 'prose' => '\CodeIgniter\View\Filters::prose', + 'round' => '\CodeIgniter\View\Filters::round', + 'strip_tags' => '\CodeIgniter\View\Filters::strip_tags', + 'title' => '\CodeIgniter\View\Filters::title', + 'upper' => '\CodeIgniter\View\Filters::upper', + ]; + + public function __construct() + { + $this->filters = array_merge($this->filters, $this->coreFilters); + } + +} diff --git a/system/View/Filters.php b/system/View/Filters.php new file mode 100644 index 000000000000..fc5d32ce4491 --- /dev/null +++ b/system/View/Filters.php @@ -0,0 +1,337 @@ +format($format); + } + + //-------------------------------------------------------------------- + + /** + * Given a string or DateTime object, will return the date modified + * by the given value. + * + * Example: + * my_date|date_modify(+1 day) + * + * @param $value + * @param string $format + * + * @return string + */ + public static function date_modify($value, string $format): string + { + + } + + //-------------------------------------------------------------------- + + /** + * Returns the given default value if $value is empty or undefined. + * + * @param $value + * + * @return string + */ + public static function default($value, string $default): string + { + return empty($value) + ? $default + : $value; + } + + //-------------------------------------------------------------------- + + /** + * Escapes the given value with our `esc()` helper function. + * + * @param $value + * @param string $context + * + * @return string + */ + public static function esc($value, string $context='html'): string + { + return esc($value, $context); + } + + //-------------------------------------------------------------------- + + /** + * Returns an excerpt of the given string. + * + * @param string $value + * @param int $radius + * + * @return string + */ + public static function excerpt(string $value, int $radius = 100): string + { + helper('text'); + + return excerpt($value, null, $radius); + } + + //-------------------------------------------------------------------- + + /** + * Highlights a given phrase within the text using '' tags. + * + * @param string $value + * @param string $phrase + * + * @return string + */ + public static function highlight(string $value, string $phrase): string + { + helper('text'); + + return highlight_phrase($value, $phrase); + } + + //-------------------------------------------------------------------- + + /** + * Highlights code samples with HTML/CSS. + * + * @param $value + * + * @return string + */ + public static function highlight_code($value): string + { + helper('text'); + + return highlight_code($value); + } + + //-------------------------------------------------------------------- + + /** + * Performs an implode on an array, joining with $glue. + * + * @param array $value + * @param string $glue + * + * @return string + */ + public static function join(array $value, string $glue=''): string + { + return implode($glue, $value); + } + + //-------------------------------------------------------------------- + + /** + * Limits the number of chracters to $limit, and trails of with an ellipsis. + * Will break at word break so may be more or less than $limit. + * + * @param $value + * @param int $limit + * + * @return string + */ + public static function limit_chars($value, int $limit=500): string + { + helper('text'); + + return character_limiter($value, $limit); + } + + //-------------------------------------------------------------------- + + /** + * Limits the number of words to $limit, and trails of with an ellipsis. + * + * @param $value + * @param int $limit + * + * @return string + */ + public static function limit_words($value, int $limit=100): string + { + helper('text'); + + return word_limiter($value, $limit); + } + + //-------------------------------------------------------------------- + + /** + * Converts a string to lowercase. + * + * @param string $value + * + * @return string + */ + public static function lower(string $value): string + { + return strtolower($value); + } + + //-------------------------------------------------------------------- + + /** + * Returns a string with all instances of newline character (\n) + * converted to an HTML
    tag. + * + * @param string $value + * + * @return string + */ + public static function nl2br(string $value): string + { + return str_replace("\n", '
    ', $value); + } + + //-------------------------------------------------------------------- + + /** + * Wraps PHP number_format function for use within the parser. + * + * @param string $value + * @param string $decimal + * @param string $separator + * + * @return string + */ + public static function number_format(string $value, string $decimal = '.', string $separator = ','): string + { + return number_format($value, $decimal, $separator); + } + + //-------------------------------------------------------------------- + + /** + * Takes a body of text and uses the auto_typography() method to + * turn it into prettier, easier-to-read, prose. + * + * @param string $value + * + * @return string + */ + public static function prose(string $value): string + { + $typography = \Config\Services::typography(); + + return $typography->autoTypography($value); + } + + //-------------------------------------------------------------------- + + /** + * Rounds a given $value in one of 3 ways; + * + * - common Normal rounding + * - ceil always rounds up + * - floor always rounds down + * + * @param string $value + * @param int $precision + * @param string $type + * + * @return string + */ + public static function round($value, int $precision=2, $type='common') + { + switch ($type) + { + case 'common': + return round($value, $precision); + break; + case 'ceil': + return ceil($value); + break; + case 'floor': + return floor($value); + break; + } + + // Still here, just return the value. + return $value; + } + + //-------------------------------------------------------------------- + + /** + * Wraps PHP's striptags function for use in the Parser. + * + * @param string $value + * @param string $allowed + * + * @return string + */ + public static function strip_tags(string $value, string $allowed=''): string + { + return strip_tags($value, $allowed); + } + + //-------------------------------------------------------------------- + + /** + * Returns a "title case" version of the string. + * + * @param string $value + * + * @return string + */ + public static function title(string $value): string + { + return ucwords(strtolower($value)); + } + + //-------------------------------------------------------------------- + + /** + * Converts text to all uppercase. + * + * @param string $value + * + * @return string + */ + public static function upper(string $value): string + { + return strtoupper($value); + } + + //-------------------------------------------------------------------- +} diff --git a/system/View/Parser.php b/system/View/Parser.php index 3ede9ec5703f..40008bda4ef6 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -221,13 +221,13 @@ protected function parse(string $template, array $data = [], array $options = nu $template = $this->parseConditionals($template); // build the variable substitution list - $replace = array(); + $replace = []; foreach ($data as $key => $val) { $replace = array_merge( $replace, is_array($val) ? $this->parsePair($key, $val, $template) - : $this->parseSingle($key, (string) $val, $template) + : $this->parseSingle($key, (string)$val, $template) ); } @@ -235,7 +235,9 @@ protected function parse(string $template, array $data = [], array $options = nu // do the substitutions foreach ($replace as $pattern => $content) { - $template = preg_replace($pattern, $content, $template); + $template = preg_replace_callback($pattern, function($matches) use($content) { + return $this->prepareReplacement($matches, $content); + }, $template); } $template = $this->insertNoparse($template); @@ -278,7 +280,7 @@ protected function parseSingle(string $key, string $val, string $template): arra { $val = esc($val, 'html'); - $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'.$this->rightDelimiter.'#ms'; + $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z\(\):\-\s]+)*\s*'.$this->rightDelimiter.'#ms'; return [$pattern => (string) $val]; } @@ -345,8 +347,12 @@ protected function parsePair(string $variable, array $data, string $template): a { $val = 'Resource'; } + else + { + $val = esc($val, 'html'); + } - $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'. $this->rightDelimiter.'#s'] = esc($val, 'html'); + $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'. $this->rightDelimiter.'#s'] = $val; } // Now replace our placeholders with the new content. @@ -505,4 +511,40 @@ public function setDelimiters($leftDelimiter = '{', $rightDelimiter = '}'): Rend } //-------------------------------------------------------------------- + + /** + * Callback used during parse() to apply any filters to the value. + * + * @param array $matches + * @param string $replace + * + * @return mixed|string + */ + protected function prepareReplacement(array $matches, string $replace) + { + // No filters? Get outta here.. + if (count($matches) === 1) return $replace; + + $orig = array_shift($matches); + + // Determine the requested filters + foreach ($matches as $filter) + { + // Grab any parameter we might need to send + if (! preg_match('/\([a-zA-Z0-9\-:_ ]+\)/', $filter, $param)) continue; + + // Remove the () and spaces to we have just tha parameter left + $param = trim($param[0], '() '); + + // Get our filter name + $filter = strtolower(substr($filter, 0, strpos($filter, '('))); + + if (! array_key_exists($filter, $this->config->filters)) continue; + + // Filter it.... + $replace = call_user_func($this->config->filters[$filter], $replace, $param); + } + + return $replace; + } } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 3ca31ee95cb1..123aee1165dc 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -205,6 +205,40 @@ public function testEscHandling($value, $expected = null) // ------------------------------------------------------------------------ + public function testFilterWithNoArgument() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'that_thing' => '' + ]; + + $template = '{ that_thing|esc) }'; + + $parser->setData($data); + $this->assertEquals('<script>alert("ci4")</script>', $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + public function testFilterWithArgument() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $date = time(); + + $data = [ + 'my_date' => $date + ]; + + $template = '{ my_date| date(Y-m-d ) }'; + + $parser->setData($data); + $this->assertEquals(date('Y-m-d', $date), $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + public function testParserEscapesDataDefaultsToHTML() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); @@ -223,6 +257,21 @@ public function testParserEscapesDataDefaultsToHTML() //-------------------------------------------------------------------- +// public function testParserEscapesChooseContext() +// { +// $parser = new Parser($this->config, $this->viewsDir, $this->loader); +// +// $data = [ +// 'title' => "", +// ]; +// +// $template = "{ title|esc(attr) }"; +// $parser->setData($data); +// $this->assertEquals('<script>alert("ci4")</script>', $parser->renderString($template)); +// } + + //-------------------------------------------------------------------- + public function testIgnoresComments() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); From facfc0b94f1cf3177e24f77344cf7f416b8c1d3c Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 5 Apr 2017 00:26:47 -0500 Subject: [PATCH 0596/1807] Better nl2br function --- system/View/Filters.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/View/Filters.php b/system/View/Filters.php index fc5d32ce4491..b3189e680322 100644 --- a/system/View/Filters.php +++ b/system/View/Filters.php @@ -220,7 +220,9 @@ public static function lower(string $value): string */ public static function nl2br(string $value): string { - return str_replace("\n", '
    ', $value); + $typography = \Config\Services::typography(); + + return $typography->nl2brExceptPre($value); } //-------------------------------------------------------------------- From 03410059aa1a6944ba9c8e7a0817c9b04bbb6eb7 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 5 Apr 2017 22:40:45 -0500 Subject: [PATCH 0597/1807] Tests for all Parser filters --- system/Config/View.php | 1 + system/Helpers/text_helper.php | 2 +- system/View/Filters.php | 55 ++--- system/View/Parser.php | 46 ++-- tests/system/View/ParserFilterTest.php | 305 +++++++++++++++++++++++++ 5 files changed, 367 insertions(+), 42 deletions(-) create mode 100644 tests/system/View/ParserFilterTest.php diff --git a/system/Config/View.php b/system/Config/View.php index e4db963297d8..fd27f5995b86 100644 --- a/system/Config/View.php +++ b/system/Config/View.php @@ -16,6 +16,7 @@ class View { 'limit_chars' => '\CodeIgniter\View\Filters::limit_chars', 'lower' => '\CodeIgniter\View\Filters::lower', 'nl2br' => '\CodeIgniter\View\Filters::nl2br', + 'number_format' => '\CodeIgniter\View\Filters::number_format', 'prose' => '\CodeIgniter\View\Filters::prose', 'round' => '\CodeIgniter\View\Filters::round', 'strip_tags' => '\CodeIgniter\View\Filters::strip_tags', diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php index 13ee89b1978b..d59e71f93fa0 100755 --- a/system/Helpers/text_helper.php +++ b/system/Helpers/text_helper.php @@ -859,7 +859,7 @@ function excerpt(string $text, string $phrase = null, int $radius = 100, string $ellPre = $phrase ? $ellipsis : ''; - return $ellPre.$prev.$phrase.$post.$ellipsis; + return str_replace(' ', ' ', $ellPre.$prev.$phrase.$post.$ellipsis); } //-------------------------------------------------------------------- diff --git a/system/View/Filters.php b/system/View/Filters.php index b3189e680322..46c5df17ee49 100644 --- a/system/View/Filters.php +++ b/system/View/Filters.php @@ -40,27 +40,34 @@ public static function capitalize(string $value): string */ public static function date($value, string $format): string { - $date = new \DateTime(strtotime($value)); - return $date->format($format); + if (is_string($value) && ! is_numeric($value)) + { + $value = strtotime($value); + } + + return date($format, $value); } //-------------------------------------------------------------------- /** * Given a string or DateTime object, will return the date modified - * by the given value. + * by the given value. Returns the value as a unix timestamp * * Example: * my_date|date_modify(+1 day) * * @param $value - * @param string $format + * @param string $adjustment * * @return string + * @internal param string $format + * */ - public static function date_modify($value, string $format): string + public static function date_modify($value, string $adjustment): string { - + $value = self::date($value, 'Y-m-d H:i:s'); + return strtotime($adjustment, strtotime($value)); } //-------------------------------------------------------------------- @@ -68,7 +75,8 @@ public static function date_modify($value, string $format): string /** * Returns the given default value if $value is empty or undefined. * - * @param $value + * @param $value + * @param string $default * * @return string */ @@ -104,11 +112,11 @@ public static function esc($value, string $context='html'): string * * @return string */ - public static function excerpt(string $value, int $radius = 100): string + public static function excerpt(string $value, string $phrase, int $radius = 100): string { helper('text'); - return excerpt($value, null, $radius); + return excerpt($value, $phrase, $radius); } //-------------------------------------------------------------------- @@ -146,21 +154,6 @@ public static function highlight_code($value): string //-------------------------------------------------------------------- - /** - * Performs an implode on an array, joining with $glue. - * - * @param array $value - * @param string $glue - * - * @return string - */ - public static function join(array $value, string $glue=''): string - { - return implode($glue, $value); - } - - //-------------------------------------------------------------------- - /** * Limits the number of chracters to $limit, and trails of with an ellipsis. * Will break at word break so may be more or less than $limit. @@ -231,14 +224,15 @@ public static function nl2br(string $value): string * Wraps PHP number_format function for use within the parser. * * @param string $value + * @param int $places * @param string $decimal * @param string $separator * * @return string */ - public static function number_format(string $value, string $decimal = '.', string $separator = ','): string + public static function number_format(string $value, int $places): string { - return number_format($value, $decimal, $separator); + return number_format($value, $places); } //-------------------------------------------------------------------- @@ -273,8 +267,15 @@ public static function prose(string $value): string * * @return string */ - public static function round($value, int $precision=2, $type='common') + public static function round($value, $precision=2, $type='common') { + + if (! is_numeric($precision)) + { + $type = $precision; + $precision = 2; + } + switch ($type) { case 'common': diff --git a/system/View/Parser.php b/system/View/Parser.php index 40008bda4ef6..09879894cce5 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -278,9 +278,7 @@ function strpos_all($haystack, $needle) */ protected function parseSingle(string $key, string $val, string $template): array { - $val = esc($val, 'html'); - - $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z\(\):\-\s]+)*\s*'.$this->rightDelimiter.'#ms'; + $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*'.$this->rightDelimiter.'#ms'; return [$pattern => (string) $val]; } @@ -347,12 +345,8 @@ protected function parsePair(string $variable, array $data, string $template): a { $val = 'Resource'; } - else - { - $val = esc($val, 'html'); - } - $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*'. $this->rightDelimiter.'#s'] = $val; + $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*'. $this->rightDelimiter.'#s'] = $val; } // Now replace our placeholders with the new content. @@ -527,22 +521,46 @@ protected function prepareReplacement(array $matches, string $replace) $orig = array_shift($matches); + // Our regex earlier will leave all chained values on a single line + // so we need to break them apart so we can apply them all. + $filters = explode('|', $matches[0]); + // Determine the requested filters - foreach ($matches as $filter) + foreach ($filters as $filter) { // Grab any parameter we might need to send - if (! preg_match('/\([a-zA-Z0-9\-:_ ]+\)/', $filter, $param)) continue; + preg_match('/\([a-zA-Z0-9\-:_ +,<>=]+\)/', $filter, $param); + + // Remove the () and spaces to we have just the parameter left + $param = ! empty($param) + ? trim($param[0], '() ') + : null; + + // Params can be separated by commas to allow multiple parameters for the filter + if (! empty($param)) + { + $param = explode(',', $param); - // Remove the () and spaces to we have just tha parameter left - $param = trim($param[0], '() '); + // Clean it up + foreach ($param as &$p) + { + $p = trim($p, ' "'); + } + } + else + { + $param = []; + } // Get our filter name - $filter = strtolower(substr($filter, 0, strpos($filter, '('))); + $filter = $param !== [] + ? trim(strtolower(substr($filter, 0, strpos($filter, '(')))) + : trim($filter); if (! array_key_exists($filter, $this->config->filters)) continue; // Filter it.... - $replace = call_user_func($this->config->filters[$filter], $replace, $param); + $replace = call_user_func($this->config->filters[$filter], $replace, ...$param); } return $replace; diff --git a/tests/system/View/ParserFilterTest.php b/tests/system/View/ParserFilterTest.php new file mode 100644 index 000000000000..394d42f66630 --- /dev/null +++ b/tests/system/View/ParserFilterTest.php @@ -0,0 +1,305 @@ +loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload()); + $this->viewsDir = __DIR__.'/Views'; + $this->config = new Config\View(); + } + + //-------------------------------------------------------------------- + + public function testAbs() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'value1' => -5, + 'value2' => 5 + ]; + + $template = '{ value1|abs }{ value2|abs }'; + + $parser->setData($data); + $this->assertEquals('55', $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + public function testCapitalize() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'value1' => 'wonder', + 'value2' => 'TWInS' + ]; + + $template = '{ value1|capitalize } { value2|capitalize }'; + + $parser->setData($data); + $this->assertEquals('Wonder Twins', $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + public function testDate() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $today = date('Y-m-d'); + + $data = [ + 'value1' => time(), + 'value2' => date('Y-m-d H:i:s'), + ]; + + $template = '{ value1|date(Y-m-d) } { value2|date(Y-m-d) }'; + + $parser->setData($data); + $this->assertEquals("{$today} {$today}", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + public function testDateModify() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $today = date('Y-m-d'); + $tommorrow = date('Y-m-d', strtotime('+1 day')); + + $data = [ + 'value1' => time(), + 'value2' => date('Y-m-d H:i:s'), + ]; + + $template = '{ value1|date_modify(+1 day)|date(Y-m-d) } { value2|date_modify(+1 day)|date(Y-m-d) }'; + + $parser->setData($data); + $this->assertEquals("{$tommorrow} {$tommorrow}", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + public function testDefault() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'value1' => null, + 'value2' => 0, + 'value3' => 'test', + ]; + + $template = '{ value1|default(foo) } { value2|default(bar) } { value3|default(baz) }'; + + $parser->setData($data); + $this->assertEquals("foo bar test", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + public function testEsc() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $value1 = esc('' ]; - $template = '{ that_thing|esc) }'; + $template = '{ that_thing|esc }'; $parser->setData($data); $this->assertEquals('<script>alert("ci4")</script>', $parser->renderString($template)); From e89529ac4b2d481639b0da82e36ae562d206b471 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 14 Apr 2017 00:00:53 -0500 Subject: [PATCH 0607/1807] AutoEscaping --- system/View/Parser.php | 94 ++++++++++++++++++-------------- tests/system/View/ParserTest.php | 23 ++++++-- 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 0f032ab946ac..19d22f44c9a5 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -233,17 +233,25 @@ protected function parse(string $template, array $data = [], array $options = nu // the content as we go. foreach ($data as $key => $val) { - $replace = is_array($val) - ? $this->parsePair($key, $val, $template) - : $this->parseSingle($key, (string)$val, $template); + $escape = true; - $pattern = key($replace); - $content = array_shift($replace); + if (is_array($val)) + { + $escape = false; + $replace = $this->parsePair($key, $val, $template); + } + else + { + $replace = $this->parseSingle($key, (string)$val, $template); + } - // Replace the content in the template - $template = preg_replace_callback($pattern, function($matches) use($content) { - return $this->prepareReplacement($matches, $content); - }, $template); + foreach ($replace as $pattern => $content) + { + // Replace the content in the template + $template = preg_replace_callback($pattern, function ($matches) use ($content, $escape) { + return $this->prepareReplacement($matches, $content, $escape); + }, $template); + } } $template = $this->insertNoparse($template); @@ -358,7 +366,9 @@ protected function parsePair(string $variable, array $data, string $template): a // Now replace our placeholders with the new content. foreach ($temp as $pattern => $content) { - $out = preg_replace($pattern, $content, $out); + $out = preg_replace_callback($pattern, function($matches) use($content) { + return $this->prepareReplacement($matches, $content); + }, $out); } $str .= $out; @@ -520,16 +530,20 @@ public function setDelimiters($leftDelimiter = '{', $rightDelimiter = '}'): Rend * * @return mixed|string */ - protected function prepareReplacement(array $matches, string $replace) + protected function prepareReplacement(array $matches, string $replace, bool $escape=true) { - // No filters? Then we outta here. - if (count($matches) === 1) return $replace; - $orig = array_shift($matches); // Our regex earlier will leave all chained values on a single line // so we need to break them apart so we can apply them all. - $filters = explode('|', $matches[0]); + $filters = isset($matches[0]) + ? explode('|', $matches[0]) + : []; + + if ($escape && (! isset($matches[0]) || $this->shouldAddEscaping($orig))) + { + $filters[] = 'esc(html)'; + } $replace = $this->applyFilters($replace, $filters); @@ -540,36 +554,32 @@ protected function prepareReplacement(array $matches, string $replace) /** * Checks the placeholder the view provided to see if we need to provide any autoescaping. - * Keeps a list of all that do so it can be checked prior to replacement. * * @param string $key + * + * @return bool */ -// public function checkEscaping(string $key) -// { -// $escape = false; -// -// // No pipes, then we know we need to escape -// if (strpos($key, '|') === false) { -// $escape = true; -// } -// // If there's a `noescape` then we're definitely false. -// elseif (strpos($key, 'noescape') !== false) -// { -// $escape = false; -// } -// // If no `esc` filter is found, then we'll need to add one. -// elseif (! preg_match('/^|\s+esc/', $key)) -// { -// $escape = true; -// } -// // No need to store an explicit instruction otherwise. -// else -// { -// return; -// } -// -// $this->autoescapes[$key] = $escape; -// } + public function shouldAddEscaping(string $key) + { + $escape = false; + + // No pipes, then we know we need to escape + if (strpos($key, '|') === false) { + $escape = true; + } + // If there's a `noescape` then we're definitely false. + elseif (strpos($key, 'noescape') !== false) + { + $escape = false; + } + // If no `esc` filter is found, then we'll need to add one. + elseif (! preg_match('/^|\s+esc/', $key)) + { + $escape = true; + } + + return $escape; + } //-------------------------------------------------------------------- diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index b96137c0a2cf..795fbe6bfaf5 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -113,20 +113,35 @@ public function testParseNoTemplate() // -------------------------------------------------------------------- - public function testParseNested() + public function testParseArraySingle() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'title' => 'Super Heroes', + 'powers' => [ + ['invisibility' => 'yes', 'flying' => 'no'], + ], + ]; + + $template = "{ title }\n{ powers }{invisibility}\n{flying}{/powers}"; + + $parser->setData($data); + $this->assertEquals("Super Heroes\nyes\nno", $parser->renderString($template)); + } + + public function testParseArrayMulti() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); $data = [ - 'title' => 'Super Heroes', 'powers' => [ ['invisibility' => 'yes', 'flying' => 'no'], ], ]; - $template = "{ title }\n{ powers }{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{ /powers}"; + $template = "{ powers }{invisibility}\n{flying}{/powers}\nsecond:{powers} {invisibility} {flying}{ /powers}"; $parser->setData($data); - $this->assertEquals("Super Heroes\nyes\nno\nsecond: yes no", $parser->renderString($template)); + $this->assertEquals("yes\nno\nsecond: yes no", $parser->renderString($template)); } // -------------------------------------------------------------------- From 367bdbbac0c9d27c768dc86ea5acfe4e7a002872 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Fri, 14 Apr 2017 23:29:17 -0500 Subject: [PATCH 0608/1807] No-escape tags implemented. Updated docs re escaping, no-escape, and filters. --- system/View/Parser.php | 16 +- tests/system/View/ParserTest.php | 15 ++ user_guide_src/source/general/view_parser.rst | 215 +++++++++++++----- 3 files changed, 178 insertions(+), 68 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 19d22f44c9a5..716d0add6fb1 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -242,13 +242,20 @@ protected function parse(string $template, array $data = [], array $options = nu } else { - $replace = $this->parseSingle($key, (string)$val, $template); + $replace = $this->parseSingle($key, (string)$val); } foreach ($replace as $pattern => $content) { // Replace the content in the template $template = preg_replace_callback($pattern, function ($matches) use ($content, $escape) { + + // Check for {! !} syntax to not-escape this one. + if (substr($matches[0], 0, 2) == '{!' && substr($matches[0], -2) == '!}') + { + $escape = false; + } + return $this->prepareReplacement($matches, $content, $escape); }, $template); } @@ -287,12 +294,11 @@ function strpos_all($haystack, $needle) * * @param string $key * @param string $val - * @param string $template * @return array */ - protected function parseSingle(string $key, string $val, string $template): array + protected function parseSingle(string $key, string $val): array { - $pattern = '#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*'.$this->rightDelimiter.'#ms'; + $pattern = '#'.$this->leftDelimiter.'!?\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*!?'.$this->rightDelimiter.'#ms'; return [$pattern => (string) $val]; } @@ -360,7 +366,7 @@ protected function parsePair(string $variable, array $data, string $template): a $val = 'Resource'; } - $temp['#'.$this->leftDelimiter.'\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*'. $this->rightDelimiter.'#s'] = $val; + $temp['#'.$this->leftDelimiter.'!?\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*!?'. $this->rightDelimiter.'#s'] = $val; } // Now replace our placeholders with the new content. diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 795fbe6bfaf5..f3c680e6e759 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -272,6 +272,21 @@ public function testParserEscapesDataDefaultsToHTML() //-------------------------------------------------------------------- + public function testParserNoEscape() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $data = [ + 'title' => '', + ]; + + $template = "{! title!}"; + $parser->setData($data); + $this->assertEquals('', $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + // public function testParserEscapesChooseContext() // { // $parser = new Parser($this->config, $this->viewsDir, $this->loader); diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index d66178d515f3..e7cc7c0d1113 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -39,11 +39,9 @@ representations that allow you to eliminate PHP from your templates Using the View Parser Class *************************** -If you have the ``Parser`` as your default renderer, then the parsing will occur -transparently. If you want to work with it more directly, you can access the -Parser service directly:: +The simplest method to load the parser class is through its service:: - $parser = \Config\Services::renderer(); + $parser = \Config\Services::parser(); Alternately, if you are not using the ``Parser`` class as your default renderer, you can instantiate it directly:: @@ -65,13 +63,12 @@ What It Does ============ The ``Parser`` class processes "PHP/HTML scripts" stored in the application's view path. -These scripts have a ``.php`` extension, but should not contain any PHP. +These scripts have a ``.php`` extension, but can not contain any PHP. Each view parameter (which we refer to as a pseudo-variable) triggers a substitution, based on the type of value you provided for it. Pseudo-variables are not extracted into PHP variables; instead their value is accessed through the pseudo-variable syntax, where its name is referenced inside braces. -This means that your view parameter names need not be legal PHP variable names. The Parser class uses an associative array internally, to accumulate pseudo-variable settings until you call its ``render()``. This means that your pseudo-variable names @@ -87,10 +84,10 @@ Parser templates You can use the ``render()`` method to parse (or render) simple templates, like this:: - $data = array( - 'blog_title' => 'My Blog Title', + $data = [ + 'blog_title' => 'My Blog Title', 'blog_heading' => 'My Blog Heading' - ); + ]; echo $parser->setData($data) ->render('blog_template'); @@ -99,8 +96,8 @@ View parameters are passed to ``setData()`` as an associative array of data to be replaced in the template. In the above example, the template would contain two variables: {blog_title} and {blog_heading} The first parameter to ``render()`` contains the name of the :doc:`view -file <../general/views>` (in this example the file would be called -blog_template.php), + file <../general/views>` (in this example the file would be called + blog_template.php), Parser Configuration Options @@ -117,20 +114,26 @@ Several options can be passed to the ``render()`` or ``renderString()`` methods. - ``cascadeData`` - true if pseudo-variable settings should be passed on to nested substitutions; default is **true** +:: + + echo $parser->render('blog_template', [ + 'cache' => HOUR, + 'cache_name' => 'something_unique', + ]); + *********************** Substitution Variations *********************** There are three types of substitution supported: simple, looping, and nested. -Substitutions are performed in the same sequence that -pseudo-variables were added. +Substitutions are performed in the same sequence that pseudo-variables were added. The **simple substitution** performed by the parser is a one-to-one replacement of pseudo-variables where the corresponding data parameter has either a scalar or string value, as in this example:: $template = '{blog_title}'; - $data = ['blog_title' => 'My ramblings']; + $data = ['blog_title' => 'My ramblings']; echo $parser->setData($data)->renderString($template); @@ -253,7 +256,7 @@ Comments ======== You can place comments in your templates that will be ignored and removed during parsing by wrapping the -comments in a ``{# ... #}`` symbols. +comments in a ``{# #}`` symbols. :: @@ -265,10 +268,41 @@ comments in a ``{# ... #}`` symbols. {/blog_entry} +Cascading Data +============== + +With both a nested and a loop substitution, you have the option of cascading +data pairs into the inner substitution. + +The following example is not impacted by cascading:: + + $template = '{name} lives in {location}{city} on {planet}{/location}.'; + + $data = ['name' => 'George', + 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; + + echo $parser->setData($data)->renderString($template); + // Result: George lives in Red City on Mars. + +This example gives different results, depending on cascading:: + + $template = '{location}{name} lives in {city} on {planet}{/location}.'; + + $data = ['name' => 'George', + 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; + + echo $parser->setData($data)->renderString($template, ['cascadeData'=>false]); + // Result: {name} lives in Red City on Mars. + + echo $parser->setData($data)->renderString($template, ['cascadeData'=>true]); + // Result: George lives in Red City on Mars. + + Preventing Parsing ================== -You can specify portions of the page to not be parsed with the ``{noparse}{/noparse}`` tag pair. +You can specify portions of the page to not be parsed with the ``{noparse}{/noparse}`` tag pair. Anything in this +section will stay exactly as it is, with no variable substition, looping, etc, happening to the markup between the brackets. :: @@ -288,11 +322,11 @@ blocks must be closed with an ``endif`` tag:: This simple block is converted to the following during parsing:: - +

    Welcome, Admin!

    -All variables used within if statement must have been previously set with the same name. Other than that, it is +All variables used within if statements must have been previously set with the same name. Other than that, it is treated exactly like a standard PHP conditional, and all standard PHP rules would apply here. You can use any of the comparison operators you would normally, like ``==``, ``===``, ``!==``, ``<``, ``>``, etc. @@ -310,34 +344,89 @@ of the comparison operators you would normally, like ``==``, ``===``, ``!==``, ` .. note:: In the background, conditionals are parsed using an **eval()**, so you must ensure that you take care with the user data that is used within conditionals, or you could open your application up to security risks. -Cascading Data -============== +Escaping Data +============= -With both a nested and a loop substitution, you have the option of cascading -data pairs into the inner substitution. +By default, all variable substitution is escaped to help prevent XSS attacks on your pages. CodeIgniter's ``esc`` method +supports several different contexts, like general **html**, when it's in an HTML **attr*, in **css**, etc. If nothing +else is specified, the data will be assumed to be in an HTML context. You can specify the context used by using the **esc** +filter:: -The following example is not impacted by cascading:: + { user_styles | esc(css) } + { title } - $template = '{name} lives in {location}{city} on {planet}{/location}.'; +There will be times when you absolutely need something to used and NOT escaped. You can do this by adding exclamation +marks to the opening and closing braces:: - $data = ['name' => 'George', - 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; + {! unescaped_var !} - echo $parser->setData($data)->renderString($template); - // Result: George lives in Red City on Mars. +Filters +======= -This example gives different results, depending on cascading:: +Any single variable substitution can have one or more filters applied to it to modify the way it is presented. These +are not intended to drastically change the output, but provide ways to reuse the same variable data but with different +presentations. The **esc** filter discussed above is one example. Dates are another common use case, where you might +need to format the same data differently in several sections on the same page. - $template = '{location}{name} lives in {city} on {planet}{/location}.'; +Filters are commands that come after the pseudo-variable name, and are separated by the pipe symbol, ``|``:: - $data = ['name' => 'George', - 'location' => [ 'city' => 'Red City', 'planet' => 'Mars' ] ]; + // -55 is displayed as 55 + { value|abs } - echo $parser->setData($data)->renderString($template, ['cascadeData'=>false]); - // Result: {name} lives in Red City on Mars. +If the parameter takes any arguments, they must be separated by commas and enclosed in parentheses:: - echo $parser->setData($data)->renderString($template, ['cascadeData'=>true]); - // Result: George lives in Red City on Mars. + { created_at|date(Y-m-d) } + +Multiple filters can be applied to the value by piping multiple ones together. They are processed in order, from +left to right:: + + { created_at|date_modify(+5 days)|date(Y-m-d) } + +Provided Filters +---------------- + +The following filters are available when using the parser: + +==================== ========================== =================================================================== ========================== +Filter Arguments Description Example +==================== ========================== =================================================================== ========================== +abs Displays the absolute value of a number. { v|abs } +capitalize Displays the string in sentence case: all lowercase with first { v|capitalize} + letter capitalized. +date format (Y-m-d) A PHP **date**-compatible formatting string. { v|date(Y-m-d) } +date_modify value to add/subtract A **strtotime** compatible string to modify the date, like { v|date_modify(+1 day) } + ``+5 day`` or ``-1 week``. +default default value Displays the default value if the variable is empty or undefined. { v|default(just in case) } +esc html, attr, css, js Specifies the context to escape the data. { v|esc(attr) } +excerpt phrase, radius Returns the text within a radius of words from a given phrase. { v|excerpt(green giant, 20) } + Same as **excerpt** helper function. +highlight phrase Highlights a given phrase within the text using '' tags { v|highlight(view parser) } +highlight_code Highlights code samples with HTML/CSS. { v|highlight_code } +limit_chars limit Limits the number of chracters to $limit. { v|limit_chars(100) } +limit_words limit Limits the number of words to $limit. { v|limit_words(20) } +lower Converts a string to lowercase. { v|lower } +nl2br Replaces all newline characters (\n) to an HTML
    tag. { v|nl2br } +number_format places Wraps PHP **number_format** function for use within the parser. { v|number_format(3) } +prose Takes a body of text and uses the **auto_typography()** method to { v|prose } + turn it into prettier, easier-to-read, prose. +round places, type Rounds a number to the specified places. Types of **ceil** and { v|round(3) } { v|round(ceil) } + **floor** can be passed to use those functions instead. +strip_tags allowed chars Wraps PHP **strip_tags**. Can accept a string of allowed tags. { v|strip_tags(
    ) } +title Displays a "title case" version of the string, with all lowercase, { v|title } + and each word capitalized. +upper Displays the string in all lowercase. { v|lower } + +Custom Filters +-------------- + +You can easily create your own filters by editing **application/Config/View.php** and adding new entries to the +``$filters`` array. Each key is the name the filter is called by in the view, and its value is any valid PHP +callable:: + + public $filters = [ + 'abs' => '\CodeIgniter\View\Filters::abs', + 'capitalize' => '\CodeIgniter\View\Filters::capitalize', + ]; *********** @@ -460,16 +549,16 @@ Class Reference .. php:method:: render($view[, $options[, $saveData=false]]]) :param string $view: File name of the view source - :param array $options: Array of options, as key/value pairs - :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. - :returns: The rendered text for the chosen view - :rtype: string + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string - Builds the output based upon a file name and any data that has already been set:: + Builds the output based upon a file name and any data that has already been set:: echo $parser->render('myview'); - Options supported: + Options supported: - ``cache`` - the time in seconds, to save a view's results - ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath @@ -484,54 +573,54 @@ Class Reference .. php:method:: renderString($template[, $options[, $saveData=false]]]) :param string $template: View source provided as a string - :param array $options: Array of options, as key/value pairs - :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. - :returns: The rendered text for the chosen view - :rtype: string + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string - Builds the output based upon a provided template source and any data that has already been set:: + Builds the output based upon a provided template source and any data that has already been set:: echo $parser->render('myview'); - Options supported, and behavior, as above. + Options supported, and behavior, as above. .. php:method:: setData([$data[, $context=null]]) :param array $data: Array of view data strings, as key/value pairs - :param string $context: The context to use for data escaping. - :returns: The Renderer, for method chaining - :rtype: CodeIgniter\\View\\RendererInterface. + :param string $context: The context to use for data escaping. + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RendererInterface. - Sets several pieces of view data at once:: + Sets several pieces of view data at once:: $renderer->setData(['name'=>'George', 'position'=>'Boss']); - Supported escape contexts: html, css, js, url, or attr or raw. + Supported escape contexts: html, css, js, url, or attr or raw. If 'raw', no escaping will happen. .. php:method:: setVar($name[, $value=null[, $context=null]]) :param string $name: Name of the view data variable - :param mixed $value: The value of this view data - :param string $context: The context to use for data escaping. - :returns: The Renderer, for method chaining - :rtype: CodeIgniter\\View\\RendererInterface. + :param mixed $value: The value of this view data + :param string $context: The context to use for data escaping. + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RendererInterface. - Sets a single piece of view data:: + Sets a single piece of view data:: $renderer->setVar('name','Joe','html'); - Supported escape contexts: html, css, js, url, attr or raw. + Supported escape contexts: html, css, js, url, attr or raw. If 'raw', no escaping will happen. .. php:method:: setDelimiters($leftDelimiter = '{', $rightDelimiter = '}') :param string $leftDelimiter: Left delimiter for substitution fields - :param string $rightDelimiter: right delimiter for substitution fields - :returns: The Renderer, for method chaining - :rtype: CodeIgniter\\View\\RendererInterface. + :param string $rightDelimiter: right delimiter for substitution fields + :returns: The Renderer, for method chaining + :rtype: CodeIgniter\\View\\RendererInterface. - Over-ride the substitution field delimiters:: + Over-ride the substitution field delimiters:: $renderer->setDelimiters('[',']'); From 53b1ec7d9ab6497a20d9404067c65628d89853b4 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sat, 15 Apr 2017 22:37:49 -0500 Subject: [PATCH 0609/1807] ReverseRouting no longer throws exception when route not found, which should allow redirect to work properly. Fixes #465 --- system/Router/RouteCollection.php | 6 +++--- system/Router/RouteCollectionInterface.php | 4 ++-- tests/system/Router/RouteCollectionTest.php | 10 ++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 0eee9b3fca67..d6193e41e3db 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -924,9 +924,9 @@ public function environment(string $env, \Closure $callback): RouteCollectionInt * @param string $search * @param ...$params * - * @return string + * @return string|false */ - public function reverseRoute(string $search, ...$params): string + public function reverseRoute(string $search, ...$params) { // Named routes get higher priority. if (array_key_exists($search, $this->routes)) @@ -964,7 +964,7 @@ public function reverseRoute(string $search, ...$params): string } // If we're still here, then we did not find a match. - throw new \InvalidArgumentException('Unable to locate a valid route.'); + return false; } //-------------------------------------------------------------------- diff --git a/system/Router/RouteCollectionInterface.php b/system/Router/RouteCollectionInterface.php index 589a09b4ee25..ee29e48937b2 100644 --- a/system/Router/RouteCollectionInterface.php +++ b/system/Router/RouteCollectionInterface.php @@ -245,9 +245,9 @@ public function getHTTPVerb(); * * @param string $search * @param ...$params - * @return string + * @return string|false */ - public function reverseRoute(string $search, ...$params): string; + public function reverseRoute(string $search, ...$params); //-------------------------------------------------------------------- diff --git a/tests/system/Router/RouteCollectionTest.php b/tests/system/Router/RouteCollectionTest.php index 004a3634d0da..583befef346f 100644 --- a/tests/system/Router/RouteCollectionTest.php +++ b/tests/system/Router/RouteCollectionTest.php @@ -578,26 +578,24 @@ public function testReverseRoutingFindsSimpleMatch() //-------------------------------------------------------------------- - public function testReverseRoutingThrowsExceptionWithBadParamCount() + public function testReverseRoutingReturnsFalseWithBadParamCount() { $routes = new RouteCollection(); $routes->add('path/(:any)/to/(:num)', 'myController::goto/$1'); - $this->expectException('InvalidArgumentException'); - $match = $routes->reverseRoute('myController::goto', 'string', 13); + $this->assertFalse($routes->reverseRoute('myController::goto', 'string', 13)); } //-------------------------------------------------------------------- - public function testReverseRoutingThrowsExceptionWithNoMatch() + public function testReverseRoutingReturnsFalseWithNoMatch() { $routes = new RouteCollection(); $routes->add('path/(:any)/to/(:num)', 'myController::goto/$1/$2'); - $this->expectException('InvalidArgumentException'); - $match = $routes->reverseRoute('myBadController::goto', 'string', 13); + $this->assertFalse($routes->reverseRoute('myBadController::goto', 'string', 13)); } //-------------------------------------------------------------------- From 0c34b55c8c41347ea70f8d3d5c305363eb4477c7 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 16 Apr 2017 22:26:32 -0500 Subject: [PATCH 0610/1807] Extract replacement of single pseudo-vars into its own method. --- system/View/Parser.php | 46 +++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 716d0add6fb1..afb4be78b5b4 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -247,17 +247,7 @@ protected function parse(string $template, array $data = [], array $options = nu foreach ($replace as $pattern => $content) { - // Replace the content in the template - $template = preg_replace_callback($pattern, function ($matches) use ($content, $escape) { - - // Check for {! !} syntax to not-escape this one. - if (substr($matches[0], 0, 2) == '{!' && substr($matches[0], -2) == '!}') - { - $escape = false; - } - - return $this->prepareReplacement($matches, $content, $escape); - }, $template); + $template = $this->replaceSingle($pattern, $content, $template, $escape); } } @@ -372,9 +362,7 @@ protected function parsePair(string $variable, array $data, string $template): a // Now replace our placeholders with the new content. foreach ($temp as $pattern => $content) { - $out = preg_replace_callback($pattern, function($matches) use($content) { - return $this->prepareReplacement($matches, $content); - }, $out); + $out = $this->replaceSingle($pattern, $content, $out, true); } $str .= $out; @@ -528,6 +516,36 @@ public function setDelimiters($leftDelimiter = '{', $rightDelimiter = '}'): Rend //-------------------------------------------------------------------- + /** + * Handles replacing a pseudo-variable with teh actual content. Will double-check + * for escaping brackets. + * + * @param $pattern + * @param $content + * @param $template + * @param bool $escape + * + * @return string + */ + protected function replaceSingle($pattern, $content, $template, bool $escape=false): string + { + // Replace the content in the template + $template = preg_replace_callback($pattern, function ($matches) use ($content, $escape) { + + // Check for {! !} syntax to not-escape this one. + if (substr($matches[0], 0, 2) == '{!' && substr($matches[0], -2) == '!}') + { + $escape = false; + } + + return $this->prepareReplacement($matches, $content, $escape); + }, $template); + + return $template; + } + + //-------------------------------------------------------------------- + /** * Callback used during parse() to apply any filters to the value. * From d82bbfe7441aaab22eeabe1cd45d4e5e769ef92f Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 17 Apr 2017 00:30:17 -0500 Subject: [PATCH 0611/1807] Getting a start on the plugin system --- application/Config/View.php | 7 +++ system/Config/View.php | 5 ++ system/View/Parser.php | 96 ++++++++++++++++++++++++++++++-- tests/system/View/ParserTest.php | 70 +++++++++++++++++++++++ 4 files changed, 173 insertions(+), 5 deletions(-) diff --git a/application/Config/View.php b/application/Config/View.php index 3c7be64c8db8..6d33a2225226 100644 --- a/application/Config/View.php +++ b/application/Config/View.php @@ -24,4 +24,11 @@ class View extends \CodeIgniter\Config\View * { created_on|date(Y-m-d)|esc(attr) } */ public $filters = []; + + /** + * Parser Plugins provide a way to extend the functionality provided + * by the core Parser by creating aliases that will be replaced with + * any callable. Can be single or tag pair. + */ + public $plugins = []; } diff --git a/system/Config/View.php b/system/Config/View.php index fd27f5995b86..eaddf2d23477 100644 --- a/system/Config/View.php +++ b/system/Config/View.php @@ -24,9 +24,14 @@ class View { 'upper' => '\CodeIgniter\View\Filters::upper', ]; + protected $corePlugins = [ + + ]; + public function __construct() { $this->filters = array_merge($this->filters, $this->coreFilters); + $this->plugins = array_merge($this->plugins, $this->corePlugins); } } diff --git a/system/View/Parser.php b/system/View/Parser.php index afb4be78b5b4..6455ba6a4d95 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -76,13 +76,10 @@ class Parser extends View { protected $noparseBlocks = []; /** - * Stores the details about any variables - * that are extracted during parsing along - * with escaping information. - * + * Stores any plugins registered at run-time. * @var array */ - protected $extractions = []; + protected $plugins = []; //-------------------------------------------------------------------- @@ -97,6 +94,9 @@ class Parser extends View { */ public function __construct($config, string $viewPath = null, $loader = null, bool $debug = null, Logger $logger = null) { + // Ensure user plugins override core plugins. + $this->plugins = $config->plugins ?? []; + parent::__construct($config, $viewPath, $loader, $debug, $logger); } @@ -229,6 +229,10 @@ protected function parse(string $template, array $data = [], array $options = nu // Replace any conditional code here so we don't have to parse as much $template = $this->parseConditionals($template); + // Handle any plugins before normal data, so that + // it can potentially modify any template between its tags. + $template = $this->parsePlugins($template); + // loop over the data variables, replacing // the content as we go. foreach ($data as $key => $val) @@ -658,4 +662,86 @@ protected function applyFilters(string $replace, array $filters): string return $replace; } + + //-------------------------------------------------------------------- + // Plugins + //-------------------------------------------------------------------- + + /** + * Scans the template for any parser plugins, and attempts to execute them. + * Plugins are notated based on {+ +} opening and closing braces. + * + * When encountered, + * + * @param string $template + * + * @return string + */ + protected function parsePlugins(string $template) + { + /** + * $matches[0] = entire string + * $matches[1] = opening tag, with all parameters + * $matches[2] = closing tag (if any) + */ + preg_match('/\{\+([a-zA-Z0-9-_\\\+:=\s]*)\+}.*(\{\+\s*\/[a-zA-Z0-9-_\\\+:=\s]*\+\})?/m', $template, $matches); + + if (empty($matches)) return $template; + + $params = explode(' ', trim($matches[1])); + $tag = trim(array_shift($params)); + + if (! array_key_exists($tag, $this->plugins)) + { + // @todo log missing plugin or throw exception? + return $template; + } + + // Single tag? + if (empty($matches[2])) + { + $template = str_replace($matches[0], $this->plugins[$tag](), $template); + } + // Tag pair + else + { + + } + + return $template; + } + + /** + * Makes a new plugin available during the parsing of the template. + * + * @param string $alias + * @param callable $callback + * + * @return $this + */ + public function addPlugin(string $alias, callable $callback) + { + $this->plugins[$alias] = $callback; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Removes a plugin from the available plugins. + * + * @param string $alias + * + * @return $this + */ + public function removePlugin(string $alias) + { + unset($this->plugins[$alias]); + + return $this; + } + + //-------------------------------------------------------------------- + } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index f3c680e6e759..1ae78ab8f05c 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -430,4 +430,74 @@ public function testParseHandlesSpaces() // -------------------------------------------------------------------- + public function testParseRuns() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $data = [ + 'title' => 'Page Title', + 'body' => 'Lorem ipsum dolor sit amet.', + ]; + + $template = "{ title}\n{ body }"; + + $result = implode("\n", $data); + + $parser->setData($data); + $this->assertEquals($result, $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + /** + * @group parserplugins + */ + public function testCanAddAndRemovePlugins() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $parser->addPlugin('first', function($str){ return $str; }); + + $setParsers = $this->getPrivateProperty($parser, 'plugins'); + + $this->assertTrue(array_key_exists('first', $setParsers)); + + $parser->removePlugin('first'); + + $setParsers = $this->getPrivateProperty($parser, 'plugins'); + + $this->assertFalse(array_key_exists('first', $setParsers)); + } + + //-------------------------------------------------------------------- + + /** + * @group parserplugins + */ + public function testParserPluginNoMatches() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + + $template = "hit:it"; + + $this->assertEquals("hit:it", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + /** + * @group parserplugins + */ + public function testParserPluginSingleNoParams() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $parser->addPlugin('hit:it', function(){ + return "Hip to the Hop"; + }); + + $template = "{+ hit:it +}"; + + $this->assertEquals("Hip to the Hop", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + } From fbc38bbd74651f46fa45a73e4ea0e41075e2de61 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 17 Apr 2017 23:04:46 -0500 Subject: [PATCH 0612/1807] Parser plugins now working. --- system/View/Parser.php | 67 ++++++++++++++++++-------------- tests/system/View/ParserTest.php | 37 +++++++++++++++--- 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index 6455ba6a4d95..f8c5fc618ddd 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -360,7 +360,7 @@ protected function parsePair(string $variable, array $data, string $template): a $val = 'Resource'; } - $temp['#'.$this->leftDelimiter.'!?\s*'.preg_quote($key).'\s*\|*\s*([|a-zA-Z0-9<>=\(\),:_\-\s\+]+)*\s*!?'. $this->rightDelimiter.'#s'] = $val; + $temp['#'.$this->leftDelimiter.'!?\s*'.preg_quote($key).'\s*\|*\s*([|\w<>=\(\),:_\-\s\+]+)*\s*!?'. $this->rightDelimiter.'#s'] = $val; } // Now replace our placeholders with the new content. @@ -679,34 +679,43 @@ protected function applyFilters(string $replace, array $filters): string */ protected function parsePlugins(string $template) { - /** - * $matches[0] = entire string - * $matches[1] = opening tag, with all parameters - * $matches[2] = closing tag (if any) - */ - preg_match('/\{\+([a-zA-Z0-9-_\\\+:=\s]*)\+}.*(\{\+\s*\/[a-zA-Z0-9-_\\\+:=\s]*\+\})?/m', $template, $matches); - - if (empty($matches)) return $template; - - $params = explode(' ', trim($matches[1])); - $tag = trim(array_shift($params)); - - if (! array_key_exists($tag, $this->plugins)) - { - // @todo log missing plugin or throw exception? - return $template; - } - - // Single tag? - if (empty($matches[2])) - { - $template = str_replace($matches[0], $this->plugins[$tag](), $template); - } - // Tag pair - else - { - - } + foreach($this->plugins as $plugin => $callable) + { + /** + * Match tag pairs + * + * Each match is an array: + * $matches[0] = entire matched string + * $matches[1] = all parameters string in opening tag + * $matches[2] = content between the tags to send to the plugin. + */ + preg_match_all('#{\+\s*'.$plugin.'([\w\d=-_:\+\s()]*)?\s*\+}(.+?){\+\s*/'.$plugin.'\s*\+}#ims', + $template, + $matches, + PREG_SET_ORDER); + + if (empty($matches)) + { + continue; + } + + foreach ($matches as $match) + { + $params = []; + $parts = explode(' ', trim($match[1])); + + foreach ($parts as $part) + { + if (empty($part)) continue; + + list($a, $b) = explode('=',$part); + $params[$a] = $b; + } + unset($parts); + + $template = str_replace($match[0], $callable($match[2], $params), $template); + } + } return $template; } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 1ae78ab8f05c..873cc61039d9 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -486,18 +486,45 @@ public function testParserPluginNoMatches() /** * @group parserplugins */ - public function testParserPluginSingleNoParams() + public function testParserPluginNoParams() { $parser = new Parser($this->config, $this->viewsDir, $this->loader); - $parser->addPlugin('hit:it', function(){ - return "Hip to the Hop"; + $parser->addPlugin('hit:it', function($str){ + return str_replace('here', "Hip to the Hop", $str); }); - $template = "{+ hit:it +}"; + $template = "{+ hit:it +} stuff here {+ /hit:it +}"; - $this->assertEquals("Hip to the Hop", $parser->renderString($template)); + $this->assertEquals(" stuff Hip to the Hop ", $parser->renderString($template)); } //-------------------------------------------------------------------- + /** + * @group parserplugins + */ + public function testParserPluginParams() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $parser->addPlugin('growth', function($str, array $params){ + $step = $params['step'] ?? 1; + $count = $params['count'] ?? 2; + + $out = ''; + + for ($i=1; $i <= $count; $i++) + { + $out .= " ".$i * $step; + } + + return $out; + }); + + $template = "{+ growth step=2 count=4 +} {+ /growth +}"; + + $this->assertEquals(" 2 4 6 8", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + } From 2fb89d974eb188f8003a402485398807edf91555 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 17 Apr 2017 23:50:08 -0500 Subject: [PATCH 0613/1807] Added basic parser plugin docs --- user_guide_src/source/general/view_parser.rst | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index e7cc7c0d1113..2a04e9dc8255 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -428,6 +428,61 @@ callable:: 'capitalize' => '\CodeIgniter\View\Filters::capitalize', ]; +Parser Plugins +============== + +Plugins allow you to extend the parser, adding custom features for each project. They can be any PHP callable, making +them very simple to implement. Within templates, plugins are specified by ``{+ +}`` tags:: + + {+ foo +} inner content {+ /foo +} + +This example shows a plugin named **foo**. It can manipulate any of the content between its opening and closing tags. +In this example, it could work with the text " inner content ". Plugins are processed before any pseudo-variable +replacements happen. + +While plugins will often consist of tag pairs, like shown above, they can also be a single tag, with no closing tag:: + + {+ foo +} + +Opening tags can also contain parameters that can customize how the plugin works. The parameters are represented as +key/value pairs:: + + {+ foo bar=2 baz="x y" } + +Registering a Plugin +-------------------- + +At its simplest, all you need to do to register a new plugin and make it ready for use is to add it to the +**application/Config/View.php**, under the **$plugins** array. The key is the name of the plugin that is +used within the template file. The value is any valid PHP callable, including static class methods, and closures:: + + public $plugins = [ + 'foo' => '\Some\Class::methodName', + 'bar' => function($str, array $params=[]) { + return $str; + }, + ]; + +If the callable is on its own, it is treated as a single tag, not a open/close one. It will be replaced by +the return value from the plugin:: + + public $plugins = [ + 'foo' => '\Some\Class::methodName' + ]; + + // Tag is replaced by the return value of Some\Class::methodName static function. + {+ foo +} + +If the callable is wrapped in an array, it is treated as an open/close tag pair that can operate on any of +the content between its tags:: + + public $plugins = [ + 'foo' => ['\Some\Class::methodName'] + ]; + + {+ foo +} inner content {+ /foo +} + + *********** Usage Notes From 44e72dfa9ec5fb1afc05d491747cba2c37ffa9fd Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 17 Apr 2017 23:58:36 -0500 Subject: [PATCH 0614/1807] Parser plugin now supports single and paired tags --- system/View/Parser.php | 43 +++++++++++++++++++++----------- tests/system/View/ParserTest.php | 38 ++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/system/View/Parser.php b/system/View/Parser.php index f8c5fc618ddd..f8d0950ef33d 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -681,6 +681,16 @@ protected function parsePlugins(string $template) { foreach($this->plugins as $plugin => $callable) { + // Paired tags are enclosed in an array in the config array. + $isPair = is_array($callable); + $callable = $isPair + ? array_shift($callable) + : $callable; + + $pattern = $isPair + ? '#{\+\s*'.$plugin.'([\w\d=-_:\+\s()]*)?\s*\+}(.+?){\+\s*/'.$plugin.'\s*\+}#ims' + : '#{\+\s*'.$plugin.'([\w\d=-_:\+\s()]*)?\s*\+}#ims'; + /** * Match tag pairs * @@ -689,10 +699,7 @@ protected function parsePlugins(string $template) * $matches[1] = all parameters string in opening tag * $matches[2] = content between the tags to send to the plugin. */ - preg_match_all('#{\+\s*'.$plugin.'([\w\d=-_:\+\s()]*)?\s*\+}(.+?){\+\s*/'.$plugin.'\s*\+}#ims', - $template, - $matches, - PREG_SET_ORDER); + preg_match_all($pattern,$template, $matches,PREG_SET_ORDER); if (empty($matches)) { @@ -713,24 +720,30 @@ protected function parsePlugins(string $template) } unset($parts); - $template = str_replace($match[0], $callable($match[2], $params), $template); + $template = $isPair + ? str_replace($match[0], $callable($match[2], $params), $template) + : str_replace($match[0], $callable($params), $template); } } return $template; } - /** - * Makes a new plugin available during the parsing of the template. - * - * @param string $alias - * @param callable $callback - * - * @return $this - */ - public function addPlugin(string $alias, callable $callback) + /** + * Makes a new plugin available during the parsing of the template. + * + * @param string $alias + * @param callable $callback + * + * @param bool $isPair + * + * @return $this + */ + public function addPlugin(string $alias, callable $callback, bool $isPair=false) { - $this->plugins[$alias] = $callback; + $this->plugins[$alias] = $isPair + ? [$callback] + : $callback; return $this; } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 873cc61039d9..46c430a062ab 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -491,7 +491,7 @@ public function testParserPluginNoParams() $parser = new Parser($this->config, $this->viewsDir, $this->loader); $parser->addPlugin('hit:it', function($str){ return str_replace('here', "Hip to the Hop", $str); - }); + }, true); $template = "{+ hit:it +} stuff here {+ /hit:it +}"; @@ -518,7 +518,7 @@ public function testParserPluginParams() } return $out; - }); + }, true); $template = "{+ growth step=2 count=4 +} {+ /growth +}"; @@ -527,4 +527,38 @@ public function testParserPluginParams() //-------------------------------------------------------------------- + /** + * @group parserplugins + */ + public function testParserSingleTag() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $parser->addPlugin('hit:it', function(){ + return "Hip to the Hop"; + }, false); + + $template = "{+ hit:it +}"; + + $this->assertEquals("Hip to the Hop", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + + /** + * @group parserplugins + */ + public function testParserSingleTagWithParams() + { + $parser = new Parser($this->config, $this->viewsDir, $this->loader); + $parser->addPlugin('hit:it', function(array $params=[]){ + return "{$params['first']} to the {$params['last']}"; + }, false); + + $template = "{+ hit:it first=foo last=bar +}"; + + $this->assertEquals("foo to the bar", $parser->renderString($template)); + } + + //-------------------------------------------------------------------- + } From 4493ec80f1b353e34248566d552f49690285dea3 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 18 Apr 2017 00:25:48 -0500 Subject: [PATCH 0615/1807] Added some basic plugins --- system/Config/View.php | 5 ++- system/View/Parser.php | 4 +-- system/View/Plugins.php | 48 ++++++++++++++++++++++++++ tests/system/View/ParserPluginTest.php | 47 +++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 system/View/Plugins.php create mode 100644 tests/system/View/ParserPluginTest.php diff --git a/system/Config/View.php b/system/Config/View.php index eaddf2d23477..dab9528f95ff 100644 --- a/system/Config/View.php +++ b/system/Config/View.php @@ -25,7 +25,10 @@ class View { ]; protected $corePlugins = [ - + 'current_url' => '\CodeIgniter\View\Plugins::currentURL', + 'previous_url' => '\CodeIgniter\View\Plugins::previousURL', + 'mailto' => '\CodeIgniter\View\Plugins::mailto', + 'safe_mailto' => '\CodeIgniter\View\Plugins::safeMailto', ]; public function __construct() diff --git a/system/View/Parser.php b/system/View/Parser.php index f8d0950ef33d..eb62e6000414 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -688,8 +688,8 @@ protected function parsePlugins(string $template) : $callable; $pattern = $isPair - ? '#{\+\s*'.$plugin.'([\w\d=-_:\+\s()]*)?\s*\+}(.+?){\+\s*/'.$plugin.'\s*\+}#ims' - : '#{\+\s*'.$plugin.'([\w\d=-_:\+\s()]*)?\s*\+}#ims'; + ? '#{\+\s*'.$plugin.'([\w\d=-_:\+\s()\"@.]*)?\s*\+}(.+?){\+\s*/'.$plugin.'\s*\+}#ims' + : '#{\+\s*'.$plugin.'([\w\d=-_:\+\s()\"@.]*)?\s*\+}#ims'; /** * Match tag pairs diff --git a/system/View/Plugins.php b/system/View/Plugins.php new file mode 100644 index 000000000000..237095115610 --- /dev/null +++ b/system/View/Plugins.php @@ -0,0 +1,48 @@ +parser = \Config\Services::parser(); + } + + public function testCurrentURL() + { + helper('url'); + $template = "{+ current_url +}"; + + $this->assertEquals(current_url(), $this->parser->renderString($template)); + } + + public function testPreviousURL() + { + helper('url'); + $template = "{+ previous_url +}"; + + $this->assertEquals(previous_url(), $this->parser->renderString($template)); + } + + public function testMailto() + { + helper('url'); + $template = '{+ mailto email=foo@example.com title=Silly +}'; + + $this->assertEquals(mailto('foo@example.com', 'Silly'), $this->parser->renderString($template)); + } + + public function testSafeMailto() + { + helper('url'); + $template = '{+ safe_mailto email=foo@example.com title=Silly +}'; + + $this->assertEquals(safe_mailto('foo@example.com', 'Silly'), $this->parser->renderString($template)); + } +} From c41e95bae2570f4b6b0e0981b0314dc3faf2845d Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 18 Apr 2017 07:26:26 -0500 Subject: [PATCH 0616/1807] Fixing tests --- tests/system/Helpers/TextHelperTest.php | 4 ++-- tests/system/View/ParserPluginTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/system/Helpers/TextHelperTest.php b/tests/system/Helpers/TextHelperTest.php index 57f2bb3e285b..4f5b38d43fdf 100755 --- a/tests/system/Helpers/TextHelperTest.php +++ b/tests/system/Helpers/TextHelperTest.php @@ -237,7 +237,7 @@ public function test_default_word_wrap_charlim() public function test_excerpt() { $string = $this->_long_string; - $result = ' Once upon a time, a framework had no tests. It sad So some nice people began to write tests. The more time that went on, the happier it became. ...'; + $result = ' Once upon a time, a framework had no tests. It sad So some nice people began to write tests. The more time that went on, the happier it became. ...'; $this->assertEquals(excerpt($string), $result); } @@ -247,7 +247,7 @@ public function test_excerpt_radius() { $string = $this->_long_string; $phrase = 'began'; - $result = '... people began to ...'; + $result = '... people began to ...'; $this->assertEquals(excerpt($string, $phrase, 10), $result); } } diff --git a/tests/system/View/ParserPluginTest.php b/tests/system/View/ParserPluginTest.php index 557883636416..eea1f23434da 100644 --- a/tests/system/View/ParserPluginTest.php +++ b/tests/system/View/ParserPluginTest.php @@ -2,7 +2,7 @@ use CodeIgniter\View\Parser; -class ParserFilterTest extends \CIUnitTestCase +class ParserPluginTest extends \CIUnitTestCase { protected $parser; From 2f00a10118344e632527f9069b33d2f8fdf9e026 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 18 Apr 2017 07:31:45 -0500 Subject: [PATCH 0617/1807] Allow database instances to actually be cached. Fixes #466 --- system/Database/Config.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/system/Database/Config.php b/system/Database/Config.php index 23e85a257827..55913fb320c5 100644 --- a/system/Database/Config.php +++ b/system/Database/Config.php @@ -79,13 +79,6 @@ public static function connect($group = null, $getShared = true) $group = 'custom'; } - if ($getShared && isset(self::$instances[$group])) - { - return self::$instances[$group]; - } - - self::ensureFactory(); - $config = $config ?? new \Config\Database(); if (empty($group)) @@ -98,6 +91,14 @@ public static function connect($group = null, $getShared = true) throw new \InvalidArgumentException($group.' is not a valid database connection group.'); } + if ($getShared && isset(self::$instances[$group])) + { + return self::$instances[$group]; + } + + self::ensureFactory(); + + if (isset($config->$group)) { $config = $config->$group; From e445ebf5f14de0e0cbe2a446103ccef60eb8aa38 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 18 Apr 2017 23:44:32 -0500 Subject: [PATCH 0618/1807] Moving Files into their own base class, that UploadedFile is extended from. --- system/Files/Exceptions.php | 4 + system/Files/File.php | 197 ++++++++++++++++++ system/HTTP/Files/UploadedFile.php | 196 +++-------------- system/HTTP/Files/UploadedFileInterface.php | 52 +---- tests/system/Files/FileTest.php | 38 ++++ .../system/HTTP/Files/FileCollectionTest.php | 16 +- user_guide_src/source/libraries/files.rst | 97 +++++++++ user_guide_src/source/libraries/index.rst | 1 + .../source/libraries/uploaded_files.rst | 39 +--- 9 files changed, 374 insertions(+), 266 deletions(-) create mode 100644 system/Files/Exceptions.php create mode 100644 system/Files/File.php create mode 100644 tests/system/Files/FileTest.php create mode 100644 user_guide_src/source/libraries/files.rst diff --git a/system/Files/Exceptions.php b/system/Files/Exceptions.php new file mode 100644 index 000000000000..ffd6dd78516a --- /dev/null +++ b/system/Files/Exceptions.php @@ -0,0 +1,4 @@ +size)) + { + $this->size = filesize($this->getPathname()); + } + + switch (strtolower($unit)) + { + case 'kb': + return number_format($this->size / 1024, 3); + break; + case 'mb': + return number_format(($this->size / 1024) / 1024, 3); + break; + } + + return $this->size; + } + + //-------------------------------------------------------------------- + + /** + * Attempts to determine the file extension based on the trusted + * getType() method. If the mime type is unknown, will return null. + * + * @return string + */ + public function guessExtension(): string + { + return \Config\Mimes::guessExtensionFromType($this->getMimeType()); + } + + //-------------------------------------------------------------------- + + /** + * Retrieve the media type of the file. SHOULD not use information from + * the $_FILES array, but should use other methods to more accurately + * determine the type of file, like finfo, or mime_content_type(). + * + * @return string|null The media type we determined it to be. + */ + public function getMimeType(): string + { + if (function_exists('finfo_file')) + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = finfo_file($finfo, $this->getPath()); + finfo_close($finfo); + } + else + { + $mimeType = mime_content_type($this->getPath()); + } + + return $mimeType; + } + + //-------------------------------------------------------------------- + + /** + * Generates a random names based on a simple hash and the time, with + * the correct file extension attached. + * + * @return string + */ + public function getRandomName(): string + { + return time().'_'.bin2hex(random_bytes(10)).'.'.$this->getExtension(); + } + + //-------------------------------------------------------------------- + + /** + * Moves a file to a new location. + * + * @param string $targetPath + * @param string|null $name + * @param bool $overwrite + * + * @return bool + */ + public function move(string $targetPath, string $name = null, bool $overwrite = false) + { + $targetPath = rtrim($targetPath, '/').'/'; + $name = $name ?? $this->getBaseName(); + $destination = $overwrite + ? $this->getDestination($targetPath.$name) + : $targetPath.$name; + + if (! @rename($this->path, $destination)) + { + $error = error_get_last(); + throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', $this->getBasename(),$targetPath, strip_tags($error['message']))); + } + + @chmod($targetPath, 0777 & ~umask()); + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Returns the destination path for the move operation where overwriting is not expected. + * + * First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the + * last element is an integer as there may be cases that the delimiter may be present in the filename. + * For the all other cases, it appends an integer starting from zero before the file's extension. + * + * @param string $destination + * @param string $delimiter + * @param int $i + * + * @return string + */ + public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string + { + while (file_exists($destination)) + { + $info = pathinfo($destination); + if (strpos($info['filename'], $delimiter) !== false) + { + $parts = explode($delimiter, $info['filename']); + if (is_numeric(end($parts))) + { + $i = end($parts); + array_pop($parts); + array_push($parts, ++$i); + $destination = $info['dirname'] . '/' . implode($delimiter, $parts) . '.' . $info['extension']; + } + else + { + $destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++$i . '.' . $info['extension']; + } + } + else + { + $destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++$i . '.' . $info['extension']; + } + } + return $destination; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index 488321634c7e..0954bf19755d 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -1,5 +1,8 @@ originalMimeType = $mimeType; $this->size = $size; $this->error = $error; + + parent::__construct($path, false); } //-------------------------------------------------------------------- @@ -152,7 +142,10 @@ public function __construct(string $path, string $originalName, string $mimeType * * @param string $targetPath Path to which to move the uploaded file. * @param string $name the name to rename the file to. - * @param bool $overwrite State for indicating whether to overwrite the previously generated file with the same name or not. + * @param bool $overwrite State for indicating whether to overwrite the previously generated file with the same + * name or not. + * + * @return bool * * @throws \InvalidArgumentException if the $path specified is invalid. * @throws \RuntimeException on any error during the move operation. @@ -162,12 +155,12 @@ public function move(string $targetPath, string $name = null, bool $overwrite = { if ($this->hasMoved) { - throw new \RuntimeException('The file has already been moved.'); + throw new FileException('The file has already been moved.'); } if (! $this->isValid()) { - throw new \RuntimeException('The original file is not a valid file.'); + throw new FileException('The original file is not a valid file.'); } $targetPath = rtrim($targetPath, '/').'/'; @@ -206,43 +199,6 @@ public function hasMoved(): bool //-------------------------------------------------------------------- - - /** - * Retrieve the file size. - * - * Implementations SHOULD return the value stored in the "size" key of - * the file in the $_FILES array if available, as PHP calculates this based - * on the actual size transmitted. - * - * @param string $unit The unit to return: - * - b Bytes - * - kb Kilobytes - * - mb Megabytes - * - * @return int|null The file size in bytes or null if unknown. - */ - public function getSize(string $unit='b'): int - { - if (is_null($this->size)) - { - $this->size = filesize($this->path); - } - - switch (strtolower($unit)) - { - case 'kb': - return number_format($this->size / 1024, 3); - break; - case 'mb': - return number_format(($this->size / 1024) / 1024, 3); - break; - } - - return $this->size; - } - - //-------------------------------------------------------------------- - /** * Retrieve the error associated with the uploaded file. * @@ -296,6 +252,20 @@ public function getErrorString() //-------------------------------------------------------------------- + /** + * Returns the mime type as provided by the client. + * This is NOT a trusted value. + * For a trusted version, use getMimeType() instead. + * + * @return string|null The media type sent by the client or null if none + * was provided. + */ + public function getClientMimeType(): string + { + return $this->originalMimeType; + } + + //-------------------------------------------------------------------- /** * Retrieve the filename. This will typically be the filename sent @@ -324,32 +294,6 @@ public function getTempName(): string //-------------------------------------------------------------------- - /** - * Generates a random names based on a simple hash and the time, with - * the correct file extension attached. - * - * @return string - */ - public function getRandomName(): string - { - return time().'_'.bin2hex(random_bytes(10)).'.'.$this->getExtension(); - } - - //-------------------------------------------------------------------- - - /** - * Attempts to determine the file extension based on the trusted - * getType() method. If the mime type is unknown, will return null. - * - * @return string - */ - public function getExtension(): string - { - return \Config\Mimes::guessExtensionFromType($this->getType()); - } - - //-------------------------------------------------------------------- - /** * Returns the original file extension, based on the file name that * was uploaded. This is NOT a trusted source. @@ -364,51 +308,6 @@ public function getClientExtension(): string //-------------------------------------------------------------------- - /** - * Retrieve the media type of the file. SHOULD not use information from - * the $_FILES array, but should use other methods to more accurately - * determine the type of file, like finfo, or mime_content_type(). - * - * @return string|null The media type we determined it to be. - */ - public function getType(): string - { - if (! is_null($this->mimeType)) - { - return $this->mimeType; - } - - if (function_exists('finfo_file')) - { - $finfo = finfo_open(FILEINFO_MIME_TYPE); - $this->mimeType = finfo_file($finfo, $this->path); - finfo_close($finfo); - } - else - { - $this->mimeType = mime_content_type($this->path); - } - - return $this->mimeType; - } - - //-------------------------------------------------------------------- - - /** - * Returns the mime type as provided by the client. - * This is NOT a trusted value. - * For a trusted version, use getMimeType() instead. - * - * @return string|null The media type sent by the client or null if none - * was provided. - */ - public function getClientType(): string - { - return $this->originalMimeType; - } - - //-------------------------------------------------------------------- - /** * Returns whether the file was uploaded successfully, based on whether * it was uploaded via HTTP and has no errors. @@ -422,47 +321,4 @@ public function isValid(): bool //-------------------------------------------------------------------- - /** - * Returns the destination path for the move operation where overwriting is not expected. - * - * First, it checks whether the delimiter is present in the filename, if it is, then it checks whether the - * last element is an integer as there may be cases that the delimiter may be present in the filename. - * For the all other cases, it appends an integer starting from zero before the file's extension. - * - * @param string $destination - * @param string $delimiter - * @param int $i - * - * @return string - */ - public function getDestination(string $destination, string $delimiter = '_', int $i = 0): string - { - while (file_exists($destination)) - { - $info = pathinfo($destination); - if (strpos($info['filename'], $delimiter) !== false) - { - $parts = explode($delimiter, $info['filename']); - if (is_numeric(end($parts))) - { - $i = end($parts); - array_pop($parts); - array_push($parts, ++$i); - $destination = $info['dirname'] . '/' . implode($delimiter, $parts) . '.' . $info['extension']; - } - else - { - $destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++$i . '.' . $info['extension']; - } - } - else - { - $destination = $info['dirname'] . '/' . $info['filename'] . $delimiter . ++$i . '.' . $info['extension']; - } - } - return $destination; - } - - //-------------------------------------------------------------------- - } diff --git a/system/HTTP/Files/UploadedFileInterface.php b/system/HTTP/Files/UploadedFileInterface.php index 34d1778005f0..1015f362fccf 100644 --- a/system/HTTP/Files/UploadedFileInterface.php +++ b/system/HTTP/Files/UploadedFileInterface.php @@ -104,24 +104,6 @@ public function hasMoved(): bool; //-------------------------------------------------------------------- - /** - * Retrieve the file size. - * - * Implementations SHOULD return the value stored in the "size" key of - * the file in the $_FILES array if available, as PHP calculates this based - * on the actual size transmitted. - * - * @param string $unit The unit to return: - * - b Bytes - * - kb Kilobytes - * - mb Megabytes - * - * @return int|null The file size in bytes or null if unknown. - */ - public function getSize(string $unit='b'): int; - - //-------------------------------------------------------------------- - /** * Retrieve the error associated with the uploaded file. * @@ -166,26 +148,6 @@ public function getTempName(): string; //-------------------------------------------------------------------- - /** - * Generates a random names based on a simple hash and the time, with - * the correct file extension attached. - * - * @return string - */ - public function getRandomName(): string; - - //-------------------------------------------------------------------- - - /** - * Attempts to determine the file extension based on the trusted - * getMimeType() method. If the mime type is unknown, will return null. - * - * @return string - */ - public function getExtension(): string; - - //-------------------------------------------------------------------- - /** * Returns the original file extension, based on the file name that * was uploaded. This is NOT a trusted source. @@ -197,18 +159,6 @@ public function getClientExtension(): string; //-------------------------------------------------------------------- - /** - * Retrieve the media type of the file. SHOULD not use information from - * the $_FILES array, but should use other methods to more accurately - * determine the type of file, like finfo, or mime_content_type(). - * - * @return string|null The media type sent by the client or null if none - * was provided. - */ - public function getType(): string; - - //-------------------------------------------------------------------- - /** * Returns the mime type as provided by the client. * This is NOT a trusted value. @@ -216,7 +166,7 @@ public function getType(): string; * * @return string|null */ - public function getClientType(): string; + public function getClientMimeType(): string; //-------------------------------------------------------------------- diff --git a/tests/system/Files/FileTest.php b/tests/system/Files/FileTest.php new file mode 100644 index 000000000000..60760b4a2032 --- /dev/null +++ b/tests/system/Files/FileTest.php @@ -0,0 +1,38 @@ +assertEquals('file', $file->getType()); + } + + public function testGetSizeReturnsKB() + { + $file = new File(BASEPATH.'Common.php'); + + $size = number_format(filesize(BASEPATH.'Common.php') / 1024, 3); + + $this->assertEquals($size, $file->getSize('kb')); + } + + public function testGetSizeReturnsMB() + { + $file = new File(BASEPATH.'Common.php'); + + $size = number_format(filesize(BASEPATH.'Common.php') / 1024 / 1024, 3); + + $this->assertEquals($size, $file->getSize('mb')); + } + + /** + * @expectedException \CodeIgniter\Files\FileNotFoundException + */ + public function testThrowsExceptionIfNotAFile() + { + $file = new File(BASEPATH.'Commoner.php'); + } + +} diff --git a/tests/system/HTTP/Files/FileCollectionTest.php b/tests/system/HTTP/Files/FileCollectionTest.php index be33803e24f9..fa132f8b6d34 100644 --- a/tests/system/HTTP/Files/FileCollectionTest.php +++ b/tests/system/HTTP/Files/FileCollectionTest.php @@ -69,7 +69,7 @@ public function testAllReturnsValidMultipleFilesSameName() $this->assertEquals('fileA.txt', $file->getName()); $this->assertEquals('/tmp/fileA.txt', $file->getTempName()); $this->assertEquals('txt', $file->getClientExtension()); - $this->assertEquals('text/plain', $file->getClientType()); + $this->assertEquals('text/plain', $file->getClientMimeType()); $this->assertEquals(124, $file->getSize()); } @@ -106,7 +106,7 @@ public function testAllReturnsValidMultipleFilesDifferentName() $this->assertEquals('fileA.txt', $file->getName()); $this->assertEquals('/tmp/fileA.txt', $file->getTempName()); $this->assertEquals('txt', $file->getClientExtension()); - $this->assertEquals('text/plain', $file->getClientType()); + $this->assertEquals('text/plain', $file->getClientMimeType()); $this->assertEquals(124, $file->getSize()); $file = array_pop($files); @@ -115,7 +115,7 @@ public function testAllReturnsValidMultipleFilesDifferentName() $this->assertEquals('fileB.txt', $file->getName()); $this->assertEquals('/tmp/fileB.txt', $file->getTempName()); $this->assertEquals('txt', $file->getClientExtension()); - $this->assertEquals('text/csv', $file->getClientType()); + $this->assertEquals('text/csv', $file->getClientMimeType()); $this->assertEquals(248, $file->getSize()); } @@ -165,7 +165,7 @@ public function testAllReturnsValidSingleFileNestedName() $this->assertEquals('fileA.txt', $file->getName()); $this->assertEquals('/tmp/fileA.txt', $file->getTempName()); $this->assertEquals('txt', $file->getClientExtension()); - $this->assertEquals('text/plain', $file->getClientType()); + $this->assertEquals('text/plain', $file->getClientMimeType()); $this->assertEquals(124, $file->getSize()); } @@ -337,7 +337,7 @@ public function testFileReturnValidMultipleFiles() { $this->assertEquals('fileA.txt', $file_1->getName()); $this->assertEquals('/tmp/fileA.txt', $file_1->getTempName()); $this->assertEquals('txt', $file_1->getClientExtension()); - $this->assertEquals('text/plain', $file_1->getClientType()); + $this->assertEquals('text/plain', $file_1->getClientMimeType()); $this->assertEquals(124, $file_1->getSize()); $file_2 = $collection->getFile('userfile.1'); @@ -345,7 +345,7 @@ public function testFileReturnValidMultipleFiles() { $this->assertEquals('fileB.txt', $file_2->getName()); $this->assertEquals('/tmp/fileB.txt', $file_2->getTempName()); $this->assertEquals('txt', $file_2->getClientExtension()); - $this->assertEquals('text/csv', $file_2->getClientType()); + $this->assertEquals('text/csv', $file_2->getClientMimeType()); $this->assertEquals(248, $file_2->getSize()); } @@ -389,7 +389,7 @@ public function testFileWithMultipleFilesNestedName() { $this->assertEquals('fileA.txt', $file_1->getName()); $this->assertEquals('/tmp/fileA.txt', $file_1->getTempName()); $this->assertEquals('txt', $file_1->getClientExtension()); - $this->assertEquals('text/plain', $file_1->getClientType()); + $this->assertEquals('text/plain', $file_1->getClientMimeType()); $this->assertEquals(125, $file_1->getSize()); $file_2 = $collection->getFile('my-form.details.avatars.1'); @@ -397,7 +397,7 @@ public function testFileWithMultipleFilesNestedName() { $this->assertEquals('fileB.txt', $file_2->getName()); $this->assertEquals('/tmp/fileB.txt', $file_2->getTempName()); $this->assertEquals('txt', $file_2->getClientExtension()); - $this->assertEquals('text/plain', $file_2->getClientType()); + $this->assertEquals('text/plain', $file_2->getClientMimeType()); $this->assertEquals(243, $file_2->getSize()); } diff --git a/user_guide_src/source/libraries/files.rst b/user_guide_src/source/libraries/files.rst new file mode 100644 index 000000000000..ea7d6ddd8d4a --- /dev/null +++ b/user_guide_src/source/libraries/files.rst @@ -0,0 +1,97 @@ +****************** +Working with Files +****************** + +CodeIgniter provides a File class that wraps the `SplFileInfo `_ class +and provides some additional convenience methods. This class is the base class for :doc:`uploaded files ` +and :doc:`images `. + +.. contents:: Page Contents + :local: + +Getting a File instance +======================= + +You create a new File instance by passing in the path to the file in the constructor. The file does not need to exist. + +:: + + $file = new \CodeIgniter\Files\File($path); + +Taking Advantage of Spl +======================= + +Once you have an instance, you have the full power of the SplFileInfo class at the ready, including:: + + // Get the file's basename + echo $file->getBasename(); + // Get last modified time + echo $file->getMTime(); + // Get the true realpath + echo $file->getRealpath(); + // Get the file permissions + echo $file->getPerms(); + + // Write CSV rows to it. + if ($file->isWritable()) + { + $csv = $file->openFile('w'); + + foreach ($rows as $row) + { + $csv->fputcsv($row); + } + } + +New Features +============ + +In addition to all of the methods in the SplFileInfo class, you get some new tools. + +**getRandomName()** + +You can generate a cryptographically secure random filename, with the current timestamp prepended, with the ``getRandomName()`` +method. This is especially useful to rename files when moving it so that the filename is unguessable:: + + // Generates something like: 1465965676_385e33f741.jpg + $newName = $file->getRandomName(); + +**getSize()** + +Returns the size of the uploaded file in bytes. You can pass in either 'kb' or 'mb' as the first parameter to get +the results in kilobytes or megabytes, respectively:: + + $bytes = $file->getSize(); // 256901 + $kilobytes = $file->getSize('kb'); // 250.880 + $megabytes = $file->getSize('mb'); // 0.245 + +**getMimeType()** + +Retrieve the media type (mime type) of the file. Uses methods that are considered as secure as possible when determining +the type of file:: + + $type = $file->getMimeType(); + + echo $type; // image/png + +**guessExtension()** + +Attempts to determine the file extension based on the trusted ``getMimeType()`` method. If the mime type is unknown, +will return null. This is often a more trusted source than simply using the extension provided by the filename. Uses +the values in **application/Config/Mimes.php** to determine extension:: + + // Returns 'jpg' (WITHOUT the period) + $ext = $file->guessExtension(); + +Moving Files +------------ + +Each file can be moved to its new location with the aptly named ``move()`` method. This takes the directory to move +the file to as the first parameter:: + + $file->move(WRITEPATH.'uploads'); + +By default, the original filename was used. You can specify a new filename by passing it as the second parameter:: + + $newName = $file->getRandomName(); + $file->move(WRITEPATH.'uploads', $newName); diff --git a/user_guide_src/source/libraries/index.rst b/user_guide_src/source/libraries/index.rst index 146671e02d87..3574314ef1a1 100644 --- a/user_guide_src/source/libraries/index.rst +++ b/user_guide_src/source/libraries/index.rst @@ -21,6 +21,7 @@ Library Reference sessions throttler typography + files uploaded_files uri validation diff --git a/user_guide_src/source/libraries/uploaded_files.rst b/user_guide_src/source/libraries/uploaded_files.rst index d2f0d4e77879..d8b8e0699986 100644 --- a/user_guide_src/source/libraries/uploaded_files.rst +++ b/user_guide_src/source/libraries/uploaded_files.rst @@ -3,7 +3,7 @@ Working with Uploaded Files *************************** CodeIgniter makes working with files uploaded through a form much simpler and more secure than using PHP's ``$_FILES`` -array directly. +array directly. This extends the :doc:`File class ` and thus gains all of the features of that class. .. note:: This is not the same as the File Uploading class in previous versions of CodeIgniter. This provides a raw interface to the uploaded files with a few small features. An uploader class will be present in the final release. @@ -125,7 +125,7 @@ In controller:: Working With the File ===================== -Once you've gotten the UploadedFile instance, you can retrieve information about the file in safe ways, as well as +Once you've retrieved the UploadedFile instance, you can retrieve information about the file in safe ways, as well as move the file to a new location. Verify A File @@ -167,27 +167,10 @@ To get the name of the temp file that was created during the upload, you can use $tempfile = $file->getTempName(); -**getRandomName()** - -You can generate a cryptographically secure random filename, with the current timestamp prepended, with the ``getRandomName()`` -method. This is especially useful to rename files when moving it so that the filename is unguessable:: - - // Generates something like: 1465965676_385e33f741.jpg - $newName = $file->getRandomName(); - Other File Info --------------- -**getExtension()** - -Attempts to determine the file extension based on the trusted ``getType()`` method instead of the value listed -in ``$_FILES``. If the mime type is unknown, will return null. Uses the values in **application/Config/Mimes.php** -to determine extension:: - - // Returns 'jpg' (WITHOUT the period) - $ext = $file->getExtension(); - **getClientExtension()** Returns the original file extension, based on the file name that was uploaded. This is NOT a trusted source. For a @@ -195,15 +178,6 @@ trusted version, use ``getExtension()`` instead:: $ext = $file->getClientExtension(); -**getType()** - -Retrieve the media type (mime type) of the file. Does not use information from the $_FILES array, but uses other methods to more -accurately determine the type of file, like ``finfo``, or ``mime_content_type``:: - - $type = $file->getType(); - - echo $type; // image/png - **getClientType()** Returns the mime type (mime type) of the file as provided by the client. This is NOT a trusted value. For a trusted @@ -213,15 +187,6 @@ version, use ``getType()`` instead:: echo $type; // image/png -**getSize()** - -Returns the size of the uploaded file in bytes. You can pass in either 'kb' or 'mb' as the first parameter to get -the results in kilobytes or megabytes, respectively:: - - $bytes = $file->getSize(); // 256901 - $kilobytes = $file->getSize('kb'); // 250.880 - $megabytes = $file->getSize('mb'); // 0.245 - Moving Files ------------ From 0b4740b119c14386c708bfa2dbaba978d5f2cdcd Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 18 Apr 2017 23:57:04 -0500 Subject: [PATCH 0619/1807] Actually using host in serve script. Fixes #469 --- serve.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serve.php b/serve.php index 5bcac3121f46..dc829edca18d 100644 --- a/serve.php +++ b/serve.php @@ -33,4 +33,4 @@ * base path to the public folder, and to use the rewrite file * to ensure our environment is set and it simulates basic mod_rewrite. */ -passthru($php.' -S localhost:'.$port.' -t public/ rewrite.php'); +passthru("{$php} -S {$host}:{$port} -t public/ rewrite.php"); From 48d990b77559982db2369183ec445b8b51345627 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Tue, 18 Apr 2017 23:57:47 -0500 Subject: [PATCH 0620/1807] Fixing typo on files path. --- system/Files/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Files/File.php b/system/Files/File.php index cdc104da942b..1a9b1184a0e5 100644 --- a/system/Files/File.php +++ b/system/Files/File.php @@ -138,7 +138,7 @@ public function move(string $targetPath, string $name = null, bool $overwrite = ? $this->getDestination($targetPath.$name) : $targetPath.$name; - if (! @rename($this->path, $destination)) + if (! @rename($this->getPath(), $destination)) { $error = error_get_last(); throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', $this->getBasename(),$targetPath, strip_tags($error['message']))); From ec49bf9c2dd17ff0578707e3f1be4a489e829c4b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 19 Apr 2017 21:41:55 -0500 Subject: [PATCH 0621/1807] Ensure router works with leading slash --- tests/system/Router/RouterTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index fb3172bcf779..a257772d6ebb 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -28,6 +28,7 @@ public function setUp() 'closure/(:num)/(:alpha)' => function ($num, $str) { return $num.'-'.$str; }, '{locale}/pages' => 'App\Pages::list_all', 'Admin/Admins' => 'App\Admin\Admins::list_all', + '/some/slash' => 'App\Slash::index', ]; $this->collection->map($routes); @@ -205,4 +206,16 @@ public function testRouteResource() //-------------------------------------------------------------------- + public function testRouteWithLeadingSlash() + { + $router = new Router($this->collection); + + $router->handle('some/slash'); + + $this->assertEquals('\App\Slash', $router->controllerName()); + $this->assertEquals('index', $router->methodName()); + } + + //-------------------------------------------------------------------- + } From e5a15b7616956983e95b72e124a48bc88d5c8b62 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 19 Apr 2017 22:05:40 -0500 Subject: [PATCH 0622/1807] More routing tests. --- system/Common.php | 141 +++++++++++++++------------ tests/system/CommonFunctionsTest.php | 50 ++++++++++ 2 files changed, 128 insertions(+), 63 deletions(-) diff --git a/system/Common.php b/system/Common.php index 321a7dce1fd9..c934b1d1a361 100644 --- a/system/Common.php +++ b/system/Common.php @@ -26,12 +26,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * - * @package CodeIgniter - * @author CodeIgniter Dev Team + * @package CodeIgniter + * @author CodeIgniter Dev Team * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 3.0.0 + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 * @filesource */ @@ -85,7 +85,7 @@ function cache(string $key = null) //-------------------------------------------------------------------- -if ( ! function_exists('view')) +if (! function_exists('view')) { /** * Grabs the current RendererInterface-compatible class @@ -117,7 +117,7 @@ function view(string $name, array $data = [], array $options = []) } return $renderer->setData($data, 'raw') - ->render($name, $options, $saveData); + ->render($name, $options, $saveData); } } @@ -138,13 +138,14 @@ function view(string $name, array $data = [], array $options = []) */ function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null) { - return Services::viewcell()->render($library, $params, $ttl, $cacheName); + return Services::viewcell() + ->render($library, $params, $ttl, $cacheName); } } //-------------------------------------------------------------------- -if ( ! function_exists('env')) +if (! function_exists('env')) { /** * Allows user to retrieve values from the environment @@ -190,7 +191,7 @@ function env(string $key, $default = null) //-------------------------------------------------------------------- -if ( ! function_exists('esc')) +if (! function_exists('esc')) { /** * Performs simple auto-escaping of data for security reasons. @@ -208,7 +209,7 @@ function env(string $key, $default = null) * * @return $data */ - function esc($data, $context = 'html', $encoding=null) + function esc($data, $context = 'html', $encoding = null) { if (is_array($data)) { @@ -230,7 +231,7 @@ function esc($data, $context = 'html', $encoding=null) return $data; } - if ( ! in_array($context, ['html', 'js', 'css', 'url', 'attr'])) + if (! in_array($context, ['html', 'js', 'css', 'url', 'attr'])) { throw new \InvalidArgumentException('Invalid escape context provided.'); } @@ -247,7 +248,7 @@ function esc($data, $context = 'html', $encoding=null) // @todo Optimize this to only load a single instance during page request. $escaper = new \Zend\Escaper\Escaper($encoding); - $data = $escaper->$method($data); + $data = $escaper->$method($data); } return $data; @@ -349,7 +350,7 @@ function service(string $name, ...$params) * Allow cleaner access to a Service. * Always returns a new instance of the class. * - * @param string $name + * @param string $name * @param array|null $params */ function single_service(string $name, ...$params) @@ -374,17 +375,17 @@ function single_service(string $name, ...$params) * * @return string */ - function lang(string $line, array $args=[], string $locale=null) + function lang(string $line, array $args = [], string $locale = null) { - return Services::language($locale)->getLine($line, $args); + return Services::language($locale) + ->getLine($line, $args); } } //-------------------------------------------------------------------- - -if ( ! function_exists('log_message')) +if (! function_exists('log_message')) { /** * A convenience/compatibility method for logging events through @@ -400,9 +401,9 @@ function lang(string $line, array $args=[], string $locale=null) * - info * - debug * - * @param string $level - * @param string $message - * @param array|null $context + * @param string $level + * @param string $message + * @param array|null $context * * @return mixed */ @@ -414,17 +415,18 @@ function log_message(string $level, string $message, array $context = []) if (ENVIRONMENT == 'testing') { $logger = new \CodeIgniter\Log\TestLogger(new \Config\Logger()); + return $logger->log($level, $message, $context); } return Services::logger(true) - ->log($level, $message, $context); + ->log($level, $message, $context); } } //-------------------------------------------------------------------- -if ( ! function_exists('is_cli')) +if (! function_exists('is_cli')) { /** @@ -442,7 +444,7 @@ function is_cli() //-------------------------------------------------------------------- -if ( ! function_exists('route_to')) +if (! function_exists('route_to')) { /** * Given a controller/method string and any params, @@ -467,7 +469,7 @@ function route_to(string $method, ...$params): string //-------------------------------------------------------------------- -if ( ! function_exists('remove_invisible_characters')) +if (! function_exists('remove_invisible_characters')) { /** * Remove Invisible Characters @@ -477,11 +479,12 @@ function route_to(string $method, ...$params): string * * @param string * @param bool + * * @return string */ - function remove_invisible_characters($str, $url_encoded = TRUE) + function remove_invisible_characters($str, $url_encoded = true) { - $non_displayables = array(); + $non_displayables = []; // every control character except newline (dec 10), // carriage return (dec 13) and horizontal tab (dec 09) @@ -496,8 +499,7 @@ function remove_invisible_characters($str, $url_encoded = TRUE) do { $str = preg_replace($non_displayables, '', $str, -1, $count); - } - while ($count); + } while ($count); return $str; } @@ -608,7 +610,7 @@ function csrf_hash() */ function csrf_field() { - return ''; + return ''; } } @@ -624,15 +626,21 @@ function csrf_field() * * @see https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security * - * @param int $duration How long should the SSL header be set for? (in seconds) - * Defaults to 1 year. - * @param RequestInterface $request + * @param int $duration How long should the SSL header be set for? (in seconds) + * Defaults to 1 year. + * @param RequestInterface $request * @param ResponseInterface $response */ function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) { - if (is_null($request)) $request = Services::request(null, true); - if (is_null($response)) $response = Services::response(null, true); + if (is_null($request)) + { + $request = Services::request(null, true); + } + if (is_null($response)) + { + $response = Services::response(null, true); + } if ($request->isSecure()) { @@ -643,19 +651,20 @@ function force_https(int $duration = 31536000, RequestInterface $request = null, // the session ID for safety sake. if (class_exists('Session', false)) { - Services::session(null, true)->regenerate(); + Services::session(null, true) + ->regenerate(); } $uri = $request->uri; $uri->setScheme('https'); $uri = \CodeIgniter\HTTP\URI::createURIString( - $uri->getScheme(), - $uri->getAuthority(true), - $uri->getPath(), // Absolute URIs should use a "/" for an empty path - $uri->getQuery(), - $uri->getFragment() - ); + $uri->getScheme(), + $uri->getAuthority(true), + $uri->getPath(), // Absolute URIs should use a "/" for an empty path + $uri->getQuery(), + $uri->getFragment() + ); // Set an HSTS header $response->setHeader('Strict-Transport-Security', 'max-age='.$duration); @@ -677,8 +686,8 @@ function force_https(int $duration = 31536000, RequestInterface $request = null, * * If more control is needed, you must use $response->redirect explicitly. * - * @param string $uri - * @param $params + * @param string $uri + * @param $params */ function redirect(string $uri, ...$params) { @@ -690,7 +699,7 @@ function redirect(string $uri, ...$params) $uri = $route; } - $response->redirect($uri); + return $response->redirect($uri); } } @@ -718,19 +727,19 @@ function redirect_with_input(string $uri, ...$params) } $input = [ - 'get' => $_GET ?? [], - 'post' => $_POST ?? [] + 'get' => $_GET ?? [], + 'post' => $_POST ?? [], ]; - $session->setFlashdata('_ci_old_input', $input); + $session->setFlashdata('_ci_old_input', $input); - redirect($uri, ...$params); + redirect($uri, ...$params); } } //-------------------------------------------------------------------- -if ( ! function_exists('stringify_attributes')) +if (! function_exists('stringify_attributes')) { /** * Stringify attributes for use in HTML tags. @@ -740,9 +749,10 @@ function redirect_with_input(string $uri, ...$params) * * @param mixed string, array, object * @param bool + * * @return string */ - function stringify_attributes($attributes, $js = FALSE) : string + function stringify_attributes($attributes, $js = false): string { $atts = ''; @@ -756,7 +766,7 @@ function stringify_attributes($attributes, $js = FALSE) : string return ' '.$attributes; } - $attributes = (array) $attributes; + $attributes = (array)$attributes; foreach ($attributes as $key => $val) { @@ -771,7 +781,7 @@ function stringify_attributes($attributes, $js = FALSE) : string //-------------------------------------------------------------------- -if ( ! function_exists('is_really_writable')) +if (! function_exists('is_really_writable')) { /** * Tests for file writability @@ -781,7 +791,9 @@ function stringify_attributes($attributes, $js = FALSE) : string * on Unix servers if safe_mode is on. * * @link https://bugs.php.net/bug.php?id=54709 + * * @param string + * * @return bool */ function is_really_writable($file) @@ -798,36 +810,39 @@ function is_really_writable($file) if (is_dir($file)) { $file = rtrim($file, '/').'/'.md5(mt_rand()); - if (($fp = @fopen($file, 'ab')) === FALSE) + if (($fp = @fopen($file, 'ab')) === false) { - return FALSE; + return false; } fclose($fp); @chmod($file, 0777); @unlink($file); - return TRUE; + + return true; } - elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE) + elseif (! is_file($file) OR ($fp = @fopen($file, 'ab')) === false) { - return FALSE; + return false; } fclose($fp); - return TRUE; + + return true; } } //-------------------------------------------------------------------- -if ( ! function_exists('slash_item')) +if (! function_exists('slash_item')) { //Unlike CI3, this function is placed here because //it's not a config, or part of a config. /** * Fetch a config file item with slash appended (if not empty) * - * @param string $item Config item name + * @param string $item Config item name + * * @return string|null The configuration item or NULL if * the item doesn't exist */ @@ -836,12 +851,12 @@ function slash_item($item) $config = new \Config\App(); $configItem = $config->{$item}; - if ( ! isset($configItem) || empty(trim($configItem))) + if (! isset($configItem) || empty(trim($configItem))) { return $configItem; } - return rtrim($configItem, '/') . '/'; + return rtrim($configItem, '/').'/'; } } //-------------------------------------------------------------------- diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php index 46494adefc7a..e1e5cf79323b 100644 --- a/tests/system/CommonFunctionsTest.php +++ b/tests/system/CommonFunctionsTest.php @@ -64,4 +64,54 @@ public function testEnvGetsFromENV() $this->assertEquals('bar', env('foo', 'baz')); } + + public function testRedirectReturnsNamedRouteFirst() + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $response = $this->createMock(\CodeIgniter\HTTP\Response::class); + $routes = new \CodeIgniter\Router\RouteCollection(); + \CodeIgniter\Services::injectMock('response', $response); + \CodeIgniter\Services::injectMock('routes', $routes); + + $routes->add('home/base', 'Controller::index', ['as' => 'base']); + + $response->method('redirect') + ->will($this->returnArgument(0)); + + $this->assertEquals('/home/base', redirect('base')); + } + + public function testRedirectReverseRouting() + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $response = $this->createMock(\CodeIgniter\HTTP\Response::class); + $routes = new \CodeIgniter\Router\RouteCollection(); + \CodeIgniter\Services::injectMock('response', $response); + \CodeIgniter\Services::injectMock('routes', $routes); + + $routes->add('home/base', 'Controller::index', ['as' => 'base']); + + $response->method('redirect') + ->will($this->returnArgument(0)); + + $this->assertEquals('/home/base', redirect('Controller::index')); + } + + public function testRedirectNormalRouting() + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $response = $this->createMock(\CodeIgniter\HTTP\Response::class); + $routes = new \CodeIgniter\Router\RouteCollection(); + \CodeIgniter\Services::injectMock('response', $response); + \CodeIgniter\Services::injectMock('routes', $routes); + + $response->method('redirect') + ->will($this->returnArgument(0)); + + $this->assertEquals('/home/base', redirect('/home/base')); + $this->assertEquals('home/base', redirect('home/base')); + } } From 8dd50f491783174cbcd3a4ff4e795802c9b66a9a Mon Sep 17 00:00:00 2001 From: Antony Garand Date: Thu, 20 Apr 2017 20:57:00 -0400 Subject: [PATCH 0623/1807] Fixed unit test to be OS agnostic --- tests/system/Helpers/FilesystemHelperTest.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/system/Helpers/FilesystemHelperTest.php b/tests/system/Helpers/FilesystemHelperTest.php index 46b377ea3109..4048c0d0ee1e 100644 --- a/tests/system/Helpers/FilesystemHelperTest.php +++ b/tests/system/Helpers/FilesystemHelperTest.php @@ -29,15 +29,15 @@ public function testDirectoryMapDefaults() ]; $expected = [ - 'foo/' => [ + 'foo' . DIRECTORY_SEPARATOR => [ 'bar', 'baz' ], - 'boo/' => [ + 'boo' . DIRECTORY_SEPARATOR => [ 'far', 'faz' ], - 'AnEmptyFolder/' => [], + 'AnEmptyFolder' . DIRECTORY_SEPARATOR => [], 'simpleFile' ]; @@ -69,15 +69,15 @@ public function testDirectoryMapShowsHiddenFiles() ]; $expected = [ - 'foo/' => [ + 'foo' . DIRECTORY_SEPARATOR => [ 'bar', 'baz' ], - 'boo/' => [ + 'boo' . DIRECTORY_SEPARATOR => [ 'far', 'faz' ], - 'AnEmptyFolder/' => [], + 'AnEmptyFolder' . DIRECTORY_SEPARATOR => [], 'simpleFile', '.hidden' ]; @@ -109,9 +109,9 @@ public function testDirectoryMapLimitsRecursion() ]; $expected = [ - 'foo/', - 'boo/', - 'AnEmptyFolder/', + 'foo' . DIRECTORY_SEPARATOR, + 'boo' . DIRECTORY_SEPARATOR, + 'AnEmptyFolder' . DIRECTORY_SEPARATOR, 'simpleFile', '.hidden' ]; @@ -285,10 +285,10 @@ public function testGetFilenamesWithSource() // Not sure the directory names should actually show up // here but this matches v3.x results. $expected = [ - '/foo', - '/boo', - '/AnEmptyFolder', - '/simpleFile' + DIRECTORY_SEPARATOR . 'foo', + DIRECTORY_SEPARATOR . 'boo', + DIRECTORY_SEPARATOR . 'AnEmptyFolder', + DIRECTORY_SEPARATOR . 'simpleFile' ]; $vfs = vfsStream::setup('root', null, $structure); From d6392c974a96efaa1ab4842c093644bdd131ab4b Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Mon, 24 Apr 2017 21:26:09 -0500 Subject: [PATCH 0624/1807] Begun flushing out the new Image classes. --- application/Controllers/Checks.php | 5 + system/Config/AutoloadConfig.php | 138 +++++++------- system/Images/Exceptions.php | 3 + system/Images/Handlers/BaseHandler.php | 129 +++++++++++++ system/Images/Image.php | 237 ++++++++++++++++++++++++ system/Images/ImageHandlerInterface.php | 122 ++++++++++++ 6 files changed, 565 insertions(+), 69 deletions(-) create mode 100644 system/Images/Exceptions.php create mode 100644 system/Images/Handlers/BaseHandler.php create mode 100644 system/Images/Image.php create mode 100644 system/Images/ImageHandlerInterface.php diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php index 5135805a8e9a..4078a6d76da3 100644 --- a/application/Controllers/Checks.php +++ b/application/Controllers/Checks.php @@ -146,5 +146,10 @@ public function catch() echo $body; } + public function redirect() + { + redirect('/checks/model'); + } + } diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index 89633948eb59..90ad76e44b02 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -111,76 +111,76 @@ public function __construct() * ]; */ $this->classmap = [ - 'CodeIgniter\CodeIgniter' => BASEPATH.'CodeIgniter.php', - 'CodeIgniter\CLI\CLI' => BASEPATH.'CLI/CLI.php', - 'CodeIgniter\Loader' => BASEPATH.'Loader.php', - 'CodeIgniter\Cache\CacheFactory' => BASEPATH.'Cache/CacheFactory.php', - 'CodeIgniter\Cache\CacheInterface' => BASEPATH.'Cache/CacheInterface.php', - 'CodeIgniter\Cache\Handlers\DummyHandler' => BASEPATH.'Cache/Handlers/DummyHandler.php', - 'CodeIgniter\Cache\Handlers\FileHandler' => BASEPATH.'Cache/Handlers/FileHandler.php', - 'CodeIgniter\Cache\Handlers\MemcachedHandler' => BASEPATH.'Cache/Handlers/MemcachedHandler.php', - 'CodeIgniter\Cache\Handlers\PredisHandler' => BASEPATH.'Cache/Handlers/PredisHandler.php', - 'CodeIgniter\Cache\Handlers\RedisHandler' => BASEPATH.'Cache/Handlers/RedisHandler.php', - 'CodeIgniter\Cache\Handlers\WincacheHandler' => BASEPATH.'Cache/Handlers/WincacheHandler.php', - 'CodeIgniter\Controller' => BASEPATH.'Controller.php', - 'CodeIgniter\Config\AutoloadConfig' => BASEPATH.'Config/Autoload.php', - 'CodeIgniter\Config\BaseConfig' => BASEPATH.'Config/BaseConfig.php', - 'CodeIgniter\Config\Database' => BASEPATH.'Config/Database.php', - 'CodeIgniter\Config\Database\Connection' => BASEPATH.'Config/Database/Connection.php', - 'CodeIgniter\Config\Database\Connection\MySQLi' => BASEPATH.'Config/Database/Connection/MySQLi.php', - 'CodeIgniter\Config\DotEnv' => BASEPATH.'Config/DotEnv.php', - 'CodeIgniter\Database\BaseBuilder' => BASEPATH.'Database/BaseBuilder.php', - 'CodeIgniter\Database\BaseConnection' => BASEPATH.'Database/BaseConnection.php', - 'CodeIgniter\Database\BaseResult' => BASEPATH.'Database/BaseResult.php', - 'CodeIgniter\Database\Config' => BASEPATH.'Database/Config.php', - 'CodeIgniter\Database\ConnectionInterface' => BASEPATH.'Database/ConnectionInterface.php', - 'CodeIgniter\Database\Database' => BASEPATH.'Database/Database.php', - 'CodeIgniter\Database\Query' => BASEPATH.'Database/Query.php', - 'CodeIgniter\Database\QueryInterface' => BASEPATH.'Database/QueryInterface.php', - 'CodeIgniter\Database\ResultInterface' => BASEPATH.'Database/ResultInterface.php', - 'CodeIgniter\Database\Migration' => BASEPATH.'Database/Migration.php', - 'CodeIgniter\Database\MigrationRunner' => BASEPATH.'Database/MigrationRunner.php', - 'CodeIgniter\Debug\Exceptions' => BASEPATH.'Debug/Exceptions.php', - 'CodeIgniter\Debug\Timer' => BASEPATH.'Debug/Timer.php', - 'CodeIgniter\Debug\Iterator' => BASEPATH.'Debug/Iterator.php', - 'CodeIgniter\Events\Events' => BASEPATH.'Events/Events.php', - 'CodeIgniter\HTTP\CLIRequest' => BASEPATH.'HTTP/CLIRequest.php', - 'CodeIgniter\HTTP\ContentSecurityPolicy' => BASEPATH.'HTTP/ContentSecurityPolicy.php', - 'CodeIgniter\HTTP\CURLRequest' => BASEPATH.'HTTP/CURLRequest.php', - 'CodeIgniter\HTTP\IncomingRequest' => BASEPATH.'HTTP/IncomingRequest.php', - 'CodeIgniter\HTTP\Message' => BASEPATH.'HTTP/Message.php', - 'CodeIgniter\HTTP\Negotiate' => BASEPATH.'HTTP/Negotiate.php', - 'CodeIgniter\HTTP\Request' => BASEPATH.'HTTP/Request.php', - 'CodeIgniter\HTTP\RequestInterface' => BASEPATH.'HTTP/RequestInterface.php', - 'CodeIgniter\HTTP\Response' => BASEPATH.'HTTP/Response.php', - 'CodeIgniter\HTTP\ResponseInterface' => BASEPATH.'HTTP/ResponseInterface.php', - 'CodeIgniter\HTTP\URI' => BASEPATH.'HTTP/URI.php', - 'CodeIgniter\Log\Logger' => BASEPATH.'Log/Logger.php', - 'Psr\Log\LoggerAwareInterface' => BASEPATH.'ThirdParty/PSR/Log/LoggerAwareInterface.php', - 'Psr\Log\LoggerAwareTrait' => BASEPATH.'ThirdParty/PSR/Log/LoggerAwareTrait.php', - 'Psr\Log\LoggerInterface' => BASEPATH.'ThirdParty/PSR/Log/LoggerInterface.php', - 'Psr\Log\LogLevel' => BASEPATH.'ThirdParty/PSR/Log/LogLevel.php', - 'CodeIgniter\Log\Handlers\BaseHandler' => BASEPATH.'Log/Handlers/BaseHandler.php', - 'CodeIgniter\Log\Handlers\ChromeLoggerHandler' => BASEPATH.'Log/Handlers/ChromeLoggerHandler.php', - 'CodeIgniter\Log\Handlers\FileHandler' => BASEPATH.'Log/Handlers/FileHandler.php', - 'CodeIgniter\Log\Handlers\HandlerInterface' => BASEPATH.'Log/Handlers/HandlerInterface.php', - 'CodeIgniter\Router\RouteCollection' => BASEPATH.'Router/RouteCollection.php', - 'CodeIgniter\Router\RouteCollectionInterface' => BASEPATH.'Router/RouteCollectionInterface.php', - 'CodeIgniter\Router\Router' => BASEPATH.'Router/Router.php', - 'CodeIgniter\Router\RouterInterface' => BASEPATH.'Router/RouterInterface.php', - 'CodeIgniter\Security\Security' => BASEPATH.'Security/Security.php', - 'CodeIgniter\Session\Session' => BASEPATH.'Session/Session.php', - 'CodeIgniter\Session\SessionInterface' => BASEPATH.'Session/SessionInterface.php', - 'CodeIgniter\Session\Handlers\BaseHandler' => BASEPATH.'Session/Handlers/BaseHandler.php', - 'CodeIgniter\Session\Handlers\FileHandler' => BASEPATH.'Session/Handlers/FileHandler.php', - 'CodeIgniter\Session\Handlers\MemcachedHandler' => BASEPATH.'Session/Handlers/MemcachedHandler.php', - 'CodeIgniter\Session\Handlers\RedisHandler' => BASEPATH.'Session/Handlers/RedisHandler.php', - 'CodeIgniter\View\RendererInterface' => BASEPATH.'View/RendererInterface.php', - 'CodeIgniter\View\View' => BASEPATH.'View/View.php', - 'CodeIgniter\View\Parser' => BASEPATH.'View/Parser.php', - 'CodeIgniter\View\Cell' => BASEPATH.'View/Cell.php', +// 'CodeIgniter\CodeIgniter' => BASEPATH.'CodeIgniter.php', +// 'CodeIgniter\CLI\CLI' => BASEPATH.'CLI/CLI.php', +// 'CodeIgniter\Loader' => BASEPATH.'Loader.php', +// 'CodeIgniter\Cache\CacheFactory' => BASEPATH.'Cache/CacheFactory.php', +// 'CodeIgniter\Cache\CacheInterface' => BASEPATH.'Cache/CacheInterface.php', +// 'CodeIgniter\Cache\Handlers\DummyHandler' => BASEPATH.'Cache/Handlers/DummyHandler.php', +// 'CodeIgniter\Cache\Handlers\FileHandler' => BASEPATH.'Cache/Handlers/FileHandler.php', +// 'CodeIgniter\Cache\Handlers\MemcachedHandler' => BASEPATH.'Cache/Handlers/MemcachedHandler.php', +// 'CodeIgniter\Cache\Handlers\PredisHandler' => BASEPATH.'Cache/Handlers/PredisHandler.php', +// 'CodeIgniter\Cache\Handlers\RedisHandler' => BASEPATH.'Cache/Handlers/RedisHandler.php', +// 'CodeIgniter\Cache\Handlers\WincacheHandler' => BASEPATH.'Cache/Handlers/WincacheHandler.php', +// 'CodeIgniter\Controller' => BASEPATH.'Controller.php', +// 'CodeIgniter\Config\AutoloadConfig' => BASEPATH.'Config/Autoload.php', +// 'CodeIgniter\Config\BaseConfig' => BASEPATH.'Config/BaseConfig.php', +// 'CodeIgniter\Config\Database' => BASEPATH.'Config/Database.php', +// 'CodeIgniter\Config\Database\Connection' => BASEPATH.'Config/Database/Connection.php', +// 'CodeIgniter\Config\Database\Connection\MySQLi' => BASEPATH.'Config/Database/Connection/MySQLi.php', +// 'CodeIgniter\Config\DotEnv' => BASEPATH.'Config/DotEnv.php', +// 'CodeIgniter\Database\BaseBuilder' => BASEPATH.'Database/BaseBuilder.php', +// 'CodeIgniter\Database\BaseConnection' => BASEPATH.'Database/BaseConnection.php', +// 'CodeIgniter\Database\BaseResult' => BASEPATH.'Database/BaseResult.php', +// 'CodeIgniter\Database\Config' => BASEPATH.'Database/Config.php', +// 'CodeIgniter\Database\ConnectionInterface' => BASEPATH.'Database/ConnectionInterface.php', +// 'CodeIgniter\Database\Database' => BASEPATH.'Database/Database.php', +// 'CodeIgniter\Database\Query' => BASEPATH.'Database/Query.php', +// 'CodeIgniter\Database\QueryInterface' => BASEPATH.'Database/QueryInterface.php', +// 'CodeIgniter\Database\ResultInterface' => BASEPATH.'Database/ResultInterface.php', +// 'CodeIgniter\Database\Migration' => BASEPATH.'Database/Migration.php', +// 'CodeIgniter\Database\MigrationRunner' => BASEPATH.'Database/MigrationRunner.php', +// 'CodeIgniter\Debug\Exceptions' => BASEPATH.'Debug/Exceptions.php', +// 'CodeIgniter\Debug\Timer' => BASEPATH.'Debug/Timer.php', +// 'CodeIgniter\Debug\Iterator' => BASEPATH.'Debug/Iterator.php', +// 'CodeIgniter\Events\Events' => BASEPATH.'Events/Events.php', +// 'CodeIgniter\HTTP\CLIRequest' => BASEPATH.'HTTP/CLIRequest.php', +// 'CodeIgniter\HTTP\ContentSecurityPolicy' => BASEPATH.'HTTP/ContentSecurityPolicy.php', +// 'CodeIgniter\HTTP\CURLRequest' => BASEPATH.'HTTP/CURLRequest.php', +// 'CodeIgniter\HTTP\IncomingRequest' => BASEPATH.'HTTP/IncomingRequest.php', +// 'CodeIgniter\HTTP\Message' => BASEPATH.'HTTP/Message.php', +// 'CodeIgniter\HTTP\Negotiate' => BASEPATH.'HTTP/Negotiate.php', +// 'CodeIgniter\HTTP\Request' => BASEPATH.'HTTP/Request.php', +// 'CodeIgniter\HTTP\RequestInterface' => BASEPATH.'HTTP/RequestInterface.php', +// 'CodeIgniter\HTTP\Response' => BASEPATH.'HTTP/Response.php', +// 'CodeIgniter\HTTP\ResponseInterface' => BASEPATH.'HTTP/ResponseInterface.php', +// 'CodeIgniter\HTTP\URI' => BASEPATH.'HTTP/URI.php', +// 'CodeIgniter\Log\Logger' => BASEPATH.'Log/Logger.php', +// 'Psr\Log\LoggerAwareInterface' => BASEPATH.'ThirdParty/PSR/Log/LoggerAwareInterface.php', +// 'Psr\Log\LoggerAwareTrait' => BASEPATH.'ThirdParty/PSR/Log/LoggerAwareTrait.php', +// 'Psr\Log\LoggerInterface' => BASEPATH.'ThirdParty/PSR/Log/LoggerInterface.php', +// 'Psr\Log\LogLevel' => BASEPATH.'ThirdParty/PSR/Log/LogLevel.php', +// 'CodeIgniter\Log\Handlers\BaseHandler' => BASEPATH.'Log/Handlers/BaseHandler.php', +// 'CodeIgniter\Log\Handlers\ChromeLoggerHandler' => BASEPATH.'Log/Handlers/ChromeLoggerHandler.php', +// 'CodeIgniter\Log\Handlers\FileHandler' => BASEPATH.'Log/Handlers/FileHandler.php', +// 'CodeIgniter\Log\Handlers\HandlerInterface' => BASEPATH.'Log/Handlers/HandlerInterface.php', +// 'CodeIgniter\Router\RouteCollection' => BASEPATH.'Router/RouteCollection.php', +// 'CodeIgniter\Router\RouteCollectionInterface' => BASEPATH.'Router/RouteCollectionInterface.php', +// 'CodeIgniter\Router\Router' => BASEPATH.'Router/Router.php', +// 'CodeIgniter\Router\RouterInterface' => BASEPATH.'Router/RouterInterface.php', +// 'CodeIgniter\Security\Security' => BASEPATH.'Security/Security.php', +// 'CodeIgniter\Session\Session' => BASEPATH.'Session/Session.php', +// 'CodeIgniter\Session\SessionInterface' => BASEPATH.'Session/SessionInterface.php', +// 'CodeIgniter\Session\Handlers\BaseHandler' => BASEPATH.'Session/Handlers/BaseHandler.php', +// 'CodeIgniter\Session\Handlers\FileHandler' => BASEPATH.'Session/Handlers/FileHandler.php', +// 'CodeIgniter\Session\Handlers\MemcachedHandler' => BASEPATH.'Session/Handlers/MemcachedHandler.php', +// 'CodeIgniter\Session\Handlers\RedisHandler' => BASEPATH.'Session/Handlers/RedisHandler.php', +// 'CodeIgniter\View\RendererInterface' => BASEPATH.'View/RendererInterface.php', +// 'CodeIgniter\View\View' => BASEPATH.'View/View.php', +// 'CodeIgniter\View\Parser' => BASEPATH.'View/Parser.php', +// 'CodeIgniter\View\Cell' => BASEPATH.'View/Cell.php', 'Zend\Escaper\Escaper' => BASEPATH.'ThirdParty/ZendEscaper/Escaper.php', - 'CodeIgniter\Log\TestLogger' => BASEPATH.'../tests/_support/Log/TestLogger.php', +// 'CodeIgniter\Log\TestLogger' => BASEPATH.'../tests/_support/Log/TestLogger.php', 'CIDatabaseTestCase' => BASEPATH.'../tests/_support/CIDatabaseTestCase.php' ]; } diff --git a/system/Images/Exceptions.php b/system/Images/Exceptions.php new file mode 100644 index 000000000000..3aec8a1b1b17 --- /dev/null +++ b/system/Images/Exceptions.php @@ -0,0 +1,3 @@ +handler = $handler; + + return $this; + } + + //-------------------------------------------------------------------- + + public function save(): bool + { + + } + + //-------------------------------------------------------------------- + + public function copy(string $target, int $perms=0644) + { + + } + + //-------------------------------------------------------------------- + + /** + * Returns a boolean flag whether any errors were encountered. + * + * @return bool + */ + public function hasErrors(): bool + { + return ! empty($this->errors); + } + + //-------------------------------------------------------------------- + + /** + * Returns all error messages that were encountered during processing. + * + * @return array + */ + public function getErrors(): array + { + return $this->errors ?? []; + } + + //-------------------------------------------------------------------- + + /** + * Resize the image + * + * @param int $width + * @param int $height + * @param bool $maintainRatio If true, will get the closest match possible while keeping aspect ratio true. + * + * @return $this + */ + public function resize(int $width, int $height, bool $maintainRatio = false) + { + try { + $this->handler->resize($width, $height, $maintainRatio); + } + catch (ImageException $e) + { + $this->errors[] = $e->getMessage(); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Crops the image to the desired height and width. If one of the height/width values + * is not provided, that value will be set the appropriate value based on offsets and + * image dimensions. + * + * @param int|null $width + * @param int|null $height + * @param int|null $x X-axis coord to start cropping from the left of image + * @param int|null $y Y-axis coord to start cropping from the top of image + * + * @return $this + */ + public function crop(int $width = null, int $height = null, int $x = null, int $y = null) + { + try { + $this->handler->crop($width, $height, $x, $y); + } + catch (ImageException $e) + { + $this->errors[] = $e->getMessage(); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Rotates the image on the current canvas. + * + * @param float $angle + * + * @return mixed + */ + public function rotate(float $angle) + { + try { + $this->handler->rotate($angle); + } + catch (ImageException $e) + { + $this->errors[] = $e->getMessage(); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * @return $this + */ + public function watermark() + { + + } + + //-------------------------------------------------------------------- + + /** + * Reads the EXIF information from the image and modifies the orientation + * so that displays correctly in the browser. + * + * @return $this + */ + public function reorient(): bool + { + try { + $this->handler->reorient(); + } + catch (ImageException $e) + { + $this->errors[] = $e->getMessage(); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Retrieve the EXIF information from the image, if possible. Returns + * an array of the information, or null if nothing can be found. + * + * @param string|null $key If specified, will only return this piece of EXIF data. + * + * @return mixed + */ + public function getEXIF(string $key = null) + { + try { + $this->handler->getEXIF($key); + } + catch (ImageException $e) + { + $this->errors[] = $e->getMessage(); + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Combine cropping and resizing into a single command. + * + * Supported positions: + * - top-left + * - top + * - top-right + * - left + * - center + * - right + * - bottom-left + * - bottom + * - bottom-right + * + * @param int $width + * @param int $height + * @param string $position + * + * @return $this + */ + public function fit(int $width, int $height, string $position) + { + try { + $this->handler->fit($width, $height, $position); + } + catch (ImageException $e) + { + $this->errors[] = $e->getMessage(); + } + + return $this; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Images/ImageHandlerInterface.php b/system/Images/ImageHandlerInterface.php new file mode 100644 index 000000000000..6e9c96666eeb --- /dev/null +++ b/system/Images/ImageHandlerInterface.php @@ -0,0 +1,122 @@ + Date: Mon, 24 Apr 2017 22:01:26 -0500 Subject: [PATCH 0625/1807] Validation rules should run with arrays as field inputs. Fixes #472 --- system/Validation/Rules.php | 21 ++++++++++++++++----- tests/_support/Models/SimpleEntity.php | 2 ++ tests/system/Validation/ValidationTest.php | 3 ++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php index b6b5e369a3fa..6d872a794f8f 100644 --- a/system/Validation/Rules.php +++ b/system/Validation/Rules.php @@ -284,8 +284,8 @@ public function required($str=null): bool * required_with[password] * * @param $str - * @param string $fields - * @param array $data + * @param string $fields List of fields that we should check if present + * @param array $data Complete list of fields from the form * * @return bool */ @@ -305,11 +305,22 @@ public function required_with($str=null, string $fields, array $data): bool // Still here? Then we fail this test if // any of the fields are present in $data - $requiredFields = array_intersect($fields, $data); + // as $fields is the lis + $requiredFields = []; + + foreach ($fields as $field) + { + if (array_key_exists($field, $data)) + { + $requiredFields[] = $field; + } + } - $requiredFields = array_filter($requiredFields, function($item) + // Remove any keys with empty values since, that means they + // weren't truly there, as far as this is concerned. + $requiredFields = array_filter($requiredFields, function($item) use($data) { - return ! empty($item); + return ! empty($data[$item]); }); return ! (bool)count($requiredFields); diff --git a/tests/_support/Models/SimpleEntity.php b/tests/_support/Models/SimpleEntity.php index 88d6c148e864..1c5cefe05cbf 100644 --- a/tests/_support/Models/SimpleEntity.php +++ b/tests/_support/Models/SimpleEntity.php @@ -14,6 +14,8 @@ class SimpleEntity protected $name; protected $description; + protected $datamap = []; + public function __get($key) { if (isset($this->$key)) diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 4bc228439ca0..d492151f9eb4 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -1479,6 +1479,7 @@ public function testRequiredWith($field, $check, $expected = false) 'foo' => 'bar', 'bar' => 'something', 'baz' => null, + 'ar' => [] // Was running into issues with array values ]; $this->validation->setRules([ @@ -1497,7 +1498,7 @@ public function requiredWithProvider() ['foo', 'bar', true], ['nope', 'baz', true], [null, null, true], - [null, 'foo', true], + [null, 'foo', false], ['foo', null, true] ]; } From 54813e86990a6bea33e329cee95d6d17a58c92f9 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 26 Apr 2017 09:17:56 -0500 Subject: [PATCH 0626/1807] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d4937f483c3c..5118438b9fd6 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ while still keeping as many of the things intact that has made people love the f More information about the plans for version 4 can be found in [the announcement](http://forum.codeigniter.com/thread-62615.html) on the forums. +### Documentation + +The current documentation can be found [here](https://bcit-ci.github.io/CodeIgniter4/). As with the rest of the framwork, it is currently a work in progress, and will see changes over time to structure, explanations, etc. + ## Important Change with index.php index.php is no longer in the root of the project! It has been moved inside the *public* folder, From 0ade928fc128dba1e9ab014297947d69e4b5c047 Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Wed, 26 Apr 2017 21:22:25 +0200 Subject: [PATCH 0627/1807] Update debugging.rst Typo fix (missed ') --- user_guide_src/source/general/debugging.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/general/debugging.rst b/user_guide_src/source/general/debugging.rst index 5fc34775fecb..e21ec31d3b76 100644 --- a/user_guide_src/source/general/debugging.rst +++ b/user_guide_src/source/general/debugging.rst @@ -193,11 +193,11 @@ outer array's key is the name of the section on the Vars tab:: $data = [ 'section 1' => [ - 'foo' => 'bar, + 'foo' => 'bar', 'bar' => 'baz' ], 'section 2' => [ - 'foo' => 'bar, + 'foo' => 'bar', 'bar' => 'baz' ] ]; From 5807054fc8dc61da731b65b1e6ea641a7f1e3301 Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Wed, 26 Apr 2017 21:59:15 +0200 Subject: [PATCH 0628/1807] Update logging.rst Missed dot --- user_guide_src/source/general/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/general/logging.rst b/user_guide_src/source/general/logging.rst index 35121c486f48..c0510f3e5498 100644 --- a/user_guide_src/source/general/logging.rst +++ b/user_guide_src/source/general/logging.rst @@ -13,7 +13,7 @@ The second parameter is the message itself:: There are eight different log levels, matching to the `RFC 5424 `_ levels, and they are as follows: -* debug - Detailed debug information +* debug - Detailed debug information. * info - Interesting events in your application, like a user logging in, logging SQL queries, etc. * notice - Normal, but significant events in your application. * warning - Exceptional occurrences that are not errors, like the user of deprecated APIs, poor use of an API, or other undesirable things that are not necessarily wrong. From 00c2e057cec74f4a4fc2e48cb2f96acd9a16c56c Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Wed, 26 Apr 2017 22:06:33 +0200 Subject: [PATCH 0629/1807] Update controllers.rst Deleted extra colon (:) --- user_guide_src/source/general/controllers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst index 86e64e336769..b4f0d2a0d1e8 100644 --- a/user_guide_src/source/general/controllers.rst +++ b/user_guide_src/source/general/controllers.rst @@ -323,7 +323,7 @@ Validating $_POST data The controller also provides a convenience method to make validating $_POST data a little simpler, ``validate()`` that takes the current Request as the first instance, an array of rules to test against as the second parameter, and, optionally, an array of custom error messages to display if the items don't pass. The :doc:`Validation Library docs ` -has details on the format of the rules and messages arrays, as well as available rules.:: +has details on the format of the rules and messages arrays, as well as available rules.: public function updateUser(int $userID) { From a39d8370ce095edfc3cb6def7e2aa64b9efd3026 Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Wed, 26 Apr 2017 22:13:30 +0200 Subject: [PATCH 0630/1807] Update filters.rst Extra dot deleted --- user_guide_src/source/general/filters.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/general/filters.rst b/user_guide_src/source/general/filters.rst index d02f62e91463..6a2bbd24badd 100644 --- a/user_guide_src/source/general/filters.rst +++ b/user_guide_src/source/general/filters.rst @@ -8,7 +8,7 @@ modify the Request, while after filters can act on and even modify the Response, and power. Some common examples of tasks that might be performed with filters are: * Performing CSRF protection on the incoming requests -* Restricting areas of your site based upon their Role. +* Restricting areas of your site based upon their Role * Perform rate limiting on certain endpoints * Display a "Down for Maintenance" page * Perform automatic content negotiation From 3881aee00ea0906dc894da328d8b8ad777cebbb6 Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Wed, 26 Apr 2017 22:16:47 +0200 Subject: [PATCH 0631/1807] Update cli.rst --- user_guide_src/source/general/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/general/cli.rst b/user_guide_src/source/general/cli.rst index 030c79de650f..4984483d52da 100644 --- a/user_guide_src/source/general/cli.rst +++ b/user_guide_src/source/general/cli.rst @@ -21,7 +21,7 @@ Why run via the command-line? There are many reasons for running CodeIgniter from the command-line, but they are not always obvious. -- Run your cron-jobs without needing to use *wget* or *curl* +- Run your cron-jobs without needing to use *wget* or *curl*. - Make your cron-jobs inaccessible from being loaded in the URL by checking the return value of :php:func:`is_cli()`. - Make interactive "tasks" that can do things like set permissions, From 4205b8c148762a2839395988360843270e2fdb4c Mon Sep 17 00:00:00 2001 From: Filis Futsarov Date: Wed, 26 Apr 2017 22:28:55 +0200 Subject: [PATCH 0632/1807] Update controllers.rst --- user_guide_src/source/general/controllers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst index b4f0d2a0d1e8..86e64e336769 100644 --- a/user_guide_src/source/general/controllers.rst +++ b/user_guide_src/source/general/controllers.rst @@ -323,7 +323,7 @@ Validating $_POST data The controller also provides a convenience method to make validating $_POST data a little simpler, ``validate()`` that takes the current Request as the first instance, an array of rules to test against as the second parameter, and, optionally, an array of custom error messages to display if the items don't pass. The :doc:`Validation Library docs ` -has details on the format of the rules and messages arrays, as well as available rules.: +has details on the format of the rules and messages arrays, as well as available rules.:: public function updateUser(int $userID) { From 27ebfa3cc26696f588f101bb96c45186e7175919 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Wed, 26 Apr 2017 23:53:28 -0500 Subject: [PATCH 0633/1807] Refactoring things a bit. GD driver moving along nicely. --- application/Config/Images.php | 33 +++ application/Controllers/Checks.php | 9 + system/Config/Services.php | 27 ++ system/Images/Handlers/BaseHandler.php | 245 +++++++++++++++-- system/Images/Handlers/GDHandler.php | 333 ++++++++++++++++++++++++ system/Images/Image.php | 247 +++++------------- system/Images/ImageHandlerInterface.php | 26 -- system/Language/en/Images.php | 21 ++ tests/_support/ci-logo.png | Bin 0 -> 7760 bytes tests/system/Images/GDHandlerTest.php | 19 ++ tests/system/Images/ImageTest.php | 56 ++++ 11 files changed, 790 insertions(+), 226 deletions(-) create mode 100644 application/Config/Images.php create mode 100644 system/Images/Handlers/GDHandler.php create mode 100644 system/Language/en/Images.php create mode 100644 tests/_support/ci-logo.png create mode 100644 tests/system/Images/GDHandlerTest.php create mode 100644 tests/system/Images/ImageTest.php diff --git a/application/Config/Images.php b/application/Config/Images.php new file mode 100644 index 000000000000..995c92203070 --- /dev/null +++ b/application/Config/Images.php @@ -0,0 +1,33 @@ + \CodeIgniter\Images\Handlers\GDHandler::class, + 'imagick' => \CodeIgniter\Images\Handlers\ImageMagickHandler::class, + 'gm' => \CodeIgniter\Images\Handlers\GraphicsMagickHandler::class, + 'pbm' => \CodeIgniter\Images\Handlers\NetPBMHandler::class, + ]; +} diff --git a/application/Controllers/Checks.php b/application/Controllers/Checks.php index 4078a6d76da3..3b03a10ac9ea 100644 --- a/application/Controllers/Checks.php +++ b/application/Controllers/Checks.php @@ -151,5 +151,14 @@ public function redirect() redirect('/checks/model'); } + public function image() + { + $images = Services::image('gd') + ->withFile("/Users/kilishan/Documents/BobHeader.jpg") + ->crop(200, 75, 20, 0, false) + ->save('/Users/kilishan/temp.jpg', 100); + + ddd($images); + } } diff --git a/system/Config/Services.php b/system/Config/Services.php index 43024dd79f07..ccc9e317e3d3 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -208,6 +208,33 @@ public static function filters($config = null, $getShared = true) //-------------------------------------------------------------------- + /** + * Acts as a factory for ImageHandler classes and returns an instance + * of the handler. Used like Services::image()->withFile($path)->rotate(90)->save(); + */ + public static function image(string $handler=null, $config = null, $getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('image', $handler, $config); + } + + if (empty($config)) + { + $config = new \Config\Images(); + } + + $handler = is_null($handler) + ? $config->defaultHandler + : $handler; + + $class = $config->handlers[$handler]; + + return new $class($config); + } + + //-------------------------------------------------------------------- + /** * The Iterator class provides a simple way of looping over a function * and timing the results and memory usage. Used when debugging and diff --git a/system/Images/Handlers/BaseHandler.php b/system/Images/Handlers/BaseHandler.php index e038d1d76380..078274ddd3bf 100644 --- a/system/Images/Handlers/BaseHandler.php +++ b/system/Images/Handlers/BaseHandler.php @@ -1,17 +1,130 @@ -config = $config; + } + + //-------------------------------------------------------------------- + + /** + * Sets another image for this handler to work on. + * Keeps us from needing to continually instantiate the handler. + * + * @param string $path + * + * @return $this + */ + public function withFile(string $path) + { + $this->image = new Image($path, true); + + $this->image->getProperties(); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the image instance. + * + * @return \CodeIgniter\Images\Image + */ + public function getFile() + { + return $this->image; + } + + //-------------------------------------------------------------------- + + /** + * Returns a boolean flag whether any errors were encountered. + * + * @return bool + */ + public function hasErrors(): bool + { + return ! empty($this->errors); + } + + //-------------------------------------------------------------------- + + /** + * Returns all error messages that were encountered during processing. + * + * @return array + */ + public function getErrors(): array + { + return $this->errors ?? []; + } + + //-------------------------------------------------------------------- + /** * Resize the image * * @param int $width * @param int $height - * @param bool $maintainRation If true, will get the closest match possible while keeping aspect ratio true. + * @param bool $maintainRation If true, will get the closest match possible while keeping aspect ratio true. + * + * @return bool|\CodeIgniter\Images\Handlers\GDHandler */ - public abstract function resize(int $width, int $height, bool $maintainRatio = false); + public function resize(int $width, int $height, bool $maintainRatio = false, string $masterDim = 'auto') + { + // If the target width/height match the source, then we have nothing to do here. + if ($this->image->origWidth === $width && $this->image->origHeight === $height) + { + return true; + } + + $this->width = $width; + $this->height = $height; + + if ($maintainRatio) + { + $this->masterDim = $masterDim; + $this->reproportion(); + } + + return $this->process('resize'); + } //-------------------------------------------------------------------- @@ -22,12 +135,28 @@ public abstract function resize(int $width, int $height, bool $maintainRatio = f * * @param int|null $width * @param int|null $height - * @param int|null $x X-axis coord to start cropping from the left of image - * @param int|null $y Y-axis coord to start cropping from the top of image + * @param int|null $x X-axis coord to start cropping from the left of image + * @param int|null $y Y-axis coord to start cropping from the top of image + * @param bool $maintainRatio + * @param string $masterDim * * @return mixed */ - public abstract function crop(int $width = null, int $height = null, int $x = null, int $y = null); + public function crop(int $width = null, int $height = null, int $x = null, int $y = null, bool $maintainRatio = false, string $masterDim = 'auto') + { + $this->width = $width; + $this->height = $height; + $this->xAxis = $x; + $this->yAxis = $y; + + if ($maintainRatio) + { + $this->masterDim = $masterDim; + $this->reproportion(); + } + + return $this->process('crop'); + } //-------------------------------------------------------------------- @@ -63,7 +192,7 @@ public abstract function reorient(): bool; * Retrieve the EXIF information from the image, if possible. Returns * an array of the information, or null if nothing can be found. * - * @param string|null $key If specified, will only return this piece of EXIF data. + * @param string|null $key If specified, will only return this piece of EXIF data. * * @return mixed */ @@ -96,34 +225,112 @@ public abstract function fit(int $width, int $height, string $position): bool; //-------------------------------------------------------------------- /** - * Allows any option to be easily set. We don't mind doing it - * this way here, since the Handler is not something the user - * will be directly interfacing with. + * Get the version of the image library in use. + * + * @return string + */ + public abstract function getVersion(); + + //-------------------------------------------------------------------- + + /** + * Saves any changes that have been made to file. + * + * Example: + * $image->resize(100, 200, true) + * ->save($target); * - * @param string $key - * @param null $value + * @param string $target + * @param int $quality * * @return mixed */ - public function setOption(string $key, $value = null) - { + public abstract function save(string $target = null, int $quality = 90); + + //-------------------------------------------------------------------- + + /** + * Does the driver-specific processing of the image. + * + * @param string $action + * + * @return mixed + */ + protected abstract function process(string $action); + //-------------------------------------------------------------------- + + /** + * Provide access to the Image class' methods if they don't exist + * on the handler itself. + * + * @param string $name + * @param array $args + */ + public function __call(string $name, array $args = []) + { + if (method_exists($this->image, $name)) + { + return $this->image->$name(...$args); + } } //-------------------------------------------------------------------- /** - * Allows multiple options to be set at once through an array of - * key value pairs, where the keys must be Handler properties. + * Re-proportion Image Width/Height * - * @param array $options + * When creating thumbs, the desired width/height + * can end up warping the image due to an incorrect + * ratio between the full-sized image and the thumb. * - * @return mixed + * This function lets us re-proportion the width/height + * if users choose to maintain the aspect ratio when resizing. + * + * @return void */ - public function setOptions(array $options) + protected function reproportion() { + if (($this->width === 0 && $this->height === 0) OR $this->image->origWidth === 0 OR $this->image->origHeight === 0 + OR (! ctype_digit((string)$this->width) && ! ctype_digit((string)$this->height)) + OR ! ctype_digit((string)$this->image->origWidth) OR ! ctype_digit((string)$this->image->origHeight) + ) + { + return; + } + + // Sanitize + $this->width = (int)$this->width; + $this->height = (int)$this->height; + if ($this->masterDim !== 'width' && $this->masterDim !== 'height') + { + if ($this->width > 0 && $this->height > 0) + { + $this->masterDim = ((($this->image->origHeight / $this->image->origWidth)-($this->height / $this->width)) < 0) + ? 'width' : 'height'; + } + else + { + $this->masterDim = ($this->height === 0) ? 'width' : 'height'; + } + } + elseif (($this->masterDim === 'width' && $this->width === 0) OR ($this->masterDim === 'height' && $this->height === 0) + ) + { + return; + } + + if ($this->masterDim === 'width') + { + $this->height = (int)ceil($this->width*$this->image->origHeight/$this->image->origWidth); + } + else + { + $this->width = (int)ceil($this->image->origWidth*$this->height/$this->image->origHeight); + } } //-------------------------------------------------------------------- + } diff --git a/system/Images/Handlers/GDHandler.php b/system/Images/Handlers/GDHandler.php new file mode 100644 index 000000000000..4eec8d0fc195 --- /dev/null +++ b/system/Images/Handlers/GDHandler.php @@ -0,0 +1,333 @@ +image->origWidth; + $origHeight = $this->image->origHeight; + + if ($action == 'crop') + { + // Reassign the source width/height if cropping + $origWidth = $this->width; + $origHeight = $this->height; + } + + // Create the image handle + if (! ($src = $this->createImage())) + { + return false; + } + + if (function_exists('imagecreatetruecolor')) + { + $create = 'imagecreatetruecolor'; + $copy = 'imagecopyresampled'; + } + else + { + $create = 'imagecreate'; + $copy = 'imagecopyresized'; + } + + $dest = $create($this->width, $this->height); + + if ($this->image->imageType === IMAGETYPE_PNG) // png we can actually preserve transparency + { + imagealphablending($dest, false); + imagesavealpha($dest, true); + } + + $copy($dest, $src, 0, 0, $this->xAxis, $this->yAxis, $this->width, $this->height, $origWidth, $origHeight); + + $this->resource = $dest; + imagedestroy($src); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Saves any changes that have been made to file. If no new filename is + * provided, the existing image is overwritten, otherwise a copy of the + * file is made at $target. + * + * Example: + * $image->resize(100, 200, true) + * ->save(); + * + * @param string|null $target + * @param int $quality + * + * @return bool + */ + public function save(string $target = null, int $quality=90) + { + $target = empty($target) + ? $this->image->getPathname() + : $target; + + switch ($this->image->imageType) + { + case IMAGETYPE_GIF: + if (! function_exists('imagegif')) + { + $this->errors[] = lang('images.unsupportedImagecreate'); + $this->errors[] = lang('images.gifNotSupported'); + + return false; + } + + if (! @imagegif($this->resource, $target)) + { + $this->errors[] = lang('images.saveFailed'); + + return false; + } + break; + case IMAGETYPE_JPEG: + if (! function_exists('imagejpeg')) + { + $this->errors[] = lang('images.unsupportedImagecreate'); + $this->errors[] = lang('images.jpgNotSupported'); + + return false; + } + + if (! @imagejpeg($this->resource, $target, $quality)) + { + $this->errors[] = lang('images.saveFailed'); + + return false; + } + break; + case IMAGETYPE_PNG: + if (! function_exists('imagepng')) + { + $this->errors[] = lang('images.unsupportedImagecreate'); + $this->errors[] = lang('images.pngNotSupported'); + + return false; + } + + if (! @imagepng($this->resource, $target)) + { + $this->errors[] = lang('images.saveFailed'); + + return false; + } + break; + default: + $this->errors[] = lang('images.unsupportedImagecreate'); + + return false; + break; + } + + imagedestroy($this->resource); + + chmod($target, $this->filePermissions); + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Create Image Resource + * + * This simply creates an image resource handle + * based on the type of image being processed + * + * @param string + * @param string + * + * @return resource|bool + */ + protected function createImage($path = '', $imageType = '') + { + if ($this->resource !== null) + { + return clone($this->resource); + } + + if ($path === '') + { + $path = $this->image->getPathname(); + } + + if ($imageType === '') + { + $imageType = $this->image->imageType; + } + + switch ($imageType) + { + case IMAGETYPE_GIF: + if (! function_exists('imagecreatefromgif')) + { + $this->errors[] = lang('images.gifNotSupported'); + + return false; + } + + return imagecreatefromgif($path); + case IMAGETYPE_JPEG: + if (! function_exists('imagecreatefromjpeg')) + { + $this->errors[] = lang('images.jpgNotSupported'); + + return false; + } + + return imagecreatefromjpeg($path); + case IMAGETYPE_PNG: + if (! function_exists('imagecreatefrompng')) + { + $this->errors[] = lang('images.pngNotSupported'); + + return false; + } + + return imagecreatefrompng($path); + default: + $this->errors[] = lang('images.unsupportedImagecreate'); + + return false; + } + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Images/Image.php b/system/Images/Image.php index 014917cf88a3..0592940a719f 100644 --- a/system/Images/Image.php +++ b/system/Images/Image.php @@ -2,234 +2,119 @@ use CodeIgniter\Files\File; -class Image extends File { - - /** - * @var \CodeIgniter\Images\ImageHandlerInterface - */ - protected $handler; - +class Image extends File +{ /** - * Stores any errors that were encountered. + * The original image width in pixels. * - * @var array + * @var */ - protected $errors = []; - - //-------------------------------------------------------------------- - + public $origWidth; /** - * Sets the Image processign handler that should be used. - * - * @param \CodeIgniter\Images\ImageHandlerInterface $handler + * The original image height in pixels. * - * @return $this + * @var */ - public function setHandler(ImageHandlerInterface $handler) - { - $this->handler = $handler; - - return $this; - } - - //-------------------------------------------------------------------- - - public function save(): bool - { - - } - - //-------------------------------------------------------------------- - - public function copy(string $target, int $perms=0644) - { - - } - - //-------------------------------------------------------------------- - + public $origHeight; /** - * Returns a boolean flag whether any errors were encountered. + * The image type constant. + * @see http://php.net/manual/en/image.constants.php * - * @return bool + * @var int */ - public function hasErrors(): bool - { - return ! empty($this->errors); - } - - //-------------------------------------------------------------------- + public $imageType; /** - * Returns all error messages that were encountered during processing. + * attributes string with size info: + * 'height="100" width="200"' * - * @return array + * @var string */ - public function getErrors(): array - { - return $this->errors ?? []; - } - - //-------------------------------------------------------------------- + public $sizeStr; /** - * Resize the image - * - * @param int $width - * @param int $height - * @param bool $maintainRatio If true, will get the closest match possible while keeping aspect ratio true. + * The image's mime type, i.e. image/jpeg * - * @return $this + * @var string */ - public function resize(int $width, int $height, bool $maintainRatio = false) - { - try { - $this->handler->resize($width, $height, $maintainRatio); - } - catch (ImageException $e) - { - $this->errors[] = $e->getMessage(); - } - - return $this; - } - - //-------------------------------------------------------------------- + public $mime; /** - * Crops the image to the desired height and width. If one of the height/width values - * is not provided, that value will be set the appropriate value based on offsets and - * image dimensions. + * Makes a copy of itself to the new location. If no filename is provided + * it will use the existing filename. * - * @param int|null $width - * @param int|null $height - * @param int|null $x X-axis coord to start cropping from the left of image - * @param int|null $y Y-axis coord to start cropping from the top of image + * @param string $targetPath The directory to store the file in + * @param string|null $targetName The new name of the copied file. + * @param int $perms File permissions to be applied after copy. * - * @return $this + * @return bool */ - public function crop(int $width = null, int $height = null, int $x = null, int $y = null) + public function copy(string $targetPath, string $targetName = null, int $perms = 0644) { - try { - $this->handler->crop($width, $height, $x, $y); - } - catch (ImageException $e) - { - $this->errors[] = $e->getMessage(); - } + $targetPath = rtrim($targetPath, '/ ').'/'; - return $this; - } - - //-------------------------------------------------------------------- + $targetName = is_null($targetName) + ? $this->getFilename() + : $targetName; - /** - * Rotates the image on the current canvas. - * - * @param float $angle - * - * @return mixed - */ - public function rotate(float $angle) - { - try { - $this->handler->rotate($angle); - } - catch (ImageException $e) + if (empty($targetName)) { - $this->errors[] = $e->getMessage(); + throw new ImageException('Invalid file name.'); } - return $this; - } - - //-------------------------------------------------------------------- - - /** - * @return $this - */ - public function watermark() - { - - } - - //-------------------------------------------------------------------- - - /** - * Reads the EXIF information from the image and modifies the orientation - * so that displays correctly in the browser. - * - * @return $this - */ - public function reorient(): bool - { - try { - $this->handler->reorient(); - } - catch (ImageException $e) + if (! is_dir($targetPath)) { - $this->errors[] = $e->getMessage(); + mkdir($targetName, 0755, true); } - return $this; - } - - //-------------------------------------------------------------------- - - /** - * Retrieve the EXIF information from the image, if possible. Returns - * an array of the information, or null if nothing can be found. - * - * @param string|null $key If specified, will only return this piece of EXIF data. - * - * @return mixed - */ - public function getEXIF(string $key = null) - { - try { - $this->handler->getEXIF($key); - } - catch (ImageException $e) + if (! copy($this->getPathname(),"{$targetPath}{$targetName}")) { - $this->errors[] = $e->getMessage(); + throw new ImageException('Unable to copy image to new destination.'); } - return $this; + chmod("{$targetPath}/{$targetName}", $perms); + + return true; } //-------------------------------------------------------------------- /** - * Combine cropping and resizing into a single command. + * Get image properties * - * Supported positions: - * - top-left - * - top - * - top-right - * - left - * - center - * - right - * - bottom-left - * - bottom - * - bottom-right + * A helper function that gets info about the file * - * @param int $width - * @param int $height - * @param string $position + * @param string + * @param bool * - * @return $this + * @return mixed */ - public function fit(int $width, int $height, string $position) + public function getProperties($return = false) { - try { - $this->handler->fit($width, $height, $position); - } - catch (ImageException $e) + $path = $this->getPathname(); + + $vals = getimagesize($path); + $types = [1 => 'gif', 2 => 'jpeg', 3 => 'png']; + $mime = (isset($types[$vals[2]])) ? 'image/'.$types[$vals[2]] : 'image/jpg'; + + if ($return === true) { - $this->errors[] = $e->getMessage(); + return [ + 'width' => $vals[0], + 'height' => $vals[1], + 'image_type' => $vals[2], + 'size_str' => $vals[3], + 'mime_type' => $mime, + ]; } - return $this; + $this->origWidth = $vals[0]; + $this->origHeight = $vals[1]; + $this->imageType = $vals[2]; + $this->sizeStr = $vals[3]; + $this->mime = $mime; + + return true; } //-------------------------------------------------------------------- diff --git a/system/Images/ImageHandlerInterface.php b/system/Images/ImageHandlerInterface.php index 6e9c96666eeb..e9a690a949fd 100644 --- a/system/Images/ImageHandlerInterface.php +++ b/system/Images/ImageHandlerInterface.php @@ -93,30 +93,4 @@ public function fit(int $width, int $height, string $position): bool; //-------------------------------------------------------------------- - /** - * Allows any option to be easily set. We don't mind doing it - * this way here, since the Handler is not something the user - * will be directly interfacing with. - * - * @param string $key - * @param null $value - * - * @return mixed - */ - public function setOption(string $key, $value = null); - - //-------------------------------------------------------------------- - - /** - * Allows multiple options to be set at once through an array of - * key value pairs, where the keys must be Handler properties. - * - * @param array $options - * - * @return mixed - */ - public function setOptions(array $options); - - //-------------------------------------------------------------------- - } diff --git a/system/Language/en/Images.php b/system/Language/en/Images.php new file mode 100644 index 000000000000..0b11ceb5672c --- /dev/null +++ b/system/Language/en/Images.php @@ -0,0 +1,21 @@ + 'You must specify a source image in your preferences.', + 'gdRequired' => 'The GD image library is required to use this feature.', + 'gdRequiredForProps' => 'Your server must support the GD image library in order to determine the image properties.', + 'gifNotSupported' => 'GIF images are often not supported due to licensing restrictions. You may have to use JPG or PNG images instead.', + 'jpgNotSupported' => 'JPG images are not supported.', + 'pngNotSupported' => 'PNG images are not supported.', + 'unsupportedImagecreate' => 'Your server does not support the GD function required to process this type of image.', + 'jpgOrPngRequired' => 'The image resize protocol specified in your preferences only works with JPEG or PNG image types.', + 'copyError' => 'An error was encountered while attempting to replace the file. Please make sure your file directory is writable.', + 'rotateUnsupported' => 'Image rotation does not appear to be supported by your server.', + 'libPathInvalid' => 'The path to your image library is not correct. Please set the correct path in your image preferences.', + 'imageProcessFailed' => 'Image processing failed. Please verify that your server supports the chosen protocol and that the path to your image library is correct.', + 'rotationAngleRequired' => 'An angle of rotation is required to rotate the image.', + 'invalidPath' => 'The path to the image is not correct.', + 'copyFailed' => 'The image copy routine failed.', + 'missingFont' => 'Unable to find a font to use.', + 'saveFailed' => 'Unable to save the image. Please make sure the image and file directory are writable.', +]; diff --git a/tests/_support/ci-logo.png b/tests/_support/ci-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a61f163022b70ae20a247aaebe0ef4aa0cb533f1 GIT binary patch literal 7760 zcmV-W9KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000wvNkl8|Bf z)m!d;@BTiY`vF3y>(+h0ckbCw28VLE2-pZb0d%?J*cYDXxkCsL6M;VhmjWZ*aSSG# z-{s&BKLq-KYk^Tf#vO?xTUa;?@PJO>8^Ab6I7hayFbY_qgzE!t0>%JYM>a>cF!1k6 zHV^0qt^^KpWb;q9tmBYxtjPQK2H<*No--Q!!;#HDHV*jjCg5|xM}Py|@i+vDbAjHx zt2DP>17-lt?izL1Xi(@;at*{p;Pb!{D(xKHH5w|W0ha^q?o1uof^vVvRm_9&vDFs~Gi2h{_Mn0%qdeAr4hy zJn$-m-5$l(O5kK*XwnbHxjur&A&85^FD3zVk?2C5J3e=fR)XWGJj-Jia7-Gcn^6goy1#RUxNEc=h9g1t zT5uW?H*I!gb0~!ms*+y|4g@}|#LYOeITXVs0TJXhU;-)63UOpBj?;k;1oXQm1Mes0 zF;hpj;^*?&fY0Z2U<9zAJ6?x;oI`)IWKe4(aHtPr+Ry}CN9o+PYS0QCn`XVu zk!>&*C@F$QMt|=RT!FI#a4^sj@b#I$o&ipvdMB~FMiYVz@I}HcJf48$#WlKXG~Jj5 zTpaRev?77|A&zY6#W>(!BD|^_)2O#OvPt^S&3eT}fy03oQW%(bWP?uw=SBE=?JC*o z9of=_vw$x}{F$RvXLDpr5hen+5Pj;;fk?=z&XFx07!LdxDP)ODPjuF33eW=FiuC7* z0}V)NpfeCsfUA*q^A3(|q2el}fuDmTTd0^DgXQUvhqRdAe+ArS$oK65*62T-WJ?(4 z06#G7#oFs6Tk>!oa1UmsVsAuR+oz6jNtSF=f$w5o&i@8v;99RETQYDq@E=HM$TX}2 z_M}l~OX4+pA#f+=CMdp!^Z;>^EdfNk9kVlG*U;an#z{7dt@>A=Sen4HVZKy$>37;VNCMfeMy4FwkU`OhjQ{S^e@`83*4eHB zZm?*-dw}N=hpvM~xQg%!iNjjY0Y{OrhR#VYQqBU`0$;Q6x%>{;nO5V#wrg|<@J(Q$ zjRF8z2E0v5gYY(yEdzW9=>%yb*6Zpb#jxQTZ3J$!m}saKx(e9s$Y!=g`xfvq%g^~` zU>hlnhuT86p}_Znk6V7myO35{slGx@C)?GQU!6JZK&JF{JF=OHBvnDJ0Jb}_nMt-Q z5kFc|T1qWLd~1$uhVADcfv=OS-eoJ0P?hJ%W)9{f4f&Epv|Yd^QkvyqD%mbTItnGN z8{=xk<>7+wm=QrZ6Unem;(UOONIcX@HUlKvv;-3ky+U`A;&`YjWSa=w3ye+h`EF9Z z&5zzo8l4L=o0%@Y)v^}wS8KDP(B12{4bWLv0TIPGLx1)LbNloTBaYRzdvs^>(^?$)Y(L~Cg5pDCSPk2GUv2bPyZR} z#I1Ed&#L3O(H63O+0qBUG$yP0r%121YB5`{<)HSnMxDUTrjyN9H^&m-?~zQ}I6nXP zNJUvC5BO-{dm-(o1NbD+YAV@=t66Uw(O%Y(t&-WDGzN*j8{-C>qg*R`W-GR<9Sc(+5ueQ=Sq#@8f^f6W8on_se$R7 z2YrT%-PS^` zy@?bk4Xc%G)i@Tf%F}sl!B(UodJBWAoqmKvxx|Vu3{XEJ!BG_keN99rPo7BxQ3`C+drvq%_j6H*|t--!OL1Co7SN;KBP|H(NVaIrp4?4t#&=FzQ9tk zzjkE8v6En>*B-S{Tf>xWO-QzBorQ1YQ^-KsQe%8NkTFGlPJop}qw4Has3OhXFWJu2 zWgB)OSq*-MT`X3M)uA-TtHXC}7zRVVr4HO&vB}g5-oiE{K<=*0^6Sg$mB<8njh+ac zZs}XN2ARw5xAWsBq!-CrC%}9;_TNVZBQ493txiRzt$0n1no64Sy?_k+*y;FG_7+$O zjL&2;`vpO^6Oj0!t=OVOt5orS7?|e>R~9pnjFtU^AlqE*p8Wf!UZK@knGXPejYJ-jcKr6ww8T~)tEa1B}8X!r-Of@W<{gUl8BwacQXjjo5M$+F_;4-~FRyv*m za42vpFrtujD@3*n6ZGXIb{g5B|5<94RmwT8wOy z^}0zST7g+e_$^Awy$&f7IS+U#MP$n$spCU4nM_?FvYkq@2YQ{O2QVUr&wfD9_j{yg zg!k#Hx(q2swj^UD@OOysEsnurn~}<-St-%w#v((E2a)XnqZUmAxK8S4Z$L=J_N_2@&AikjH829l8(&Xk6B*8POXMHGn^}0E-O(Wbfg(hJF za3vC?9}@Fk?m%L!k0kW0Cn8Ce`{k4Eh?JS;(-A*dn*m1Ai@=A0uadl={v_4i8gpc8 z)IUfY7OGSkVZdIxkv=LHAl3Ov!9jYy4LPzMtaoeLFhU>jF){0J`M4kW0MhR_DSF%J zoVt=#%j^Ksi9>pxAIv;GniqyAw+&iD|ZF_X#E)gg|~`J@*Uk(8Kc=x=!%H#!j) ziHA5xZFaV=2Oa~qXNhp0xWTvpsef!In2M~R@I_=Wh|N)lcGcOkS&|zMtpxSJr;+Rs z(^QJvkS59-Ej`;@g~1pj3?4utAhv|Jyd2qVipQ-5J_X#Z zS6x^*5;zRAGhyxouD3}_WG&k3^Ll+ei1?ij3;A3QQ>)Zuhw4|5smzXS0pdBt5B8Yq zDs8HChHaTPVRp}xUf^;hbHsR_*+91Cs-yic{j&_3Be0?;DA`QcX*VJ1?%NFAUmr3W zz%!j}>ws&JxMg?VpT8>teX0R_VvC*0Wa~u64lbc^7Wa~9avURJH`+`q>?AIZh`ga-;`qE$KY7RlllrFN=^yc1f540a8Y{BgSX@O<-(pxNUAj zugA2RJ|r9!L{NRU>Ic>C7OZDa|NVYzeZc$-{D&}r(XEb;&!k4;BukEc~zM{syIa?1$_~lxlMXc1=C@tbNGZ1p^^uM)B?L!5G9Sw-7P&ty825N7+<+mS@glJ&iX$hI5! zK}d!|H+e;?4v}I^NWXWvs$N?PS)k>}2JmyFlr3mnhf+HlcHbsJg>9d4;Lfb zPNdQ)XlzC@N1bty49T`wEzli)ez;Uc{7;aWT9C+MUt5DDpaE&d6C_?irqFEgJg=u# zvh4uAPOt&oHY9(o-aY?F(S+1i1bKbl3_S07p6?W~a#H32BxO7(?59M_xaS`!4kFme zi+2GJQM?yRRb<-@Tt#r9rDlE0Ts!<|aeRQk>j|U)`i)9&JwN)>AAp+z)Y;mxuS2=* zI3b|lvrbj3m4uicHXKPU4k9=GcBFjJNw;WFkF?UN^||dvhPI8&|K<5L2IW>IIE8*6 zSuN{Hr1g7~<3S{tqPkTr&#W7n%i2Ehmxo0*fX@X$wynS!$c#^i@NrE5&+Pli5R$qm z$u<;eRaMKz_X3|G{MPag^1Pm}@p=77)t-XxsxZk0;v9Un#xL+wWP-7C@P>;SH9n_@ zRpn_Y_5op&EoVyCq6e(jgJ^Ml2o?WX!!ud}98=D75Cz#lhOKthaFt$5IIR~P`US$S zDxc4zNN1^ravu^E*#NGn!9xALzB$c~7lESz$=s>tx!jBN2k{kBjFN015r%4|+b5Aw zibL?a+TJRj$&Z0aR1i*#nrtA$9(t?za!Y}uaS??e(Sl^_RPr450AEAmrxnGm;~*PI zyU|MffOR4rl-e9e0>kwcK7&o_iaM~G4~vUzISE&(4)-k55YXvvwPQN{#YBGA3g7~2 z7&;dx*#PDvMJ<&O?%$B6e9rGyJw_>c{GRzQ)ncvb%;P2-z^O<(2ftr@8!#6a!m1W^ z6YA1%d2~)|I!yKePTd9NUP^;DvZZL)*lT%8)tUm)zB7!P5)5!+n zK>TO5jEWuF2H;}CZC!60+EuSDc4m*L0T5Xhv<+m-VG?i;#ph1Fr4nwq;~7&ZTuab} zlv`dz3{z@sB3lk;=o{NtM8aK8xLtTm_n}?N;JI9TRgx_LM#Qx_8_AZ#R3+W!0&j0F zP@ETaYxMvrUlrHj^!fBY_)`PKEY$+qQLs+T47T5UuE<1gl^5O?dJ8JX3bB(mjWoa%1BQo~^hFq33ti!`h5^%zo2 zbUzX+Ha=~>60+Iqr9TRp0DT0~wD2LM_k?W|V}>Bb-^U_3ea|AZC|path); + + $this->assertTrue(is_array($image->getProperties(true))); + } + + public function () + { + + } + +} diff --git a/tests/system/Images/ImageTest.php b/tests/system/Images/ImageTest.php new file mode 100644 index 000000000000..bb8f76a3542c --- /dev/null +++ b/tests/system/Images/ImageTest.php @@ -0,0 +1,56 @@ +path); + + $this->assertEquals('ci-logo.png', $image->getFilename()); + $this->assertEquals(ROOTPATH.$this->path, $image->getPathname()); + $this->assertEquals(ROOTPATH.'tests/_support', $image->getPath()); + $this->assertEquals('ci-logo.png', $image->getBasename()); + } + + + public function testGetProperties() + { + $image = new Image(ROOTPATH.$this->path); + + $expected = [ + 'width' => 155, + 'height' => 200, + 'image_type' => IMAGETYPE_PNG, + 'size_str' => 'width="155" height="200"', + 'mime_type' => "image/png", + ]; + + $this->assertEquals($expected, $image->getProperties(true)); + } + + + public function testCanCopyDefaultName() + { + $image = new Image(ROOTPATH.$this->path); + + $image->copy(WRITEPATH); + + $this->assertFileExists(WRITEPATH.'ci-logo.png'); + + unlink(WRITEPATH.'ci-logo.png'); + } + + public function testCanCopyNewName() + { + $image = new Image(ROOTPATH.$this->path); + + $image->copy(WRITEPATH, 'new-logo.png'); + + $this->assertFileExists(WRITEPATH.'new-logo.png'); + + unlink(WRITEPATH.'new-logo.png'); + } + +} From 8bfef0826c9299f3a3c680c866140fdb1f267a54 Mon Sep 17 00:00:00 2001 From: Filisko Date: Fri, 28 Apr 2017 11:52:21 +0200 Subject: [PATCH 0634/1807] Fixed 44 warnings --- user_guide_src/source/changelog.rst | 15 ++-- .../source/contributing/documentation.rst | 2 +- .../source/database/{hooks.rst => events.rst} | 20 +++--- user_guide_src/source/database/index.rst | 2 +- user_guide_src/source/database/metadata.rst | 12 ++-- user_guide_src/source/general/controllers.rst | 1 - user_guide_src/source/general/filters.rst | 2 +- user_guide_src/source/general/view_parser.rst | 29 ++++---- .../source/general/view_renderer.rst | 25 ++++--- user_guide_src/source/helpers/form_helper.rst | 8 +-- user_guide_src/source/helpers/html_helper.rst | 30 ++++---- .../source/libraries/api_responses.rst | 49 ++++++------- user_guide_src/source/libraries/benchmark.rst | 11 ++- .../source/libraries/incomingrequest.rst | 8 +-- .../source/libraries/localization.rst | 3 + .../source/libraries/pagination.rst | 70 +++++++++---------- .../source/libraries/uploaded_files.rst | 9 +-- .../source/tutorial/create_news_items.rst | 4 +- .../source/tutorial/news_section.rst | 4 +- 19 files changed, 151 insertions(+), 153 deletions(-) rename user_guide_src/source/database/{hooks.rst => events.rst} (58%) diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index 005cfc007e5c..fe4fabeab306 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -23,30 +23,30 @@ New packages: - CLI \\ CLI - Commands \\ MigrationsCommand - Config \\ AutoloadConfig, BaseConfig, DotEnv, Routes - - Database + - Database - \\ BaseBuilder, BaseConnection, BaseResult, BaseUtils, Config, - ConnectionInterface, Database, Forge, Migration, MigrationRunner, Query, + ConnectionInterface, Database, Forge, Migration, MigrationRunner, Query, QueryInterface, ResultInterface, Seeder - \\ MySQLi \\ Builder, Connection, Forge, Result - \\ Postgre \\ Builder, Connection, Forge, Result, Utils - - Debug + - Debug - \\ CustomExceptions, Exceptions, Iterator, Timer, Toolbar - Kint \\ Kint **third party** - HTTP - - \\ CLIRequest, CURLRequest, ContentSecurityPolicy, Header, + - \\ CLIRequest, CURLRequest, ContentSecurityPolicy, Header, IncomingRequest, Message, Negotiate, Request, RequestInterface, Response, ResponseInterface, URI - \\ Files \\ FileCollection, UploadedFile, UploadedFileInterface - Helpers ... uri - - Hooks \\ Hooks - - Log + - Events \\ Events + - Log - Logger, LoggerAwareTrait - \\ Handlers \\ BaseHandler, ChromeLoggerHandler, FileHandler, HandlerInterface @@ -64,7 +64,6 @@ New packages: - Zend \\ Escaper, Exception \\ ... **third party** - RendererInterface, View - -User Guide adapted or rewritten. +User Guide adapted or rewritten. diff --git a/user_guide_src/source/contributing/documentation.rst b/user_guide_src/source/contributing/documentation.rst index d47d90a3daef..9b2b2d192faa 100644 --- a/user_guide_src/source/contributing/documentation.rst +++ b/user_guide_src/source/contributing/documentation.rst @@ -27,7 +27,7 @@ It is created automatically by inserting the following:
    -The
    that is inserted as raw HTML is a hook for the documentation's +The
    that is inserted as raw HTML is a event for the documentation's JavaScript to dynamically add links to any function and method definitions contained in the current page. diff --git a/user_guide_src/source/database/hooks.rst b/user_guide_src/source/database/events.rst similarity index 58% rename from user_guide_src/source/database/hooks.rst rename to user_guide_src/source/database/events.rst index 039b1de23173..c0bbdc0793c8 100644 --- a/user_guide_src/source/database/hooks.rst +++ b/user_guide_src/source/database/events.rst @@ -1,25 +1,25 @@ -############## -Database Hooks -############## +############### +Database Events +############### -The Database classes contain a few :doc:`Hooks ` that you can tap into in +The Database classes contain a few :doc:`Events ` that you can tap into in order to learn more about what is happening during the database execution. These events can be used to collect data for analysis and reporting. The :doc:`Debug Toolbar ` uses this to collect the queries to display in the Toolbar. -========= -The Hooks -========= +========== +The Events +========== **DBQuery** -This hook is triggered whenever a new query has been run, whether successful or not. The only parameter is -a :doc:`Query ` instance of the current query. You could use this to display all queries +This event is triggered whenever a new query has been run, whether successful or not. The only parameter is +a :doc:`Query ` instance of the current query. You could use this to display all queries in STDOUT, or logging to a file, or even creating tools to do automatic query analysis to help you spot potentially missing indexes, slow queries, etc. An example usage might be:: // In Config\Events.php - Hooks::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); + Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); // Collect the queries so something can be done with them later. public static function collect(CodeIgniter\Database\Query $query) diff --git a/user_guide_src/source/database/index.rst b/user_guide_src/source/database/index.rst index 499374fa5c2f..1283d8889c8f 100644 --- a/user_guide_src/source/database/index.rst +++ b/user_guide_src/source/database/index.rst @@ -24,4 +24,4 @@ patterns. The database functions offer clear, simple syntax. Database Manipulation with Database Forge Database Migrations Database Seeding - Database Hooks + Database Events diff --git a/user_guide_src/source/database/metadata.rst b/user_guide_src/source/database/metadata.rst index 5fdc33d74fe0..8045ca27bda4 100644 --- a/user_guide_src/source/database/metadata.rst +++ b/user_guide_src/source/database/metadata.rst @@ -17,7 +17,7 @@ Returns an array containing the names of all the tables in the database you are currently connected to. Example:: $tables = $db->listTables(); - + foreach ($tables as $table) { echo $table; @@ -56,7 +56,7 @@ two ways: object:: $fields = $db->getFieldNames('table_name'); - + foreach ($fields as $field) { echo $field; @@ -66,7 +66,7 @@ object:: calling the function from your query result object:: $query = $db->query('SELECT * FROM some_table'); - + foreach ($query->getFieldNames() as $field) { echo $field; @@ -106,7 +106,7 @@ the column type, max length, etc. Usage example:: $fields = $db->getFieldData('table_name'); - + foreach ($fields as $field) { echo $field->name; @@ -130,8 +130,8 @@ database: - type - the type of the column List the Indexes in a Table -========================== +=========================== **$db->getIndexData()** -please write this, someone... \ No newline at end of file +please write this, someone... diff --git a/user_guide_src/source/general/controllers.rst b/user_guide_src/source/general/controllers.rst index 86e64e336769..01d6661ad3bf 100644 --- a/user_guide_src/source/general/controllers.rst +++ b/user_guide_src/source/general/controllers.rst @@ -362,4 +362,3 @@ That's it! ========== That, in a nutshell, is all there is to know about controllers. - diff --git a/user_guide_src/source/general/filters.rst b/user_guide_src/source/general/filters.rst index 6a2bbd24badd..f1c5572fe0a1 100644 --- a/user_guide_src/source/general/filters.rst +++ b/user_guide_src/source/general/filters.rst @@ -2,7 +2,7 @@ Controller Filters ################## -Controller Filters allow you to perform actions either before or after the controllers execute. Unlike :doc:`hooks `, +Controller Filters allow you to perform actions either before or after the controllers execute. Unlike :doc:`events `, you can very simply choose which URI's in your application have the filters applied to them. Incoming filters may modify the Request, while after filters can act on and even modify the Response, allowing for a lot of flexibility and power. Some common examples of tasks that might be performed with filters are: diff --git a/user_guide_src/source/general/view_parser.rst b/user_guide_src/source/general/view_parser.rst index 2a04e9dc8255..619299a7bea2 100644 --- a/user_guide_src/source/general/view_parser.rst +++ b/user_guide_src/source/general/view_parser.rst @@ -96,8 +96,7 @@ View parameters are passed to ``setData()`` as an associative array of data to be replaced in the template. In the above example, the template would contain two variables: {blog_title} and {blog_heading} The first parameter to ``render()`` contains the name of the :doc:`view - file <../general/views>` (in this example the file would be called - blog_template.php), +file <../general/views>` (in this example the file would be called blog_template.php), Parser Configuration Options @@ -387,11 +386,11 @@ Provided Filters The following filters are available when using the parser: -==================== ========================== =================================================================== ========================== +==================== ========================== =================================================================== ================================= Filter Arguments Description Example -==================== ========================== =================================================================== ========================== -abs Displays the absolute value of a number. { v|abs } -capitalize Displays the string in sentence case: all lowercase with first { v|capitalize} +==================== ========================== =================================================================== ================================= +abs Displays the absolute value of a number. { v|abs } +capitalize Displays the string in sentence case: all lowercase with first { v|capitalize} letter capitalized. date format (Y-m-d) A PHP **date**-compatible formatting string. { v|date(Y-m-d) } date_modify value to add/subtract A **strtotime** compatible string to modify the date, like { v|date_modify(+1 day) } @@ -400,7 +399,8 @@ default default value Displays the default value if th esc html, attr, css, js Specifies the context to escape the data. { v|esc(attr) } excerpt phrase, radius Returns the text within a radius of words from a given phrase. { v|excerpt(green giant, 20) } Same as **excerpt** helper function. -highlight phrase Highlights a given phrase within the text using '' tags { v|highlight(view parser) } +highlight phrase Highlights a given phrase within the text using '' + tags. { v|highlight(view parser) } highlight_code Highlights code samples with HTML/CSS. { v|highlight_code } limit_chars limit Limits the number of chracters to $limit. { v|limit_chars(100) } limit_words limit Limits the number of words to $limit. { v|limit_words(20) } @@ -414,7 +414,8 @@ round places, type Rounds a number to the specified strip_tags allowed chars Wraps PHP **strip_tags**. Can accept a string of allowed tags. { v|strip_tags(
    ) } title Displays a "title case" version of the string, with all lowercase, { v|title } and each word capitalized. -upper Displays the string in all lowercase. { v|lower } +upper Displays the string in all lowercase. { v|upper } +==================== ========================== =================================================================== ================================= Custom Filters -------------- @@ -483,7 +484,6 @@ the content between its tags:: {+ foo +} inner content {+ /foo +} - *********** Usage Notes *********** @@ -595,6 +595,7 @@ Result::
  • Second Link
  • + *************** Class Reference *************** @@ -604,10 +605,10 @@ Class Reference .. php:method:: render($view[, $options[, $saveData=false]]]) :param string $view: File name of the view source - :param array $options: Array of options, as key/value pairs - :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. - :returns: The rendered text for the chosen view - :rtype: string + :param array $options: Array of options, as key/value pairs + :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. + :returns: The rendered text for the chosen view + :rtype: string Builds the output based upon a file name and any data that has already been set:: @@ -678,5 +679,3 @@ Class Reference Over-ride the substitution field delimiters:: $renderer->setDelimiters('[',']'); - - diff --git a/user_guide_src/source/general/view_renderer.rst b/user_guide_src/source/general/view_renderer.rst index f3241cebc46c..05e603b59eff 100644 --- a/user_guide_src/source/general/view_renderer.rst +++ b/user_guide_src/source/general/view_renderer.rst @@ -2,9 +2,9 @@ View Renderer ############# -The ``view()`` function is a convenience function that grabs an instance of the -``renderer`` service, sets the data, and renders the view. While this is often -exactly what you want, you may find times where you want to work with it more directly. +The ``view()`` function is a convenience function that grabs an instance of the +``renderer`` service, sets the data, and renders the view. While this is often +exactly what you want, you may find times where you want to work with it more directly. In that case you can access the View service directly:: $view = \Config\Services::renderer(); @@ -15,17 +15,17 @@ can instantiate it directly:: $view = new \CodeIgniter\View\View(); -.. important:: You should create services only within controllers. If you need - access to the View class from a library, you should set that as a dependency +.. important:: You should create services only within controllers. If you need + access to the View class from a library, you should set that as a dependency in your library's constructor. -Then you can use any of the three standard methods that it provides: +Then you can use any of the three standard methods that it provides: **render(viewpath, options, save)**, **setVar(name, value, context)** and **setData(data, context)**. What It Does ============ -The ``View`` class processes conventional HTML/PHP scripts stored in the application's view path, +The ``View`` class processes conventional HTML/PHP scripts stored in the application's view path, after extracting view parameters into PHP variables, accessible inside the scripts. This means that your view parameter names need to be legal PHP variable names. @@ -36,13 +36,13 @@ need to be unique, or a later variable setting will over-ride an earlier one. This also impacts escaping parameter values for different contexts inside your script. You will have to give each escaped value a unique parameter name. -No special meaning is attached to parameters whose value is an array. It is up +No special meaning is attached to parameters whose value is an array. It is up to you to process the array appropriately in your PHP code. Method Chaining =============== -The `setVar()` and `setData()` methods are chainable, allowing you to combine a +The `setVar()` and `setData()` methods are chainable, allowing you to combine a number of different calls together in a chain:: $view->setVar('one', $one) @@ -97,7 +97,6 @@ Several options can be passed to the ``render()`` or ``renderString()`` methods: ignored for renderString() - ``saveData`` - true if the view data parameters should be retained for subsequent calls - *************** Class Reference *************** @@ -106,7 +105,7 @@ Class Reference .. php:method:: render($view[, $options[, $saveData=false]]]) - :param string $view: File name of the view source + :param string $view: File name of the view source :param array $options: Array of options, as key/value pairs :param boolean $saveData: If true, will save data for use with any other calls, if false, will clean the data after rendering the view. :returns: The rendered text for the chosen view @@ -136,7 +135,7 @@ Class Reference .. php:method:: setData([$data[, $context=null]]) :param array $data: Array of view data strings, as key/value pairs - :param string $context: The context to use for data escaping. + :param string $context: The context to use for data escaping. :returns: The Renderer, for method chaining :rtype: CodeIgniter\\View\\RendererInterface. @@ -154,7 +153,7 @@ Class Reference :param string $name: Name of the view data variable :param mixed $value: The value of this view data - :param string $context: The context to use for data escaping. + :param string $context: The context to use for data escaping. :returns: The Renderer, for method chaining :rtype: CodeIgniter\\View\\RendererInterface. diff --git a/user_guide_src/source/helpers/form_helper.rst b/user_guide_src/source/helpers/form_helper.rst index 3a1e1384dcde..01489be702e4 100644 --- a/user_guide_src/source/helpers/form_helper.rst +++ b/user_guide_src/source/helpers/form_helper.rst @@ -621,9 +621,9 @@ The following functions are available: The above form will show "0" when loaded for the first time. - .. note:: If you've loaded the :doc:`Form Validation Library <../libraries/form_validation>` and + .. note:: If you've loaded the :doc:`Form Validation Library <../libraries/validation>` and have set a validation rule for the field name in use with this helper, then it will - forward the call to the :doc:`Form Validation Library <../libraries/form_validation>`'s + forward the call to the :doc:`Form Validation Library <../libraries/validation>`'s own ``set_value()`` method. Otherwise, this function looks in ``$_POST`` for the field value. @@ -700,7 +700,7 @@ The following functions are available: :rtype: string Returns a validation error message from the :doc:`Form Validation Library - <../libraries/form_validation>`, associated with the specified field name. + <../libraries/validation>`, associated with the specified field name. You can optionally specify opening and closing tag(s) to put around the error message. @@ -721,7 +721,7 @@ The following functions are available: Similarly to the :php:func:`form_error()` function, returns all validation error messages produced by the :doc:`Form Validation Library - <../libraries/form_validation>`, with optional opening and closing tags + <../libraries/validation>`, with optional opening and closing tags around each of the messages. Example:: diff --git a/user_guide_src/source/helpers/html_helper.rst b/user_guide_src/source/helpers/html_helper.rst index 3214731202de..fdaa6a0909be 100755 --- a/user_guide_src/source/helpers/html_helper.rst +++ b/user_guide_src/source/helpers/html_helper.rst @@ -36,7 +36,7 @@ The following functions are available: Lets you create HTML tags. The first parameter contains the image source. Example:: - echo img('images/picture.jpg'); + echo img('images/picture.jpg'); // There is an optional second parameter that is a true/false value that @@ -44,7 +44,7 @@ The following functions are available: ``$config['indexPage']`` added to the address it creates. Presumably, this would be if you were using a media controller:: - echo img('images/picture.jpg', true); + echo img('images/picture.jpg', true); // Additionally, an associative array can be passed to the ``img()`` function @@ -129,13 +129,15 @@ The following functions are available: Further examples:: + // to be done + Additionally, an associative array can be passed to the ``script_tag()`` function for complete control over all attributes and values:: $script = array('src' => 'js/printer.js'); echo script_tag($script); - // + // .. php:function:: ul($list[, $attributes = '']) @@ -280,7 +282,7 @@ The following functions are available: Permits you to generate HTML video element from simple or source arrays. Example:: - $tracks = + $tracks = [ track('subtitles_no.vtt', 'subtitles', 'no', 'Norwegian No'), track('subtitles_yes.vtt', 'subtitles', 'yes', 'Norwegian Yes') @@ -290,8 +292,8 @@ The following functions are available: echo video ( - 'http://www.codeigniter.com/test.mp4', - 'Your browser does not support the video tag.', + 'http://www.codeigniter.com/test.mp4', + 'Your browser does not support the video tag.', 'controls', $tracks ); @@ -322,7 +324,7 @@ The following functions are available: Your browser does not support the video tag. - +