diff --git a/.gitignore b/.gitignore
index e82f525..11abea6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,7 +84,6 @@
# Composer
#-------------------------
vendor/
-composer.lock
#-------------------------
# IDE / Development Files
@@ -125,3 +124,4 @@
/results/
/phpunit*.xml
/.phpunit.*.cache
+
diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php
index 6a673d3..eaecfdf 100644
--- a/app/Config/Autoload.php
+++ b/app/Config/Autoload.php
@@ -1,6 +1,8 @@
- SYSTEMPATH,
+ * 'App' => APPPATH
+ * ];
+ *
+ * @var array
*/
- public function __construct()
- {
- parent::__construct();
+ public $psr4 = [
+ APP_NAMESPACE => APPPATH, // For custom app namespace
+ 'Config' => APPPATH . 'Config',
+ ];
- /**
- * -------------------------------------------------------------------
- * Namespaces
- * -------------------------------------------------------------------
- * This maps the locations of any namespaces in your application
- * to their location on the file system. These are used by the
- * Autoloader to locate files the first time they have been instantiated.
- *
- * The '/app' and '/system' directories are already mapped for
- * you. You may change the name of the 'App' namespace if you wish,
- * but this should be done prior to creating any namespaced classes,
- * else you will need to modify all of those classes for this to work.
- *
- * DO NOT change the name of the CodeIgniter namespace or your application
- * WILL break. *
- * Prototype:
- *
- * $Config['psr4'] = [
- * 'CodeIgniter' => SYSPATH
- * `];
- */
- $psr4 = [
- 'App' => APPPATH, // To ensure filters, etc still found,
- APP_NAMESPACE => APPPATH, // For custom namespace
- 'Config' => APPPATH . 'Config',
- ];
-
- /**
- * -------------------------------------------------------------------
- * Class Map
- * -------------------------------------------------------------------
- * The class map provides a map of class names and their exact
- * location on the drive. Classes loaded in this manner will have
- * slightly faster performance because they will not have to be
- * searched for within one or more directories as they would if they
- * were being autoloaded through a namespace.
- *
- * Prototype:
- *
- * $Config['classmap'] = [
- * 'MyClass' => '/path/to/class/file.php'
- * ];
- */
- $classmap = [];
-
- //--------------------------------------------------------------------
- // Do Not Edit Below This Line
- //--------------------------------------------------------------------
-
- $this->psr4 = array_merge($this->psr4, $psr4);
- $this->classmap = array_merge($this->classmap, $classmap);
-
- unset($psr4, $classmap);
- }
-
- //--------------------------------------------------------------------
-
+ /**
+ * -------------------------------------------------------------------
+ * Class Map
+ * -------------------------------------------------------------------
+ * The class map provides a map of class names and their exact
+ * location on the drive. Classes loaded in this manner will have
+ * slightly faster performance because they will not have to be
+ * searched for within one or more directories as they would if they
+ * were being autoloaded through a namespace.
+ *
+ * Prototype:
+ *
+ * $classmap = [
+ * 'MyClass' => '/path/to/class/file.php'
+ * ];
+ *
+ * @var array
+ */
+ public $classmap = [];
}
diff --git a/app/Config/Boot/development.php b/app/Config/Boot/development.php
index 7d6ae48..63fdd88 100644
--- a/app/Config/Boot/development.php
+++ b/app/Config/Boot/development.php
@@ -29,4 +29,4 @@
| items. It can always be used within your own application too.
*/
-defined('CI_DEBUG') || define('CI_DEBUG', 1);
+defined('CI_DEBUG') || define('CI_DEBUG', true);
diff --git a/app/Config/Boot/production.php b/app/Config/Boot/production.php
index c54bdbd..1241907 100644
--- a/app/Config/Boot/production.php
+++ b/app/Config/Boot/production.php
@@ -19,4 +19,4 @@
| release of the framework.
*/
-defined('CI_DEBUG') || define('CI_DEBUG', 0);
+defined('CI_DEBUG') || define('CI_DEBUG', false);
diff --git a/app/Config/Boot/testing.php b/app/Config/Boot/testing.php
index e6c94d7..fab6c07 100644
--- a/app/Config/Boot/testing.php
+++ b/app/Config/Boot/testing.php
@@ -30,4 +30,4 @@
| release of the framework.
*/
-defined('CI_DEBUG') || define('CI_DEBUG', 1);
+defined('CI_DEBUG') || define('CI_DEBUG', true);
diff --git a/app/Config/Events.php b/app/Config/Events.php
index 085cc4a..14bfd32 100644
--- a/app/Config/Events.php
+++ b/app/Config/Events.php
@@ -1,6 +1,7 @@
0)
+ if (ini_get('zlib.output_compression'))
{
- \ob_end_flush();
+ throw FrameworkException::forEnabledZlibOutputCompression();
}
- \ob_start(function ($buffer) {
+ while (ob_get_level() > 0)
+ {
+ ob_end_flush();
+ }
+
+ ob_start(function ($buffer) {
return $buffer;
});
}
diff --git a/app/Config/Exceptions.php b/app/Config/Exceptions.php
index c0245b2..5fe33d3 100644
--- a/app/Config/Exceptions.php
+++ b/app/Config/Exceptions.php
@@ -1,12 +1,13 @@
\CodeIgniter\Format\XMLFormatter::class,
'text/xml' => \CodeIgniter\Format\XMLFormatter::class,
];
-
+
+ /*
+ |--------------------------------------------------------------------------
+ | Formatters Options
+ |--------------------------------------------------------------------------
+ |
+ | Additional Options to adjust default formatters behaviour.
+ | For each mime type, list the additional options that should be used.
+ |
+ */
+ public $formatterOptions = [
+ 'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
+ 'application/xml' => 0,
+ 'text/xml' => 0,
+ ];
//--------------------------------------------------------------------
/**
diff --git a/app/Config/Honeypot.php b/app/Config/Honeypot.php
index f4444a5..3d9e372 100644
--- a/app/Config/Honeypot.php
+++ b/app/Config/Honeypot.php
@@ -11,6 +11,7 @@
* @var boolean
*/
public $hidden = true;
+
/**
* Honeypot Label Content
*
@@ -31,4 +32,11 @@
* @var string
*/
public $template = '';
+
+ /**
+ * Honeypot container
+ *
+ * @var string
+ */
+ public $container = '
{template}
';
}
diff --git a/app/Config/Images.php b/app/Config/Images.php
index 730ddee..a416b8b 100644
--- a/app/Config/Images.php
+++ b/app/Config/Images.php
@@ -22,7 +22,7 @@
/**
* The available handler classes.
*
- * @var array
+ * @var \CodeIgniter\Images\Handlers\BaseHandler[]
*/
public $handlers = [
'gd' => \CodeIgniter\Images\Handlers\GDHandler::class,
diff --git a/app/Config/Modules.php b/app/Config/Modules.php
index 28bbc7d..40cb987 100644
--- a/app/Config/Modules.php
+++ b/app/Config/Modules.php
@@ -1,7 +1,10 @@
-enabled)
- {
- return false;
- }
-
- $alias = strtolower($alias);
-
- return in_array($alias, $this->activeExplorers);
- }
}
diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index a2a9654..56839ca 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -38,7 +38,7 @@
* --------------------------------------------------------------------
*
* There will often be times that you need additional routing and you
- * need to it be able to override any defaults in this file. Environment
+ * need it to be able to override any defaults in this file. Environment
* based routes is one such time. require() additional route files here
* to make that happen.
*
diff --git a/app/Config/Services.php b/app/Config/Services.php
index fb85a73..c58da70 100644
--- a/app/Config/Services.php
+++ b/app/Config/Services.php
@@ -2,8 +2,6 @@
use CodeIgniter\Config\Services as CoreServices;
-require_once SYSTEMPATH . 'Config/Services.php';
-
/**
* Services Configuration file.
*
diff --git a/app/Language/en/Validation.php b/app/Language/en/Validation.php
new file mode 100644
index 0000000..54d1e7a
--- /dev/null
+++ b/app/Language/en/Validation.php
@@ -0,0 +1,4 @@
+=7.2",
- "ext-curl": "*",
- "ext-intl": "*",
- "ext-json": "*",
- "ext-mbstring": "*",
- "kint-php/kint": "^3.3",
- "psr/log": "^1.1",
- "laminas/laminas-escaper": "^2.6"
- },
- "require-dev": {
- "codeigniter4/codeigniter4-standard": "^1.0",
- "mikey179/vfsstream": "1.6.*",
- "phpunit/phpunit": "^8.5",
- "squizlabs/php_codesniffer": "^3.3"
- },
- "autoload": {
- "psr-4": {
- "CodeIgniter\\": "system/"
- }
- },
- "scripts": {
- "post-update-cmd": [
- "@composer dump-autoload",
- "CodeIgniter\\ComposerScripts::postUpdate",
- "bash admin/setup.sh"
- ]
- },
- "support": {
- "forum": "http://forum.codeigniter.com/",
- "source": "https://github.com/codeigniter4/CodeIgniter4",
- "slack": "https://codeigniterchat.slack.com"
- }
+ "name": "codeigniter4/framework",
+ "type": "project",
+ "description": "The CodeIgniter framework v4",
+ "homepage": "https://codeigniter.com",
+ "license": "MIT",
+ "require": {
+ "php": ">=7.2",
+ "ext-curl": "*",
+ "ext-intl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "kint-php/kint": "^3.3",
+ "laminas/laminas-escaper": "^2.6",
+ "psr/log": "^1.1"
+ },
+ "require-dev": {
+ "codeigniter4/codeigniter4-standard": "^1.0",
+ "fzaninotto/faker": "^1.9@dev",
+ "mikey179/vfsstream": "1.6.*",
+ "phpunit/phpunit": "^8.5",
+ "predis/predis": "^1.1",
+ "squizlabs/php_codesniffer": "^3.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "CodeIgniter\\": "system/"
+ }
+ },
+ "scripts": {
+ "post-update-cmd": [
+ "@composer dump-autoload",
+ "CodeIgniter\\ComposerScripts::postUpdate"
+ ],
+ "test": "phpunit"
+ },
+ "support": {
+ "forum": "http://forum.codeigniter.com/",
+ "source": "https://github.com/codeigniter4/CodeIgniter4",
+ "slack": "https://codeigniterchat.slack.com"
+ }
}
diff --git a/env b/env
index bc1a156..11f4161 100644
--- a/env
+++ b/env
@@ -84,10 +84,18 @@
# contentsecuritypolicy.upgradeInsecureRequests = false
#--------------------------------------------------------------------
+# ENCRYPTION
+#--------------------------------------------------------------------
+
+# encryption.key =
+# encryption.driver = OpenSSL
+
+#--------------------------------------------------------------------
# HONEYPOT
#--------------------------------------------------------------------
-# honeypot.hidden = 'true'
-# honeypot.label = 'Fill This Field'
-# honeypot.name = 'honeypot'
-# honeypot.template = ''
+# honeypot.hidden = 'true'
+# honeypot.label = 'Fill This Field'
+# honeypot.name = 'honeypot'
+# honeypot.template = ''
+# honeypot.container = '
{template}
'
diff --git a/public/.htaccess b/public/.htaccess
index 699e1c1..02026a3 100644
--- a/public/.htaccess
+++ b/public/.htaccess
@@ -18,7 +18,7 @@
# Redirect Trailing Slashes...
RewriteCond %{REQUEST_FILENAME} !-d
- RewriteRule ^(.*)/$ /$1 [L,R=301]
+ RewriteRule ^(.*)/$ /$1 [L,R=301]
# Rewrite "www.example.com -> example.com"
RewriteCond %{HTTPS} !=on
@@ -30,7 +30,7 @@
# request to the front controller, index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
- RewriteRule ^(.*)$ index.php/$1 [L]
+ RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA]
# Ensure Authorization header is passed along
RewriteCond %{HTTP:Authorization} .
diff --git a/spark b/spark
index 396ad59..0a0908d 100755
--- a/spark
+++ b/spark
@@ -24,7 +24,7 @@
*/
// Refuse to run when called from php-cgi
-if (substr(php_sapi_name(), 0, 3) === 'cgi')
+if (strpos(php_sapi_name(), 'cgi') === 0)
{
die("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n");
}
diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php
index 5aa217e..c9d2b78 100644
--- a/system/Autoloader/FileLocator.php
+++ b/system/Autoloader/FileLocator.php
@@ -171,34 +171,38 @@
{
$php = file_get_contents($file);
$tokens = token_get_all($php);
- $count = count($tokens);
$dlm = false;
$namespace = '';
$class_name = '';
- for ($i = 2; $i < $count; $i++)
+ foreach ($tokens as $i => $token)
{
- if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === 'phpnamespace' || $tokens[$i - 2][1] === 'namespace')) || ($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $tokens[$i][0] === T_STRING))
+ if ($i < 2)
+ {
+ continue;
+ }
+
+ if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === 'phpnamespace' || $tokens[$i - 2][1] === 'namespace')) || ($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $token[0] === T_STRING))
{
if (! $dlm)
{
$namespace = 0;
}
- if (isset($tokens[$i][1]))
+ if (isset($token[1]))
{
- $namespace = $namespace ? $namespace . '\\' . $tokens[$i][1] : $tokens[$i][1];
+ $namespace = $namespace ? $namespace . '\\' . $token[1] : $token[1];
$dlm = true;
}
}
- elseif ($dlm && ($tokens[$i][0] !== T_NS_SEPARATOR) && ($tokens[$i][0] !== T_STRING))
+ elseif ($dlm && ($token[0] !== T_NS_SEPARATOR) && ($token[0] !== T_STRING))
{
$dlm = false;
}
if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === 'phpclass'))
&& $tokens[$i - 1][0] === T_WHITESPACE
- && $tokens[$i][0] === T_STRING)
+ && $token[0] === T_STRING)
{
- $class_name = $tokens[$i][1];
+ $class_name = $token[1];
break;
}
}
@@ -226,25 +230,47 @@
* 'app/Modules/bar/Config/Routes.php',
* ]
*
- * @param string $path
- * @param string $ext
+ * @param string $path
+ * @param string $ext
+ * @param boolean $prioritizeApp
*
* @return array
*/
- public function search(string $path, string $ext = 'php'): array
+ public function search(string $path, string $ext = 'php', bool $prioritizeApp = true): array
{
$path = $this->ensureExt($path, $ext);
$foundPaths = [];
+ $appPaths = [];
foreach ($this->getNamespaces() as $namespace)
{
if (isset($namespace['path']) && is_file($namespace['path'] . $path))
{
- $foundPaths[] = $namespace['path'] . $path;
+ $fullPath = $namespace['path'] . $path;
+ if ($prioritizeApp)
+ {
+ $foundPaths[] = $fullPath;
+ }
+ else
+ {
+ if (strpos($fullPath, APPPATH) === 0)
+ {
+ $appPaths[] = $fullPath;
+ }
+ else
+ {
+ $foundPaths[] = $fullPath;
+ }
+ }
}
}
+ if (! $prioritizeApp && ! empty($appPaths))
+ {
+ $foundPaths = array_merge($foundPaths, $appPaths);
+ }
+
// Remove any duplicates
$foundPaths = array_unique($foundPaths);
diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php
index 7f81e9e..024daf1 100644
--- a/system/CLI/BaseCommand.php
+++ b/system/CLI/BaseCommand.php
@@ -1,4 +1,5 @@
logger = $logger;
$this->commands = $commands;
@@ -151,7 +152,7 @@
// for the command name.
array_unshift($params, $command);
- return $this->commands->index($params);
+ return $this->commands->run($command, $params);
}
//--------------------------------------------------------------------
diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php
index 476c08d..4b6d3d2 100644
--- a/system/CLI/CLI.php
+++ b/system/CLI/CLI.php
@@ -94,7 +94,7 @@
'black' => '0;30',
'dark_gray' => '1;30',
'blue' => '0;34',
- 'dark_blue' => '1;34',
+ 'dark_blue' => '0;34',
'light_blue' => '1;34',
'green' => '0;32',
'light_green' => '1;32',
@@ -104,8 +104,8 @@
'light_red' => '1;31',
'purple' => '0;35',
'light_purple' => '1;35',
- 'light_yellow' => '0;33',
- 'yellow' => '1;33',
+ 'yellow' => '0;33',
+ 'light_yellow' => '1;33',
'light_gray' => '0;37',
'white' => '1;37',
];
@@ -147,6 +147,27 @@
*/
protected static $lastWrite;
+ /**
+ * Height of the CLI window
+ *
+ * @var integer
+ */
+ protected static $height;
+
+ /**
+ * Width of the CLI window
+ *
+ * @var integer
+ */
+ protected static $width;
+
+ /**
+ * Whether the current stream supports colored output.
+ *
+ * @var boolean
+ */
+ protected static $isColored = false;
+
//--------------------------------------------------------------------
/**
@@ -154,18 +175,32 @@
*/
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
- static::$readline_support = extension_loaded('readline');
+ if (is_cli())
+ {
+ // 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');
- // clear segments & options to keep testing clean
- static::$segments = [];
- static::$options = [];
+ // clear segments & options to keep testing clean
+ static::$segments = [];
+ static::$options = [];
- static::parseCommandLine();
+ // Check our stream resource for color support
+ static::$isColored = static::hasColorSupport(STDOUT);
- static::$initialized = true;
+ static::parseCommandLine();
+
+ static::$initialized = true;
+ }
+ else
+ {
+ // If the command is being called from a controller
+ // we need to define STDOUT ourselves
+ // @codeCoverageIgnoreStart
+ define('STDOUT', 'php://output');
+ // @codeCoverageIgnoreEnd
+ }
}
//--------------------------------------------------------------------
@@ -216,7 +251,8 @@
* @param string|array $options String to a default value, array to a list of options (the first option will be the default value)
* @param string $validation Validation rules
*
- * @return string The user input
+ * @return string The user input
+ *
* @codeCoverageIgnore
*/
public static function prompt(string $field, $options = null, string $validation = null): string
@@ -251,7 +287,7 @@
$default = $options[0];
}
- fwrite(STDOUT, $field . $extra_output . ': ');
+ static::fwrite(STDOUT, $field . $extra_output . ': ');
// Read the input from keyboard.
$input = trim(static::input()) ?: $default;
@@ -276,13 +312,16 @@
* @param string $value Input value
* @param string $rules Validation rules
*
- * @return boolean
+ * @return boolean
+ *
* @codeCoverageIgnore
*/
protected static function validate(string $field, string $value, string $rules): bool
{
+ $label = $field;
+ $field = 'temp';
$validation = \Config\Services::validation(null, false);
- $validation->setRule($field, null, $rules);
+ $validation->setRule($field, $label, $rules);
$validation->run([$field => $value]);
if ($validation->hasError($field))
@@ -314,7 +353,7 @@
static::$lastWrite = null;
- fwrite(STDOUT, $text);
+ static::fwrite(STDOUT, $text);
}
/**
@@ -337,7 +376,7 @@
static::$lastWrite = 'write';
}
- fwrite(STDOUT, $text . PHP_EOL);
+ static::fwrite(STDOUT, $text . PHP_EOL);
}
//--------------------------------------------------------------------
@@ -351,12 +390,19 @@
*/
public static function error(string $text, string $foreground = 'light_red', string $background = null)
{
+ // Check color support for STDERR
+ $stdout = static::$isColored;
+ static::$isColored = static::hasColorSupport(STDERR);
+
if ($foreground || $background)
{
$text = static::color($text, $foreground, $background);
}
- fwrite(STDERR, $text . PHP_EOL);
+ static::fwrite(STDERR, $text . PHP_EOL);
+
+ // return STDOUT color support
+ static::$isColored = $stdout;
}
//--------------------------------------------------------------------
@@ -388,7 +434,7 @@
while ($time > 0)
{
- fwrite(STDOUT, $time . '... ');
+ static::fwrite(STDOUT, $time . '... ');
sleep(1);
$time --;
}
@@ -446,18 +492,17 @@
/**
* Clears the screen of output
*
- * @return void
+ * @return void
+ *
* @codeCoverageIgnore
*/
public static function clearScreen()
{
- static::isWindows()
-
- // Windows is a bit crap at this, but their terminal is tiny so shove this in
- ? static::newLine(40)
-
- // Anything with a flair of Unix will handle these magic characters
- : fwrite(STDOUT, chr(27) . '[H' . chr(27) . '[2J');
+ // Unix systems, and Windows with VT100 Terminal support (i.e. Win10)
+ // can handle CSI sequences. For lower than Win10 we just shove in 40 new lines.
+ static::isWindows() && ! static::streamSupports('sapi_windows_vt100_support', STDOUT)
+ ? static::newLine(40)
+ : static::fwrite(STDOUT, "\033[H\033[2J");
}
//--------------------------------------------------------------------
@@ -475,11 +520,9 @@
*/
public static function color(string $text, string $foreground, string $background = null, string $format = null): string
{
- if (static::isWindows() && ! isset($_SERVER['ANSICON']))
+ if (! static::$isColored)
{
- // @codeCoverageIgnoreStart
return $text;
- // @codeCoverageIgnoreEnd
}
if (! array_key_exists($foreground, static::$foreground_colors))
@@ -504,7 +547,37 @@
$string .= "\033[4m";
}
- return $string . ($text . "\033[0m");
+ // Detect if color method was already in use with this text
+ if (strpos($text, "\033[0m") !== false)
+ {
+ // Split the text into parts so that we can see
+ // if any part missing the color definition
+ $chunks = mb_split("\\033\[0m", $text);
+ // Reset text
+ $text = '';
+
+ foreach ($chunks as $chunk)
+ {
+ if ($chunk === '')
+ {
+ continue;
+ }
+
+ // If chunk doesn't have colors defined we need to add them
+ if (strpos($chunk, "\033[") === false)
+ {
+ $chunk = static::color($chunk, $foreground, $background, $format);
+ // Add color reset before chunk and clear end of the string
+ $text .= rtrim("\033[0m" . $chunk, "\033[0m");
+ }
+ else
+ {
+ $text .= $chunk;
+ }
+ }
+ }
+
+ return $string . $text . "\033[0m";
}
//--------------------------------------------------------------------
@@ -523,6 +596,7 @@
{
return 0;
}
+
foreach (static::$foreground_colors as $color)
{
$string = strtr($string, ["\033[" . $color . 'm' => '']);
@@ -541,9 +615,74 @@
//--------------------------------------------------------------------
/**
+ * Checks whether the current stream resource supports or
+ * refers to a valid terminal type device.
+ *
+ * @param string $function
+ * @param resource $resource
+ *
+ * @return boolean
+ */
+ public static function streamSupports(string $function, $resource): bool
+ {
+ if (ENVIRONMENT === 'testing')
+ {
+ // In the current setup of the tests we cannot fully check
+ // if the stream supports the function since we are using
+ // filtered streams.
+ return function_exists($function);
+ }
+
+ // @codeCoverageIgnoreStart
+ return function_exists($function) && @$function($resource);
+ // @codeCoverageIgnoreEnd
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns true if the stream resource supports colors.
+ *
+ * This is tricky on Windows, because Cygwin, Msys2 etc. emulate pseudo
+ * terminals via named pipes, so we can only check the environment.
+ *
+ * Reference: https://github.com/composer/xdebug-handler/blob/master/src/Process.php
+ *
+ * @param resource $resource
+ *
+ * @return boolean
+ */
+ public static function hasColorSupport($resource): bool
+ {
+ // Follow https://no-color.org/
+ if (isset($_SERVER['NO_COLOR']) || getenv('NO_COLOR') !== false)
+ {
+ return false;
+ }
+
+ if (getenv('TERM_PROGRAM') === 'Hyper')
+ {
+ return true;
+ }
+
+ if (static::isWindows())
+ {
+ // @codeCoverageIgnoreStart
+ return static::streamSupports('sapi_windows_vt100_support', $resource)
+ || isset($_SERVER['ANSICON'])
+ || getenv('ANSICON') !== false
+ || getenv('ConEmuANSI') === 'ON'
+ || getenv('TERM') === 'xterm';
+ // @codeCoverageIgnoreEnd
+ }
+
+ return static::streamSupports('stream_isatty', $resource);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
* Attempts to determine the width of the viewable CLI window.
- * This only works on *nix-based systems, so return a sane default
- * for Windows environments.
*
* @param integer $default
*
@@ -551,22 +690,18 @@
*/
public static function getWidth(int $default = 80): int
{
- if (static::isWindows() || (int) shell_exec('tput cols') === 0)
+ if (\is_null(static::$width))
{
- // @codeCoverageIgnoreStart
- return $default;
- // @codeCoverageIgnoreEnd
+ static::generateDimensions();
}
- return (int) shell_exec('tput cols');
+ return static::$width ?: $default;
}
//--------------------------------------------------------------------
/**
* Attempts to determine the height of the viewable CLI window.
- * This only works on *nix-based systems, so return a sane default
- * for Windows environments.
*
* @param integer $default
*
@@ -574,14 +709,67 @@
*/
public static function getHeight(int $default = 32): int
{
- if (static::isWindows())
+ if (\is_null(static::$height))
{
- // @codeCoverageIgnoreStart
- return $default;
- // @codeCoverageIgnoreEnd
+ static::generateDimensions();
}
- return (int) shell_exec('tput lines');
+ return static::$height ?: $default;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Populates the CLI's dimensions.
+ *
+ * @return void
+ */
+ public static function generateDimensions()
+ {
+ if (static::isWindows())
+ {
+ // Shells such as `Cygwin` and `Git bash` returns incorrect values
+ // when executing `mode CON`, so we use `tput` instead
+ // @codeCoverageIgnoreStart
+ if (($shell = getenv('SHELL')) && preg_match('/(?:bash|zsh)(?:\.exe)?$/', $shell) || getenv('TERM'))
+ {
+ static::$height = (int) exec('tput lines');
+ static::$width = (int) exec('tput cols');
+ }
+ else
+ {
+ $return = -1;
+ $output = [];
+ exec('mode CON', $output, $return);
+
+ if ($return === 0 && $output)
+ {
+ // Look for the next lines ending in ": "
+ // Searching for "Columns:" or "Lines:" will fail on non-English locales
+ if (preg_match('/:\s*(\d+)\n[^:]+:\s*(\d+)\n/', implode("\n", $output), $matches))
+ {
+ static::$height = (int) $matches[1];
+ static::$width = (int) $matches[2];
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ }
+ else
+ {
+ if (($size = exec('stty size')) && preg_match('/(\d+)\s+(\d+)/', $size, $matches))
+ {
+ static::$height = (int) $matches[1];
+ static::$width = (int) $matches[2];
+ }
+ else
+ {
+ // @codeCoverageIgnoreStart
+ static::$height = (int) exec('tput lines');
+ static::$width = (int) exec('tput cols');
+ // @codeCoverageIgnoreEnd
+ }
+ }
}
//--------------------------------------------------------------------
@@ -600,7 +788,7 @@
// restore cursor position when progress is continuing.
if ($inProgress !== false && $inProgress <= $thisStep)
{
- fwrite(STDOUT, "\033[1A");
+ static::fwrite(STDOUT, "\033[1A");
}
$inProgress = $thisStep;
@@ -614,13 +802,13 @@
$step = (int) round($percent / 10);
// Write the progress bar
- fwrite(STDOUT, "[\033[32m" . str_repeat('#', $step) . str_repeat('.', 10 - $step) . "\033[0m]");
+ static::fwrite(STDOUT, "[\033[32m" . str_repeat('#', $step) . str_repeat('.', 10 - $step) . "\033[0m]");
// Textual representation...
- fwrite(STDOUT, sprintf(' %3d%% Complete', $percent) . PHP_EOL);
+ static::fwrite(STDOUT, sprintf(' %3d%% Complete', $percent) . PHP_EOL);
}
else
{
- fwrite(STDOUT, "\007");
+ static::fwrite(STDOUT, "\007");
}
}
@@ -660,7 +848,7 @@
$max = $max - $pad_left;
- $lines = wordwrap($string, $max);
+ $lines = wordwrap($string, $max, PHP_EOL);
if ($pad_left > 0)
{
@@ -705,7 +893,7 @@
{
// If there's no '-' at the beginning of the argument
// then add it to our segments.
- if (mb_strpos($_SERVER['argv'][$i], '-') === false)
+ if (mb_strpos($_SERVER['argv'][$i], '-') !== 0)
{
static::$segments[] = $_SERVER['argv'][$i];
continue;
@@ -949,6 +1137,30 @@
}
//--------------------------------------------------------------------
+
+ /**
+ * While the library is intended for use on CLI commands,
+ * commands can be called from controllers and elsewhere
+ * so we need a way to allow them to still work.
+ *
+ * For now, just echo the content, but look into a better
+ * solution down the road.
+ *
+ * @param resource $handle
+ * @param string $string
+ */
+ protected static function fwrite($handle, string $string)
+ {
+ if (is_cli())
+ {
+ fwrite($handle, $string);
+ return;
+ }
+
+ // @codeCoverageIgnoreStart
+ echo $string;
+ // @codeCoverageIgnoreEnd
+ }
}
// Ensure the class is initialized. Done outside of code coverage
diff --git a/system/CLI/CommandRunner.php b/system/CLI/CommandRunner.php
index 2e1a8e1..466ef33 100644
--- a/system/CLI/CommandRunner.php
+++ b/system/CLI/CommandRunner.php
@@ -1,6 +1,5 @@
commands = service('commands');
+ }
+
+ /**
* We map all un-routed CLI methods through this function
* so we have the chance to look for a Command first.
*
@@ -100,104 +99,14 @@
{
$command = array_shift($params);
- $this->createCommandList();
-
if (is_null($command))
{
$command = 'list';
}
- return $this->runCommand($command, $params);
+ return service('commands')->run($command, $params);
}
- //--------------------------------------------------------------------
-
- /**
- * Actually runs the command.
- *
- * @param string $command
- * @param array $params
- *
- * @return mixed
- */
- protected function runCommand(string $command, array $params)
- {
- if (! isset($this->commands[$command]))
- {
- CLI::error(lang('CLI.commandNotFound', [$command]));
- 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.
- *
- * @throws \ReflectionException
- */
- protected function createCommandList()
- {
- $files = Services::locator()->listFiles('Commands/');
-
- // If no matching command files were found, bail
- if (empty($files))
- {
- // This should never happen in unit testing.
- // if it does, we have far bigger problems!
- // @codeCoverageIgnoreStart
- return;
- // @codeCoverageIgnoreEnd
- }
-
- // 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 = Services::locator()->findQualifiedNameFromPath($file);
- if (empty($className) || ! class_exists($className))
- {
- continue;
- }
-
- $class = new \ReflectionClass($className);
-
- if (! $class->isInstantiable() || ! $class->isSubclassOf(BaseCommand::class))
- {
- 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.
*
@@ -205,8 +114,6 @@
*/
public function getCommands(): array
{
- return $this->commands;
+ return $this->commands->getCommands();
}
-
- //--------------------------------------------------------------------
}
diff --git a/system/CLI/Commands.php b/system/CLI/Commands.php
new file mode 100644
index 0000000..cb6d8fd
--- /dev/null
+++ b/system/CLI/Commands.php
@@ -0,0 +1,181 @@
+logger = $logger ?? service('logger');
+ }
+
+ /**
+ * Runs a command given
+ *
+ * @param string $command
+ * @param array $params
+ */
+ public function run(string $command, array $params)
+ {
+ $this->discoverCommands();
+
+ if (! isset($this->commands[$command]))
+ {
+ CLI::error(lang('CLI.commandNotFound', [$command]));
+ 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);
+ }
+
+ /**
+ * Provide access to the list of commands.
+ *
+ * @return array
+ */
+ public function getCommands()
+ {
+ $this->discoverCommands();
+
+ return $this->commands;
+ }
+
+ /**
+ * Discovers all commands in the framework and within user code,
+ * and collects instances of them to work with.
+ */
+ public function discoverCommands()
+ {
+ if (! empty($this->commands))
+ {
+ return;
+ }
+
+ $files = service('locator')->listFiles('Commands/');
+
+ // If no matching command files were found, bail
+ if (empty($files))
+ {
+ // This should never happen in unit testing.
+ // if it does, we have far bigger problems!
+ // @codeCoverageIgnoreStart
+ return;
+ // @codeCoverageIgnoreEnd
+ }
+
+ // 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 = Services::locator()->findQualifiedNameFromPath($file);
+ if (empty($className) || ! class_exists($className))
+ {
+ continue;
+ }
+
+ try
+ {
+ $class = new \ReflectionClass($className);
+
+ if (! $class->isInstantiable() || ! $class->isSubclassOf(BaseCommand::class))
+ {
+ 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);
+ }
+ catch (\ReflectionException $e)
+ {
+ $this->logger->error($e->getMessage());
+ }
+ }
+
+ asort($this->commands);
+ }
+}
diff --git a/system/CLI/Exceptions/CLIException.php b/system/CLI/Exceptions/CLIException.php
index 6e2477f..474064e 100644
--- a/system/CLI/Exceptions/CLIException.php
+++ b/system/CLI/Exceptions/CLIException.php
@@ -1,8 +1,53 @@
-prefix = $config['prefix'] ?? '';
+ $this->prefix = $config->prefix ?: '';
if (! empty($config))
{
- $this->config = array_merge($this->config, $config['memcached']);
+ $this->config = array_merge($this->config, $config->memcached);
}
}
@@ -248,7 +246,8 @@
{
return $this->memcached->set($key, $value, $ttl);
}
- elseif ($this->memcached instanceof \Memcache)
+
+ if ($this->memcached instanceof \Memcache)
{
return $this->memcached->set($key, $value, 0, $ttl);
}
@@ -263,7 +262,7 @@
*
* @param string $key Cache item name
*
- * @return mixed
+ * @return boolean
*/
public function delete(string $key)
{
@@ -322,7 +321,7 @@
/**
* Will delete all items in the entire cache.
*
- * @return mixed
+ * @return boolean
*/
public function clean()
{
diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php
index 255a997..bdfcea0 100644
--- a/system/Cache/Handlers/PredisHandler.php
+++ b/system/Cache/Handlers/PredisHandler.php
@@ -57,8 +57,7 @@
/**
* Default config
*
- * @static
- * @var array
+ * @var array
*/
protected $config = [
'scheme' => 'tcp',
@@ -71,7 +70,7 @@
/**
* Predis connection
*
- * @var Predis
+ * @var \Predis\Client
*/
protected $redis;
@@ -80,8 +79,7 @@
/**
* Constructor.
*
- * @param type $config
- * @throws type
+ * @param \Config\Cache $config
*/
public function __construct($config)
{
@@ -203,7 +201,7 @@
*
* @param string $key Cache item name
*
- * @return mixed
+ * @return boolean
*/
public function delete(string $key)
{
@@ -245,11 +243,11 @@
/**
* Will delete all items in the entire cache.
*
- * @return mixed
+ * @return boolean
*/
public function clean()
{
- return $this->redis->flushdb();
+ return $this->redis->flushdb()->getPayload() === 'OK';
}
//--------------------------------------------------------------------
@@ -282,13 +280,15 @@
if (isset($data['__ci_value']) && $data['__ci_value'] !== false)
{
+ $time = time();
return [
- 'expire' => time() + $this->redis->ttl($key),
+ 'expire' => $time + $this->redis->ttl($key),
+ 'mtime' => $time,
'data' => $data['__ci_value'],
];
}
- return false;
+ return null;
}
//--------------------------------------------------------------------
diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php
index f24b0eb..639d5f5 100644
--- a/system/Cache/Handlers/RedisHandler.php
+++ b/system/Cache/Handlers/RedisHandler.php
@@ -58,8 +58,7 @@
/**
* Default config
*
- * @static
- * @var array
+ * @var array
*/
protected $config = [
'host' => '127.0.0.1',
@@ -72,7 +71,7 @@
/**
* Redis connection
*
- * @var Redis
+ * @var \Redis
*/
protected $redis;
@@ -81,24 +80,22 @@
/**
* Constructor.
*
- * @param type $config
- * @throws type
+ * @param \Config\Cache $config
*/
public function __construct($config)
{
- $config = (array)$config;
- $this->prefix = $config['prefix'] ?? '';
+ $this->prefix = $config->prefix ?: '';
if (! empty($config))
{
- $this->config = array_merge($this->config, $config['redis']);
+ $this->config = array_merge($this->config, $config->redis);
}
}
/**
* Class destructor
*
- * Closes the connection to Memcache(d) if present.
+ * Closes the connection to Redis if present.
*/
public function __destruct()
{
@@ -241,7 +238,7 @@
*
* @param string $key Cache item name
*
- * @return mixed
+ * @return boolean
*/
public function delete(string $key)
{
@@ -289,7 +286,7 @@
/**
* Will delete all items in the entire cache.
*
- * @return mixed
+ * @return boolean
*/
public function clean()
{
diff --git a/system/Cache/Handlers/WincacheHandler.php b/system/Cache/Handlers/WincacheHandler.php
index 305333d..2adf812 100644
--- a/system/Cache/Handlers/WincacheHandler.php
+++ b/system/Cache/Handlers/WincacheHandler.php
@@ -1,6 +1,5 @@
config->defaultLocale ?? 'en');
+
// Set default timezone on the server
date_default_timezone_set($this->config->appTimezone ?? 'UTC');
+ // Define environment variables
+ $this->detectEnvironment();
+ $this->bootstrapEnvironment();
+
// Setup Exception Handling
Services::exceptions()
->initialize();
- $this->detectEnvironment();
- $this->bootstrapEnvironment();
-
$this->initializeKint();
if (! CI_DEBUG)
diff --git a/system/Commands/Cache/ClearCache.php b/system/Commands/Cache/ClearCache.php
new file mode 100644
index 0000000..35c6581
--- /dev/null
+++ b/system/Commands/Cache/ClearCache.php
@@ -0,0 +1,73 @@
+ 'The cache driver to use',
+ ];
+
+ /**
+ * Creates a new migration file with the current timestamp.
+ *
+ * @param array $params
+ */
+ public function run(array $params = [])
+ {
+ $config = config('Cache');
+
+ $handler = $params[0] ?? $config->handler;
+ if (! array_key_exists($handler, $config->validHandlers))
+ {
+ CLI::error($handler . ' is not a valid cache handler.');
+ return;
+ }
+
+ $config->handler = $handler;
+ $cache = CacheFactory::getHandler($config);
+
+ if (! $cache->clean())
+ {
+ CLI::error('Error while clearing the cache.');
+ return;
+ }
+
+ CLI::write(CLI::color('Done', 'green'));
+ }
+}
diff --git a/system/Commands/Database/CreateSeeder.php b/system/Commands/Database/CreateSeeder.php
new file mode 100644
index 0000000..3c7d620
--- /dev/null
+++ b/system/Commands/Database/CreateSeeder.php
@@ -0,0 +1,169 @@
+ 'The seeder file name',
+ ];
+
+ /**
+ * the Command's Options
+ *
+ * @var array
+ */
+ protected $options = [
+ '-n' => 'Set seeder namespace',
+ ];
+
+ /**
+ * Creates a new migration file with the current timestamp.
+ *
+ * @param array $params
+ */
+ public function run(array $params = [])
+ {
+ helper('inflector');
+
+ $name = array_shift($params);
+
+ if (empty($name))
+ {
+ $name = CLI::prompt(lang('Seed.nameFile'), null, 'required');
+ }
+
+ $ns = $params['-n'] ?? CLI::getOption('n');
+ $homepath = APPPATH;
+
+ if (! empty($ns))
+ {
+ // Get all namespaces
+ $namespaces = Services::autoloader()->getNamespace();
+
+ foreach ($namespaces as $namespace => $path)
+ {
+ if ($namespace === $ns)
+ {
+ $homepath = realpath(reset($path)) . DIRECTORY_SEPARATOR;
+ break;
+ }
+ }
+ }
+ else
+ {
+ $ns = defined('APP_NAMESPACE') ? APP_NAMESPACE : 'App';
+ }
+
+ // full path
+ $path = $homepath . 'Database/Seeds/' . $name . '.php';
+
+ // Class name should be pascal case now (camel case with upper first letter)
+ $name = pascalize($name);
+
+ $template = <<psr4 as $ns => $path)
{
- $path = realpath($path) ?? $path;
+ $path = realpath($path) ?: $path;
$tbody[] = [
$ns,
- realpath($path) ?? $path,
+ realpath($path) ?: $path,
is_dir($path) ? 'Yes' : 'MISSING',
];
}
diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php
index 6dfef2f..4d4a2ef 100644
--- a/system/Commands/Utilities/Routes.php
+++ b/system/Commands/Utilities/Routes.php
@@ -127,7 +127,7 @@
foreach ($routes as $route => $handler)
{
// filter for strings, as callbacks aren't displayable
- if(is_string($handler))
+ if (is_string($handler))
{
$tbody[] = [
strtoupper($method),
diff --git a/system/Common.php b/system/Common.php
index 2e3fcd7..22dd413 100644
--- a/system/Common.php
+++ b/system/Common.php
@@ -112,6 +112,34 @@
}
}
+if (! function_exists('command'))
+{
+ /**
+ * Runs a single command.
+ * Input expected in a single string as would
+ * be used on the command line itself:
+ *
+ * > command('migrate:create SomeMigration');
+ *
+ * @param string $command
+ *
+ * @return false|string
+ */
+ function command(string $command)
+ {
+ $runner = service('commands');
+
+ $params = explode(' ', $command);
+ $command = array_shift($params);
+
+ ob_start();
+ $runner->run($command, $params);
+ $output = ob_get_clean();
+
+ return $output;
+ }
+}
+
if (! function_exists('config'))
{
/**
@@ -400,7 +428,7 @@
$response = Services::response(null, true);
}
- if (ENVIRONMENT !== 'testing' && (is_cli() || $request->isSecure()))
+ if ((ENVIRONMENT !== 'testing' && (is_cli() || $request->isSecure())) || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'test'))
{
// @codeCoverageIgnoreStart
return;
@@ -419,7 +447,11 @@
$baseURL = config(App::class)->baseURL;
- if (strpos($baseURL, 'http://') === 0)
+ if (strpos($baseURL, 'https://') === 0)
+ {
+ $baseURL = (string) substr($baseURL, strlen('https://'));
+ }
+ elseif (strpos($baseURL, 'http://') === 0)
{
$baseURL = (string) substr($baseURL, strlen('http://'));
}
diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php
index 38602b3..349fcb0 100644
--- a/system/Config/AutoloadConfig.php
+++ b/system/Config/AutoloadConfig.php
@@ -49,90 +49,65 @@
{
/**
- * Array of namespaces for autoloading.
+ * -------------------------------------------------------------------
+ * Namespaces
+ * -------------------------------------------------------------------
+ * This maps the locations of any namespaces in your application to
+ * their location on the file system. These are used by the autoloader
+ * to locate files the first time they have been instantiated.
+ *
+ * Do not change the name of the CodeIgniter namespace or your application
+ * will break.
*
* @var array
*/
- public $psr4 = [];
+ protected $corePsr4 = [
+ 'CodeIgniter' => SYSTEMPATH,
+ 'App' => APPPATH // To ensure filters, etc still found,
+ ];
/**
- * Map of class names and locations
+ * -------------------------------------------------------------------
+ * Class Map
+ * -------------------------------------------------------------------
+ * The class map provides a map of class names and their exact
+ * location on the drive. Classes loaded in this manner will have
+ * slightly faster performance because they will not have to be
+ * searched for within one or more directories as they would if they
+ * were being autoloaded through a namespace.
*
* @var array
*/
- public $classmap = [];
+ protected $coreClassmap = [
+ 'Psr\Log\AbstractLogger' => SYSTEMPATH . 'ThirdParty/PSR/Log/AbstractLogger.php',
+ 'Psr\Log\InvalidArgumentException' => SYSTEMPATH . 'ThirdParty/PSR/Log/InvalidArgumentException.php',
+ 'Psr\Log\LoggerAwareInterface' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareInterface.php',
+ 'Psr\Log\LoggerAwareTrait' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareTrait.php',
+ 'Psr\Log\LoggerInterface' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerInterface.php',
+ 'Psr\Log\LoggerTrait' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerTrait.php',
+ 'Psr\Log\LogLevel' => SYSTEMPATH . 'ThirdParty/PSR/Log/LogLevel.php',
+ 'Psr\Log\NullLogger' => SYSTEMPATH . 'ThirdParty/PSR/Log/NullLogger.php',
+ 'Laminas\Escaper\Escaper' => SYSTEMPATH . 'ThirdParty/Escaper/Escaper.php'
+ ];
//--------------------------------------------------------------------
/**
* Constructor.
+ *
+ * Merge the built-in and developer-configured psr4 and classmap,
+ * with preference to the developer ones.
*/
public function __construct()
{
- /**
- * -------------------------------------------------------------------
- * Namespaces
- * -------------------------------------------------------------------
- * This maps the locations of any namespaces in your application
- * to their location on the file system. These are used by the
- * Autoloader to locate files the first time they have been instantiated.
- *
- * The '/application' and '/system' directories are already mapped for
- * you. You may change the name of the 'App' namespace if you wish,
- * but this should be done prior to creating any namespaced classes,
- * else you will need to modify all of those classes for this to work.
- *
- * DO NOT change the name of the CodeIgniter namespace or your application
- * WILL break. *
- * Prototype:
- *
- * $Config['psr4'] = [
- * 'CodeIgniter' => SYSPATH
- * `];
- */
- $this->psr4 = [
- 'CodeIgniter' => realpath(SYSTEMPATH),
- ];
-
if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing')
{
$this->psr4['Tests\Support'] = SUPPORTPATH;
- }
-
- /**
- * -------------------------------------------------------------------
- * Class Map
- * -------------------------------------------------------------------
- * The class map provides a map of class names and their exact
- * location on the drive. Classes loaded in this manner will have
- * slightly faster performance because they will not have to be
- * searched for within one or more directories as they would if they
- * were being autoloaded through a namespace.
- *
- * Prototype:
- *
- * $Config['classmap'] = [
- * 'MyClass' => '/path/to/class/file.php'
- * ];
- */
- $this->classmap = [
- 'Psr\Log\AbstractLogger' => SYSTEMPATH . 'ThirdParty/PSR/Log/AbstractLogger.php',
- 'Psr\Log\InvalidArgumentException' => SYSTEMPATH . 'ThirdParty/PSR/Log/InvalidArgumentException.php',
- 'Psr\Log\LoggerAwareInterface' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareInterface.php',
- 'Psr\Log\LoggerAwareTrait' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerAwareTrait.php',
- 'Psr\Log\LoggerInterface' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerInterface.php',
- 'Psr\Log\LoggerTrait' => SYSTEMPATH . 'ThirdParty/PSR/Log/LoggerTrait.php',
- 'Psr\Log\LogLevel' => SYSTEMPATH . 'ThirdParty/PSR/Log/LogLevel.php',
- 'Psr\Log\NullLogger' => SYSTEMPATH . 'ThirdParty/PSR/Log/NullLogger.php',
- 'Laminas\Escaper\Escaper' => SYSTEMPATH . 'ThirdParty/Escaper/Escaper.php',
- ];
-
- if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing')
- {
$this->classmap['CodeIgniter\Log\TestLogger'] = SYSTEMPATH . 'Test/TestLogger.php';
$this->classmap['CIDatabaseTestCase'] = SYSTEMPATH . 'Test/CIDatabaseTestCase.php';
}
- }
- //--------------------------------------------------------------------
+ $this->psr4 = array_merge($this->corePsr4, $this->psr4);
+ $this->classmap = array_merge($this->coreClassmap, $this->classmap);
+ }
}
diff --git a/system/Config/BaseConfig.php b/system/Config/BaseConfig.php
index ceec8ba..256f376 100644
--- a/system/Config/BaseConfig.php
+++ b/system/Config/BaseConfig.php
@@ -92,6 +92,12 @@
foreach ($properties as $property)
{
$this->initEnvValue($this->$property, $property, $prefix, $shortPrefix);
+
+ // Handle hex2bin prefix
+ if ($shortPrefix === 'encryption' && $property === 'key' && strpos($this->$property, 'hex2bin:') === 0)
+ {
+ $this->$property = hex2bin(substr($this->$property, 8));
+ }
}
if (defined('ENVIRONMENT') && ENVIRONMENT !== 'testing')
diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php
index 4220d77..2b7d912 100644
--- a/system/Config/DotEnv.php
+++ b/system/Config/DotEnv.php
@@ -78,17 +78,7 @@
{
$vars = $this->parse();
- if ($vars === null)
- {
- return false;
- }
-
- foreach ($vars as $name => $value)
- {
- $this->setVariable($name, $value);
- }
-
- return true; // for success
+ return ($vars === null ? false : true);
}
//--------------------------------------------------------------------
@@ -129,6 +119,7 @@
{
list($name, $value) = $this->normaliseVariable($line);
$vars[$name] = $value;
+ $this->setVariable($name, $value);
}
}
@@ -191,6 +182,12 @@
$value = $this->resolveNestedVariables($value);
+ // Handle hex2bin prefix
+ if ($name === 'encryption.key' && strpos($value, 'hex2bin:') === 0)
+ {
+ $value = hex2bin(substr($value, 8));
+ }
+
return [
$name,
$value,
diff --git a/system/Config/Services.php b/system/Config/Services.php
index afb6512..b3c9854 100644
--- a/system/Config/Services.php
+++ b/system/Config/Services.php
@@ -152,6 +152,23 @@
//--------------------------------------------------------------------
/**
+ * The commands utility for running and working with CLI commands.
+ *
+ * @param boolean $getShared
+ *
+ * @return \CodeIgniter\CLI\Commands|mixed
+ */
+ public static function commands(bool $getShared = true)
+ {
+ if ($getShared)
+ {
+ return static::getSharedInstance('commands');
+ }
+
+ return new \CodeIgniter\CLI\Commands();
+ }
+
+ /**
* The CURL Request class acts as a simple HTTP client for interacting
* with other servers, typically through APIs.
*
@@ -340,9 +357,9 @@
* Acts as a factory for ImageHandler classes and returns an instance
* of the handler. Used like Services::image()->withFile($path)->rotate(90)->save();
*
- * @param string $handler
- * @param mixed $config
- * @param boolean $getShared
+ * @param string|null $handler
+ * @param \Config\Images|null $config
+ * @param boolean $getShared
*
* @return \CodeIgniter\Images\Handlers\BaseHandler
*/
@@ -760,7 +777,7 @@
$logger = static::logger();
$driverName = $config->sessionDriver;
- $driver = new $driverName($config, static::request()->getIpAddress());
+ $driver = new $driverName($config, static::request()->getIPAddress());
$driver->setLogger($logger);
$session = new Session($driver, $config);
diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php
index 33e7004..9e2f3eb 100644
--- a/system/Database/BaseBuilder.php
+++ b/system/Database/BaseBuilder.php
@@ -252,6 +252,20 @@
*/
protected $testMode = false;
+ /**
+ * Tables relation types
+ *
+ * @var array
+ */
+ protected $joinTypes = [
+ 'LEFT',
+ 'RIGHT',
+ 'OUTER',
+ 'INNER',
+ 'LEFT OUTER',
+ 'RIGHT OUTER',
+ ];
+
//--------------------------------------------------------------------
/**
@@ -370,7 +384,7 @@
* This prevents NULL being escaped
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1169
*/
- if (strtoupper(mb_substr(trim($val), 0, 4)) === 'NULL')
+ if (mb_stripos(trim($val), 'NULL') === 0)
{
$escape = false;
}
@@ -623,7 +637,7 @@
{
$type = strtoupper(trim($type));
- if (! in_array($type, ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'], true))
+ if (! in_array($type, $this->joinTypes, true))
{
$type = '';
}
@@ -663,6 +677,7 @@
$pos = $joints[$i][1] - strlen($joints[$i][0]);
$joints[$i] = $joints[$i][0];
}
+ ksort($conditions);
}
else
{
@@ -671,11 +686,11 @@
}
$cond = ' ON ';
- for ($i = 0, $c = count($conditions); $i < $c; $i ++)
+ foreach ($conditions as $i => $condition)
{
- $operator = $this->getOperator($conditions[$i]);
+ $operator = $this->getOperator($condition);
$cond .= $joints[$i];
- $cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)" . preg_quote($operator) . '(.*)/i', $conditions[$i], $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $conditions[$i];
+ $cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)" . preg_quote($operator) . '(.*)/i', $condition, $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $condition;
}
}
@@ -2438,7 +2453,9 @@
{
$this->resetWrite();
- if ($this->db->query($sql, $this->binds, false))
+ $result = $this->db->query($sql, $this->binds, false);
+
+ if ($result->resultID !== false)
{
// Clear our binds so we don't eat up memory
$this->binds = [];
@@ -3045,28 +3062,28 @@
{
if (! empty($this->$qb_key))
{
- for ($i = 0, $c = count($this->$qb_key); $i < $c; $i ++)
+ foreach ($this->$qb_key as &$qbkey)
{
// Is this condition already compiled?
- if (is_string($this->{$qb_key}[$i]))
+ if (is_string($qbkey))
{
continue;
}
- elseif ($this->{$qb_key}[$i]['escape'] === false)
+ elseif ($qbkey['escape'] === false)
{
- $this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
+ $qbkey = $qbkey['condition'];
continue;
}
// Split multiple conditions
$conditions = preg_split(
- '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', $this->{$qb_key}[$i]['condition'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
+ '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', $qbkey['condition'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
- for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci ++)
+ foreach ($conditions as &$condition)
{
- if (($op = $this->getOperator($conditions[$ci])) === false
- || ! preg_match('/^(\(?)(.*)(' . preg_quote($op, '/') . ')\s*(.*(?getOperator($condition)) === false
+ || ! preg_match('/^(\(?)(.*)(' . preg_quote($op, '/') . ')\s*(.*(?db->protectIdentifiers(trim($matches[2]))
+ $condition = $matches[1] . $this->db->protectIdentifiers(trim($matches[2]))
. ' ' . trim($matches[3]) . $matches[4] . $matches[5];
}
- $this->{$qb_key}[$i] = implode('', $conditions);
+ $qbkey = implode('', $conditions);
}
return ($qb_key === 'QBHaving' ? "\nHAVING " : "\nWHERE ")
@@ -3127,16 +3144,16 @@
{
if (! empty($this->QBGroupBy))
{
- for ($i = 0, $c = count($this->QBGroupBy); $i < $c; $i ++)
+ foreach ($this->QBGroupBy as &$groupBy)
{
// Is it already compiled?
- if (is_string($this->QBGroupBy[$i]))
+ if (is_string($groupBy))
{
continue;
}
- $this->QBGroupBy[$i] = ($this->QBGroupBy[$i]['escape'] === false ||
- $this->isLiteral($this->QBGroupBy[$i]['field'])) ? $this->QBGroupBy[$i]['field'] : $this->db->protectIdentifiers($this->QBGroupBy[$i]['field']);
+ $groupBy = ($groupBy['escape'] === false ||
+ $this->isLiteral($groupBy['field'])) ? $groupBy['field'] : $this->db->protectIdentifiers($groupBy['field']);
}
return "\nGROUP BY " . implode(', ', $this->QBGroupBy);
@@ -3162,14 +3179,14 @@
{
if (is_array($this->QBOrderBy) && ! empty($this->QBOrderBy))
{
- for ($i = 0, $c = count($this->QBOrderBy); $i < $c; $i ++)
+ foreach ($this->QBOrderBy as &$orderBy)
{
- if ($this->QBOrderBy[$i]['escape'] !== false && ! $this->isLiteral($this->QBOrderBy[$i]['field']))
+ if ($orderBy['escape'] !== false && ! $this->isLiteral($orderBy['field']))
{
- $this->QBOrderBy[$i]['field'] = $this->db->protectIdentifiers($this->QBOrderBy[$i]['field']);
+ $orderBy['field'] = $this->db->protectIdentifiers($orderBy['field']);
}
- $this->QBOrderBy[$i] = $this->QBOrderBy[$i]['field'] . $this->QBOrderBy[$i]['direction'];
+ $orderBy = $orderBy['field'] . $orderBy['direction'];
}
return $this->QBOrderBy = "\nORDER BY " . implode(', ', $this->QBOrderBy);
diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php
index 844cf7b..e1c4f7a 100644
--- a/system/Database/BaseConnection.php
+++ b/system/Database/BaseConnection.php
@@ -373,8 +373,15 @@
$this->connectTime = microtime(true);
- // Connect to the database and set the connection ID
- $this->connID = $this->connect($this->pConnect);
+ try
+ {
+ // Connect to the database and set the connection ID
+ $this->connID = $this->connect($this->pConnect);
+ }
+ catch (\Throwable $e)
+ {
+ log_message('error', 'Error connecting to the database: ' . $e->getMessage());
+ }
// No connection resource? Check if there is a failover else throw an error
if (! $this->connID)
@@ -394,8 +401,15 @@
}
}
- // Try to connect
- $this->connID = $this->connect($this->pConnect);
+ try
+ {
+ // Try to connect
+ $this->connID = $this->connect($this->pConnect);
+ }
+ catch (\Throwable $e)
+ {
+ log_message('error', 'Error connecting to the database: ' . $e->getMessage());
+ }
// If a connection is made break the foreach loop
if ($this->connID)
@@ -544,17 +558,6 @@
//--------------------------------------------------------------------
/**
- * Returns the last error encountered by this connection.
- *
- * @return mixed
- */
- public function getError()
- {
- }
-
- //--------------------------------------------------------------------
-
- /**
* The name of the platform in use (MySQLi, mssql, etc)
*
* @return string
diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php
index 5e6abe3..5c6500e 100644
--- a/system/Database/BaseResult.php
+++ b/system/Database/BaseResult.php
@@ -167,11 +167,11 @@
$_data = null;
if (($c = count($this->resultArray)) > 0)
{
- $_data = 'result_array';
+ $_data = 'resultArray';
}
elseif (($c = count($this->resultObject)) > 0)
{
- $_data = 'result_object';
+ $_data = 'resultObject';
}
if ($_data !== null)
diff --git a/system/Database/ConnectionInterface.php b/system/Database/ConnectionInterface.php
index dffa5f3..2df836e 100644
--- a/system/Database/ConnectionInterface.php
+++ b/system/Database/ConnectionInterface.php
@@ -124,7 +124,7 @@
*
* @return mixed
*/
- public function getError();
+ public function error(): array;
//--------------------------------------------------------------------
diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php
index 9bda5b0..206b9f0 100644
--- a/system/Database/MySQLi/Connection.php
+++ b/system/Database/MySQLi/Connection.php
@@ -103,7 +103,7 @@
{
$hostname = ($persistent === true) ? 'p:' . $this->hostname : $this->hostname;
$port = empty($this->port) ? null : $this->port;
- $socket = null;
+ $socket = '';
}
$client_flags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0;
diff --git a/system/Database/MySQLi/Result.php b/system/Database/MySQLi/Result.php
index 32f5f90..7a7c585 100644
--- a/system/Database/MySQLi/Result.php
+++ b/system/Database/MySQLi/Result.php
@@ -87,6 +87,42 @@
*/
public function getFieldData(): array
{
+ static $data_types = [
+ MYSQLI_TYPE_DECIMAL => 'decimal',
+ MYSQLI_TYPE_NEWDECIMAL => 'newdecimal',
+ MYSQLI_TYPE_FLOAT => 'float',
+ MYSQLI_TYPE_DOUBLE => 'double',
+
+ MYSQLI_TYPE_BIT => 'bit',
+ MYSQLI_TYPE_TINY => 'tiny',
+ MYSQLI_TYPE_SHORT => 'short',
+ MYSQLI_TYPE_LONG => 'long',
+ MYSQLI_TYPE_LONGLONG => 'longlong',
+ MYSQLI_TYPE_INT24 => 'int24',
+
+ MYSQLI_TYPE_YEAR => 'year',
+
+ MYSQLI_TYPE_TIMESTAMP => 'timestamp',
+ MYSQLI_TYPE_DATE => 'date',
+ MYSQLI_TYPE_TIME => 'time',
+ MYSQLI_TYPE_DATETIME => 'datetime',
+ MYSQLI_TYPE_NEWDATE => 'newdate',
+
+ MYSQLI_TYPE_INTERVAL => 'interval',
+ MYSQLI_TYPE_SET => 'set',
+ MYSQLI_TYPE_ENUM => 'enum',
+
+ MYSQLI_TYPE_VAR_STRING => 'var_string',
+ MYSQLI_TYPE_STRING => 'string',
+ MYSQLI_TYPE_CHAR => 'char',
+
+ MYSQLI_TYPE_GEOMETRY => 'geometry',
+ MYSQLI_TYPE_TINY_BLOB => 'tiny_blob',
+ MYSQLI_TYPE_MEDIUM_BLOB => 'medium_blob',
+ MYSQLI_TYPE_LONG_BLOB => 'long_blob',
+ MYSQLI_TYPE_BLOB => 'blob',
+ ];
+
$retVal = [];
$fieldData = $this->resultID->fetch_fields();
@@ -95,8 +131,10 @@
$retVal[$i] = new \stdClass();
$retVal[$i]->name = $data->name;
$retVal[$i]->type = $data->type;
+ $retVal[$i]->type_name = isset($data_types[$data->type]) ? $data_types[$data->type] : null;
$retVal[$i]->max_length = $data->max_length;
$retVal[$i]->primary_key = (int) ($data->flags & 2);
+ $retVal[$i]->length = $data->length;
$retVal[$i]->default = $data->def;
}
diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php
index fa45c45..4bace0b 100644
--- a/system/Database/Postgre/Builder.php
+++ b/system/Database/Postgre/Builder.php
@@ -405,4 +405,29 @@
}
//--------------------------------------------------------------------
+
+ /**
+ * JOIN
+ *
+ * Generates the JOIN portion of the query
+ *
+ * @param string $table
+ * @param string $cond The join condition
+ * @param string $type The type of join
+ * @param boolean $escape Whether not to try to escape identifiers
+ *
+ * @return BaseBuilder
+ */
+ public function join(string $table, string $cond, string $type = '', bool $escape = null)
+ {
+ if (! in_array('FULL OUTER', $this->joinTypes, true))
+ {
+ $this->joinTypes = array_merge($this->joinTypes, ['FULL OUTER']);
+ }
+
+ return parent::join($table, $cond, $type, $escape);
+ }
+
+ //--------------------------------------------------------------------
+
}
diff --git a/system/Database/Postgre/Forge.php b/system/Database/Postgre/Forge.php
index c474459..9b0bbdb 100644
--- a/system/Database/Postgre/Forge.php
+++ b/system/Database/Postgre/Forge.php
@@ -227,7 +227,7 @@
{
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true)
{
- $field['type'] = $field['type'] === 'NUMERIC' ? 'BIGSERIAL' : 'SERIAL';
+ $field['type'] = $field['type'] === 'NUMERIC' || $field['type'] === 'BIGINT' ? 'BIGSERIAL' : 'SERIAL';
}
}
diff --git a/system/Database/Postgre/Result.php b/system/Database/Postgre/Result.php
index a58a14e..fcdd509 100644
--- a/system/Database/Postgre/Result.php
+++ b/system/Database/Postgre/Result.php
@@ -92,8 +92,10 @@
{
$retVal[$i] = new \stdClass();
$retVal[$i]->name = pg_field_name($this->resultID, $i);
- $retVal[$i]->type = pg_field_type($this->resultID, $i);
+ $retVal[$i]->type = pg_field_type_oid($this->resultID, $i);
+ $retVal[$i]->type_name = pg_field_type($this->resultID, $i);
$retVal[$i]->max_length = pg_field_size($this->resultID, $i);
+ $retVal[$i]->length = $retVal[$i]->max_length;
// $retVal[$i]->primary_key = (int)($fieldData[$i]->flags & 2);
// $retVal[$i]->default = $fieldData[$i]->def;
}
diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php
index b302f56..3a01578 100644
--- a/system/Database/SQLite3/Builder.php
+++ b/system/Database/SQLite3/Builder.php
@@ -48,13 +48,6 @@
{
/**
- * Identifier escape character
- *
- * @var string
- */
- protected $escapeChar = '`';
-
- /**
* Default installs of SQLite typically do not
* support limiting delete clauses.
*
diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php
index d3ac1cb..b9851e3 100644
--- a/system/Database/SQLite3/Connection.php
+++ b/system/Database/SQLite3/Connection.php
@@ -56,7 +56,14 @@
*/
public $DBDriver = 'SQLite3';
- // --------------------------------------------------------------------
+ /**
+ * Identifier escape character
+ *
+ * @var string
+ */
+ public $escapeChar = '`';
+
+ //--------------------------------------------------------------------
/**
* ORDER BY random keyword
@@ -85,6 +92,11 @@
}
try
{
+ if ($this->database !== ':memory:' && strpos($this->database, DIRECTORY_SEPARATOR) === false)
+ {
+ $this->database = WRITEPATH . $this->database;
+ }
+
return (! $this->password)
? new \SQLite3($this->database)
: new \SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password);
diff --git a/system/Database/SQLite3/Result.php b/system/Database/SQLite3/Result.php
index fac0725..ae293d6 100644
--- a/system/Database/SQLite3/Result.php
+++ b/system/Database/SQLite3/Result.php
@@ -101,8 +101,10 @@
$retVal[$i] = new \stdClass();
$retVal[$i]->name = $this->resultID->columnName($i);
$type = $this->resultID->columnType($i);
- $retVal[$i]->type = isset($data_types[$type]) ? $data_types[$type] : $type;
+ $retVal[$i]->type = $type;
+ $retVal[$i]->type_name = isset($data_types[$type]) ? $data_types[$type] : null;
$retVal[$i]->max_length = null;
+ $retVal[$i]->length = null;
}
return $retVal;
diff --git a/system/Debug/Exceptions.php b/system/Debug/Exceptions.php
index 7f7ca2c..3b915e3 100644
--- a/system/Debug/Exceptions.php
+++ b/system/Debug/Exceptions.php
@@ -104,7 +104,7 @@
{
$this->ob_level = ob_get_level();
- $this->viewPath = rtrim($config->errorViewPath, '/ ') . '/';
+ $this->viewPath = rtrim($config->errorViewPath, '\\/ ') . DIRECTORY_SEPARATOR;
$this->config = $config;
@@ -139,13 +139,15 @@
* and fire an event that allows custom actions to be taken at this point.
*
* @param \Throwable $exception
+ *
+ * @codeCoverageIgnore
*/
public function exceptionHandler(Throwable $exception)
{
- // @codeCoverageIgnoreStart
- $codes = $this->determineCodes($exception);
- $statusCode = $codes[0];
- $exitCode = $codes[1];
+ [
+ $statusCode,
+ $exitCode,
+ ] = $this->determineCodes($exception);
// Log it
if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes))
@@ -172,7 +174,6 @@
$this->render($exception, $statusCode);
exit($exitCode);
- // @codeCoverageIgnoreEnd
}
//--------------------------------------------------------------------
@@ -240,7 +241,7 @@
{
// Production environments should have a custom exception file.
$view = 'production.php';
- $template_path = rtrim($template_path, '/ ') . '/';
+ $template_path = rtrim($template_path, '\\/ ') . DIRECTORY_SEPARATOR;
if (str_ireplace(['off', 'none', 'no', 'false', 'null'], '', ini_get('display_errors')))
{
@@ -254,7 +255,7 @@
}
// Allow for custom views based upon the status code
- else if (is_file($template_path . 'error_' . $exception->getCode() . '.php'))
+ if (is_file($template_path . 'error_' . $exception->getCode() . '.php'))
{
return 'error_' . $exception->getCode() . '.php';
}
@@ -272,18 +273,26 @@
*/
protected function render(Throwable $exception, int $statusCode)
{
- // Determine directory with views
- $path = $this->viewPath;
- if (empty($path))
+ // Determine possible directories of error views
+ $path = $this->viewPath;
+ $altPath = rtrim((new Paths())->viewDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR;
+
+ $path .= (is_cli() ? 'cli' : 'html') . DIRECTORY_SEPARATOR;
+ $altPath .= (is_cli() ? 'cli' : 'html') . DIRECTORY_SEPARATOR;
+
+ // Determine the views
+ $view = $this->determineView($exception, $path);
+ $altView = $this->determineView($exception, $altPath);
+
+ // Check if the view exists
+ if (is_file($path . $view))
{
- $paths = new Paths();
- $path = $paths->viewDirectory . '/errors/';
+ $viewFile = $path . $view;
}
-
- $path = is_cli() ? $path . 'cli/' : $path . 'html/';
-
- // Determine the vew
- $view = $this->determineView($exception, $path);
+ elseif (is_file($altPath . $altView))
+ {
+ $viewFile = $altPath . $altView;
+ }
// Prepare the vars
$vars = $this->collectVars($exception, $statusCode);
@@ -296,7 +305,7 @@
}
ob_start();
- include($path . $view);
+ include $viewFile;
$buffer = ob_get_contents();
ob_end_clean();
echo $buffer;
@@ -383,7 +392,7 @@
case strpos($file, FCPATH) === 0:
$file = 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH));
break;
- case defined('VENDORPATH') && strpos($file, VENDORPATH) === 0;
+ case defined('VENDORPATH') && strpos($file, VENDORPATH) === 0:
$file = 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(VENDORPATH));
break;
}
diff --git a/system/Debug/Timer.php b/system/Debug/Timer.php
index b12b52d..736f558 100644
--- a/system/Debug/Timer.php
+++ b/system/Debug/Timer.php
@@ -45,10 +45,7 @@
* Provides a simple way to measure the amount of time
* that elapses between two points.
*
- * NOTE: All methods are static since the class is intended
- * to measure throughout an entire application's life cycle.
- *
- * @package CodeIgniter\Benchmark
+ * @package CodeIgniter\Debug
*/
class Timer
{
diff --git a/system/Debug/Toolbar/Collectors/BaseCollector.php b/system/Debug/Toolbar/Collectors/BaseCollector.php
index dade0ab..a403b4c 100644
--- a/system/Debug/Toolbar/Collectors/BaseCollector.php
+++ b/system/Debug/Toolbar/Collectors/BaseCollector.php
@@ -38,6 +38,8 @@
namespace CodeIgniter\Debug\Toolbar\Collectors;
+use CodeIgniter\Debug\Exceptions;
+
/**
* Base Toolbar collector
*/
@@ -253,20 +255,7 @@
*/
public function cleanPath(string $file): string
{
- if (strpos($file, APPPATH) === 0)
- {
- $file = 'APPPATH/' . substr($file, strlen(APPPATH));
- }
- elseif (strpos($file, SYSTEMPATH) === 0)
- {
- $file = 'SYSTEMPATH/' . substr($file, strlen(SYSTEMPATH));
- }
- elseif (strpos($file, FCPATH) === 0)
- {
- $file = 'FCPATH/' . substr($file, strlen(FCPATH));
- }
-
- return $file;
+ return Exceptions::cleanPath($file);
}
/**
diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php
index 22ea7c2..f75374d 100644
--- a/system/Debug/Toolbar/Collectors/Routes.php
+++ b/system/Debug/Toolbar/Collectors/Routes.php
@@ -134,8 +134,8 @@
/*
* Defined Routes
*/
- $routes = [];
- $methods = [
+ $routes = [];
+ $methods = [
'get',
'head',
'post',
@@ -158,8 +158,8 @@
if (is_string($handler))
{
$routes[] = [
- 'method' => strtoupper($method),
- 'route' => $route,
+ 'method' => strtoupper($method),
+ 'route' => $route,
'handler' => $handler,
];
}
diff --git a/system/Debug/Toolbar/Views/_routes.tpl b/system/Debug/Toolbar/Views/_routes.tpl
index 35acdde..e277046 100644
--- a/system/Debug/Toolbar/Views/_routes.tpl
+++ b/system/Debug/Toolbar/Views/_routes.tpl
@@ -44,7 +44,7 @@
{routes}
{method}
-
{route}
+
{route}
{handler}
{/routes}
diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css
index 7a4c9c4..e2abb4c 100644
--- a/system/Debug/Toolbar/Views/toolbar.css
+++ b/system/Debug/Toolbar/Views/toolbar.css
@@ -148,7 +148,9 @@
clear: left;
display: inline-block;
float: left;
- margin: 6px 3px 6px 0; }
+ margin: 6px 3px 6px 0;
+ width: 16px !important;
+ }
#debug-bar .ci-label .badge {
border-radius: 12px;
-moz-border-radius: 12px;
@@ -593,3 +595,15 @@
.debug-bar-noverflow {
overflow: hidden; }
+
+#debug-bar td[data-debugbar-route] form {
+ display: none; }
+#debug-bar td[data-debugbar-route]:hover form {
+ display: block; }
+#debug-bar td[data-debugbar-route]:hover > div {
+ display: none; }
+#debug-bar td[data-debugbar-route] input[type=text] {
+ padding: 2px; }
+#toolbarContainer.dark td[data-debugbar-route] input[type=text] {
+ background: #000;
+ color: #fff; }
diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js
index f146da4..15fa668 100644
--- a/system/Debug/Toolbar/Views/toolbar.js
+++ b/system/Debug/Toolbar/Views/toolbar.js
@@ -20,6 +20,7 @@
ciDebugBar.setToolbarPosition();
ciDebugBar.setToolbarTheme();
ciDebugBar.toggleViewsHints();
+ ciDebugBar.routerLink();
document.getElementById('debug-bar-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
document.getElementById('debug-icon-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
@@ -545,7 +546,7 @@
}
else
{
- // In any other cases: if there is no cookie, or the cookie is set to
+ // In any other cases: if there is no cookie, or the cookie is set to
// "light", or the "prefers-color-scheme" is "light"...
ciDebugBar.createCookie('debug-bar-theme', 'dark', 365);
ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'light');
@@ -600,5 +601,61 @@
}
}
return null;
+ },
+
+ //--------------------------------------------------------------------
+
+ trimSlash: function(text) {
+ return text.replace(/^\/|\/$/g, '');
+ },
+
+ routerLink: function() {
+ var row, _location;
+ var rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"]');
+ var patt = /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/;
+
+ for (var i = 0; i < rowGet.length; i++) {
+ row = rowGet[i];
+ if (!/\/\(.+?\)/.test(rowGet[i].innerText)) {
+ row.style = 'cursor: pointer;';
+ row.setAttribute('title', location.origin + '/' + ciDebugBar.trimSlash(row.innerText));
+ row.addEventListener('click', function(ev) {
+ _location = location.origin + '/' + ciDebugBar.trimSlash(ev.target.innerText);
+ var redirectWindow = window.open(_location, '_blank');
+ redirectWindow.location;
+ });
+ }
+ else {
+ row.innerHTML = '
' + row.innerText + '
'
+ + '';
+ }
+ }
+
+ rowGet = document.querySelectorAll('#debug-bar td[data-debugbar-route="GET"] form');
+ for (var i = 0; i < rowGet.length; i++) {
+ row = rowGet[i];
+
+ row.addEventListener('submit', function(event) {
+ event.preventDefault()
+ var inputArray = [], t = 0;
+ var input = event.target.querySelectorAll('input[type=text]');
+ var tpl = event.target.getAttribute('data-debugbar-route-tpl');
+
+ for (var n = 0; n < input.length; n++) {
+ if (input[n].value.length > 0) inputArray.push(input[n].value);
+ }
+
+ if (inputArray.length > 0) {
+ _location = location.origin + '/' + tpl.replace(/\?/g, function() {return inputArray[t++]});
+ var redirectWindow = window.open(_location, '_blank');
+ redirectWindow.location;
+ }
+ })
+ }
+
}
+
};
diff --git a/system/Email/Email.php b/system/Email/Email.php
index 5ace58a..b3a3dcd 100644
--- a/system/Email/Email.php
+++ b/system/Email/Email.php
@@ -39,6 +39,7 @@
namespace CodeIgniter\Email;
+use CodeIgniter\Events\Events;
use Config\Mimes;
/**
@@ -55,6 +56,18 @@
class Email
{
/**
+ * Properties from the last successful send.
+ *
+ * @var array|null
+ */
+ public $archive;
+ /**
+ * Properties to be added to the next archive.
+ *
+ * @var array
+ */
+ protected $tmpArchive = [];
+ /**
* @var string
*/
public $fromEmail;
@@ -451,6 +464,11 @@
$this->validateEmail($this->stringToArray($returnPath));
}
}
+
+ // Store the plain text values
+ $this->tmpArchive['fromEmail'] = $from;
+ $this->tmpArchive['fromName'] = $name;
+
// prepare the display name
if ($name !== '')
{
@@ -468,6 +486,8 @@
$this->setHeader('From', $name . ' <' . $from . '>');
isset($returnPath) || $returnPath = $from;
$this->setHeader('Return-Path', '<' . $returnPath . '>');
+ $this->tmpArchive['returnPath'] = $returnPath;
+
return $this;
}
//--------------------------------------------------------------------
@@ -491,6 +511,8 @@
}
if ($name !== '')
{
+ $this->tmpArchive['replyName'] = $name;
+
// only use Q encoding if there are characters that would require it
if (! preg_match('/[\200-\377]/', $name))
{
@@ -503,7 +525,9 @@
}
}
$this->setHeader('Reply-To', $name . ' <' . $replyto . '>');
- $this->replyToFlag = true;
+ $this->replyToFlag = true;
+ $this->tmpArchive['replyTo'] = $replyto;
+
return $this;
}
//--------------------------------------------------------------------
@@ -549,6 +573,7 @@
{
$this->CCArray = $cc;
}
+ $this->tmpArchive['CCArray'] = $cc;
return $this;
}
//--------------------------------------------------------------------
@@ -579,6 +604,7 @@
else
{
$this->setHeader('Bcc', implode(', ', $bcc));
+ $this->tmpArchive['BCCArray'] = $bcc;
}
return $this;
}
@@ -592,6 +618,8 @@
*/
public function setSubject($subject)
{
+ $this->tmpArchive['subject'] = $subject;
+
$subject = $this->prepQEncoding($subject);
$this->setHeader('Subject', $subject);
return $this;
@@ -1530,8 +1558,7 @@
{
$this->setReplyTo($this->headers['From']);
}
- if (empty($this->recipients) && ! isset($this->headers['To']) && empty($this->BCCArray) && ! isset($this->headers['Bcc']) && ! isset($this->headers['Cc'])
- )
+ if (empty($this->recipients) && ! isset($this->headers['To']) && empty($this->BCCArray) && ! isset($this->headers['Bcc']) && ! isset($this->headers['Cc']))
{
$this->setErrorMessage(lang('Email.noRecipients'));
return false;
@@ -1548,10 +1575,18 @@
}
$this->buildMessage();
$result = $this->spoolEmail();
- if ($result && $autoClear)
+ if ($result)
{
- $this->clear();
+ $this->setArchiveValues();
+
+ if ($autoClear)
+ {
+ $this->clear();
+ }
+
+ Events::trigger('email', $this->archive);
}
+
return $result;
}
//--------------------------------------------------------------------
@@ -1595,6 +1630,10 @@
$this->buildMessage();
$this->spoolEmail();
}
+
+ // Update the archive
+ $this->setArchiveValues();
+ Events::trigger('email', $this->archive);
}
//--------------------------------------------------------------------
/**
@@ -1688,20 +1727,18 @@
*/
protected function sendWithMail()
{
- if (is_array($this->recipients))
- {
- $this->recipients = implode(', ', $this->recipients);
- }
+ $recipients = is_array($this->recipients) ? implode(', ', $this->recipients) : $this->recipients;
+
// _validate_email_for_shell() below accepts by reference,
// so this needs to be assigned to a variable
$from = $this->cleanEmail($this->headers['Return-Path']);
if (! $this->validateEmailForShell($from))
{
- return mail($this->recipients, $this->subject, $this->finalBody, $this->headerStr);
+ return mail($recipients, $this->subject, $this->finalBody, $this->headerStr);
}
// most documentation of sendmail using the "-f" flag lacks a space after it, however
// we've encountered servers that seem to require it to be in place.
- return mail($this->recipients, $this->subject, $this->finalBody, $this->headerStr, '-f ' . $from);
+ return mail($recipients, $this->subject, $this->finalBody, $this->headerStr, '-f ' . $from);
}
//--------------------------------------------------------------------
/**
@@ -2131,4 +2168,21 @@
}
return isset($length) ? substr($str, $start, $length) : substr($str, $start);
}
+ //--------------------------------------------------------------------
+ /**
+ * Determines the values that should be stored in $archive.
+ *
+ * @return array The updated archive values
+ */
+ protected function setArchiveValues(): array
+ {
+ // Get property values and add anything prepped in tmpArchive
+ $this->archive = array_merge(get_object_vars($this), $this->tmpArchive);
+ unset($this->archive['archive']);
+
+ // Clear tmpArchive for next run
+ $this->tmpArchive = [];
+
+ return $this->archive;
+ }
}
diff --git a/system/Entity.php b/system/Entity.php
index 1ccd6d2..b3da3ce 100644
--- a/system/Entity.php
+++ b/system/Entity.php
@@ -124,18 +124,7 @@
foreach ($data as $key => $value)
{
- $key = $this->mapProperty($key);
-
- $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
-
- if (method_exists($this, $method))
- {
- $this->$method($value);
- }
- else
- {
- $this->attributes[$key] = $value;
- }
+ $this->$key = $value;
}
return $this;
@@ -371,7 +360,7 @@
// back to the database.
if (($castTo === 'json' || $castTo === 'json-array') && function_exists('json_encode'))
{
- $value = json_encode($value);
+ $value = json_encode($value, JSON_UNESCAPED_UNICODE);
if (json_last_error() !== JSON_ERROR_NONE)
{
diff --git a/system/Exceptions/FrameworkException.php b/system/Exceptions/FrameworkException.php
index c63f828..41a3e95 100644
--- a/system/Exceptions/FrameworkException.php
+++ b/system/Exceptions/FrameworkException.php
@@ -11,6 +11,10 @@
class FrameworkException extends \RuntimeException implements ExceptionInterface
{
+ public static function forEnabledZlibOutputCompression()
+ {
+ return new static(lang('Core.enabledZlibOutputCompression'));
+ }
public static function forInvalidFile(string $path)
{
@@ -31,5 +35,4 @@
{
return new static(lang('Core.noHandlers', [$class]));
}
-
}
diff --git a/system/Files/File.php b/system/Files/File.php
index 1f59795..727665d 100644
--- a/system/Files/File.php
+++ b/system/Files/File.php
@@ -144,7 +144,9 @@
{
if (! function_exists('finfo_open'))
{
+ // @codeCoverageIgnoreStart
return $this->originalMimeType ?? 'application/octet-stream';
+ // @codeCoverageIgnoreEnd
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
@@ -193,7 +195,7 @@
throw FileException::forUnableToMove($this->getBasename(), $targetPath, strip_tags($error['message']));
}
- @chmod($targetPath, 0777 & ~umask());
+ @chmod($destination, 0777 & ~umask());
return new File($destination);
}
diff --git a/system/Filters/CSRF.php b/system/Filters/CSRF.php
index 1f0bcf8..c79d191 100644
--- a/system/Filters/CSRF.php
+++ b/system/Filters/CSRF.php
@@ -57,6 +57,7 @@
*/
class CSRF implements FilterInterface
{
+
/**
* Do whatever processing this filter needs to do.
* By default it should not return anything during
@@ -68,10 +69,12 @@
* redirects, etc.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
+ * @param array|null $arguments
*
* @return mixed
+ * @throws \CodeIgniter\Security\Exceptions\SecurityException
*/
- public function before(RequestInterface $request)
+ public function before(RequestInterface $request, $arguments = null)
{
if ($request->isCLI())
{
@@ -102,10 +105,11 @@
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
+ * @param array|null $arguments
*
* @return mixed
*/
- public function after(RequestInterface $request, ResponseInterface $response)
+ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
diff --git a/system/Filters/DebugToolbar.php b/system/Filters/DebugToolbar.php
index 465ca3e..2c3a55c 100644
--- a/system/Filters/DebugToolbar.php
+++ b/system/Filters/DebugToolbar.php
@@ -48,14 +48,16 @@
*/
class DebugToolbar implements FilterInterface
{
+
/**
* We don't need to do anything here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
+ * @param array|null $arguments
*
* @return void
*/
- public function before(RequestInterface $request)
+ public function before(RequestInterface $request, $arguments = null)
{
}
@@ -67,10 +69,11 @@
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
+ * @param array|null $arguments
*
* @return void
*/
- public function after(RequestInterface $request, ResponseInterface $response)
+ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
Services::toolbar()->prepare($request, $response);
}
diff --git a/system/Filters/Exceptions/FilterException.php b/system/Filters/Exceptions/FilterException.php
index b2aba94..43e93cf 100644
--- a/system/Filters/Exceptions/FilterException.php
+++ b/system/Filters/Exceptions/FilterException.php
@@ -1,15 +1,72 @@
-config = $config;
- $this->request = & $request;
+ $this->request = &$request;
$this->setResponse($response);
}
@@ -119,7 +119,7 @@
*/
public function setResponse(ResponseInterface $response)
{
- $this->response = & $response;
+ $this->response = &$response;
}
//--------------------------------------------------------------------
@@ -196,7 +196,7 @@
}
elseif ($position === 'after')
{
- $result = $class->after($this->request, $this->response);
+ $result = $class->after($this->request, $this->response, $this->arguments[$alias] ?? null);
if ($result instanceof ResponseInterface)
{
@@ -337,6 +337,8 @@
/**
* Returns the arguments for a specified key, or all.
*
+ * @param string|null $key
+ *
* @return mixed
*/
public function getArguments(string $key = null)
@@ -352,8 +354,9 @@
/**
* Add any applicable (not excluded) global filter settings to the mix.
*
- * @param string $uri
- * @return type
+ * @param string $uri
+ *
+ * @return void
*/
protected function processGlobals(string $uri = null)
{
@@ -369,6 +372,7 @@
'before',
'after',
];
+
foreach ($sets as $set)
{
if (isset($this->config->globals[$set]))
@@ -394,6 +398,7 @@
{
$alias = $rules; // simple name of filter to apply
}
+
if ($keep)
{
$this->filters[$set][] = $alias;
@@ -408,7 +413,7 @@
/**
* Add any method-specific flters to the mix.
*
- * @return type
+ * @return void
*/
protected function processMethods()
{
@@ -432,8 +437,9 @@
/**
* Add any applicable configured filters to the mix.
*
- * @param string $uri
- * @return type
+ * @param string $uri
+ *
+ * @return void
*/
protected function processFilters(string $uri = null)
{
@@ -456,6 +462,7 @@
$this->filters['before'][] = $alias;
}
}
+
if (isset($settings['after']))
{
$path = $settings['after'];
@@ -470,9 +477,10 @@
/**
* Check paths for match for URI
*
- * @param string $uri URI to test against
- * @param mixed $paths The path patterns to test
- * @return boolean True if any of the paths apply to the URI
+ * @param string $uri URI to test against
+ * @param mixed $paths The path patterns to test
+ *
+ * @return boolean True if any of the paths apply to the URI
*/
private function pathApplies(string $uri, $paths)
{
@@ -501,6 +509,7 @@
return true;
}
}
+
return false;
}
diff --git a/system/Filters/Honeypot.php b/system/Filters/Honeypot.php
index a5807e4..5f2d83c 100644
--- a/system/Filters/Honeypot.php
+++ b/system/Filters/Honeypot.php
@@ -1,4 +1,5 @@
hasContent($request))
@@ -73,10 +73,11 @@
*
* @param \CodeIgniter\HTTP\RequestInterface $request
* @param \CodeIgniter\HTTP\ResponseInterface $response
+ * @param array|null $arguments
*
* @return void
*/
- public function after(RequestInterface $request, ResponseInterface $response)
+ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
$honeypot = Services::honeypot(new \Config\Honeypot());
$honeypot->attachHoneypot($response);
diff --git a/system/Format/JSONFormatter.php b/system/Format/JSONFormatter.php
index 823f3ca..0dc0526 100644
--- a/system/Format/JSONFormatter.php
+++ b/system/Format/JSONFormatter.php
@@ -40,6 +40,7 @@
namespace CodeIgniter\Format;
use CodeIgniter\Format\Exceptions\FormatException;
+use Config\Format;
/**
* JSON data formatter
@@ -56,13 +57,16 @@
*/
public function format($data)
{
- $options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR;
+ $config = new Format();
+
+ $options = $config->formatterOptions['application/json'] ?? JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
+ $options = $options | JSON_PARTIAL_OUTPUT_ON_ERROR;
$options = ENVIRONMENT === 'production' ? $options : $options | JSON_PRETTY_PRINT;
$result = json_encode($data, $options, 512);
- if ( ! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION]))
+ if (! in_array(json_last_error(), [JSON_ERROR_NONE, JSON_ERROR_RECURSION]))
{
throw FormatException::forInvalidJSON(json_last_error_msg());
}
diff --git a/system/Format/XMLFormatter.php b/system/Format/XMLFormatter.php
index 3e262d6..d8dd248 100644
--- a/system/Format/XMLFormatter.php
+++ b/system/Format/XMLFormatter.php
@@ -40,6 +40,7 @@
namespace CodeIgniter\Format;
use CodeIgniter\Format\Exceptions\FormatException;
+use Config\Format;
/**
* XML data formatter
@@ -56,6 +57,8 @@
*/
public function format($data)
{
+ $config = new Format();
+
// SimpleXML is installed but default
// but best to check, and then provide a fallback.
if (! extension_loaded('simplexml'))
@@ -66,7 +69,8 @@
// @codeCoverageIgnoreEnd
}
- $output = new \SimpleXMLElement('');
+ $options = $config->formatterOptions['application/xml'] ?? 0;
+ $output = new \SimpleXMLElement('', $options);
$this->arrayToXML((array)$data, $output);
@@ -91,19 +95,21 @@
{
if (is_array($value))
{
- if (! is_numeric($key))
+ if (is_numeric($key))
{
- $subnode = $output->addChild("$key");
- $this->arrayToXML($value, $subnode);
+ $key = "item{$key}";
}
- else
- {
- $subnode = $output->addChild("item{$key}");
- $this->arrayToXML($value, $subnode);
- }
+
+ $subnode = $output->addChild("$key");
+ $this->arrayToXML($value, $subnode);
}
else
{
+ if (is_numeric($key))
+ {
+ $key = "item{$key}";
+ }
+
$output->addChild("$key", htmlspecialchars("$value"));
}
}
diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php
index f9c4838..f5b624d 100644
--- a/system/HTTP/CLIRequest.php
+++ b/system/HTTP/CLIRequest.php
@@ -223,7 +223,7 @@
{
// If there's no '-' at the beginning of the argument
// then add it to our segments.
- if (! $options_found && strpos($argv[$i], '-') === false)
+ if (! $options_found && strpos($argv[$i], '-') !== 0)
{
$this->segments[] = filter_var($argv[$i], FILTER_SANITIZE_STRING);
continue;
diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index d1a8e94..3ee990a 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -448,10 +448,9 @@
$output = $this->sendRequest($curl_options);
- $continueStr = "HTTP/1.1 100 Continue\x0d\x0a\x0d\x0a";
- if (strpos($output, $continueStr) === 0)
+ if (strpos($output, 'HTTP/1.1 100 Continue') === 0)
{
- $output = substr($output, strlen($continueStr));
+ $output = substr($output, strpos($output, "\r\n\r\n") + 4);
}
// Split out our headers and body
@@ -493,6 +492,7 @@
$this->populateHeaders();
// Otherwise, it will corrupt the request
$this->removeHeader('Host');
+ $this->removeHeader('Accept-Encoding');
}
$headers = $this->getHeaders();
@@ -542,7 +542,7 @@
if ($method === 'PUT' || $method === 'POST')
{
// See http://tools.ietf.org/html/rfc7230#section-3.3.2
- if (is_null($this->getHeader('content-length')))
+ if (is_null($this->getHeader('content-length')) && ! isset($this->config['multipart']))
{
$this->setHeader('Content-Length', '0');
}
diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php
index 910f700..031cf3e 100644
--- a/system/HTTP/ContentSecurityPolicy.php
+++ b/system/HTTP/ContentSecurityPolicy.php
@@ -285,7 +285,7 @@
*
* @return $this
*/
- public function addBaseURI($uri, ?bool $explicitReporting = null)
+ public function addBaseURI($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'baseURI', $explicitReporting ?? $this->reportOnly);
@@ -309,7 +309,7 @@
*
* @return $this
*/
- public function addChildSrc($uri, ?bool $explicitReporting = null)
+ public function addChildSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'childSrc', $explicitReporting ?? $this->reportOnly);
@@ -332,7 +332,7 @@
*
* @return $this
*/
- public function addConnectSrc($uri, ?bool $explicitReporting = null)
+ public function addConnectSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'connectSrc', $explicitReporting ?? $this->reportOnly);
@@ -355,7 +355,7 @@
*
* @return $this
*/
- public function setDefaultSrc($uri, ?bool $explicitReporting = null)
+ public function setDefaultSrc($uri, bool $explicitReporting = null)
{
$this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly];
@@ -377,7 +377,7 @@
*
* @return $this
*/
- public function addFontSrc($uri, ?bool $explicitReporting = null)
+ public function addFontSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'fontSrc', $explicitReporting ?? $this->reportOnly);
@@ -397,7 +397,7 @@
*
* @return $this
*/
- public function addFormAction($uri, ?bool $explicitReporting = null)
+ public function addFormAction($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'formAction', $explicitReporting ?? $this->reportOnly);
@@ -417,7 +417,7 @@
*
* @return $this
*/
- public function addFrameAncestor($uri, ?bool $explicitReporting = null)
+ public function addFrameAncestor($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'frameAncestors', $explicitReporting ?? $this->reportOnly);
@@ -437,7 +437,7 @@
*
* @return $this
*/
- public function addImageSrc($uri, ?bool $explicitReporting = null)
+ public function addImageSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'imageSrc', $explicitReporting ?? $this->reportOnly);
@@ -457,7 +457,7 @@
*
* @return $this
*/
- public function addMediaSrc($uri, ?bool $explicitReporting = null)
+ public function addMediaSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'mediaSrc', $explicitReporting ?? $this->reportOnly);
@@ -477,7 +477,7 @@
*
* @return $this
*/
- public function addManifestSrc($uri, ?bool $explicitReporting = null)
+ public function addManifestSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'manifestSrc', $explicitReporting ?? $this->reportOnly);
@@ -497,7 +497,7 @@
*
* @return $this
*/
- public function addObjectSrc($uri, ?bool $explicitReporting = null)
+ public function addObjectSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'objectSrc', $explicitReporting ?? $this->reportOnly);
@@ -517,7 +517,7 @@
*
* @return $this
*/
- public function addPluginType($mime, ?bool $explicitReporting = null)
+ public function addPluginType($mime, bool $explicitReporting = null)
{
$this->addOption($mime, 'pluginTypes', $explicitReporting ?? $this->reportOnly);
@@ -556,7 +556,7 @@
*
* @return $this
*/
- public function addSandbox($flags, ?bool $explicitReporting = null)
+ public function addSandbox($flags, bool $explicitReporting = null)
{
$this->addOption($flags, 'sandbox', $explicitReporting ?? $this->reportOnly);
return $this;
@@ -575,7 +575,7 @@
*
* @return $this
*/
- public function addScriptSrc($uri, ?bool $explicitReporting = null)
+ public function addScriptSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'scriptSrc', $explicitReporting ?? $this->reportOnly);
@@ -595,7 +595,7 @@
*
* @return $this
*/
- public function addStyleSrc($uri, ?bool $explicitReporting = null)
+ public function addStyleSrc($uri, bool $explicitReporting = null)
{
$this->addOption($uri, 'styleSrc', $explicitReporting ?? $this->reportOnly);
@@ -630,7 +630,7 @@
* @param string $target
* @param boolean|null $explicitReporting
*/
- protected function addOption($options, string $target, ?bool $explicitReporting = null)
+ protected function addOption($options, string $target, bool $explicitReporting = null)
{
// Ensure we have an array to work with...
if (is_string($this->{$target}))
diff --git a/system/HTTP/DownloadResponse.php b/system/HTTP/DownloadResponse.php
index 080f6b2..15b9a7f 100644
--- a/system/HTTP/DownloadResponse.php
+++ b/system/HTTP/DownloadResponse.php
@@ -58,7 +58,7 @@
/**
* Download for file
*
- * @var File?
+ * @var File
*/
private $file;
@@ -263,7 +263,18 @@
//--------------------------------------------------------------------
/**
- * {@inheritDoc}
+ * Return an instance with the specified status code and, optionally, reason phrase.
+ *
+ * If no reason phrase is specified, will default recommended reason phrase for
+ * the response's status code.
+ *
+ * @see http://tools.ietf.org/html/rfc7231#section-6
+ * @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ *
+ * @param integer $code The 3-digit integer result code to set.
+ * @param string $reason The reason phrase to use with the
+ * provided status code; if none is provided, will
+ * default to the IANA name.
*
* @throws DownloadException
*/
@@ -275,7 +286,12 @@
//--------------------------------------------------------------------
/**
- * {@inheritDoc}
+ * Gets the response response phrase associated with the status code.
+ *
+ * @see http://tools.ietf.org/html/rfc7231#section-6
+ * @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ *
+ * @return string
*/
public function getReason(): string
{
@@ -288,7 +304,11 @@
//--------------------------------------------------------------------
/**
- * {@inheritDoc}
+ * Sets the date header
+ *
+ * @param \DateTime $date
+ *
+ * @return ResponseInterface
*/
public function setDate(\DateTime $date)
{
@@ -302,7 +322,13 @@
//--------------------------------------------------------------------
/**
- * {@inheritDoc}
+ * Sets the Content Type header for this response with the mime type
+ * and, optionally, the charset.
+ *
+ * @param string $mime
+ * @param string $charset
+ *
+ * @return ResponseInterface
*/
public function setContentType(string $mime, string $charset = 'UTF-8')
{
@@ -323,7 +349,8 @@
}
/**
- * {@inheritDoc}
+ * Sets the appropriate headers to ensure this response
+ * is not cached by the browsers.
*/
public function noCache(): self
{
@@ -337,7 +364,30 @@
//--------------------------------------------------------------------
/**
- * {@inheritDoc}
+ * A shortcut method that allows the developer to set all of the
+ * cache-control headers in one method call.
+ *
+ * The options array is used to provide the cache-control directives
+ * for the header. It might look something like:
+ *
+ * $options = [
+ * 'max-age' => 300,
+ * 's-maxage' => 900
+ * 'etag' => 'abcde',
+ * ];
+ *
+ * Typical options are:
+ * - etag
+ * - last-modified
+ * - max-age
+ * - s-maxage
+ * - private
+ * - public
+ * - must-revalidate
+ * - proxy-revalidate
+ * - no-transform
+ *
+ * @param array $options
*
* @throws DownloadException
*/
@@ -434,7 +484,7 @@
}
// HTTP Status
- header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->getStatusCode(), $this->getReason()), true,
+ header(sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->getStatusCode(), $this->getReason()), true,
$this->getStatusCode());
// Send all of our headers
diff --git a/system/HTTP/Exceptions/HTTPException.php b/system/HTTP/Exceptions/HTTPException.php
index 5aa4895..661e149 100644
--- a/system/HTTP/Exceptions/HTTPException.php
+++ b/system/HTTP/Exceptions/HTTPException.php
@@ -50,9 +50,8 @@
/**
* For CurlRequest
*
- * @return \CodeIgniter\HTTP\Exceptions\HTTPException
+ * @return \CodeIgniter\HTTP\Exceptions\HTTPException
*
- * Not testable with travis-ci
* @codeCoverageIgnore
*/
public static function forMissingCurl()
@@ -251,6 +250,10 @@
/**
* For Uploaded file move
*
+ * @param string $source
+ * @param string $target
+ * @param string $error
+ *
* @return \CodeIgniter\HTTP\Exceptions\HTTPException
*/
public static function forMoveFailed(string $source, string $target, string $error)
diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php
index b7c04df..4a89067 100755
--- a/system/HTTP/IncomingRequest.php
+++ b/system/HTTP/IncomingRequest.php
@@ -166,7 +166,7 @@
$body = file_get_contents('php://input');
}
- $this->body = $body;
+ $this->body = ! empty($body) ? $body : null;
$this->config = $config;
$this->userAgent = $userAgent;
@@ -294,8 +294,7 @@
*/
public function isAJAX(): bool
{
- return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
- strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+ return $this->hasHeader('X-Requested-With') && strtolower($this->getHeader('X-Requested-With')->getValue()) === 'xmlhttprequest';
}
//--------------------------------------------------------------------
@@ -312,11 +311,11 @@
{
return true;
}
- elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
+ elseif ($this->hasHeader('X-Forwarded-Proto') && $this->getHeader('X-Forwarded-Proto')->getValue() === 'https')
{
return true;
}
- elseif (! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off')
+ elseif ($this->hasHeader('Front-End-Https') && ! empty($this->getHeader('Front-End-Https')->getValue()) && strtolower($this->getHeader('Front-End-Https')->getValue()) !== 'off')
{
return true;
}
@@ -427,7 +426,7 @@
// Use $_POST directly here, since filter_has_var only
// checks the initial POST data, not anything that might
// have been added since.
- return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : (isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost());
+ return isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : (isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : $this->getPost($index, $filter, $flags));
}
//--------------------------------------------------------------------
@@ -446,7 +445,7 @@
// Use $_GET directly here, since filter_has_var only
// checks the initial GET data, not anything that might
// have been added since.
- return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : (isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet());
+ return isset($_GET[$index]) ? $this->getGet($index, $filter, $flags) : (isset($_POST[$index]) ? $this->getPost($index, $filter, $flags) : $this->getGet($index, $filter, $flags));
}
//--------------------------------------------------------------------
diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php
index 74aa470..793e134 100644
--- a/system/HTTP/Negotiate.php
+++ b/system/HTTP/Negotiate.php
@@ -1,6 +1,5 @@
getCookies();
+
+ if (empty($cookies))
+ {
+ return $this;
+ }
+
+ foreach ($cookies as $cookie)
+ {
+ $this->setCookie(
+ $cookie['name'],
+ $cookie['value'],
+ $cookie['expires'],
+ $cookie['domain'],
+ $cookie['path'],
+ '', // prefix
+ $cookie['secure'],
+ $cookie['httponly']
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Copies any headers from the global Response instance
+ * into this RedirectResponse. Useful when you've just
+ * set a header be need to ensure its actually sent
+ * with the redirect response.
+ *
+ * @return $this|RedirectResponse
+ */
+ public function withHeaders()
+ {
+ $headers = service('response')->getHeaders();
+
+ if (empty($headers))
+ {
+ return $this;
+ }
+
+ foreach ($headers as $name => $header)
+ {
+ $this->setHeader($name, $header->getValue());
+ }
+
+ return $this;
+ }
+
+ /**
* Ensures the session is loaded and started.
*
* @return \CodeIgniter\Session\Session
diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php
index b8b222e..ff30dc9 100644
--- a/system/HTTP/Request.php
+++ b/system/HTTP/Request.php
@@ -420,6 +420,16 @@
$value = $this->globals[$method][$index] ?? null;
}
+ if (is_array($value) && ($filter !== null || $flags !== null))
+ {
+ // Iterate over array and append filter and flags
+ array_walk_recursive($value, function (&$val) use ($filter, $flags) {
+ $val = filter_var($val, $filter, $flags);
+ });
+
+ return $value;
+ }
+
// Cannot filter these types of data automatically...
if (is_array($value) || is_object($value) || is_null($value))
{
diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php
index 0d8320b..7d0e680 100644
--- a/system/HTTP/Response.php
+++ b/system/HTTP/Response.php
@@ -1,6 +1,5 @@
cookies;
+ }
+
+ /**
* Actually sets the cookies.
*/
protected function sendCookies()
diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php
index 09aa3cd..fc79756 100644
--- a/system/HTTP/URI.php
+++ b/system/HTTP/URI.php
@@ -153,6 +153,13 @@
*/
protected $showPassword = false;
+ /**
+ * If true, will continue instead of throwing exceptions.
+ *
+ * @var boolean
+ */
+ protected $silent = false;
+
//--------------------------------------------------------------------
/**
@@ -173,6 +180,23 @@
//--------------------------------------------------------------------
/**
+ * If $silent == true, then will not throw exceptions and will
+ * attempt to continue gracefully.
+ *
+ * @param boolean $silent
+ *
+ * @return URI
+ */
+ public function setSilent(bool $silent = true)
+ {
+ $this->silent = $silent;
+
+ return $this;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
* Sets and overwrites any current URI information.
*
* @param string|null $uri
@@ -187,6 +211,11 @@
if ($parts === false)
{
+ if ($this->silent)
+ {
+ return $this;
+ }
+
throw HTTPException::forUnableToParseURI($uri);
}
@@ -469,23 +498,24 @@
/**
* Returns the value of a specific segment of the URI path.
*
- * @param integer $number
+ * @param integer $number Segment number
+ * @param string $default Default value
*
* @return string The value of the segment. If no segment is found,
* throws InvalidArgumentError
*/
- public function getSegment(int $number): string
+ public function getSegment(int $number, string $default = ''): string
{
// The segment should treat the array as 1-based for the user
// but we still have to deal with a zero-based array.
$number -= 1;
- if ($number > count($this->segments))
+ if ($number > count($this->segments) && ! $this->silent)
{
throw HTTPException::forURISegmentOutOfRange($number);
}
- return $this->segments[$number] ?? '';
+ return $this->segments[$number] ?? $default;
}
/**
@@ -505,6 +535,11 @@
if ($number > count($this->segments) + 1)
{
+ if ($this->silent)
+ {
+ return $this;
+ }
+
throw HTTPException::forURISegmentOutOfRange($number);
}
@@ -568,7 +603,7 @@
if ($path !== '')
{
- $uri .= substr($uri, -1, 1) !== '/' ? '/' . ltrim($path, '/') : $path;
+ $uri .= substr($uri, -1, 1) !== '/' ? '/' . ltrim($path, '/') : ltrim($path, '/');
}
if ($query)
@@ -689,6 +724,11 @@
if ($port <= 0 || $port > 65535)
{
+ if ($this->silent)
+ {
+ return $this;
+ }
+
throw HTTPException::forInvalidPort($port);
}
@@ -749,6 +789,11 @@
{
if (strpos($query, '#') !== false)
{
+ if ($this->silent)
+ {
+ return $this;
+ }
+
throw HTTPException::forMalformedQueryString();
}
diff --git a/system/HTTP/UserAgent.php b/system/HTTP/UserAgent.php
index c6873fd..4f7777a 100644
--- a/system/HTTP/UserAgent.php
+++ b/system/HTTP/UserAgent.php
@@ -51,7 +51,7 @@
*
* @var string
*/
- protected $agent;
+ protected $agent = '';
/**
* Flag for if the user-agent belongs to a browser
@@ -130,14 +130,11 @@
*
* Sets the User Agent and runs the compilation routine
*
- * @param null $config
+ * @param null|\Config\UserAgents $config
*/
- public function __construct($config = null)
+ public function __construct(UserAgents $config = null)
{
- if (is_null($config))
- {
- $this->config = new UserAgents();
- }
+ $this->config = $config ?? new UserAgents();
if (isset($_SERVER['HTTP_USER_AGENT']))
{
diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php
index 2556e86..bf0353c 100644
--- a/system/Helpers/filesystem_helper.php
+++ b/system/Helpers/filesystem_helper.php
@@ -94,7 +94,7 @@
closedir($fp);
return $fileData;
}
- catch (\Exception $fe)
+ catch (\Throwable $e)
{
return [];
}
@@ -138,7 +138,7 @@
return is_int($result);
}
- catch (\Exception $fe)
+ catch (\Throwable $e)
{
return false;
}
@@ -160,39 +160,48 @@
* @param string $path File path
* @param boolean $del_dir Whether to delete any directories found in the path
* @param boolean $htdocs Whether to skip deleting .htaccess and index page files
- * @param integer $_level Current directory depth level (default: 0; internal use only)
+ * @param boolean $hidden Whether to include hidden files (files beginning with a period)
*
* @return boolean
*/
- function delete_files(string $path, bool $del_dir = false, bool $htdocs = false, int $_level = 0): bool
+ function delete_files(string $path, bool $del_dir = false, bool $htdocs = false, bool $hidden = false): bool
{
- // Trim the trailing slash
- $path = rtrim($path, '/\\');
+ $path = realpath($path) ?: $path;
+ $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
try
{
- $current_dir = opendir($path);
-
- while (false !== ($filename = @readdir($current_dir)))
+ foreach (new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
+ RecursiveIteratorIterator::CHILD_FIRST
+ ) as $object)
{
- if ($filename !== '.' && $filename !== '..')
+ $filename = $object->getFilename();
+
+ if (! $hidden && $filename[0] === '.')
{
- if (is_dir($path . DIRECTORY_SEPARATOR . $filename) && $filename[0] !== '.')
+ continue;
+ }
+ elseif (! $htdocs || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename))
+ {
+ $isDir = $object->isDir();
+
+ if ($isDir && $del_dir)
{
- delete_files($path . DIRECTORY_SEPARATOR . $filename, $del_dir, $htdocs, $_level + 1);
+ @rmdir($object->getPathname());
+ continue;
}
- elseif ($htdocs !== true || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename))
+
+ if (! $isDir)
{
- @unlink($path . DIRECTORY_SEPARATOR . $filename);
+ @unlink($object->getPathname());
}
}
}
- closedir($current_dir);
-
- return ($del_dir === true && $_level > 0) ? @rmdir($path) : true;
+ return true;
}
- catch (\Exception $fe)
+ catch (\Throwable $e)
{
return false;
}
@@ -311,7 +320,7 @@
return $fileData;
}
}
- catch (\Exception $fe)
+ catch (\Throwable $fe)
{
return [];
}
diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php
index e505e65..5a1ad9d 100644
--- a/system/Helpers/form_helper.php
+++ b/system/Helpers/form_helper.php
@@ -843,7 +843,7 @@
}
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
- if (intval($input) === 0 || ! empty($request->getPost()) || ! empty(old($field)))
+ if ((string) $input === '0' || ! empty($request->getPost()) || ! empty(old($field)))
{
return ($input === $value) ? ' checked="checked"' : '';
}
@@ -895,7 +895,7 @@
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
$result = '';
- if (intval($input) === 0 || ! empty($input = $request->getPost($field)) || ! empty($input = old($field)))
+ if ((string) $input === '0' || ! empty($input = $request->getPost($field)) || ! empty($input = old($field)))
{
$result = ($input === $value) ? ' checked="checked"' : '';
}
diff --git a/system/Helpers/html_helper.php b/system/Helpers/html_helper.php
index 373cec3..535812b 100755
--- a/system/Helpers/html_helper.php
+++ b/system/Helpers/html_helper.php
@@ -273,9 +273,10 @@
* @param string $title
* @param string $media
* @param boolean $indexPage should indexPage be added to the CSS path.
+ * @param string $hreflang
* @return string
*/
- function link_tag($href = '', string $rel = 'stylesheet', string $type = 'text/css', string $title = '', string $media = '', bool $indexPage = false): string
+ function link_tag($href = '', string $rel = 'stylesheet', string $type = 'text/css', string $title = '', string $media = '', bool $indexPage = false, string $hreflang = ''): string
{
$link = '';
}
}
- // ------------------------------------------------------------------------
+
+// ------------------------------------------------------------------------
if (! function_exists('video'))
{
diff --git a/system/Helpers/number_helper.php b/system/Helpers/number_helper.php
index f535198..cc9e925 100644
--- a/system/Helpers/number_helper.php
+++ b/system/Helpers/number_helper.php
@@ -214,7 +214,7 @@
function format_number(float $num, int $precision = 1, string $locale = null, array $options = []): string
{
// Locale is either passed in here, negotiated with client, or grabbed from our config file.
- $locale = $locale ?? \CodeIgniter\Config\Services::request()->getLocale();
+ $locale = $locale ?? \Config\Services::request()->getLocale();
// Type can be any of the NumberFormatter options, but provide a default.
$type = (int) ($options['type'] ?? NumberFormatter::DECIMAL);
diff --git a/system/Helpers/test_helper.php b/system/Helpers/test_helper.php
new file mode 100644
index 0000000..0f3e454
--- /dev/null
+++ b/system/Helpers/test_helper.php
@@ -0,0 +1,73 @@
+setOverrides($overrides);
+ }
+
+ return $fabricator->create();
+ }
+}
diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php
index ece4e38..e39ed27 100644
--- a/system/Helpers/url_helper.php
+++ b/system/Helpers/url_helper.php
@@ -111,7 +111,7 @@
// We should be using the configured baseURL that the user set;
// otherwise get rid of the path, because we have
// no way of knowing the intent...
- $config = \CodeIgniter\Config\Services::request()->config;
+ $config = \Config\Services::request()->config;
// If baseUrl does not have a trailing slash it won't resolve
// correctly for users hosting in a subfolder.
@@ -130,7 +130,7 @@
// If the scheme wasn't provided, check to
// see if it was a secure request
- if (empty($protocol) && \CodeIgniter\Config\Services::request()->isSecure())
+ if (empty($protocol) && \Config\Services::request()->isSecure())
{
$protocol = 'https';
}
@@ -160,25 +160,21 @@
*/
function current_url(bool $returnObject = false)
{
- $uri = clone service('request')->uri;
+ $uri = clone \Config\Services::request()->uri;
// If hosted in a sub-folder, we will have additional
// segments that show up prior to the URI path we just
// grabbed from the request, so add it on if necessary.
- $baseUri = new \CodeIgniter\HTTP\URI(config('App')->baseURL);
+ $baseUri = new \CodeIgniter\HTTP\URI(config(\Config\App::class)->baseURL);
if (! empty($baseUri->getPath()))
{
- $path = rtrim($baseUri->getPath(), '/ ') . '/' . $uri->getPath();
-
- $uri->setPath($path);
+ $uri->setPath(rtrim($baseUri->getPath(), '/ ') . '/' . $uri->getPath());
}
// Since we're basing off of the IncomingRequest URI,
// we are guaranteed to have a host based on our own configs.
- return $returnObject
- ? $uri
- : (string)$uri->setQuery('');
+ return $returnObject ? $uri : (string) $uri->setQuery('');
}
}
@@ -201,7 +197,7 @@
// Grab from the session first, if we have it,
// since it's more reliable and safer.
// Otherwise, grab a sanitized version from $_SERVER.
- $referer = $_SESSION['_ci_previous_url'] ?? \CodeIgniter\Config\Services::request()->getServer('HTTP_REFERER', FILTER_SANITIZE_URL);
+ $referer = $_SESSION['_ci_previous_url'] ?? \Config\Services::request()->getServer('HTTP_REFERER', FILTER_SANITIZE_URL);
$referer = $referer ?? site_url('/');
@@ -222,7 +218,7 @@
*/
function uri_string(): string
{
- return \CodeIgniter\Config\Services::request()->uri->getPath();
+ return \Config\Services::request()->uri->getPath();
}
}
@@ -613,4 +609,28 @@
}
}
+// ------------------------------------------------------------------------
+
+if (! function_exists('mb_url_title'))
+{
+ /**
+ * Create URL Title that takes into account accented characters
+ *
+ * Takes a "title" string as input and creates a
+ * human-friendly URL string with a "separator" string
+ * as the word separator.
+ *
+ * @param string $str Input string
+ * @param string $separator Word separator (usually '-' or '_')
+ * @param boolean $lowercase Whether to transform the output string to lowercase
+ * @return string
+ */
+ function mb_url_title(string $str, string $separator = '-', bool $lowercase = false): string
+ {
+ helper('text');
+
+ return url_title(convert_accented_characters($str), $separator, $lowercase);
+ }
+}
+
//--------------------------------------------------------------------
diff --git a/system/Honeypot/Honeypot.php b/system/Honeypot/Honeypot.php
index a8d908a..b944395 100644
--- a/system/Honeypot/Honeypot.php
+++ b/system/Honeypot/Honeypot.php
@@ -73,6 +73,11 @@
throw HoneypotException::forNoHiddenValue();
}
+ if (empty($this->config->container) || strpos($this->config->container, '{template}') === false)
+ {
+ $this->config->container = '