diff --git a/.gitignore b/.gitignore index ad151ad..e82f525 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,5 @@ .vscode/ /results/ -/phpunit*.xml \ No newline at end of file +/phpunit*.xml +/.phpunit.*.cache diff --git a/app/Config/App.php b/app/Config/App.php index 2da765f..b91c058 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -265,5 +265,4 @@ | - http://www.w3.org/TR/CSP/ */ public $CSPEnabled = false; - } diff --git a/app/Config/Events.php b/app/Config/Events.php index cfdb537..085cc4a 100644 --- a/app/Config/Events.php +++ b/app/Config/Events.php @@ -20,14 +20,17 @@ */ Events::on('pre_system', function () { - while (\ob_get_level() > 0) + if (ENVIRONMENT !== 'testing') { - \ob_end_flush(); - } + while (\ob_get_level() > 0) + { + \ob_end_flush(); + } - \ob_start(function ($buffer) { - return $buffer; - }); + \ob_start(function ($buffer) { + return $buffer; + }); + } /* * -------------------------------------------------------------------- diff --git a/app/Config/Kint.php b/app/Config/Kint.php new file mode 100644 index 0000000..9562447 --- /dev/null +++ b/app/Config/Kint.php @@ -0,0 +1,62 @@ + - - - Welcome to CodeIgniter + + + + + Welcome to CodeIgniter 4! + + + - - - + - + } + + + -
+ +
- + -

Welcome to CodeIgniter

+
-

version

+

Welcome to CodeIgniter

-
-

The page you are looking at is being generated dynamically by CodeIgniter.

+

The small framework with powerful features

-

If you would like to edit this page you'll find it located at:

+
-
-				
-					app/Views/welcome_message.php
-				
-				
+
-

The corresponding controller for this page is found at:

+ -
-				
-					app/Controllers/Home.php
-				
-				
+
-

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

+

About this page

-
+

The page you are looking at is being generated dynamically by CodeIgniter.

- +

If you would like to edit this page you will find it located at:

- +
app/Views/welcome_message.php
- +

The corresponding controller for this page can be found at:

+ +
app/Controllers/Home.php
+ + + +
+ +
+ +

Go further

+ +

+ + Learn +

+ +

The User Guide contains an introduction, tutorial, a number of "how to" + guides, and then reference documentation for the components that make up + the framework. Check the User Guide !

+ +

+ + Discuss +

+ +

CodeIgniter is a community-developed open source project, with several + venues for the community members to gather and exchange ideas. View all + the threads on CodeIgniter's forum, or chat on Slack !

+ +

+ + Contribute +

+ +

CodeIgniter is a community driven project and accepts contributions + of code and documentation from the community. Why not + + join us ?

+ +
+ +
+ + + + + + + + + + + + diff --git a/contributing.md b/contributing.md index abeea35..f0cf482 100644 --- a/contributing.md +++ b/contributing.md @@ -7,6 +7,8 @@ be documented (in the [user guide](https://codeigniter4.github.io/userguide/)), and unit tested (in the [test folder](https://github.com/codeigniter4/CodeIgniter4/tree/develop/tests)). There is a [Contributing to CodeIgniter](./contributing/README.rst) section in the repository which describes the contribution process; this page is an overview. +Note, we expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works. If pull requests are not accompanied by relevant tests, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work on the framework than you do. Please make it as painless for your contributions to be included as possible. If you need help with getting tests running on your local machines, ask for help on the forums. We would be happy to help out. + The [Open Source Guide](https://opensource.guide/) is a good first read for those new to contributing to open source! ## Issues @@ -23,7 +25,7 @@ Before we look into how to contribute to CodeIgniter4, here are some guidelines. If your Pull Requests fail to pass these guidelines, they will be declined, and you will need to re-submit when you’ve made the changes. This might sound a bit tough, but it is required -for us to maintain quality of the codebase. +for us to maintain the quality of the codebase. ### PHP Style @@ -59,7 +61,7 @@ ### Signing -You must [GPG-sign](./contributing/signing.rst) your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open source project. This is *not* just a "signed-off-by" commit, but instead, a digitally signed one. +You must [GPG-sign](./contributing/signing.rst) your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open-source project. This is *not* just a "signed-off-by" commit, but instead, a digitally signed one. ## How-to Guide diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index ad4a812..0b78e9c 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -239,7 +239,7 @@ foreach ($this->getNamespaces() as $namespace) { - if (is_file($namespace['path'] . $path)) + if (isset($namespace['path']) && is_file($namespace['path'] . $path)) { $foundPaths[] = $namespace['path'] . $path; } @@ -288,7 +288,7 @@ $namespaces = []; // Save system for last - $system = null; + $system = []; foreach ($this->autoloader->getNamespace() as $prefix => $paths) { diff --git a/system/Cache/Handlers/FileHandler.php b/system/Cache/Handlers/FileHandler.php index 1d68d02..737db09 100644 --- a/system/Cache/Handlers/FileHandler.php +++ b/system/Cache/Handlers/FileHandler.php @@ -322,7 +322,11 @@ if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl']) { - unlink($this->path . $key); + // If the file is still there then remove it + if (is_file($this->path . $key)) + { + unlink($this->path . $key); + } return false; } diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index b292bfb..f5bedc9 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -66,7 +66,7 @@ /** * The current version of CodeIgniter Framework */ - const CI_VERSION = '4.0.0-rc.4'; + const CI_VERSION = '4.0.0'; /** * App startup time. @@ -190,15 +190,75 @@ $this->detectEnvironment(); $this->bootstrapEnvironment(); - if (CI_DEBUG) + $this->initializeKint(); + + if (! CI_DEBUG) { - require_once SYSTEMPATH . 'ThirdParty/Kint/kint.php'; + \Kint::$enabled_mode = false; } } //-------------------------------------------------------------------- /** + * Initializes Kint + */ + protected function initializeKint() + { + // If we have KINT_DIR it means it's already loaded via composer + if (! defined('KINT_DIR')) + { + spl_autoload_register(function ($class) { + $class = explode('\\', $class); + + if ('Kint' !== array_shift($class)) + { + return; + } + + $file = SYSTEMPATH . 'ThirdParty/Kint/' . implode('/', $class) . '.php'; + + file_exists($file) && require_once $file; + }); + + require_once SYSTEMPATH . 'ThirdParty/Kint/init.php'; + } + + /** + * Config\Kint + */ + $config = config('Config\Kint'); + + \Kint::$max_depth = $config->maxDepth; + \Kint::$display_called_from = $config->displayCalledFrom; + \Kint::$expanded = $config->expanded; + + if (! empty($config->plugins) && is_array($config->plugins)) + { + \Kint::$plugins = $config->plugins; + } + + \Kint\Renderer\RichRenderer::$theme = $config->richTheme; + \Kint\Renderer\RichRenderer::$folder = $config->richFolder; + \Kint\Renderer\RichRenderer::$sort = $config->richSort; + if (! empty($config->richObjectPlugins) && is_array($config->richObjectPlugins)) + { + \Kint\Renderer\RichRenderer::$object_plugins = $config->richObjectPlugins; + } + if (! empty($config->richTabPlugins) && is_array($config->richTabPlugins)) + { + \Kint\Renderer\RichRenderer::$tab_plugins = $config->richTabPlugins; + } + + \Kint\Renderer\CliRenderer::$cli_colors = $config->cliColors; + \Kint\Renderer\CliRenderer::$force_utf8 = $config->cliForceUTF8; + \Kint\Renderer\CliRenderer::$detect_width = $config->cliDetectWidth; + \Kint\Renderer\CliRenderer::$min_terminal_width = $config->cliMinWidth; + } + + //-------------------------------------------------------------------- + + /** * Launch the application! * * This is "the loop" if you will. The main entry point into the script @@ -838,13 +898,16 @@ */ protected function runController($class) { + // If this is a console request then use the input segments as parameters + $params = defined('SPARKED') ? $this->request->getSegments() : $this->router->params(); + if (method_exists($class, '_remap')) { - $output = $class->_remap($this->method, ...$this->router->params()); + $output = $class->_remap($this->method, ...$params); } else { - $output = $class->{$this->method}(...$this->router->params()); + $output = $class->{$this->method}(...$params); } $this->benchmark->stop('controller'); diff --git a/system/Commands/Server/Serve.php b/system/Commands/Server/Serve.php index efca3cf..9fb68fe 100644 --- a/system/Commands/Server/Serve.php +++ b/system/Commands/Server/Serve.php @@ -95,6 +95,20 @@ protected $arguments = []; /** + * The current port offset. + * + * @var int + */ + protected $portOffset = 0; + + /** + * The max number of ports to attempt to serve from + * + * @var int + */ + protected $tries = 10; + + /** * Options * * @var array @@ -124,7 +138,7 @@ // Collect any user-supplied options and apply them. $php = CLI::getOption('php') ?? PHP_BINARY; $host = CLI::getOption('host') ?? 'localhost'; - $port = CLI::getOption('port') ?? '8080'; + $port = (int) (CLI::getOption('port') ?? '8080') + $this->portOffset; // Get the party started. CLI::write('CodeIgniter development server started on http://' . $host . ':' . $port, 'green'); @@ -139,7 +153,13 @@ // Call PHP's built-in webserver, making sure to set our // base path to the public folder, and to use the rewrite file // to ensure our environment is set and it simulates basic mod_rewrite. - passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot . ' ' . $rewrite); + passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot . ' ' . $rewrite, $status); + + if ($status && $this->portOffset < $this->tries) { + $this->portOffset += 1; + + $this->run($params); + } } } diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php index 3e16591..6dfef2f 100644 --- a/system/Commands/Utilities/Routes.php +++ b/system/Commands/Utilities/Routes.php @@ -124,24 +124,24 @@ { $routes = $collection->getRoutes($method); - foreach ($routes as $from => $to) + foreach ($routes as $route => $handler) { // filter for strings, as callbacks aren't displayable - if (is_string($to)) + if(is_string($handler)) { $tbody[] = [ - $from, - $method, - $to, + strtoupper($method), + $route, + $handler, ]; } } } $thead = [ - 'Route', 'Method', - 'Command', + 'Route', + 'Handler', ]; CLI::table($tbody, $thead); diff --git a/system/Common.php b/system/Common.php index e661703..a14bc66 100644 --- a/system/Common.php +++ b/system/Common.php @@ -37,18 +37,19 @@ * @filesource */ +use Config\App; +use Config\Logger; +use Config\Database; +use Config\Services; +use CodeIgniter\HTTP\URI; +use Laminas\Escaper\Escaper; use CodeIgniter\Config\Config; -use CodeIgniter\Files\Exceptions\FileNotFoundException; +use CodeIgniter\Test\TestLogger; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\HTTP\URI; -use Config\App; -use Config\Database; -use Config\Logger; -use Config\Services; -use Tests\Support\Log\TestLogger; -use Laminas\Escaper\Escaper; +use CodeIgniter\Database\ConnectionInterface; +use CodeIgniter\Files\Exceptions\FileNotFoundException; /** * Common Functions @@ -62,6 +63,24 @@ // Services Convenience Functions //-------------------------------------------------------------------- +if (! function_exists('app_timezone')) +{ + /** + * Returns the timezone the application has been set to display + * dates in. This might be different than the timezone set + * at the server level, as you often want to stores dates in UTC + * and convert them on the fly for the user. + * + * @return string + */ + function app_timezone(): string + { + $config = config(App::class); + + return $config->appTimezone; + } +} + if (! function_exists('cache')) { /** @@ -92,8 +111,6 @@ } } -//-------------------------------------------------------------------- - if (! function_exists('config')) { /** @@ -110,7 +127,86 @@ } } -//-------------------------------------------------------------------- +if (! function_exists('csrf_token')) +{ + /** + * Returns the CSRF token name. + * Can be used in Views when building hidden inputs manually, + * or used in javascript vars when using APIs. + * + * @return string + */ + function csrf_token(): string + { + $config = config(App::class); + + return $config->CSRFTokenName; + } +} + +if (! function_exists('csrf_header')) +{ + /** + * Returns the CSRF header name. + * Can be used in Views by adding it to the meta tag + * or used in javascript to define a header name when using APIs. + * + * @return string + */ + function csrf_header(): string + { + $config = config(App::class); + + return $config->CSRFHeaderName; + } +} + +if (! function_exists('csrf_hash')) +{ + /** + * Returns the current hash value for the CSRF protection. + * Can be used in Views when building hidden inputs manually, + * or used in javascript vars for API usage. + * + * @return string + */ + function csrf_hash(): string + { + $security = Services::security(null, true); + + return $security->getCSRFHash(); + } +} + +if (! function_exists('csrf_field')) +{ + /** + * Generates a hidden input field for use within manually generated forms. + * + * @param string|null $id + * + * @return string + */ + function csrf_field(string $id = null): string + { + return ''; + } +} + +if (! function_exists('csrf_meta')) +{ + /** + * Generates a meta tag for use within javascript calls. + * + * @param string|null $id + * + * @return string + */ + function csrf_meta(string $id = null): string + { + return ''; + } +} if (! function_exists('db_connect')) { @@ -139,69 +235,23 @@ } } -//-------------------------------------------------------------------- - -if (! function_exists('view')) +if (! function_exists('dd')) { /** - * Grabs the current RendererInterface-compatible class - * and tells it to render the specified view. Simply provides - * a convenience method that can be used in Controllers, - * libraries, and routed closures. + * Prints a Kint debug report and exits. * - * NOTE: Does not provide any escaping of the data, so that must - * all be handled manually by the developer. + * @param array ...$vars * - * @param string $name - * @param array $data - * @param array $options Unused - reserved for third-party extensions. - * - * @return string + * @codeCoverageIgnore Can't be tested ... exits */ - function view(string $name, array $data = [], array $options = []): string + function dd(...$vars) { - /** - * @var CodeIgniter\View\View $renderer - */ - $renderer = Services::renderer(); - - $saveData = null; - if (array_key_exists('saveData', $options) && $options['saveData'] === true) - { - $saveData = (bool) $options['saveData']; - unset($options['saveData']); - } - - return $renderer->setData($data, 'raw') - ->render($name, $options, $saveData); + Kint::$aliases[] = 'dd'; + Kint::dump(...$vars); + exit; } } -//-------------------------------------------------------------------- - -if (! function_exists('view_cell')) -{ - /** - * View cells are used within views to insert HTML chunks that are managed - * by other classes. - * - * @param string $library - * @param null $params - * @param integer $ttl - * @param string|null $cacheName - * - * @return string - * @throws \ReflectionException - */ - function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null): string - { - return Services::viewcell() - ->render($library, $params, $ttl, $cacheName); - } -} - -//-------------------------------------------------------------------- - if (! function_exists('env')) { /** @@ -246,8 +296,6 @@ } } -//-------------------------------------------------------------------- - if (! function_exists('esc')) { /** @@ -321,259 +369,112 @@ } } -//-------------------------------------------------------------------- - -if (! function_exists('session')) +if (! function_exists('force_https')) { /** - * A convenience method for accessing the session instance, - * or an item that has been set in the session. + * Used to force a page to be accessed in via HTTPS. + * Uses a standard redirect, plus will set the HSTS header + * for modern browsers that support, which gives best + * protection against man-in-the-middle attacks. * - * Examples: - * session()->set('foo', 'bar'); - * $foo = session('bar'); + * @see https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security * - * @param string $val + * @param integer $duration How long should the SSL header be set for? (in seconds) + * Defaults to 1 year. + * @param RequestInterface $request + * @param ResponseInterface $response * - * @return \CodeIgniter\Session\Session|mixed|null + * Not testable, as it will exit! + * + * @throws \CodeIgniter\HTTP\Exceptions\HTTPException + * @codeCoverageIgnore */ - function session(string $val = null) + function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) { - $session = Services::session(); - - // Returning a single item? - if (is_string($val)) + if (is_null($request)) { - return $session->get($val); + $request = Services::request(null, true); + } + if (is_null($response)) + { + $response = Services::response(null, true); } - return $session; - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('timer')) -{ - /** - * A convenience method for working with the timer. - * If no parameter is passed, it will return the timer instance, - * otherwise will start or stop the timer intelligently. - * - * @param string|null $name - * - * @return \CodeIgniter\Debug\Timer|mixed - */ - function timer(string $name = null) - { - $timer = Services::timer(); - - if (empty($name)) + if (is_cli() || $request->isSecure()) { - return $timer; + return; } - if ($timer->has($name)) + // If the session library is loaded, we should regenerate + // the session ID for safety sake. + if (class_exists('Session', false)) { - return $timer->stop($name); + Services::session(null, true) + ->regenerate(); } - return $timer->start($name); + $uri = $request->uri; + $uri->setScheme('https'); + + $uri = URI::createURIString( + $uri->getScheme(), $uri->getAuthority(true), $uri->getPath(), // Absolute URIs should use a "/" for an empty path + $uri->getQuery(), $uri->getFragment() + ); + + // Set an HSTS header + $response->setHeader('Strict-Transport-Security', 'max-age=' . $duration); + $response->redirect($uri); + $response->sendHeaders(); + + exit(); } } -//-------------------------------------------------------------------- - -if (! function_exists('service')) +if (! function_exists('function_usable')) { /** - * Allows cleaner access to the Services Config file. - * Always returns a SHARED instance of the class, so - * calling the function multiple times should always - * return the same instance. + * Function usable * - * These are equal: - * - $timer = service('timer') - * - $timer = \CodeIgniter\Config\Services::timer(); + * Executes a function_exists() check, and if the Suhosin PHP + * extension is loaded - checks whether the function that is + * checked might be disabled in there as well. * - * @param string $name - * @param array ...$params + * This is useful as function_exists() will return FALSE for + * functions disabled via the *disable_functions* php.ini + * setting, but not for *suhosin.executor.func.blacklist* and + * *suhosin.executor.disable_eval*. These settings will just + * terminate script execution if a disabled function is executed. * - * @return mixed + * The above described behavior turned out to be a bug in Suhosin, + * but even though a fix was committed for 0.9.34 on 2012-02-12, + * that version is yet to be released. This function will therefore + * be just temporary, but would probably be kept for a few years. + * + * @link http://www.hardened-php.net/suhosin/ + * @param string $function_name Function to check for + * @return boolean TRUE if the function exists and is safe to call, + * FALSE otherwise. + * + * @codeCoverageIgnore This is too exotic */ - function service(string $name, ...$params) + function function_usable(string $function_name): bool { - return Services::$name(...$params); - } -} + static $_suhosin_func_blacklist; -//-------------------------------------------------------------------- - -if (! function_exists('single_service')) -{ - /** - * Allow cleaner access to a Service. - * Always returns a new instance of the class. - * - * @param string $name - * @param array|null $params - * - * @return mixed - */ - function single_service(string $name, ...$params) - { - // Ensure it's NOT a shared instance - array_push($params, false); - - return Services::$name(...$params); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('lang')) -{ - /** - * A convenience method to translate a string or array of them and format - * the result with the intl extension's MessageFormatter. - * - * @param string|[] $line - * @param array $args - * @param string $locale - * - * @return string - */ - function lang(string $line, array $args = [], string $locale = null) - { - return Services::language($locale) - ->getLine($line, $args); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('log_message')) -{ - /** - * A convenience/compatibility method for logging events through - * the Log system. - * - * Allowed log levels are: - * - emergency - * - alert - * - critical - * - error - * - warning - * - notice - * - info - * - debug - * - * @param string $level - * @param string $message - * @param array|null $context - * - * @return mixed - */ - function log_message(string $level, string $message, array $context = []) - { - // When running tests, we want to always ensure that the - // TestLogger is running, which provides utilities for - // for asserting that logs were called in the test code. - if (ENVIRONMENT === 'testing') + if (function_exists($function_name)) { - $logger = new TestLogger(new Logger()); + if (! isset($_suhosin_func_blacklist)) + { + $_suhosin_func_blacklist = extension_loaded('suhosin') ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) : []; + } - return $logger->log($level, $message, $context); + return ! in_array($function_name, $_suhosin_func_blacklist, true); } - // @codeCoverageIgnoreStart - return Services::logger(true) - ->log($level, $message, $context); - // @codeCoverageIgnoreEnd + return false; } } -//-------------------------------------------------------------------- - -if (! function_exists('is_cli')) -{ - /** - * Is CLI? - * - * Test to see if a request was made from the command line. - * - * @return boolean - */ - function is_cli(): bool - { - return (PHP_SAPI === 'cli' || defined('STDIN')); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('route_to')) -{ - /** - * Given a controller/method string and any params, - * will attempt to build the relative URL to the - * matching route. - * - * NOTE: This requires the controller/method to - * have a route defined in the routes Config file. - * - * @param string $method - * @param array ...$params - * - * @return false|string - */ - function route_to(string $method, ...$params) - { - return Services::routes()->reverseRoute($method, ...$params); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('remove_invisible_characters')) -{ - /** - * Remove Invisible Characters - * - * This prevents sandwiching null characters - * between ascii characters, like Java\0script. - * - * @param string $str - * @param boolean $urlEncoded - * - * @return string - */ - function remove_invisible_characters(string $str, bool $urlEncoded = true): string - { - $nonDisplayables = []; - - // every control character except newline (dec 10), - // carriage return (dec 13) and horizontal tab (dec 09) - if ($urlEncoded) - { - $nonDisplayables[] = '/%0[0-8bcef]/'; // url encoded 00-08, 11, 12, 14, 15 - $nonDisplayables[] = '/%1[0-9a-f]/'; // url encoded 16-31 - } - - $nonDisplayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 - - do - { - $str = preg_replace($nonDisplayables, '', $str, -1, $count); - } - while ($count); - - return $str; - } -} - -//-------------------------------------------------------------------- - if (! function_exists('helper')) { /** @@ -683,298 +584,21 @@ } } -//-------------------------------------------------------------------- - -if (! function_exists('app_timezone')) +if (! function_exists('is_cli')) { /** - * Returns the timezone the application has been set to display - * dates in. This might be different than the timezone set - * at the server level, as you often want to stores dates in UTC - * and convert them on the fly for the user. + * Is CLI? * - * @return string + * Test to see if a request was made from the command line. + * + * @return boolean */ - function app_timezone(): string + function is_cli(): bool { - $config = config(App::class); - - return $config->appTimezone; + return (PHP_SAPI === 'cli' || defined('STDIN')); } } -//-------------------------------------------------------------------- - -if (! function_exists('csrf_token')) -{ - /** - * Returns the CSRF token name. - * Can be used in Views when building hidden inputs manually, - * or used in javascript vars when using APIs. - * - * @return string - */ - function csrf_token(): string - { - $config = config(App::class); - - return $config->CSRFTokenName; - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('csrf_header')) -{ - /** - * Returns the CSRF header name. - * Can be used in Views by adding it to the meta tag - * or used in javascript to define a header name when using APIs. - * - * @return string - */ - function csrf_header(): string - { - $config = config(App::class); - - return $config->CSRFHeaderName; - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('csrf_hash')) -{ - /** - * Returns the current hash value for the CSRF protection. - * Can be used in Views when building hidden inputs manually, - * or used in javascript vars for API usage. - * - * @return string - */ - function csrf_hash(): string - { - $security = Services::security(null, true); - - return $security->getCSRFHash(); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('csrf_field')) -{ - /** - * Generates a hidden input field for use within manually generated forms. - * - * @param string|null $id - * - * @return string - */ - function csrf_field(string $id = null): string - { - return ''; - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('csrf_meta')) -{ - /** - * Generates a meta tag for use within javascript calls. - * - * @param string|null $id - * - * @return string - */ - function csrf_meta(string $id = null): string - { - return ''; - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('force_https')) -{ - /** - * Used to force a page to be accessed in via HTTPS. - * Uses a standard redirect, plus will set the HSTS header - * for modern browsers that support, which gives best - * protection against man-in-the-middle attacks. - * - * @see https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security - * - * @param integer $duration How long should the SSL header be set for? (in seconds) - * Defaults to 1 year. - * @param RequestInterface $request - * @param ResponseInterface $response - * - * Not testable, as it will exit! - * - * @throws \CodeIgniter\HTTP\Exceptions\HTTPException - * @codeCoverageIgnore - */ - function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null) - { - if (is_null($request)) - { - $request = Services::request(null, true); - } - if (is_null($response)) - { - $response = Services::response(null, true); - } - - if (is_cli() || $request->isSecure()) - { - return; - } - - // If the session library is loaded, we should regenerate - // the session ID for safety sake. - if (class_exists('Session', false)) - { - Services::session(null, true) - ->regenerate(); - } - - $uri = $request->uri; - $uri->setScheme('https'); - - $uri = URI::createURIString( - $uri->getScheme(), $uri->getAuthority(true), $uri->getPath(), // Absolute URIs should use a "/" for an empty path - $uri->getQuery(), $uri->getFragment() - ); - - // Set an HSTS header - $response->setHeader('Strict-Transport-Security', 'max-age=' . $duration); - $response->redirect($uri); - $response->sendHeaders(); - - exit(); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('old')) -{ - /** - * Provides access to "old input" that was set in the session - * during a redirect()->withInput(). - * - * @param string $key - * @param null $default - * @param string|boolean $escape - * - * @return mixed|null - */ - function old(string $key, $default = null, $escape = 'html') - { - // Ensure the session is loaded - if (session_status() === PHP_SESSION_NONE && ENVIRONMENT !== 'testing') - { - session(); - } - - $request = Services::request(); - - $value = $request->getOldInput($key); - - // Return the default value if nothing - // found in the old input. - if (is_null($value)) - { - return $default; - } - - // If the result was serialized array or string, then unserialize it for use... - if (is_string($value)) - { - if (strpos($value, 'a:') === 0 || strpos($value, 's:') === 0) - { - $value = unserialize($value); - } - } - - return $escape === false ? $value : esc($value, $escape); - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('redirect')) -{ - /** - * Convenience method that works with the current global $request and - * $router instances to redirect using named/reverse-routed routes - * to determine the URL to go to. If nothing is found, will treat - * as a traditional redirect and pass the string in, letting - * $response->redirect() determine the correct method and code. - * - * If more control is needed, you must use $response->redirect explicitly. - * - * @param string $uri - * - * @return \CodeIgniter\HTTP\RedirectResponse - */ - function redirect(string $uri = null): RedirectResponse - { - $response = Services::redirectResponse(null, true); - - if (! empty($uri)) - { - return $response->route($uri); - } - - return $response; - } -} - -//-------------------------------------------------------------------- - -if (! function_exists('stringify_attributes')) -{ - /** - * Stringify attributes for use in HTML tags. - * - * Helper function used to convert a string, array, or object - * of attributes to a string. - * - * @param mixed $attributes string, array, object - * @param boolean $js - * - * @return string - */ - function stringify_attributes($attributes, bool $js = false): string - { - $atts = ''; - - if (empty($attributes)) - { - return $atts; - } - - if (is_string($attributes)) - { - return ' ' . $attributes; - } - - $attributes = (array) $attributes; - - foreach ($attributes as $key => $val) - { - $atts .= ($js) ? $key . '=' . esc($val, 'js') . ',' : ' ' . $key . '="' . esc($val, 'attr') . '"'; - } - - return rtrim($atts, ','); - } -} - -//-------------------------------------------------------------------- - if (! function_exists('is_really_writable')) { /** @@ -1029,7 +653,283 @@ } } -//-------------------------------------------------------------------- +if (! function_exists('lang')) +{ + /** + * A convenience method to translate a string or array of them and format + * the result with the intl extension's MessageFormatter. + * + * @param string|[] $line + * @param array $args + * @param string $locale + * + * @return string + */ + function lang(string $line, array $args = [], string $locale = null) + { + return Services::language($locale) + ->getLine($line, $args); + } +} + +if (! function_exists('log_message')) +{ + /** + * A convenience/compatibility method for logging events through + * the Log system. + * + * Allowed log levels are: + * - emergency + * - alert + * - critical + * - error + * - warning + * - notice + * - info + * - debug + * + * @param string $level + * @param string $message + * @param array|null $context + * + * @return mixed + */ + function log_message(string $level, string $message, array $context = []) + { + // When running tests, we want to always ensure that the + // TestLogger is running, which provides utilities for + // for asserting that logs were called in the test code. + if (ENVIRONMENT === 'testing') + { + $logger = new TestLogger(new Logger()); + + return $logger->log($level, $message, $context); + } + + // @codeCoverageIgnoreStart + return Services::logger(true) + ->log($level, $message, $context); + // @codeCoverageIgnoreEnd + } +} + +if (! function_exists('model')) +{ + /** + * More simple way of getting model instances + * + * @param string $name + * @param boolean $getShared + * @param ConnectionInterface|null $conn + * + * @return mixed + */ + function model(string $name, bool $getShared = true, ConnectionInterface &$conn = null) + { + return \CodeIgniter\Database\ModelFactory::get($name, $getShared, $conn); + } +} + +if (! function_exists('old')) +{ + /** + * Provides access to "old input" that was set in the session + * during a redirect()->withInput(). + * + * @param string $key + * @param null $default + * @param string|boolean $escape + * + * @return mixed|null + */ + function old(string $key, $default = null, $escape = 'html') + { + // Ensure the session is loaded + if (session_status() === PHP_SESSION_NONE && ENVIRONMENT !== 'testing') + { + session(); + } + + $request = Services::request(); + + $value = $request->getOldInput($key); + + // Return the default value if nothing + // found in the old input. + if (is_null($value)) + { + return $default; + } + + // If the result was serialized array or string, then unserialize it for use... + if (is_string($value)) + { + if (strpos($value, 'a:') === 0 || strpos($value, 's:') === 0) + { + $value = unserialize($value); + } + } + + return $escape === false ? $value : esc($value, $escape); + } +} + +if (! function_exists('redirect')) +{ + /** + * Convenience method that works with the current global $request and + * $router instances to redirect using named/reverse-routed routes + * to determine the URL to go to. If nothing is found, will treat + * as a traditional redirect and pass the string in, letting + * $response->redirect() determine the correct method and code. + * + * If more control is needed, you must use $response->redirect explicitly. + * + * @param string $uri + * + * @return \CodeIgniter\HTTP\RedirectResponse + */ + function redirect(string $uri = null): RedirectResponse + { + $response = Services::redirectResponse(null, true); + + if (! empty($uri)) + { + return $response->route($uri); + } + + return $response; + } +} + +if (! function_exists('remove_invisible_characters')) +{ + /** + * Remove Invisible Characters + * + * This prevents sandwiching null characters + * between ascii characters, like Java\0script. + * + * @param string $str + * @param boolean $urlEncoded + * + * @return string + */ + function remove_invisible_characters(string $str, bool $urlEncoded = true): string + { + $nonDisplayables = []; + + // every control character except newline (dec 10), + // carriage return (dec 13) and horizontal tab (dec 09) + if ($urlEncoded) + { + $nonDisplayables[] = '/%0[0-8bcef]/'; // url encoded 00-08, 11, 12, 14, 15 + $nonDisplayables[] = '/%1[0-9a-f]/'; // url encoded 16-31 + } + + $nonDisplayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 + + do + { + $str = preg_replace($nonDisplayables, '', $str, -1, $count); + } + while ($count); + + return $str; + } +} + +if (! function_exists('route_to')) +{ + /** + * Given a controller/method string and any params, + * will attempt to build the relative URL to the + * matching route. + * + * NOTE: This requires the controller/method to + * have a route defined in the routes Config file. + * + * @param string $method + * @param array ...$params + * + * @return false|string + */ + function route_to(string $method, ...$params) + { + return Services::routes()->reverseRoute($method, ...$params); + } +} + +if (! function_exists('session')) +{ + /** + * A convenience method for accessing the session instance, + * or an item that has been set in the session. + * + * Examples: + * session()->set('foo', 'bar'); + * $foo = session('bar'); + * + * @param string $val + * + * @return \CodeIgniter\Session\Session|mixed|null + */ + function session(string $val = null) + { + $session = Services::session(); + + // Returning a single item? + if (is_string($val)) + { + return $session->get($val); + } + + return $session; + } +} + +if (! function_exists('service')) +{ + /** + * Allows cleaner access to the Services Config file. + * Always returns a SHARED instance of the class, so + * calling the function multiple times should always + * return the same instance. + * + * These are equal: + * - $timer = service('timer') + * - $timer = \CodeIgniter\Config\Services::timer(); + * + * @param string $name + * @param array ...$params + * + * @return mixed + */ + function service(string $name, ...$params) + { + return Services::$name(...$params); + } +} + +if (! function_exists('single_service')) +{ + /** + * Allow cleaner access to a Service. + * Always returns a new instance of the class. + * + * @param string $name + * @param array|null $params + * + * @return mixed + */ + function single_service(string $name, ...$params) + { + // Ensure it's NOT a shared instance + array_push($params, false); + + return Services::$name(...$params); + } +} if (! function_exists('slash_item')) { @@ -1056,74 +956,74 @@ return rtrim($configItem, '/') . '/'; } } -//-------------------------------------------------------------------- -if (! function_exists('function_usable')) +if (! function_exists('stringify_attributes')) { /** - * Function usable + * Stringify attributes for use in HTML tags. * - * Executes a function_exists() check, and if the Suhosin PHP - * extension is loaded - checks whether the function that is - * checked might be disabled in there as well. + * Helper function used to convert a string, array, or object + * of attributes to a string. * - * This is useful as function_exists() will return FALSE for - * functions disabled via the *disable_functions* php.ini - * setting, but not for *suhosin.executor.func.blacklist* and - * *suhosin.executor.disable_eval*. These settings will just - * terminate script execution if a disabled function is executed. + * @param mixed $attributes string, array, object + * @param boolean $js * - * The above described behavior turned out to be a bug in Suhosin, - * but even though a fix was committed for 0.9.34 on 2012-02-12, - * that version is yet to be released. This function will therefore - * be just temporary, but would probably be kept for a few years. - * - * @link http://www.hardened-php.net/suhosin/ - * @param string $function_name Function to check for - * @return boolean TRUE if the function exists and is safe to call, - * FALSE otherwise. - * - * @codeCoverageIgnore This is too exotic + * @return string */ - function function_usable(string $function_name): bool + function stringify_attributes($attributes, bool $js = false): string { - static $_suhosin_func_blacklist; + $atts = ''; - if (function_exists($function_name)) + if (empty($attributes)) { - if (! isset($_suhosin_func_blacklist)) - { - $_suhosin_func_blacklist = extension_loaded('suhosin') ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) : []; - } - - return ! in_array($function_name, $_suhosin_func_blacklist, true); + return $atts; } - return false; + if (is_string($attributes)) + { + return ' ' . $attributes; + } + + $attributes = (array) $attributes; + + foreach ($attributes as $key => $val) + { + $atts .= ($js) ? $key . '=' . esc($val, 'js') . ',' : ' ' . $key . '="' . esc($val, 'attr') . '"'; + } + + return rtrim($atts, ','); } } -//-------------------------------------------------------------------- - -if (! function_exists('dd')) +if (! function_exists('timer')) { /** - * Prints a Kint debug report and exits. + * A convenience method for working with the timer. + * If no parameter is passed, it will return the timer instance, + * otherwise will start or stop the timer intelligently. * - * @param array ...$vars + * @param string|null $name * - * @codeCoverageIgnore Can't be tested ... exits + * @return \CodeIgniter\Debug\Timer|mixed */ - function dd(...$vars) + function timer(string $name = null) { - Kint::$aliases[] = 'dd'; - Kint::dump(...$vars); - exit; + $timer = Services::timer(); + + if (empty($name)) + { + return $timer; + } + + if ($timer->has($name)) + { + return $timer->stop($name); + } + + return $timer->start($name); } } -//-------------------------------------------------------------------- - if (! function_exists('trace')) { /** @@ -1135,3 +1035,60 @@ Kint::trace(); } } + +if (! function_exists('view')) +{ + /** + * Grabs the current RendererInterface-compatible class + * and tells it to render the specified view. Simply provides + * a convenience method that can be used in Controllers, + * libraries, and routed closures. + * + * NOTE: Does not provide any escaping of the data, so that must + * all be handled manually by the developer. + * + * @param string $name + * @param array $data + * @param array $options Unused - reserved for third-party extensions. + * + * @return string + */ + function view(string $name, array $data = [], array $options = []): string + { + /** + * @var CodeIgniter\View\View $renderer + */ + $renderer = Services::renderer(); + + $saveData = true; + if (array_key_exists('saveData', $options) && $options['saveData'] === true) + { + $saveData = (bool) $options['saveData']; + unset($options['saveData']); + } + + return $renderer->setData($data, 'raw') + ->render($name, $options, $saveData); + } +} + +if (! function_exists('view_cell')) +{ + /** + * View cells are used within views to insert HTML chunks that are managed + * by other classes. + * + * @param string $library + * @param null $params + * @param integer $ttl + * @param string|null $cacheName + * + * @return string + * @throws \ReflectionException + */ + function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null): string + { + return Services::viewcell() + ->render($library, $params, $ttl, $cacheName); + } +} diff --git a/system/ComposerScripts.php b/system/ComposerScripts.php index 9666d00..d53108f 100644 --- a/system/ComposerScripts.php +++ b/system/ComposerScripts.php @@ -149,6 +149,29 @@ } } + protected static function copyDir($source, $dest) + { + $dir = opendir($source); + @mkdir($dest); + + while (false !== ( $file = readdir($dir))) + { + if (( $file !== '.' ) && ( $file !== '..' )) + { + if (is_dir($source . '/' . $file)) + { + static::copyDir($source . '/' . $file, $dest . '/' . $file); + } + else + { + copy($source . '/' . $file, $dest . '/' . $file); + } + } + } + + closedir($dir); + } + /** * Moves the Laminas Escaper files into our base repo so that it's * available for packaged releases where the users don't user Composer. @@ -194,9 +217,9 @@ */ public static function moveKint() { - $filename = 'vendor/kint-php/kint/build/kint-aante-light.php'; + $dir = 'vendor/kint-php/kint/src'; - if (is_file($filename)) + if (is_dir($dir)) { $base = basename(__DIR__) . '/' . static::$basePath . 'Kint'; @@ -212,10 +235,10 @@ mkdir($base, 0755); } - if (! static::moveFile($filename, $base . '/kint.php')) - { - die('Error moving: ' . $filename); - } + static::copyDir($dir, $base); + static::copyDir($dir . '/../resources', $base . '/resources'); + copy($dir . '/../init.php', $base . '/init.php'); + copy($dir . '/../init_helpers.php', $base . '/init_helpers.php'); } } } diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index ea4e0fd..38602b3 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -129,8 +129,8 @@ if (isset($_SERVER['CI_ENVIRONMENT']) && $_SERVER['CI_ENVIRONMENT'] === 'testing') { - $this->classmap['CodeIgniter\Log\TestLogger'] = SUPPORTPATH . 'Log/TestLogger.php'; - $this->classmap['CIDatabaseTestCase'] = SUPPORTPATH . 'CIDatabaseTestCase.php'; + $this->classmap['CodeIgniter\Log\TestLogger'] = SYSTEMPATH . 'Test/TestLogger.php'; + $this->classmap['CIDatabaseTestCase'] = SYSTEMPATH . 'Test/CIDatabaseTestCase.php'; } } diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index a3eeaed..65aceb1 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -66,6 +66,7 @@ /** * Cache for instance of any services that * have been requested as a "shared" instance. + * Keys should be lowercase service names. * * @var array */ @@ -106,6 +107,8 @@ */ protected static function getSharedInstance(string $key, ...$params) { + $key = strtolower($key); + // Returns mock if exists if (isset(static::$mocks[$key])) { diff --git a/system/Config/DotEnv.php b/system/Config/DotEnv.php index cbf64e7..541334a 100644 --- a/system/Config/DotEnv.php +++ b/system/Config/DotEnv.php @@ -76,19 +76,44 @@ */ public function load(): bool { - // We don't want to enforce the presence of a .env file, - // they should be optional. - if (! is_file($this->path)) + $vars = $this->parse(); + + if ($vars === null) { return false; } - // Ensure file is readable + foreach ($vars as $name => $value) + { + $this->setVariable($name, $value); + } + + return true; // for success + } + + //-------------------------------------------------------------------- + + /** + * Parse the .env file into an array of key => value + * + * @return array|null + */ + public function parse(): ?array + { + // We don't want to enforce the presence of a .env file, they should be optional. + if (! is_file($this->path)) + { + return null; + } + + // Ensure the file is readable if (! is_readable($this->path)) { throw new \InvalidArgumentException("The .env file is not readable: {$this->path}"); } + $vars = []; + $lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) @@ -99,15 +124,15 @@ continue; } - // If there is an equal sign, then we know we - // are assigning a variable. + // If there is an equal sign, then we know we are assigning a variable. if (strpos($line, '=') !== false) { - $this->setVariable($line); + list($name, $value) = $this->normaliseVariable($line); + $vars[$name] = $value; } } - return true; // for success + return $vars; } //-------------------------------------------------------------------- @@ -122,8 +147,6 @@ */ protected function setVariable(string $name, string $value = '') { - list($name, $value) = $this->normaliseVariable($name, $value); - if (! getenv($name, true)) { putenv("$name=$value"); diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index e7ee3a1..800247f 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -3045,6 +3045,17 @@ if (! empty($matches[4])) { + $protectIdentifiers = false; + if (strpos($matches[4], '.') !== false) + { + $protectIdentifiers = true; + } + + if (strpos($matches[4], ':') === false) + { + $matches[4] = $this->db->protectIdentifiers(trim($matches[4]), false, $protectIdentifiers); + } + $matches[4] = ' ' . $matches[4]; } diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 56d71fb..de9d510 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1215,7 +1215,7 @@ // This can happen when this function is being called from a JOIN. if ($fieldExists === false) { - $i ++; + $i++; } // Verify table prefix and replace if necessary diff --git a/system/Database/Forge.php b/system/Database/Forge.php index ebe31fd..b40dc5e 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -816,13 +816,13 @@ /** * Column Drop * - * @param string $table Table name - * @param string $column_name Column name + * @param string $table Table name + * @param string|array $column_name Column name Array or comma separated * * @return mixed * @throws \CodeIgniter\Database\Exceptions\DatabaseException */ - public function dropColumn(string $table, string $column_name) + public function dropColumn(string $table, $column_name) { $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $column_name); if ($sql === false) @@ -897,24 +897,33 @@ * * @param string $alter_type ALTER type * @param string $table Table name - * @param mixed $field Column definition + * @param mixed $fields Column definition * * @return string|string[] */ - protected function _alterTable(string $alter_type, string $table, $field) + protected function _alterTable(string $alter_type, string $table, $fields) { $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' '; // DROP has everything it needs now. if ($alter_type === 'DROP') { - return $sql . 'DROP COLUMN ' . $this->db->escapeIdentifiers($field); + if (is_string($fields)) + { + $fields = explode(',', $fields); + } + + $fields = array_map(function ($field) { + return 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)); + }, $fields); + + return $sql . implode(', ', $fields); } $sql .= ($alter_type === 'ADD') ? 'ADD ' : $alter_type . ' COLUMN '; $sqls = []; - foreach ($field as $data) + foreach ($fields as $data) { $sqls[] = $sql . ($data['_literal'] !== false ? $data['_literal'] : $this->_processColumn($data)); diff --git a/system/Database/ModelFactory.php b/system/Database/ModelFactory.php new file mode 100644 index 0000000..6b7d514 --- /dev/null +++ b/system/Database/ModelFactory.php @@ -0,0 +1,119 @@ +locateFile($name, 'Models'); + + if (empty($file)) + { + // No file found - check if the class was namespaced + if (strpos($name, '\\') !== false) + { + // Class was namespaced and locateFile couldn't find it + return null; + } + + // Check all namespaces + $files = $locator->search('Models/' . $name); + if (empty($files)) + { + return null; + } + + // Get the first match (prioritizes user and framework) + $file = reset($files); + } + + $name = $locator->getClassname($file); + + if (empty($name)) + { + return null; + } + + return new $name($connection); + } +} diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 95cd66d..26b213c 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -203,7 +203,7 @@ "Database: Unable to set the configured connection charset ('{$this->charset}')."); $this->mysqli->close(); - if ($this->db->debug) + if ($this->DBDebug) { throw new DatabaseException('Unable to set client connection character set: ' . $this->charset); } diff --git a/system/Database/SQLite3/Table.php b/system/Database/SQLite3/Table.php index 46264da..0845a35 100644 --- a/system/Database/SQLite3/Table.php +++ b/system/Database/SQLite3/Table.php @@ -185,15 +185,29 @@ } /** - * Drops a column from the table. + * Drops columns from the table. * - * @param string $column + * @param string|array $columns * * @return \CodeIgniter\Database\SQLite3\Table */ - public function dropColumn(string $column) + public function dropColumn($columns) { - unset($this->fields[$column]); + //unset($this->fields[$column]); + + if (is_string($columns)) + { + $columns = explode(',', $columns); + } + + foreach ($columns as $column) + { + $column = trim($column); + if (isset($this->fields[$column])) + { + unset($this->fields[$column]); + } + } return $this; } diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index 9642ce0..02e50fc 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -393,7 +393,7 @@ if (strpos($response->getBody(), '') !== false) { $response->setBody( - str_replace('', $script . '', $response->getBody()) + str_replace('', '' . $script, $response->getBody()) ); return; diff --git a/system/Debug/Toolbar/Collectors/Routes.php b/system/Debug/Toolbar/Collectors/Routes.php index 2a80c55..22ea7c2 100644 --- a/system/Debug/Toolbar/Collectors/Routes.php +++ b/system/Debug/Toolbar/Collectors/Routes.php @@ -132,17 +132,38 @@ ]; /* - * Defined Routes - */ - $rawRoutes = $rawRoutes->getRoutes(); + * Defined Routes + */ $routes = []; + $methods = [ + 'get', + 'head', + 'post', + 'patch', + 'put', + 'delete', + 'options', + 'trace', + 'connect', + 'cli', + ]; - foreach ($rawRoutes as $from => $to) + foreach ($methods as $method) { - $routes[] = [ - 'from' => $from, - 'to' => $to, - ]; + $raw = $rawRoutes->getRoutes($method); + + foreach ($raw as $route => $handler) + { + // filter for strings, as callbacks aren't displayable + if (is_string($handler)) + { + $routes[] = [ + 'method' => strtoupper($method), + 'route' => $route, + 'handler' => $handler, + ]; + } + } } return [ diff --git a/system/Debug/Toolbar/Views/_routes.tpl b/system/Debug/Toolbar/Views/_routes.tpl index 0988049..35acdde 100644 --- a/system/Debug/Toolbar/Views/_routes.tpl +++ b/system/Debug/Toolbar/Views/_routes.tpl @@ -33,11 +33,19 @@

Defined Routes

+ + + + + + + {routes} - - + + + {/routes} diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css index f53b8fe..7a4c9c4 100644 --- a/system/Debug/Toolbar/Views/toolbar.css +++ b/system/Debug/Toolbar/Views/toolbar.css @@ -1,407 +1,595 @@ +/* CodeIgniter 4 - Debug bar + ============================================================================ */ +/* Forum: https://forum.codeigniter.com + * Github: https://github.com/codeigniter4/codeigniter4 + * Slack: https://codeigniterchat.slack.com + * Website: https://codeigniter.com + */ #debug-icon { - position: fixed; - bottom: 0; - right: 0; - width: 36px; - height: 36px; - background: #fff; - border: 1px solid #ddd; - margin: 0px; - z-index: 10000; - box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1); - clear: both; - text-align: center; -} - -#debug-icon a svg { - margin: 4px; - max-width: 26px; - max-height: 26px; -} - -#debug-bar a:active, #debug-bar a:link, #debug-bar a:visited { - color: #dd4814; -} + bottom: 0; + position: fixed; + right: 0; + z-index: 10000; + height: 36px; + width: 36px; + margin: 0px; + padding: 0px; + clear: both; + text-align: center; } + #debug-icon a svg { + margin: 8px; + max-width: 20px; + max-height: 20px; } + #debug-icon.fixed-top { + bottom: auto; + top: 0; } + #debug-icon .debug-bar-ndisplay { + display: none; } #debug-bar { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 36px; - background: #fff; - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 36px; - z-index: 10000; -} - -#debug-bar pre { + bottom: 0; + left: 0; + position: fixed; + right: 0; + z-index: 10000; + height: 36px; + line-height: 36px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + font-weight: 400; } + #debug-bar h1 { + bottom: 0; + display: inline-block; + font-size: 14px; + font-weight: normal; + margin: 0 16px 0 0; + padding: 0; + position: absolute; + right: 30px; + text-align: left; + top: 0; } + #debug-bar h2 { + font-size: 16px; + margin: 0; + padding: 5px 0 10px 0; } + #debug-bar h2 span { + font-size: 13px; } + #debug-bar h3 { + font-size: 12px; + font-weight: 200; + margin: 0 0 0 10px; + padding: 0; + text-transform: uppercase; } + #debug-bar p { + font-size: 12px; + margin: 0 0 0 15px; + padding: 0; } + #debug-bar a { + text-decoration: none; } + #debug-bar a:hover { + text-decoration: underline; } + #debug-bar button { + border: 1px solid; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + cursor: pointer; + line-height: 15px; } + #debug-bar button:hover { + text-decoration: underline; } + #debug-bar table { + border-collapse: collapse; + font-size: 14px; line-height: normal; -} - -#debug-bar h1, -#debug-bar h2, -#debug-bar h3 { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #666; - line-height: 1.5; -} - -#debug-bar p { - font-size: 12px; - margin: 0 0 10px 20px; - padding: 0; -} - -#debug-bar a { - text-decoration: none; -} - -#debug-bar a:hover { - text-decoration: underline; - text-decoration-color: #4e4a4a; -} - -#debug-bar .muted, -#debug-bar .muted td { - color: #bbb; -} - -#debug-bar .toolbar { - display: block; - background: inherit; - overflow: hidden; - overflow-y: auto; - white-space: nowrap; - box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1); - padding: 0 12px 0 12px; /* give room for OS X scrollbar */ - z-index: 10000; -} - -#debug-bar #toolbar-position > a { - padding: 0 6px; -} - -#debug-icon.fixed-top, -#debug-bar.fixed-top { - top: 0; - bottom: auto; -} - -#debug-icon.fixed-top, -#debug-bar.fixed-top .toolbar { - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); -} - -#debug-bar h1 { - font-size: 16px; - line-height: 36px; - font-weight: 500; - margin: 0 16px 0 0; - padding: 0; - text-align: left; - display: inline-block; - position: absolute; - right: 30px; - top: 0; - bottom: 0; -} - -#debug-bar-link { - padding: 6px; - position: absolute; - display: inline-block; - top: 0; - bottom: 0; - right: 10px; - font-size: 16px; - line-height: 36px; - width: 24px; -} - -#debug-bar h2 { - font-size: 16px; - font-weight: 500; - margin: 0; - padding: 0; -} - -#debug-bar h2 span { - font-size: 13px; -} - -#debug-bar h3 { - text-transform: uppercase; - font-size: 11px; - font-weight: 200; - margin-left: 10pt; -} - -#debug-bar span.ci-label { - display: inline-block; - font-size: 14px; - line-height: 36px; - vertical-align: baseline; -} - -#debug-bar span.ci-label img { - display: inline-block; - margin: 6px 3px 6px 0; - float: left; - clear: left; -} - -#debug-bar span.ci-label .badge { - display: inline-block; - padding: 3px 6px; - font-size: 75%; - font-weight: 500; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: 10rem; - background-color: #5bc0de; - margin-left: 0.5em; -} - -#debug-bar span.ci-label .badge.active { - background-color: red; -} - -#debug-bar button { - border: 1px solid #ddd; - background-color: #fff; - cursor: pointer; - border-radius: 4px; - color: #333; -} - -#debug-bar button:hover { - background-color: #eaeaea; -} - -#debug-bar tr[data-active="1"] { - background-color: #dff0d8; -} - -#debug-bar tr[data-active="1"]:hover { - background-color: #a7d499; -} - -#debug-bar tr.current { - background-color: #FDC894; -} - -#debug-bar tr.current:hover { - background-color: #DD4814; -} - -#debug-bar table strong { - font-weight: 500; - color: rgba(0, 0, 0, 0.3); -} - -#debug-bar .ci-label { - text-shadow: none; -} - -#debug-bar .ci-label:hover { - background-color: #eaeaea; - cursor: pointer; -} - -#debug-bar .ci-label a { - display: block; - padding: 0 10px; - color: inherit; - text-decoration: none; - letter-spacing: normal; -} - -#debug-bar .ci-label.active { - background-color: #eaeaea; - border-color: #bbb; -} - -#debug-bar .tab { - display: none; - background: inherit; - padding: 1em 2em; - border: solid #ddd; - border-width: 1px 0; - position: fixed; - bottom: 35px; - left: 0; - right: 0; - z-index: 9999; - box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1); - overflow: hidden; - overflow-y: auto; - max-height: 62%; -} - -#debug-bar.fixed-top .tab { - top: 36px; - bottom: auto; - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); -} - -#debug-bar table { - margin: 0 0 10px 20px; - font-size: 0.9rem; - border-collapse: collapse; - width: 100%; -} - -#debug-bar td, -#debug-bar th { - display: table-cell; - text-align: left; -} - -#debug-bar tr { - border: none; -} - -#debug-bar td { - border: none; - padding: 0 10px 0 5px; - margin: 0; -} - -#debug-bar th { - padding-bottom: 0.7em; -} - -#debug-bar tr td:first-child { - max-width: 20%; -} - -#debug-bar tr td:first-child.narrow { - width: 7em; -} - -#debug-bar tr:hover { - background-color: #f3f3f3; -} - -#debug-bar table.timeline { - width: 100%; - margin-left: 0; -} - -#debug-bar table.timeline th { - font-size: 0.7em; - font-weight: 200; - text-align: left; - padding-bottom: 1em; -} - -#debug-bar table.timeline td, -#debug-bar table.timeline th { - border-left: 1px solid #ddd; - padding: 0 1em; - position: relative; -} - -#debug-bar table.timeline tr td:first-child, -#debug-bar table.timeline tr th:first-child { - border-left: 0; - padding-left: 0; -} - -#debug-bar table.timeline td { - padding: 5px; -} - -#debug-bar table.timeline .timer { - position: absolute; - display: inline-block; - padding: 5px; - top: 40%; - border-radius: 4px; - background-color: #999; -} - -#debug-bar .route-params, -#debug-bar .route-params-item { - vertical-align: top; -} - -#debug-bar .route-params-item td:first-child { - padding-left: 1em; - text-align: right; - font-style: italic; -} + margin: 5px 10px 15px 10px; + width: calc(100% - 10px); } + #debug-bar table strong { + font-weight: 500; } + #debug-bar table th { + display: table-cell; + font-weight: 600; + padding-bottom: 0.7em; + text-align: left; } + #debug-bar table tr { + border: none; } + #debug-bar table td { + border: none; + display: table-cell; + margin: 0; + text-align: left; } + #debug-bar table td:first-child { + max-width: 20%; } + #debug-bar table td:first-child.narrow { + width: 7em; } + #debug-bar .toolbar { + display: block; + overflow: hidden; + overflow-y: auto; + padding: 0 12px 0 12px; + /* give room for OS X scrollbar */ + white-space: nowrap; + z-index: 10000; } + #debug-bar.fixed-top { + bottom: auto; + top: 0; } + #debug-bar.fixed-top .tab { + bottom: auto; + top: 36px; } + #debug-bar #toolbar-position a, + #debug-bar #toolbar-theme a { + float: left; + padding: 0 6px; } + #debug-bar #toolbar-position a:hover, + #debug-bar #toolbar-theme a:hover { + text-decoration: none; } + #debug-bar #debug-bar-link { + bottom: 0; + display: inline-block; + font-size: 16px; + line-height: 36px; + padding: 6px; + position: absolute; + right: 10px; + top: 0; + width: 24px; } + #debug-bar .ci-label { + display: inline-block; + font-size: 14px; + vertical-align: baseline; } + #debug-bar .ci-label:hover { + cursor: pointer; } + #debug-bar .ci-label a { + color: inherit; + display: block; + letter-spacing: normal; + padding: 0 10px; + text-decoration: none; } + #debug-bar .ci-label img { + clear: left; + display: inline-block; + float: left; + margin: 6px 3px 6px 0; } + #debug-bar .ci-label .badge { + border-radius: 12px; + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + display: inline-block; + font-size: 75%; + font-weight: bold; + line-height: 12px; + margin-left: 5px; + padding: 2px 5px; + text-align: center; + vertical-align: baseline; + white-space: nowrap; } + #debug-bar .tab { + bottom: 35px; + display: none; + left: 0; + max-height: 62%; + overflow: hidden; + overflow-y: auto; + padding: 1em 2em; + position: fixed; + right: 0; + z-index: 9999; } + #debug-bar .timeline { + margin-left: 0; + width: 100%; } + #debug-bar .timeline th { + border-left: 1px solid; + font-size: 12px; + font-weight: 200; + padding: 5px 5px 10px 5px; + position: relative; + text-align: left; } + #debug-bar .timeline th:first-child { + border-left: 0; } + #debug-bar .timeline td { + border-left: 1px solid; + padding: 5px; + position: relative; } + #debug-bar .timeline td:first-child { + border-left: 0; } + #debug-bar .timeline .timer { + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + display: inline-block; + padding: 5px; + position: absolute; + top: 30%; } + #debug-bar .route-params, + #debug-bar .route-params-item { + vertical-align: top; } + #debug-bar .route-params td:first-child, + #debug-bar .route-params-item td:first-child { + font-style: italic; + padding-left: 1em; + text-align: right; } .debug-view.show-view { - border: 1px solid #dd4814; - margin: 4px; -} + border: 1px solid; + margin: 4px; } .debug-view-path { - background-color: #fdc894; - color: #000; - padding: 2px; - font-family: monospace; - font-size: 11px; - min-height: 16px; - text-align: left; -} + font-family: monospace; + font-size: 12px; + letter-spacing: normal; + min-height: 16px; + padding: 2px; + text-align: left; } .show-view .debug-view-path { - display: block !important; -} + display: block !important; } -@media screen and (max-width: 748px) { - .hide-sm { - display: none !important; - } -} +@media screen and (max-width: 1024px) { + .hide-sm { + display: none !important; } } +#debug-icon { + background-color: #FFFFFF; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #debug-icon a:active, #debug-icon a:link, #debug-icon a:visited { + color: #DD8615; } -/** -simple styles to replace inline styles - */ +#debug-bar { + background-color: #FFFFFF; + color: #434343; } + #debug-bar h1, + #debug-bar h2, + #debug-bar h3, + #debug-bar p, + #debug-bar a, + #debug-bar button, + #debug-bar table, + #debug-bar thead, + #debug-bar tr, + #debug-bar td, + #debug-bar button, + #debug-bar .toolbar { + background-color: transparent; + color: #434343; } + #debug-bar button { + background-color: #FFFFFF; } + #debug-bar table strong { + color: #FDC894; } + #debug-bar table tbody tr:hover { + background-color: #DFDFDF; } + #debug-bar table tbody tr.current { + background-color: #FDC894; } + #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; } + #debug-bar .toolbar { + background-color: #FFFFFF; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #debug-bar .toolbar img { + filter: brightness(0) invert(0.4); } + #debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #DFDFDF; + -moz-box-shadow: 0 1px 4px #DFDFDF; + -webkit-box-shadow: 0 1px 4px #DFDFDF; } + #debug-bar .muted { + color: #434343; } + #debug-bar .muted td { + color: #DFDFDF; } + #debug-bar .muted:hover td { + color: #434343; } + #debug-bar #toolbar-position, + #debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); } + #debug-bar .ci-label.active { + background-color: #DFDFDF; } + #debug-bar .ci-label:hover { + background-color: #DFDFDF; } + #debug-bar .ci-label .badge { + background-color: #5BC0DE; + color: #FFFFFF; } + #debug-bar .tab { + background-color: #FFFFFF; + box-shadow: 0 -1px 4px #DFDFDF; + -moz-box-shadow: 0 -1px 4px #DFDFDF; + -webkit-box-shadow: 0 -1px 4px #DFDFDF; } + #debug-bar .timeline th, + #debug-bar .timeline td { + border-color: #DFDFDF; } + #debug-bar .timeline .timer { + background-color: #DD8615; } + +.debug-view.show-view { + border-color: #DD8615; } + +.debug-view-path { + background-color: #FDC894; + color: #434343; } + +@media (prefers-color-scheme: dark) { + #debug-icon { + background-color: #252525; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #debug-icon a:active, #debug-icon a:link, #debug-icon a:visited { + color: #DD8615; } + + #debug-bar { + background-color: #252525; + color: #DFDFDF; } + #debug-bar h1, + #debug-bar h2, + #debug-bar h3, + #debug-bar p, + #debug-bar a, + #debug-bar button, + #debug-bar table, + #debug-bar thead, + #debug-bar tr, + #debug-bar td, + #debug-bar button, + #debug-bar .toolbar { + background-color: transparent; + color: #DFDFDF; } + #debug-bar button { + background-color: #252525; } + #debug-bar table strong { + color: #FDC894; } + #debug-bar table tbody tr:hover { + background-color: #434343; } + #debug-bar table tbody tr.current { + background-color: #FDC894; } + #debug-bar table tbody tr.current td { + color: #252525; } + #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; } + #debug-bar .toolbar { + background-color: #434343; + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; } + #debug-bar .toolbar img { + filter: brightness(0) invert(1); } + #debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; } + #debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #434343; + -moz-box-shadow: 0 1px 4px #434343; + -webkit-box-shadow: 0 1px 4px #434343; } + #debug-bar .muted { + color: #DFDFDF; } + #debug-bar .muted td { + color: #434343; } + #debug-bar .muted:hover td { + color: #DFDFDF; } + #debug-bar #toolbar-position, + #debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); } + #debug-bar .ci-label.active { + background-color: #252525; } + #debug-bar .ci-label:hover { + background-color: #252525; } + #debug-bar .ci-label .badge { + background-color: #5BC0DE; + color: #DFDFDF; } + #debug-bar .tab { + background-color: #252525; + box-shadow: 0 -1px 4px #434343; + -moz-box-shadow: 0 -1px 4px #434343; + -webkit-box-shadow: 0 -1px 4px #434343; } + #debug-bar .timeline th, + #debug-bar .timeline td { + border-color: #434343; } + #debug-bar .timeline .timer { + background-color: #DD8615; } + + .debug-view.show-view { + border-color: #DD8615; } + + .debug-view-path { + background-color: #FDC894; + color: #434343; } } +#toolbarContainer.dark #debug-icon { + background-color: #252525; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #toolbarContainer.dark #debug-icon a:active, #toolbarContainer.dark #debug-icon a:link, #toolbarContainer.dark #debug-icon a:visited { + color: #DD8615; } +#toolbarContainer.dark #debug-bar { + background-color: #252525; + color: #DFDFDF; } + #toolbarContainer.dark #debug-bar h1, + #toolbarContainer.dark #debug-bar h2, + #toolbarContainer.dark #debug-bar h3, + #toolbarContainer.dark #debug-bar p, + #toolbarContainer.dark #debug-bar a, + #toolbarContainer.dark #debug-bar button, + #toolbarContainer.dark #debug-bar table, + #toolbarContainer.dark #debug-bar thead, + #toolbarContainer.dark #debug-bar tr, + #toolbarContainer.dark #debug-bar td, + #toolbarContainer.dark #debug-bar button, + #toolbarContainer.dark #debug-bar .toolbar { + background-color: transparent; + color: #DFDFDF; } + #toolbarContainer.dark #debug-bar button { + background-color: #252525; } + #toolbarContainer.dark #debug-bar table strong { + color: #FDC894; } + #toolbarContainer.dark #debug-bar table tbody tr:hover { + background-color: #434343; } + #toolbarContainer.dark #debug-bar table tbody tr.current { + background-color: #FDC894; } + #toolbarContainer.dark #debug-bar table tbody tr.current td { + color: #252525; } + #toolbarContainer.dark #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; } + #toolbarContainer.dark #debug-bar .toolbar { + background-color: #434343; + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; } + #toolbarContainer.dark #debug-bar .toolbar img { + filter: brightness(0) invert(1); } + #toolbarContainer.dark #debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #434343; + -moz-box-shadow: 0 0 4px #434343; + -webkit-box-shadow: 0 0 4px #434343; } + #toolbarContainer.dark #debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #434343; + -moz-box-shadow: 0 1px 4px #434343; + -webkit-box-shadow: 0 1px 4px #434343; } + #toolbarContainer.dark #debug-bar .muted { + color: #DFDFDF; } + #toolbarContainer.dark #debug-bar .muted td { + color: #434343; } + #toolbarContainer.dark #debug-bar .muted:hover td { + color: #DFDFDF; } + #toolbarContainer.dark #debug-bar #toolbar-position, + #toolbarContainer.dark #debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); } + #toolbarContainer.dark #debug-bar .ci-label.active { + background-color: #252525; } + #toolbarContainer.dark #debug-bar .ci-label:hover { + background-color: #252525; } + #toolbarContainer.dark #debug-bar .ci-label .badge { + background-color: #5BC0DE; + color: #DFDFDF; } + #toolbarContainer.dark #debug-bar .tab { + background-color: #252525; + box-shadow: 0 -1px 4px #434343; + -moz-box-shadow: 0 -1px 4px #434343; + -webkit-box-shadow: 0 -1px 4px #434343; } + #toolbarContainer.dark #debug-bar .timeline th, + #toolbarContainer.dark #debug-bar .timeline td { + border-color: #434343; } + #toolbarContainer.dark #debug-bar .timeline .timer { + background-color: #DD8615; } +#toolbarContainer.dark .debug-view.show-view { + border-color: #DD8615; } +#toolbarContainer.dark .debug-view-path { + background-color: #FDC894; + color: #434343; } + +#toolbarContainer.light #debug-icon { + background-color: #FFFFFF; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #toolbarContainer.light #debug-icon a:active, #toolbarContainer.light #debug-icon a:link, #toolbarContainer.light #debug-icon a:visited { + color: #DD8615; } +#toolbarContainer.light #debug-bar { + background-color: #FFFFFF; + color: #434343; } + #toolbarContainer.light #debug-bar h1, + #toolbarContainer.light #debug-bar h2, + #toolbarContainer.light #debug-bar h3, + #toolbarContainer.light #debug-bar p, + #toolbarContainer.light #debug-bar a, + #toolbarContainer.light #debug-bar button, + #toolbarContainer.light #debug-bar table, + #toolbarContainer.light #debug-bar thead, + #toolbarContainer.light #debug-bar tr, + #toolbarContainer.light #debug-bar td, + #toolbarContainer.light #debug-bar button, + #toolbarContainer.light #debug-bar .toolbar { + background-color: transparent; + color: #434343; } + #toolbarContainer.light #debug-bar button { + background-color: #FFFFFF; } + #toolbarContainer.light #debug-bar table strong { + color: #FDC894; } + #toolbarContainer.light #debug-bar table tbody tr:hover { + background-color: #DFDFDF; } + #toolbarContainer.light #debug-bar table tbody tr.current { + background-color: #FDC894; } + #toolbarContainer.light #debug-bar table tbody tr.current:hover td { + background-color: #DD4814; + color: #FFFFFF; } + #toolbarContainer.light #debug-bar .toolbar { + background-color: #FFFFFF; + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #toolbarContainer.light #debug-bar .toolbar img { + filter: brightness(0) invert(0.4); } + #toolbarContainer.light #debug-bar.fixed-top .toolbar { + box-shadow: 0 0 4px #DFDFDF; + -moz-box-shadow: 0 0 4px #DFDFDF; + -webkit-box-shadow: 0 0 4px #DFDFDF; } + #toolbarContainer.light #debug-bar.fixed-top .tab { + box-shadow: 0 1px 4px #DFDFDF; + -moz-box-shadow: 0 1px 4px #DFDFDF; + -webkit-box-shadow: 0 1px 4px #DFDFDF; } + #toolbarContainer.light #debug-bar .muted { + color: #434343; } + #toolbarContainer.light #debug-bar .muted td { + color: #DFDFDF; } + #toolbarContainer.light #debug-bar .muted:hover td { + color: #434343; } + #toolbarContainer.light #debug-bar #toolbar-position, + #toolbarContainer.light #debug-bar #toolbar-theme { + filter: brightness(0) invert(0.6); } + #toolbarContainer.light #debug-bar .ci-label.active { + background-color: #DFDFDF; } + #toolbarContainer.light #debug-bar .ci-label:hover { + background-color: #DFDFDF; } + #toolbarContainer.light #debug-bar .ci-label .badge { + background-color: #5BC0DE; + color: #FFFFFF; } + #toolbarContainer.light #debug-bar .tab { + background-color: #FFFFFF; + box-shadow: 0 -1px 4px #DFDFDF; + -moz-box-shadow: 0 -1px 4px #DFDFDF; + -webkit-box-shadow: 0 -1px 4px #DFDFDF; } + #toolbarContainer.light #debug-bar .timeline th, + #toolbarContainer.light #debug-bar .timeline td { + border-color: #DFDFDF; } + #toolbarContainer.light #debug-bar .timeline .timer { + background-color: #DD8615; } +#toolbarContainer.light .debug-view.show-view { + border-color: #DD8615; } +#toolbarContainer.light .debug-view-path { + background-color: #FDC894; + color: #434343; } + .debug-bar-width30 { - width: 30%; -} + width: 30%; } .debug-bar-width10 { - width: 10%; -} + width: 10%; } .debug-bar-width70p { - width: 70px; -} + width: 70px; } .debug-bar-width140p { - width: 140px; -} + width: 140px; } .debug-bar-width20e { - width: 20em; -} + width: 20em; } .debug-bar-width6r { - width: 6rem; -} + width: 6rem; } .debug-bar-ndisplay { - display: none; -} + display: none; } .debug-bar-alignRight { - text-align: right; -} + text-align: right; } .debug-bar-alignLeft { - text-align: left; -} + text-align: left; } .debug-bar-noverflow { - overflow: hidden; -} + overflow: hidden; } diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js index b8d0e83..f146da4 100644 --- a/system/Debug/Toolbar/Views/toolbar.js +++ b/system/Debug/Toolbar/Views/toolbar.js @@ -4,18 +4,21 @@ var ciDebugBar = { + toolbarContainer : null, toolbar : null, icon : null, //-------------------------------------------------------------------- init : function () { + this.toolbarContainer = document.getElementById('toolbarContainer'); this.toolbar = document.getElementById('debug-bar'); this.icon = document.getElementById('debug-icon'); ciDebugBar.createListeners(); ciDebugBar.setToolbarState(); ciDebugBar.setToolbarPosition(); + ciDebugBar.setToolbarTheme(); ciDebugBar.toggleViewsHints(); document.getElementById('debug-bar-link').addEventListener('click', ciDebugBar.toggleToolbar, true); @@ -504,6 +507,56 @@ //-------------------------------------------------------------------- + setToolbarTheme: function () { + var btnTheme = document.getElementById('toolbar-theme'); + var isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; + var isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches; + + // If a cookie is set with a value, we force the color scheme + if (ciDebugBar.readCookie('debug-bar-theme') === 'dark') + { + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'light'); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'dark'); + } + else if (ciDebugBar.readCookie('debug-bar-theme') === 'light') + { + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark'); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light'); + } + + btnTheme.addEventListener('click', function () { + var theme = ciDebugBar.readCookie('debug-bar-theme'); + + if (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches) + { + // If there is no cookie, and "prefers-color-scheme" is set to "dark" + // It means that the user wants to switch to light mode + ciDebugBar.createCookie('debug-bar-theme', 'light', 365); + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark'); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light'); + } + else + { + if (theme === 'dark') + { + ciDebugBar.createCookie('debug-bar-theme', 'light', 365); + ciDebugBar.removeClass(ciDebugBar.toolbarContainer, 'dark'); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'light'); + } + else + { + // 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'); + ciDebugBar.addClass(ciDebugBar.toolbarContainer, 'dark'); + } + } + }, true); + }, + + //-------------------------------------------------------------------- + /** * Helper to create a cookie. * diff --git a/system/Debug/Toolbar/Views/toolbar.tpl.php b/system/Debug/Toolbar/Views/toolbar.tpl.php index b746364..5f5d7c4 100644 --- a/system/Debug/Toolbar/Views/toolbar.tpl.php +++ b/system/Debug/Toolbar/Views/toolbar.tpl.php @@ -49,6 +49,7 @@
+ 🔅 diff --git a/system/Email/Email.php b/system/Email/Email.php index ee0e014..5ace58a 100644 --- a/system/Email/Email.php +++ b/system/Email/Email.php @@ -510,7 +510,7 @@ /** * Set Recipients * - * @param string $to + * @param string|array $to * * @return Email */ @@ -892,7 +892,7 @@ /** * Validate Email Address * - * @param string $email + * @param string|array $email * * @return boolean */ @@ -907,7 +907,7 @@ { if (! $this->isValidEmail($val)) { - $this->setErrorMessage(lang('Email.invalidAddress', $val)); + $this->setErrorMessage(lang('Email.invalidAddress', [$val])); return false; } } diff --git a/system/Files/File.php b/system/Files/File.php index 15ff417..8b5fc2e 100644 --- a/system/Files/File.php +++ b/system/Files/File.php @@ -86,29 +86,36 @@ * the file in the $_FILES array if available, as PHP calculates this based * on the actual size transmitted. * - * @param string $unit The unit to return: - * - b Bytes - * - kb Kilobytes - * - mb Megabytes - * - * @return integer|null The file size in bytes or null if unknown. + * @return integer The file size in bytes */ - public function getSize(string $unit = 'b') + public function getSize() { if (is_null($this->size)) { - $this->size = filesize($this->getPathname()); + $this->size = parent::getSize(); } + return $this->size; + } + + /** + * Retrieve the file size by unit. + * + * @param string $unit + * + * @return integer|string + */ + public function getSizeByUnit(string $unit = 'b') + { switch (strtolower($unit)) { case 'kb': - return number_format($this->size / 1024, 3); + return number_format($this->getSize() / 1024, 3); case 'mb': - return number_format(($this->size / 1024) / 1024, 3); + return number_format(($this->getSize() / 1024) / 1024, 3); + default: + return $this->getSize(); } - - return (int) $this->size; } //-------------------------------------------------------------------- diff --git a/system/HTTP/Files/UploadedFile.php b/system/HTTP/Files/UploadedFile.php index 1826a7a..62a245b 100644 --- a/system/HTTP/Files/UploadedFile.php +++ b/system/HTTP/Files/UploadedFile.php @@ -182,8 +182,9 @@ } catch (Exception $e) { - $error = error_get_last(); - throw HTTPException::forMoveFailed(basename($this->path), $targetPath, strip_tags($error['message'])); + $error = error_get_last(); + $message = isset($error['message']) ? strip_tags($error['message']) : ''; + throw HTTPException::forMoveFailed(basename($this->path), $targetPath, $message); } @chmod($targetPath, 0777 & ~umask()); diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index dca44d2..a6d3c5e 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -716,7 +716,7 @@ } // HTTP Status - header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->statusCode, $this->reason), true, $this->statusCode); + header(sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->statusCode, $this->reason), true, $this->statusCode); // Send all of our headers foreach ($this->getHeaders() as $name => $values) diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php index 22813b3..2959333 100644 --- a/system/Helpers/url_helper.php +++ b/system/Helpers/url_helper.php @@ -106,6 +106,7 @@ { $uri = implode('/', $uri); } + $uri = trim($uri, '/'); // We should be using the configured baseURL that the user set; // otherwise get rid of the path, because we have diff --git a/system/Images/Exceptions/ImageException.php b/system/Images/Exceptions/ImageException.php index 1021824..c514c7c 100644 --- a/system/Images/Exceptions/ImageException.php +++ b/system/Images/Exceptions/ImageException.php @@ -5,6 +5,16 @@ class ImageException extends FrameworkException implements ExceptionInterface { + public static function forMissingImage() + { + return new static(lang('Images.sourceImageRequired')); + } + + public static function forFileNotSupported() + { + return new static(lang('Images.fileNotSupported')); + } + public static function forMissingAngle() { return new static(lang('Images.rotationAngleRequired')); @@ -15,6 +25,11 @@ return new static(lang('Images.invalidDirection', [$dir])); } + public static function forInvalidPath() + { + return new static(lang('Images.invalidPath')); + } + public static function forEXIFUnsupported() { return new static(lang('Images.exifNotSupported')); diff --git a/system/Images/Handlers/BaseHandler.php b/system/Images/Handlers/BaseHandler.php index 3bd61e8..990ad23 100644 --- a/system/Images/Handlers/BaseHandler.php +++ b/system/Images/Handlers/BaseHandler.php @@ -63,6 +63,13 @@ protected $image = null; /** + * Whether the image file has been confirmed. + * + * @var bool + */ + protected $verified = false; + + /** * Image width. * * @var integer @@ -158,6 +165,7 @@ // Clear out the old resource so that // it doesn't try to use a previous image $this->resource = null; + $this->verified = false; $this->image = new Image($path, true); @@ -177,9 +185,9 @@ { if ($this->resource === null) { - $path = $this->image->getPathname(); + $path = $this->image()->getPathname(); // if valid image type, make corresponding image resource - switch ($this->image->imageType) + switch ($this->image()->imageType) { case IMAGETYPE_GIF: $this->resource = imagecreatefromgif($path); @@ -206,6 +214,43 @@ return $this->image; } + /** + * Verifies that a file has been supplied and it is an image. + * + * @return Image The image instance + * @throws type ImageException + */ + protected function image(): ?Image + { + if ($this->verified) + { + return $this->image; + } + + // Verify withFile has been called + if (empty($this->image)) + { + throw ImageException::forMissingImage(); + } + + // Verify the loaded image is an Image instance + if (! $this->image instanceof Image) + { + throw ImageException::forInvalidPath(); + } + + // File::__construct has verified the file exists - make sure it is an image + if (! is_int($this->image->imageType)) + { + throw ImageException::forFileNotSupported(); + } + + // Note that the image has been verified + $this->verified = true; + + return $this->image; + } + //-------------------------------------------------------------------- /** @@ -236,7 +281,7 @@ public function resize(int $width, int $height, bool $maintainRatio = false, string $masterDim = 'auto') { // If the target width/height match the source, then we have nothing to do here. - if ($this->image->origWidth === $width && $this->image->origHeight === $height) + if ($this->image()->origWidth === $width && $this->image()->origHeight === $height) { return $this; } @@ -302,7 +347,7 @@ */ public function convert(int $imageType) { - $this->image->imageType = $imageType; + $this->image()->imageType = $imageType; return $this; } @@ -359,8 +404,8 @@ */ public function flatten(int $red = 255, int $green = 255, int $blue = 255) { - $this->width = $this->image->origWidth; - $this->height = $this->image->origHeight; + $this->width = $this->image()->origWidth; + $this->height = $this->image()->origHeight; return $this->_flatten(); } @@ -538,11 +583,11 @@ } $exif = null; // default - switch ($this->image->imageType) + switch ($this->image()->imageType) { case IMAGETYPE_JPEG: case IMAGETYPE_TIFF_II: - $exif = exif_read_data($this->image->getPathname()); + $exif = exif_read_data($this->image()->getPathname()); if (! is_null($key) && is_array($exif)) { $exif = $exif[$key] ?? false; @@ -576,8 +621,8 @@ */ public function fit(int $width, int $height = null, string $position = 'center') { - $origWidth = $this->image->origWidth; - $origHeight = $this->image->origHeight; + $origWidth = $this->image()->origWidth; + $origHeight = $this->image()->origHeight; list($cropWidth, $cropHeight) = $this->calcAspectRatio($width, $height, $origWidth, $origHeight); @@ -749,9 +794,9 @@ */ public function __call(string $name, array $args = []) { - if (method_exists($this->image, $name)) + if (method_exists($this->image(), $name)) { - return $this->image->$name(...$args); + return $this->image()->$name(...$args); } } @@ -772,11 +817,11 @@ protected function reproportion() { if (($this->width === 0 && $this->height === 0) || - $this->image->origWidth === 0 || - $this->image->origHeight === 0 || + $this->image()->origWidth === 0 || + $this->image()->origHeight === 0 || ( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height)) || - ! ctype_digit((string) $this->image->origWidth) || - ! ctype_digit((string) $this->image->origHeight) + ! ctype_digit((string) $this->image()->origWidth) || + ! ctype_digit((string) $this->image()->origHeight) ) { return; @@ -790,7 +835,7 @@ { if ($this->width > 0 && $this->height > 0) { - $this->masterDim = ((($this->image->origHeight / $this->image->origWidth) - ($this->height / $this->width)) < 0) ? 'width' : 'height'; + $this->masterDim = ((($this->image()->origHeight / $this->image()->origWidth) - ($this->height / $this->width)) < 0) ? 'width' : 'height'; } else { @@ -805,11 +850,11 @@ if ($this->masterDim === 'width') { - $this->height = (int) ceil($this->width * $this->image->origHeight / $this->image->origWidth); + $this->height = (int) ceil($this->width * $this->image()->origHeight / $this->image()->origWidth); } else { - $this->width = (int) ceil($this->image->origWidth * $this->height / $this->image->origHeight); + $this->width = (int) ceil($this->image()->origWidth * $this->height / $this->image()->origHeight); } } diff --git a/system/Images/Handlers/GDHandler.php b/system/Images/Handlers/GDHandler.php index 7f24af5..0ef75aa 100644 --- a/system/Images/Handlers/GDHandler.php +++ b/system/Images/Handlers/GDHandler.php @@ -151,8 +151,8 @@ { $srcImg = $this->createImage(); - $width = $this->image->origWidth; - $height = $this->image->origHeight; + $width = $this->image()->origWidth; + $height = $this->image()->origHeight; if ($direction === 'horizontal') { @@ -254,8 +254,8 @@ */ protected function process(string $action) { - $origWidth = $this->image->origWidth; - $origHeight = $this->image->origHeight; + $origWidth = $this->image()->origWidth; + $origHeight = $this->image()->origHeight; if ($action === 'crop') { @@ -266,8 +266,8 @@ // Modify the "original" width/height to the new // values so that methods that come after have the // correct size to work with. - $this->image->origHeight = $this->height; - $this->image->origWidth = $this->width; + $this->image()->origHeight = $this->height; + $this->image()->origWidth = $this->width; } // Create the image handle @@ -286,7 +286,7 @@ $dest = $create($this->width, $this->height); - if ($this->image->imageType === IMAGETYPE_PNG) // png we can actually preserve transparency + if ($this->image()->imageType === IMAGETYPE_PNG) // png we can actually preserve transparency { imagealphablending($dest, false); imagesavealpha($dest, true); @@ -318,9 +318,9 @@ */ public function save(string $target = null, int $quality = 90): bool { - $target = empty($target) ? $this->image->getPathname() : $target; + $target = empty($target) ? $this->image()->getPathname() : $target; - switch ($this->image->imageType) + switch ($this->image()->imageType) { case IMAGETYPE_GIF: if (! function_exists('imagegif')) @@ -389,12 +389,12 @@ if ($path === '') { - $path = $this->image->getPathname(); + $path = $this->image()->getPathname(); } if ($imageType === '') { - $imageType = $this->image->imageType; + $imageType = $this->image()->imageType; } switch ($imageType) @@ -490,21 +490,21 @@ if ($options['vAlign'] === 'middle') { // Don't apply padding when you're in the middle of the image. - $yAxis += ($this->image->origHeight / 2) + ($fontheight / 2) - $options['padding']; + $yAxis += ($this->image()->origHeight / 2) + ($fontheight / 2) - $options['padding']; } elseif ($options['vAlign'] === 'bottom') { - $yAxis = ($this->image->origHeight - $fontheight - $options['shadowOffset'] - ($fontheight / 2)) - $yAxis; + $yAxis = ($this->image()->origHeight - $fontheight - $options['shadowOffset'] - ($fontheight / 2)) - $yAxis; } // Set horizontal alignment if ($options['hAlign'] === 'right') { - $xAxis += ($this->image->origWidth - ($fontwidth * strlen($text)) - $options['shadowOffset']) - (2 * $options['padding']); + $xAxis += ($this->image()->origWidth - ($fontwidth * strlen($text)) - $options['shadowOffset']) - (2 * $options['padding']); } elseif ($options['hAlign'] === 'center') { - $xAxis += floor(($this->image->origWidth - ($fontwidth * strlen($text))) / 2); + $xAxis += floor(($this->image()->origWidth - ($fontwidth * strlen($text))) / 2); } $options['xAxis'] = $xAxis; diff --git a/system/Images/Handlers/ImageMagickHandler.php b/system/Images/Handlers/ImageMagickHandler.php index d786166..cc5f577 100644 --- a/system/Images/Handlers/ImageMagickHandler.php +++ b/system/Images/Handlers/ImageMagickHandler.php @@ -97,7 +97,7 @@ */ public function _resize(bool $maintainRatio = false) { - $source = ! empty($this->resource) ? $this->resource : $this->image->getPathname(); + $source = ! empty($this->resource) ? $this->resource : $this->image()->getPathname(); $destination = $this->getResourcePath(); $escape = '\\'; @@ -123,7 +123,7 @@ */ public function _crop() { - $source = ! empty($this->resource) ? $this->resource : $this->image->getPathname(); + $source = ! empty($this->resource) ? $this->resource : $this->image()->getPathname(); $destination = $this->getResourcePath(); $action = ' -crop ' . $this->width . 'x' . $this->height . '+' . $this->xAxis . '+' . $this->yAxis . ' "' . $source . '" "' . $destination . '"'; @@ -148,7 +148,7 @@ { $angle = '-rotate ' . $angle; - $source = ! empty($this->resource) ? $this->resource : $this->image->getPathname(); + $source = ! empty($this->resource) ? $this->resource : $this->image()->getPathname(); $destination = $this->getResourcePath(); $action = ' ' . $angle . ' "' . $source . '" "' . $destination . '"'; @@ -174,7 +174,7 @@ { $flatten = "-background RGB({$red},{$green},{$blue}) -flatten"; - $source = ! empty($this->resource) ? $this->resource : $this->image->getPathname(); + $source = ! empty($this->resource) ? $this->resource : $this->image()->getPathname(); $destination = $this->getResourcePath(); $action = ' ' . $flatten . ' "' . $source . '" "' . $destination . '"'; @@ -198,7 +198,7 @@ { $angle = $direction === 'horizontal' ? '-flop' : '-flip'; - $source = ! empty($this->resource) ? $this->resource : $this->image->getPathname(); + $source = ! empty($this->resource) ? $this->resource : $this->image()->getPathname(); $destination = $this->getResourcePath(); $action = ' ' . $angle . ' "' . $source . '" "' . $destination . '"'; @@ -286,7 +286,7 @@ */ public function save(string $target = null, int $quality = 90): bool { - $target = empty($target) ? $this->image : $target; + $target = empty($target) ? $this->image() : $target; // If no new resource has been created, then we're // simply copy the existing one. @@ -295,7 +295,7 @@ $name = basename($target); $path = pathinfo($target, PATHINFO_DIRNAME); - return $this->image->copy($path, $name); + return $this->image()->copy($path, $name); } // Copy the file through ImageMagick so that it has @@ -433,7 +433,7 @@ // Text $cmd .= " -annotate 0 '{$text}'"; - $source = ! empty($this->resource) ? $this->resource : $this->image->getPathname(); + $source = ! empty($this->resource) ? $this->resource : $this->image()->getPathname(); $destination = $this->getResourcePath(); $cmd = " '{$source}' {$cmd} '{$destination}'"; diff --git a/system/Images/Image.php b/system/Images/Image.php index 904c78b..f67e8b3 100644 --- a/system/Images/Image.php +++ b/system/Images/Image.php @@ -137,7 +137,11 @@ { $path = $this->getPathname(); - $vals = getimagesize($path); + if (! $vals = getimagesize($path)) + { + throw ImageException::forFileNotSupported(); + } + $types = [ 1 => 'gif', 2 => 'jpeg', diff --git a/system/Language/en/Images.php b/system/Language/en/Images.php index 9d11da5..3dd8c0e 100644 --- a/system/Language/en/Images.php +++ b/system/Language/en/Images.php @@ -21,6 +21,7 @@ 'gifNotSupported' => 'GIF images are often not supported due to licensing restrictions. You may have to use JPG or PNG images instead.', 'jpgNotSupported' => 'JPG images are not supported.', 'pngNotSupported' => 'PNG images are not supported.', + 'fileNotSupported' => 'The supplied file is not a supported image type.', 'unsupportedImageCreate' => 'Your server does not support the GD function required to process this type of image.', 'jpgOrPngRequired' => 'The image resize protocol specified in your preferences only works with JPEG or PNG image types.', 'rotateUnsupported' => 'Image rotation does not appear to be supported by your server.', diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php index ac047df..3b15241 100644 --- a/system/Language/en/Validation.php +++ b/system/Language/en/Validation.php @@ -24,9 +24,10 @@ // Rule Messages 'alpha' => 'The {field} field may only contain alphabetical characters.', - 'alpha_dash' => 'The {field} field may only contain alpha-numeric characters, underscores, and dashes.', - 'alpha_numeric' => 'The {field} field may only contain alpha-numeric characters.', - 'alpha_numeric_space' => 'The {field} field may only contain alpha-numeric characters and spaces.', + 'alpha_dash' => 'The {field} field may only contain alphanumeric, underscore, and dash characters.', + 'alpha_numeric' => 'The {field} field may only contain alphanumeric characters.', + 'alpha_numeric_punct' => 'The {field} field may contain only alphanumeric characters, spaces, and ~ ! # $ % & * - _ + = | : . characters.', + 'alpha_numeric_space' => 'The {field} field may only contain alphanumeric and space characters.', 'alpha_space' => 'The {field} field may only contain alphabetical characters and spaces.', 'decimal' => 'The {field} field must contain a decimal number.', 'differs' => 'The {field} field must differ from the {param} field.', diff --git a/system/Model.php b/system/Model.php index 38a5488..8d02e78 100644 --- a/system/Model.php +++ b/system/Model.php @@ -493,17 +493,18 @@ //-------------------------------------------------------------------- /** + * Captures the builder's set() method so that we can validate the * data here. This allows it to be used with any of the other * builder methods and still get validated data, like replace. * - * @param mixed $key - * @param string $value - * @param boolean|null $escape + * @param mixed $key Field name, or an array of field/value pairs + * @param string $value Field value, if $key is a single field + * @param boolean $escape Whether to escape values and identifiers * * @return $this */ - public function set($key, string $value = '', bool $escape = null) + public function set($key, ?string $value = '', bool $escape = null) { $data = is_array($key) ? $key diff --git a/system/Test/CIDatabaseTestCase.php b/system/Test/CIDatabaseTestCase.php index 926e1a9..1ce43a3 100644 --- a/system/Test/CIDatabaseTestCase.php +++ b/system/Test/CIDatabaseTestCase.php @@ -52,7 +52,6 @@ */ class CIDatabaseTestCase extends CIUnitTestCase { - /** * Should the db be refreshed before * each test? @@ -62,27 +61,30 @@ protected $refresh = true; /** - * The name of the fixture used for all tests - * within this test case. + * The seed file(s) used for all tests within this test case. + * Should be fully-namespaced or relative to $basePath * - * @var string + * @var string|array */ protected $seed = ''; /** - * The path to where we can find the seeds directory. + * The path to the seeds directory. * Allows overriding the default application directories. * * @var string */ - protected $basePath = TESTPATH . '_support/Database'; + protected $basePath = SUPPORTPATH . 'Database'; /** - * The namespace to help us find the migration classes. + * The namespace(s) to help us find the migration classes. + * Empty is equivalent to running `spark migrate -all`. + * Note that running "all" runs migrations in date order, + * but specifying namespaces runs them in namespace order (then date) * - * @var string + * @var string|array|null */ - protected $namespace = 'Tests\Support\DatabaseTestMigrations'; + protected $namespace = 'Tests\Support'; /** * The name of the database group to connect to. @@ -163,19 +165,10 @@ { parent::setUp(); - // Add namespaces we need for testing - Services::autoloader()->addNamespace('Tests\Support\DatabaseTestMigrations', TESTPATH . '_support/DatabaseTestMigrations'); - $this->loadDependencies(); if ($this->refresh === true) { - if (! empty($this->namespace)) - { - $this->migrations->setNamespace($this->namespace); - } - $this->migrations->regress(0, 'tests'); - // Delete all of the tables to ensure we're at a clean start. $tables = $this->db->listTables(); @@ -187,6 +180,7 @@ { if ($table === $this->db->DBPrefix . 'migrations') { + $this->db->table($table)->truncate(); continue; } @@ -194,7 +188,23 @@ } } - $this->migrations->latest('tests'); + // If no namespace was specified then migrate all + if (empty($this->namespace)) + { + $this->migrations->setNamespace(null); + $this->migrations->latest('tests'); + } + + // Run migrations for each specified namespace + else + { + $namespaces = is_array($this->namespace) ? $this->namespace : [$this->namespace]; + foreach ($namespaces as $namespace) + { + $this->migrations->setNamespace($namespace); + $this->migrations->latest('tests'); + } + } } if (! empty($this->seed)) @@ -204,7 +214,11 @@ $this->seeder->setPath(rtrim($this->basePath, '/') . '/Seeds'); } - $this->seed($this->seed); + $seeds = is_array($this->seed) ? $this->seed : [$this->seed]; + foreach ($seeds as $seed) + { + $this->seed($seed); + } } } @@ -240,7 +254,6 @@ } //-------------------------------------------------------------------- - //-------------------------------------------------------------------- // Database Test Helpers //-------------------------------------------------------------------- diff --git a/system/Test/CIUnitTestCase.php b/system/Test/CIUnitTestCase.php index 54208bf..7599d39 100644 --- a/system/Test/CIUnitTestCase.php +++ b/system/Test/CIUnitTestCase.php @@ -42,7 +42,7 @@ use Config\Paths; use CodeIgniter\Events\Events; use PHPUnit\Framework\TestCase; -use Tests\Support\Log\TestLogger; +use CodeIgniter\Test\TestLogger; /** * PHPunit test case. diff --git a/system/Test/Mock/MockAppConfig.php b/system/Test/Mock/MockAppConfig.php new file mode 100644 index 0000000..fba7b76 --- /dev/null +++ b/system/Test/Mock/MockAppConfig.php @@ -0,0 +1,34 @@ +output = $output; + + return $this; + } + + //-------------------------------------------------------------------- + + protected function sendRequest(array $curl_options = []): string + { + // Save so we can access later. + $this->curl_options = $curl_options; + + return $this->output; + } + + //-------------------------------------------------------------------- + // for testing purposes only + public function getBaseURI() + { + return $this->baseURI; + } + + // for testing purposes only + public function getDelay() + { + return $this->delay; + } + +} diff --git a/system/Test/Mock/MockCache.php b/system/Test/Mock/MockCache.php new file mode 100644 index 0000000..855cb7b --- /dev/null +++ b/system/Test/Mock/MockCache.php @@ -0,0 +1,198 @@ +prefix . $key; + + return array_key_exists($key, $this->cache) + ? $this->cache[$key] + : null; + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by Mamcache in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param boolean $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, int $ttl = 60, bool $raw = false) + { + $key = $this->prefix . $key; + + $this->cache[$key] = $value; + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + unset($this->cache[$key]); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param integer $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, int $offset = 1) + { + $key = $this->prefix . $key; + + $data = $this->cache[$key] ?: null; + + if (empty($data)) + { + $data = 0; + } + elseif (! is_int($data)) + { + return false; + } + + return $this->save($key, $data + $offset); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param integer $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, int $offset = 1) + { + $key = $this->prefix . $key; + + $data = $this->cache[$key] ?: null; + + if (empty($data)) + { + $data = 0; + } + elseif (! is_int($data)) + { + return false; + } + + return $this->save($key, $data - $offset); + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + $this->cache = []; + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return []; + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + return false; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return true; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Test/Mock/MockChromeLogger.php b/system/Test/Mock/MockChromeLogger.php new file mode 100644 index 0000000..3bb64b1 --- /dev/null +++ b/system/Test/Mock/MockChromeLogger.php @@ -0,0 +1,25 @@ +json['rows'][0]; + } + +} diff --git a/system/Test/Mock/MockCodeIgniter.php b/system/Test/Mock/MockCodeIgniter.php new file mode 100644 index 0000000..5580fd1 --- /dev/null +++ b/system/Test/Mock/MockCodeIgniter.php @@ -0,0 +1,11 @@ +returnValues[$method] = $return; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Orchestrates a query against the database. Queries must use + * Database\Statement objects to store the query and build it. + * This method works with the cache. + * + * Should automatically handle different connections for read/write + * queries if needed. + * + * @param string $sql + * @param mixed ...$binds + * @param boolean $setEscapeFlags + * @param string $queryClass + * + * @return \CodeIgniter\Database\BaseResult|\CodeIgniter\Database\Query|false + */ + + public function query(string $sql, $binds = null, bool $setEscapeFlags = true, string $queryClass = 'CodeIgniter\\Database\\Query') + { + $queryClass = str_replace('Connection', 'Query', get_class($this)); + + $query = new $queryClass($this); + + $query->setQuery($sql, $binds, $setEscapeFlags); + + if (! empty($this->swapPre) && ! empty($this->DBPrefix)) + { + $query->swapPrefix($this->DBPrefix, $this->swapPre); + } + + $startTime = microtime(true); + + $this->lastQuery = $query; + + // Run the query + if (false === ($this->resultID = $this->simpleQuery($query->getQuery()))) + { + $query->setDuration($startTime, $startTime); + + // @todo deal with errors + + return false; + } + + $query->setDuration($startTime); + + $resultClass = str_replace('Connection', 'Result', get_class($this)); + + return new $resultClass($this->connID, $this->resultID); + } + + //-------------------------------------------------------------------- + + /** + * Connect to the database. + * + * @param boolean $persistent + * + * @return mixed + */ + public function connect(bool $persistent = false) + { + $return = $this->returnValues['connect'] ?? true; + + if (is_array($return)) + { + // By removing the top item here, we can + // get a different value for, say, testing failover connections. + $return = array_shift($this->returnValues['connect']); + } + + return $return; + } + + //-------------------------------------------------------------------- + + /** + * Keep or establish the connection if no queries have been sent for + * a length of time exceeding the server's idle timeout. + * + * @return boolean + */ + public function reconnect(): bool + { + return true; + } + + //-------------------------------------------------------------------- + + /** + * Select a specific database table to use. + * + * @param string $databaseName + * + * @return mixed + */ + public function setDatabase(string $databaseName) + { + $this->database = $databaseName; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns a string containing the version of the database being used. + * + * @return string + */ + public function getVersion(): string + { + return CodeIgniter::CI_VERSION; + } + + //-------------------------------------------------------------------- + + /** + * Executes the query against the database. + * + * @param string $sql + * + * @return mixed + */ + protected function execute(string $sql) + { + return $this->returnValues['execute']; + } + + //-------------------------------------------------------------------- + + /** + * Returns the total number of rows affected by this query. + * + * @return integer + */ + public function affectedRows(): int + { + return 1; + } + + //-------------------------------------------------------------------- + + /** + * Returns the last error code and message. + * + * Must return an array with keys 'code' and 'message': + * + * return ['code' => null, 'message' => null); + * + * @return array + */ + public function error(): array + { + return [ + 'code' => null, + 'message' => null, + ]; + } + + //-------------------------------------------------------------------- + + /** + * Insert ID + * + * @return integer + */ + public function insertID(): int + { + return $this->connID->insert_id; + } + + //-------------------------------------------------------------------- + + /** + * Generates the SQL for listing tables in a platform-dependent manner. + * + * @param boolean $constrainByPrefix + * + * @return string + */ + protected function _listTables(bool $constrainByPrefix = false): string + { + return ''; + } + + //-------------------------------------------------------------------- + + /** + * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string $table + * + * @return string + */ + protected function _listColumns(string $table = ''): string + { + return ''; + } + + /** + * @param string $table + * @return array + */ + protected function _fieldData(string $table): array + { + return []; + } + + /** + * @param string $table + * @return array + */ + protected function _indexData(string $table): array + { + return []; + } + + /** + * @param string $table + * @return array + */ + protected function _foreignKeyData(string $table): array + { + return []; + } + + //-------------------------------------------------------------------- + + /** + * Close the connection. + */ + protected function _close() + { + return; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return boolean + */ + protected function _transBegin(): bool + { + return true; + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return boolean + */ + protected function _transCommit(): bool + { + return true; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return boolean + */ + protected function _transRollback(): bool + { + return true; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Test/Mock/MockEvents.php b/system/Test/Mock/MockEvents.php new file mode 100644 index 0000000..e8b3f43 --- /dev/null +++ b/system/Test/Mock/MockEvents.php @@ -0,0 +1,66 @@ +handles = $config['handles'] ?? []; + $this->destination = $this->path . 'log-' . date('Y-m-d') . '.' . $this->fileExtension; + } + +} diff --git a/system/Test/Mock/MockIncomingRequest.php b/system/Test/Mock/MockIncomingRequest.php new file mode 100644 index 0000000..64d7b2e --- /dev/null +++ b/system/Test/Mock/MockIncomingRequest.php @@ -0,0 +1,17 @@ +language[$locale ?? $this->locale][$file] = $data; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Provides an override that allows us to set custom + * data to be returned easily during testing. + * + * @param string $path + * + * @return array|mixed + */ + protected function requireFile(string $path): array + { + return $this->data ?? []; + } + + //-------------------------------------------------------------------- + + /** + * Arbitrarily turnoff internationalization support for testing + */ + public function disableIntlSupport() + { + $this->intlSupport = false; + } + +} diff --git a/system/Test/Mock/MockLogger.php b/system/Test/Mock/MockLogger.php new file mode 100644 index 0000000..837c366 --- /dev/null +++ b/system/Test/Mock/MockLogger.php @@ -0,0 +1,103 @@ + [ + /* + * The log levels that this handler will handle. + */ + 'handles' => [ + 'critical', + 'alert', + 'emergency', + 'debug', + 'error', + 'info', + 'notice', + 'warning', + ], + ], + ]; + +} diff --git a/system/Test/Mock/MockQuery.php b/system/Test/Mock/MockQuery.php new file mode 100644 index 0000000..851ad3e --- /dev/null +++ b/system/Test/Mock/MockQuery.php @@ -0,0 +1,8 @@ +model; + } + + public function getModelName() + { + return $this->modelName; + } + + public function getFormat() + { + return $this->format; + } + +} diff --git a/system/Test/Mock/MockResourcePresenter.php b/system/Test/Mock/MockResourcePresenter.php new file mode 100644 index 0000000..84f831c --- /dev/null +++ b/system/Test/Mock/MockResourcePresenter.php @@ -0,0 +1,23 @@ +model; + } + + public function getModelName() + { + return $this->modelName; + } + + public function getFormat() + { + return $this->format; + } + +} diff --git a/system/Test/Mock/MockResponse.php b/system/Test/Mock/MockResponse.php new file mode 100755 index 0000000..67f1a7d --- /dev/null +++ b/system/Test/Mock/MockResponse.php @@ -0,0 +1,30 @@ +pretend; + } + + // artificial error for testing + public function misbehave() + { + $this->statusCode = 0; + } + +} diff --git a/system/Test/Mock/MockResult.php b/system/Test/Mock/MockResult.php new file mode 100644 index 0000000..a76e9dd --- /dev/null +++ b/system/Test/Mock/MockResult.php @@ -0,0 +1,93 @@ +CSRFHash; + + return $this; + } + + //-------------------------------------------------------------------- + +} diff --git a/system/Test/Mock/MockServices.php b/system/Test/Mock/MockServices.php new file mode 100644 index 0000000..5d9e736 --- /dev/null +++ b/system/Test/Mock/MockServices.php @@ -0,0 +1,27 @@ + TESTPATH . '_support/', + ]; + public $classmap = []; + + //-------------------------------------------------------------------- + + public function __construct() + { + // Don't call the parent since we don't want the default mappings. + // parent::__construct(); + } + + //-------------------------------------------------------------------- + public static function locator(bool $getShared = true) + { + return new \CodeIgniter\Autoloader\FileLocator(static::autoloader()); + } + +} diff --git a/system/Test/Mock/MockSession.php b/system/Test/Mock/MockSession.php new file mode 100644 index 0000000..e69183e --- /dev/null +++ b/system/Test/Mock/MockSession.php @@ -0,0 +1,72 @@ +driver, true); + } + + //-------------------------------------------------------------------- + + /** + * Starts the session. + * Extracted for testing reasons. + */ + protected function startSession() + { + // session_start(); + } + + //-------------------------------------------------------------------- + + /** + * Takes care of setting the cookie on the client side. + * Extracted for testing reasons. + */ + protected function setCookie() + { + $this->cookies[] = [ + $this->sessionCookieName, + session_id(), + (empty($this->sessionExpiration) ? 0 : time() + $this->sessionExpiration), + $this->cookiePath, + $this->cookieDomain, + $this->cookieSecure, + true, + ]; + } + + //-------------------------------------------------------------------- + + public function regenerate(bool $destroy = false) + { + $this->didRegenerate = true; + $_SESSION['__ci_last_regenerate'] = time(); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Test/Mock/MockTable.php b/system/Test/Mock/MockTable.php new file mode 100644 index 0000000..3e5758b --- /dev/null +++ b/system/Test/Mock/MockTable.php @@ -0,0 +1,16 @@ +assertLogged() methods. + * + * @param string $level + * @param string $message + * @param array $context + * + * @return boolean + */ + public function log($level, $message, array $context = []): bool + { + // While this requires duplicate work, we want to ensure + // we have the final message to test against. + $log_message = $this->interpolate($message, $context); + + // Determine the file and line by finding the first + // backtrace that is not part of our logging system. + $trace = debug_backtrace(); + $file = null; + + foreach ($trace as $row) + { + if (! in_array($row['function'], ['log', 'log_message'])) + { + $file = basename($row['file'] ?? ''); + break; + } + } + + self::$op_logs[] = [ + 'level' => $level, + 'message' => $log_message, + 'file' => $file, + ]; + + // Let the parent do it's thing. + return parent::log($level, $message, $context); + } + + //-------------------------------------------------------------------- + + /** + * Used by CIUnitTestCase class to provide ->assertLogged() methods. + * + * @param string $level + * @param string $message + * + * @return boolean + */ + public static function didLog(string $level, $message) + { + foreach (self::$op_logs as $log) + { + if (strtolower($log['level']) === strtolower($level) && $message === $log['message']) + { + return true; + } + } + + return false; + } + + //-------------------------------------------------------------------- + // Expose cleanFileNames() + public function cleanup($file) + { + return $this->cleanFileNames($file); + } + +} diff --git a/system/Test/bootstrap.php b/system/Test/bootstrap.php new file mode 100644 index 0000000..8fde540 --- /dev/null +++ b/system/Test/bootstrap.php @@ -0,0 +1,60 @@ +appDirectory) . DIRECTORY_SEPARATOR); +defined('WRITEPATH') || define('WRITEPATH', realpath($paths->writableDirectory) . DIRECTORY_SEPARATOR); +defined('SYSTEMPATH') || define('SYSTEMPATH', realpath($paths->systemDirectory) . DIRECTORY_SEPARATOR); +defined('ROOTPATH') || define('ROOTPATH', realpath(APPPATH . '../') . DIRECTORY_SEPARATOR); +defined('CIPATH') || define('CIPATH', realpath(SYSTEMPATH . '../') . DIRECTORY_SEPARATOR); +defined('FCPATH') || define('FCPATH', realpath(PUBLICPATH) . DIRECTORY_SEPARATOR); +defined('TESTPATH') || define('TESTPATH', realpath(HOMEPATH . 'tests/') . DIRECTORY_SEPARATOR); +defined('SUPPORTPATH') || define('SUPPORTPATH', realpath(TESTPATH . '_support/') . DIRECTORY_SEPARATOR); +defined('COMPOSER_PATH') || define('COMPOSER_PATH', realpath(HOMEPATH . 'vendor/autoload.php')); + +// Load Common.php from App then System +if (file_exists(APPPATH . 'Common.php')) +{ + require_once APPPATH . 'Common.php'; +} + +require_once SYSTEMPATH . 'Common.php'; + +// Set environment values that would otherwise stop the framework from functioning during tests. +if (! isset($_SERVER['app.baseURL'])) +{ + $_SERVER['app.baseURL'] = 'http://example.com'; +} + +// Load necessary components +require_once APPPATH . 'Config/Autoload.php'; +require_once APPPATH . 'Config/Constants.php'; +require_once APPPATH . 'Config/Modules.php'; + +require_once SYSTEMPATH . 'Autoloader/Autoloader.php'; +require_once SYSTEMPATH . 'Config/BaseService.php'; +require_once APPPATH . 'Config/Services.php'; + +// Use Config\Services as CodeIgniter\Services +if (! class_exists('CodeIgniter\Services', false)) +{ + class_alias('Config\Services', 'CodeIgniter\Services'); +} + +// Launch the autoloader to gather namespaces (includes composer.json's "autoload-dev") +$loader = \CodeIgniter\Services::autoloader(); +$loader->initialize(new Config\Autoload(), new Config\Modules()); + +// Register the loader with the SPL autoloader stack. +$loader->register(); diff --git a/system/ThirdParty/Kint/CallFinder.php b/system/ThirdParty/Kint/CallFinder.php new file mode 100644 index 0000000..e7192a8 --- /dev/null +++ b/system/ThirdParty/Kint/CallFinder.php @@ -0,0 +1,473 @@ + true, + T_COMMENT => true, + T_DOC_COMMENT => true, + T_INLINE_HTML => true, + T_OPEN_TAG => true, + T_OPEN_TAG_WITH_ECHO => true, + T_WHITESPACE => true, + ); + + /** + * Things we need to do specially for operator tokens: + * - Refuse to strip spaces around them + * - Wrap the access path in parentheses if there + * are any of these in the final short parameter. + */ + private static $operator = array( + T_AND_EQUAL => true, + T_BOOLEAN_AND => true, + T_BOOLEAN_OR => true, + T_ARRAY_CAST => true, + T_BOOL_CAST => true, + T_CLONE => true, + T_CONCAT_EQUAL => true, + T_DEC => true, + T_DIV_EQUAL => true, + T_DOUBLE_CAST => true, + T_INC => true, + T_INCLUDE => true, + T_INCLUDE_ONCE => true, + T_INSTANCEOF => true, + T_INT_CAST => true, + T_IS_EQUAL => true, + T_IS_GREATER_OR_EQUAL => true, + T_IS_IDENTICAL => true, + T_IS_NOT_EQUAL => true, + T_IS_NOT_IDENTICAL => true, + T_IS_SMALLER_OR_EQUAL => true, + T_LOGICAL_AND => true, + T_LOGICAL_OR => true, + T_LOGICAL_XOR => true, + T_MINUS_EQUAL => true, + T_MOD_EQUAL => true, + T_MUL_EQUAL => true, + T_NEW => true, + T_OBJECT_CAST => true, + T_OR_EQUAL => true, + T_PLUS_EQUAL => true, + T_REQUIRE => true, + T_REQUIRE_ONCE => true, + T_SL => true, + T_SL_EQUAL => true, + T_SR => true, + T_SR_EQUAL => true, + T_STRING_CAST => true, + T_UNSET_CAST => true, + T_XOR_EQUAL => true, + '!' => true, + '%' => true, + '&' => true, + '*' => true, + '+' => true, + '-' => true, + '.' => true, + '/' => true, + ':' => true, + '<' => true, + '=' => true, + '>' => true, + '?' => true, + '^' => true, + '|' => true, + '~' => true, + ); + + private static $strip = array( + '(' => true, + ')' => true, + '[' => true, + ']' => true, + '{' => true, + '}' => true, + T_OBJECT_OPERATOR => true, + T_DOUBLE_COLON => true, + T_NS_SEPARATOR => true, + ); + + public static function getFunctionCalls($source, $line, $function) + { + static $up = array( + '(' => true, + '[' => true, + '{' => true, + T_CURLY_OPEN => true, + T_DOLLAR_OPEN_CURLY_BRACES => true, + ); + static $down = array( + ')' => true, + ']' => true, + '}' => true, + ); + static $modifiers = array( + '!' => true, + '@' => true, + '~' => true, + '+' => true, + '-' => true, + ); + static $identifier = array( + T_DOUBLE_COLON => true, + T_STRING => true, + T_NS_SEPARATOR => true, + ); + + if (KINT_PHP56) { + self::$operator[T_POW] = true; + self::$operator[T_POW_EQUAL] = true; + } + + if (KINT_PHP70) { + self::$operator[T_SPACESHIP] = true; + } + + if (KINT_PHP74) { + self::$operator[T_COALESCE_EQUAL] = true; + } + + $tokens = \token_get_all($source); + $cursor = 1; + $function_calls = array(); + /** @var array Performance optimization preventing backwards loops */ + $prev_tokens = array(null, null, null); + + if (\is_array($function)) { + $class = \explode('\\', $function[0]); + $class = \strtolower(\end($class)); + $function = \strtolower($function[1]); + } else { + $class = null; + $function = \strtolower($function); + } + + // Loop through tokens + foreach ($tokens as $index => $token) { + if (!\is_array($token)) { + continue; + } + + // Count newlines for line number instead of using $token[2] + // since certain situations (String tokens after whitespace) may + // not have the correct line number unless you do this manually + $cursor += \substr_count($token[1], "\n"); + if ($cursor > $line) { + break; + } + + // Store the last real tokens for later + if (isset(self::$ignore[$token[0]])) { + continue; + } + + $prev_tokens = array($prev_tokens[1], $prev_tokens[2], $token); + + // Check if it's the right type to be the function we're looking for + if (T_STRING !== $token[0] || \strtolower($token[1]) !== $function) { + continue; + } + + // Check if it's a function call + $nextReal = self::realTokenIndex($tokens, $index); + if (!isset($nextReal, $tokens[$nextReal]) || '(' !== $tokens[$nextReal]) { + continue; + } + + // Check if it matches the signature + if (null === $class) { + if ($prev_tokens[1] && \in_array($prev_tokens[1][0], array(T_DOUBLE_COLON, T_OBJECT_OPERATOR), true)) { + continue; + } + } else { + if (!$prev_tokens[1] || T_DOUBLE_COLON !== $prev_tokens[1][0]) { + continue; + } + + if (!$prev_tokens[0] || T_STRING !== $prev_tokens[0][0] || \strtolower($prev_tokens[0][1]) !== $class) { + continue; + } + } + + $inner_cursor = $cursor; + $depth = 1; // The depth respective to the function call + $offset = $nextReal + 1; // The start of the function call + $instring = false; // Whether we're in a string or not + $realtokens = false; // Whether the current scope contains anything meaningful or not + $paramrealtokens = false; // Whether the current parameter contains anything meaningful + $params = array(); // All our collected parameters + $shortparam = array(); // The short version of the parameter + $param_start = $offset; // The distance to the start of the parameter + + // Loop through the following tokens until the function call ends + while (isset($tokens[$offset])) { + $token = $tokens[$offset]; + + // Ensure that the $inner_cursor is correct and + // that $token is either a T_ constant or a string + if (\is_array($token)) { + $inner_cursor += \substr_count($token[1], "\n"); + } + + if (!isset(self::$ignore[$token[0]]) && !isset($down[$token[0]])) { + $paramrealtokens = $realtokens = true; + } + + // If it's a token that makes us to up a level, increase the depth + if (isset($up[$token[0]])) { + if (1 === $depth) { + $shortparam[] = $token; + $realtokens = false; + } + + ++$depth; + } elseif (isset($down[$token[0]])) { + --$depth; + + // If this brings us down to the parameter level, and we've had + // real tokens since going up, fill the $shortparam with an ellipsis + if (1 === $depth) { + if ($realtokens) { + $shortparam[] = '...'; + } + $shortparam[] = $token; + } + } elseif ('"' === $token[0]) { + // Strings use the same symbol for up and down, but we can + // only ever be inside one string, so just use a bool for that + if ($instring) { + --$depth; + if (1 === $depth) { + $shortparam[] = '...'; + } + } else { + ++$depth; + } + + $instring = !$instring; + + $shortparam[] = '"'; + } elseif (1 === $depth) { + if (',' === $token[0]) { + $params[] = array( + 'full' => \array_slice($tokens, $param_start, $offset - $param_start), + 'short' => $shortparam, + ); + $shortparam = array(); + $paramrealtokens = false; + $param_start = $offset + 1; + } elseif (T_CONSTANT_ENCAPSED_STRING === $token[0] && \strlen($token[1]) > 2) { + $shortparam[] = $token[1][0].'...'.$token[1][0]; + } else { + $shortparam[] = $token; + } + } + + // Depth has dropped to 0 (So we've hit the closing paren) + if ($depth <= 0) { + if ($paramrealtokens) { + $params[] = array( + 'full' => \array_slice($tokens, $param_start, $offset - $param_start), + 'short' => $shortparam, + ); + } + + break; + } + + ++$offset; + } + + // If we're not passed (or at) the line at the end + // of the function call, we're too early so skip it + if ($inner_cursor < $line) { + continue; + } + + // Format the final output parameters + foreach ($params as &$param) { + $name = self::tokensFormatted($param['short']); + $expression = false; + foreach ($name as $token) { + if (self::tokenIsOperator($token)) { + $expression = true; + break; + } + } + + $param = array( + 'name' => self::tokensToString($name), + 'path' => self::tokensToString(self::tokensTrim($param['full'])), + 'expression' => $expression, + ); + } + + // Get the modifiers + --$index; + + while (isset($tokens[$index])) { + if (!isset(self::$ignore[$tokens[$index][0]]) && !isset($identifier[$tokens[$index][0]])) { + break; + } + + --$index; + } + + $mods = array(); + + while (isset($tokens[$index])) { + if (isset(self::$ignore[$tokens[$index][0]])) { + --$index; + continue; + } + + if (isset($modifiers[$tokens[$index][0]])) { + $mods[] = $tokens[$index]; + --$index; + continue; + } + + break; + } + + $function_calls[] = array( + 'parameters' => $params, + 'modifiers' => $mods, + ); + } + + return $function_calls; + } + + private static function realTokenIndex(array $tokens, $index) + { + ++$index; + + while (isset($tokens[$index])) { + if (!isset(self::$ignore[$tokens[$index][0]])) { + return $index; + } + + ++$index; + } + + return null; + } + + /** + * We need a separate method to check if tokens are operators because we + * occasionally add "..." to short parameter versions. If we simply check + * for `$token[0]` then "..." will incorrectly match the "." operator. + * + * @param array|string $token The token to check + * + * @return bool + */ + private static function tokenIsOperator($token) + { + return '...' !== $token && isset(self::$operator[$token[0]]); + } + + private static function tokensToString(array $tokens) + { + $out = ''; + + foreach ($tokens as $token) { + if (\is_string($token)) { + $out .= $token; + } elseif (\is_array($token)) { + $out .= $token[1]; + } + } + + return $out; + } + + private static function tokensTrim(array $tokens) + { + foreach ($tokens as $index => $token) { + if (isset(self::$ignore[$token[0]])) { + unset($tokens[$index]); + } else { + break; + } + } + + $tokens = \array_reverse($tokens); + + foreach ($tokens as $index => $token) { + if (isset(self::$ignore[$token[0]])) { + unset($tokens[$index]); + } else { + break; + } + } + + return \array_reverse($tokens); + } + + private static function tokensFormatted(array $tokens) + { + $space = false; + + $tokens = self::tokensTrim($tokens); + + $output = array(); + $last = null; + + foreach ($tokens as $index => $token) { + if (isset(self::$ignore[$token[0]])) { + if ($space) { + continue; + } + + $next = $tokens[self::realTokenIndex($tokens, $index)]; + + if (isset(self::$strip[$last[0]]) && !self::tokenIsOperator($next)) { + continue; + } + + if (isset(self::$strip[$next[0]]) && $last && !self::tokenIsOperator($last)) { + continue; + } + + $token = ' '; + $space = true; + } else { + $space = false; + $last = $token; + } + + $output[] = $token; + } + + return $output; + } +} diff --git a/system/ThirdParty/Kint/Object/BasicObject.php b/system/ThirdParty/Kint/Object/BasicObject.php new file mode 100644 index 0000000..d69347e --- /dev/null +++ b/system/ThirdParty/Kint/Object/BasicObject.php @@ -0,0 +1,248 @@ +representations[$rep->getName()])) { + return false; + } + + if (null === $pos) { + $this->representations[$rep->getName()] = $rep; + } else { + $this->representations = \array_merge( + \array_slice($this->representations, 0, $pos), + array($rep->getName() => $rep), + \array_slice($this->representations, $pos) + ); + } + + return true; + } + + public function replaceRepresentation(Representation $rep, $pos = null) + { + if (null === $pos) { + $this->representations[$rep->getName()] = $rep; + } else { + $this->removeRepresentation($rep); + $this->addRepresentation($rep, $pos); + } + } + + public function removeRepresentation($rep) + { + if ($rep instanceof Representation) { + unset($this->representations[$rep->getName()]); + } elseif (\is_string($rep)) { + unset($this->representations[$rep]); + } + } + + public function getRepresentation($name) + { + if (isset($this->representations[$name])) { + return $this->representations[$name]; + } + } + + public function getRepresentations() + { + return $this->representations; + } + + public function clearRepresentations() + { + $this->representations = array(); + } + + public function getType() + { + return $this->type; + } + + public function getModifiers() + { + $out = $this->getAccess(); + + if ($this->const) { + $out .= ' const'; + } + + if ($this->static) { + $out .= ' static'; + } + + if (\strlen($out)) { + return \ltrim($out); + } + } + + public function getAccess() + { + switch ($this->access) { + case self::ACCESS_PRIVATE: + return 'private'; + case self::ACCESS_PROTECTED: + return 'protected'; + case self::ACCESS_PUBLIC: + return 'public'; + } + } + + public function getName() + { + return $this->name; + } + + public function getOperator() + { + switch ($this->operator) { + case self::OPERATOR_ARRAY: + return '=>'; + case self::OPERATOR_OBJECT: + return '->'; + case self::OPERATOR_STATIC: + return '::'; + } + } + + public function getSize() + { + return $this->size; + } + + public function getValueShort() + { + if ($rep = $this->value) { + if ('boolean' === $this->type) { + return $rep->contents ? 'true' : 'false'; + } + + if ('integer' === $this->type || 'double' === $this->type) { + return $rep->contents; + } + } + } + + public function getAccessPath() + { + return $this->access_path; + } + + public function transplant(BasicObject $old) + { + $this->name = $old->name; + $this->size = $old->size; + $this->access_path = $old->access_path; + $this->access = $old->access; + $this->static = $old->static; + $this->const = $old->const; + $this->type = $old->type; + $this->depth = $old->depth; + $this->owner_class = $old->owner_class; + $this->operator = $old->operator; + $this->reference = $old->reference; + $this->value = $old->value; + $this->representations += $old->representations; + $this->hints = \array_merge($this->hints, $old->hints); + } + + /** + * Creates a new basic object with a name and access path. + * + * @param null|string $name + * @param null|string $access_path + * + * @return \Kint\Object\BasicObject + */ + public static function blank($name = null, $access_path = null) + { + $o = new self(); + $o->name = $name; + $o->access_path = $access_path; + + return $o; + } + + public static function sortByAccess(BasicObject $a, BasicObject $b) + { + static $sorts = array( + self::ACCESS_PUBLIC => 1, + self::ACCESS_PROTECTED => 2, + self::ACCESS_PRIVATE => 3, + self::ACCESS_NONE => 4, + ); + + return $sorts[$a->access] - $sorts[$b->access]; + } + + public static function sortByName(BasicObject $a, BasicObject $b) + { + $ret = \strnatcasecmp($a->name, $b->name); + + if (0 === $ret) { + return (int) \is_int($b->name) - (int) \is_int($a->name); + } + + return $ret; + } +} diff --git a/system/ThirdParty/Kint/Object/BlobObject.php b/system/ThirdParty/Kint/Object/BlobObject.php new file mode 100644 index 0000000..66d508f --- /dev/null +++ b/system/ThirdParty/Kint/Object/BlobObject.php @@ -0,0 +1,177 @@ +encoding) { + return 'binary '.$this->type; + } + + if ('ASCII' === $this->encoding) { + return $this->type; + } + + return $this->encoding.' '.$this->type; + } + + public function getValueShort() + { + if ($rep = $this->value) { + return '"'.$rep->contents.'"'; + } + } + + public function transplant(BasicObject $old) + { + parent::transplant($old); + + if ($old instanceof self) { + $this->encoding = $old->encoding; + } + } + + public static function strlen($string, $encoding = false) + { + if (\function_exists('mb_strlen')) { + if (false === $encoding) { + $encoding = self::detectEncoding($string); + } + + if ($encoding && 'ASCII' !== $encoding) { + return \mb_strlen($string, $encoding); + } + } + + return \strlen($string); + } + + public static function substr($string, $start, $length = null, $encoding = false) + { + if (\function_exists('mb_substr')) { + if (false === $encoding) { + $encoding = self::detectEncoding($string); + } + + if ($encoding && 'ASCII' !== $encoding) { + return \mb_substr($string, $start, $length, $encoding); + } + } + + // Special case for substr/mb_substr discrepancy + if ('' === $string) { + return ''; + } + + return \substr($string, $start, isset($length) ? $length : PHP_INT_MAX); + } + + public static function detectEncoding($string) + { + if (\function_exists('mb_detect_encoding')) { + if ($ret = \mb_detect_encoding($string, self::$char_encodings, true)) { + return $ret; + } + } + + // Pretty much every character encoding uses first 32 bytes as control + // characters. If it's not a multi-byte format it's safe to say matching + // any control character besides tab, nl, and cr means it's binary. + if (\preg_match('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/', $string)) { + return false; + } + + if (\function_exists('iconv')) { + foreach (self::$legacy_encodings as $encoding) { + if (@\iconv($encoding, $encoding, $string) === $string) { + return $encoding; + } + } + } elseif (!\function_exists('mb_detect_encoding')) { // @codeCoverageIgnore + // If a user has neither mb_detect_encoding, nor iconv, nor the + // polyfills, there's not much we can do about it... + // Pretend it's ASCII and pray the browser renders it properly. + return 'ASCII'; // @codeCoverageIgnore + } + + return false; + } +} diff --git a/system/ThirdParty/Kint/Object/ClosureObject.php b/system/ThirdParty/Kint/Object/ClosureObject.php new file mode 100644 index 0000000..344eceb --- /dev/null +++ b/system/ThirdParty/Kint/Object/ClosureObject.php @@ -0,0 +1,68 @@ +access_path) { + return parent::getAccessPath().'('.$this->getParams().')'; + } + } + + public function getSize() + { + } + + public function getParams() + { + if (null !== $this->paramcache) { + return $this->paramcache; + } + + $out = array(); + + foreach ($this->parameters as $p) { + $type = $p->getType(); + + $ref = $p->reference ? '&' : ''; + + if ($type) { + $out[] = $type.' '.$ref.$p->getName(); + } else { + $out[] = $ref.$p->getName(); + } + } + + return $this->paramcache = \implode(', ', $out); + } +} diff --git a/system/ThirdParty/Kint/Object/DateTimeObject.php b/system/ThirdParty/Kint/Object/DateTimeObject.php new file mode 100644 index 0000000..f8b1b3f --- /dev/null +++ b/system/ThirdParty/Kint/Object/DateTimeObject.php @@ -0,0 +1,53 @@ +dt = clone $dt; + } + + public function getValueShort() + { + $stamp = $this->dt->format('Y-m-d H:i:s'); + if ((int) ($micro = $this->dt->format('u'))) { + $stamp .= '.'.$micro; + } + $stamp .= $this->dt->format('P T'); + + return $stamp; + } +} diff --git a/system/ThirdParty/Kint/Object/InstanceObject.php b/system/ThirdParty/Kint/Object/InstanceObject.php new file mode 100644 index 0000000..943b33d --- /dev/null +++ b/system/ThirdParty/Kint/Object/InstanceObject.php @@ -0,0 +1,78 @@ +classname; + } + + public function transplant(BasicObject $old) + { + parent::transplant($old); + + if ($old instanceof self) { + $this->classname = $old->classname; + $this->hash = $old->hash; + $this->filename = $old->filename; + $this->startline = $old->startline; + } + } + + public static function sortByHierarchy($a, $b) + { + if (\is_string($a) && \is_string($b)) { + $aclass = $a; + $bclass = $b; + } elseif (!($a instanceof BasicObject) || !($b instanceof BasicObject)) { + return 0; + } elseif ($a instanceof self && $b instanceof self) { + $aclass = $a->classname; + $bclass = $b->classname; + } else { + return 0; + } + + if (\is_subclass_of($aclass, $bclass)) { + return -1; + } + + if (\is_subclass_of($bclass, $aclass)) { + return 1; + } + + return 0; + } +} diff --git a/system/ThirdParty/Kint/Object/MethodObject.php b/system/ThirdParty/Kint/Object/MethodObject.php new file mode 100644 index 0000000..78d49de --- /dev/null +++ b/system/ThirdParty/Kint/Object/MethodObject.php @@ -0,0 +1,253 @@ +name = $method->getName(); + $this->filename = $method->getFileName(); + $this->startline = $method->getStartLine(); + $this->endline = $method->getEndLine(); + $this->internal = $method->isInternal(); + $this->docstring = $method->getDocComment(); + $this->return_reference = $method->returnsReference(); + + foreach ($method->getParameters() as $param) { + $this->parameters[] = new ParameterObject($param); + } + + if (KINT_PHP70) { + $this->returntype = $method->getReturnType(); + if ($this->returntype) { + $this->returntype = Utils::getTypeString($this->returntype); + } + } + + if ($method instanceof ReflectionMethod) { + $this->static = $method->isStatic(); + $this->operator = $this->static ? BasicObject::OPERATOR_STATIC : BasicObject::OPERATOR_OBJECT; + $this->abstract = $method->isAbstract(); + $this->final = $method->isFinal(); + $this->owner_class = $method->getDeclaringClass()->name; + $this->access = BasicObject::ACCESS_PUBLIC; + if ($method->isProtected()) { + $this->access = BasicObject::ACCESS_PROTECTED; + } elseif ($method->isPrivate()) { + $this->access = BasicObject::ACCESS_PRIVATE; + } + } + + if ($this->internal) { + return; + } + + $docstring = new DocstringRepresentation( + $this->docstring, + $this->filename, + $this->startline + ); + + $docstring->implicit_label = true; + $this->addRepresentation($docstring); + $this->value = $docstring; + } + + public function setAccessPathFrom(InstanceObject $parent) + { + static $magic = array( + '__call' => true, + '__callstatic' => true, + '__clone' => true, + '__construct' => true, + '__debuginfo' => true, + '__destruct' => true, + '__get' => true, + '__invoke' => true, + '__isset' => true, + '__set' => true, + '__set_state' => true, + '__sleep' => true, + '__tostring' => true, + '__unset' => true, + '__wakeup' => true, + ); + + $name = \strtolower($this->name); + + if ('__construct' === $name) { + $this->access_path = 'new \\'.$parent->getType(); + } elseif ('__invoke' === $name) { + $this->access_path = $parent->access_path; + } elseif ('__clone' === $name) { + $this->access_path = 'clone '.$parent->access_path; + $this->showparams = false; + } elseif ('__tostring' === $name) { + $this->access_path = '(string) '.$parent->access_path; + $this->showparams = false; + } elseif (isset($magic[$name])) { + $this->access_path = null; + } elseif ($this->static) { + $this->access_path = '\\'.$this->owner_class.'::'.$this->name; + } else { + $this->access_path = $parent->access_path.'->'.$this->name; + } + } + + public function getValueShort() + { + if (!$this->value || !($this->value instanceof DocstringRepresentation)) { + return parent::getValueShort(); + } + + $ds = $this->value->getDocstringWithoutComments(); + + if (!$ds) { + return null; + } + + $ds = \explode("\n", $ds); + + $out = ''; + + foreach ($ds as $line) { + if (0 === \strlen(\trim($line)) || '@' === $line[0]) { + break; + } + + $out .= $line.' '; + } + + if (\strlen($out)) { + return \rtrim($out); + } + } + + public function getModifiers() + { + $mods = array( + $this->abstract ? 'abstract' : null, + $this->final ? 'final' : null, + $this->getAccess(), + $this->static ? 'static' : null, + ); + + $out = ''; + + foreach ($mods as $word) { + if (null !== $word) { + $out .= $word.' '; + } + } + + if (\strlen($out)) { + return \rtrim($out); + } + } + + public function getAccessPath() + { + if (null !== $this->access_path) { + if ($this->showparams) { + return parent::getAccessPath().'('.$this->getParams().')'; + } + + return parent::getAccessPath(); + } + } + + public function getParams() + { + if (null !== $this->paramcache) { + return $this->paramcache; + } + + $out = array(); + + foreach ($this->parameters as $p) { + $type = $p->getType(); + if ($type) { + $type .= ' '; + } + + $default = $p->getDefault(); + if ($default) { + $default = ' = '.$default; + } + + $ref = $p->reference ? '&' : ''; + + $out[] = $type.$ref.$p->getName().$default; + } + + return $this->paramcache = \implode(', ', $out); + } + + public function getPhpDocUrl() + { + if (!$this->internal) { + return null; + } + + if ($this->owner_class) { + $class = \strtolower($this->owner_class); + } else { + $class = 'function'; + } + + $funcname = \str_replace('_', '-', \strtolower($this->name)); + + if (0 === \strpos($funcname, '--') && 0 !== \strpos($funcname, '-', 2)) { + $funcname = \substr($funcname, 2); + } + + return 'https://secure.php.net/'.$class.'.'.$funcname; + } +} diff --git a/system/ThirdParty/Kint/Object/ParameterObject.php b/system/ThirdParty/Kint/Object/ParameterObject.php new file mode 100644 index 0000000..4bed551 --- /dev/null +++ b/system/ThirdParty/Kint/Object/ParameterObject.php @@ -0,0 +1,100 @@ +getType()) { + $this->type_hint = Utils::getTypeString($type); + } + } else { + if ($param->isArray()) { + $this->type_hint = 'array'; + } else { + try { + if ($this->type_hint = $param->getClass()) { + $this->type_hint = $this->type_hint->name; + } + } catch (ReflectionException $e) { + \preg_match('/\\[\\s\\<\\w+?>\\s([\\w]+)/s', $param->__toString(), $matches); + $this->type_hint = isset($matches[1]) ? $matches[1] : ''; + } + } + } + + $this->reference = $param->isPassedByReference(); + $this->name = $param->getName(); + $this->position = $param->getPosition(); + + if ($param->isDefaultValueAvailable()) { + /** @var mixed Psalm bug workaround */ + $default = $param->getDefaultValue(); + switch (\gettype($default)) { + case 'NULL': + $this->default = 'null'; + break; + case 'boolean': + $this->default = $default ? 'true' : 'false'; + break; + case 'array': + $this->default = \count($default) ? 'array(...)' : 'array()'; + break; + default: + $this->default = \var_export($default, true); + break; + } + } + } + + public function getType() + { + return $this->type_hint; + } + + public function getName() + { + return '$'.$this->name; + } + + public function getDefault() + { + return $this->default; + } +} diff --git a/system/ThirdParty/Kint/Object/Representation/ColorRepresentation.php b/system/ThirdParty/Kint/Object/Representation/ColorRepresentation.php new file mode 100644 index 0000000..d6a072f --- /dev/null +++ b/system/ThirdParty/Kint/Object/Representation/ColorRepresentation.php @@ -0,0 +1,576 @@ + 'f0f8ff', + 'antiquewhite' => 'faebd7', + 'aqua' => '00ffff', + 'aquamarine' => '7fffd4', + 'azure' => 'f0ffff', + 'beige' => 'f5f5dc', + 'bisque' => 'ffe4c4', + 'black' => '000000', + 'blanchedalmond' => 'ffebcd', + 'blue' => '0000ff', + 'blueviolet' => '8a2be2', + 'brown' => 'a52a2a', + 'burlywood' => 'deb887', + 'cadetblue' => '5f9ea0', + 'chartreuse' => '7fff00', + 'chocolate' => 'd2691e', + 'coral' => 'ff7f50', + 'cornflowerblue' => '6495ed', + 'cornsilk' => 'fff8dc', + 'crimson' => 'dc143c', + 'cyan' => '00ffff', + 'darkblue' => '00008b', + 'darkcyan' => '008b8b', + 'darkgoldenrod' => 'b8860b', + 'darkgray' => 'a9a9a9', + 'darkgreen' => '006400', + 'darkgrey' => 'a9a9a9', + 'darkkhaki' => 'bdb76b', + 'darkmagenta' => '8b008b', + 'darkolivegreen' => '556b2f', + 'darkorange' => 'ff8c00', + 'darkorchid' => '9932cc', + 'darkred' => '8b0000', + 'darksalmon' => 'e9967a', + 'darkseagreen' => '8fbc8f', + 'darkslateblue' => '483d8b', + 'darkslategray' => '2f4f4f', + 'darkslategrey' => '2f4f4f', + 'darkturquoise' => '00ced1', + 'darkviolet' => '9400d3', + 'deeppink' => 'ff1493', + 'deepskyblue' => '00bfff', + 'dimgray' => '696969', + 'dimgrey' => '696969', + 'dodgerblue' => '1e90ff', + 'firebrick' => 'b22222', + 'floralwhite' => 'fffaf0', + 'forestgreen' => '228b22', + 'fuchsia' => 'ff00ff', + 'gainsboro' => 'dcdcdc', + 'ghostwhite' => 'f8f8ff', + 'gold' => 'ffd700', + 'goldenrod' => 'daa520', + 'gray' => '808080', + 'green' => '008000', + 'greenyellow' => 'adff2f', + 'grey' => '808080', + 'honeydew' => 'f0fff0', + 'hotpink' => 'ff69b4', + 'indianred' => 'cd5c5c', + 'indigo' => '4b0082', + 'ivory' => 'fffff0', + 'khaki' => 'f0e68c', + 'lavender' => 'e6e6fa', + 'lavenderblush' => 'fff0f5', + 'lawngreen' => '7cfc00', + 'lemonchiffon' => 'fffacd', + 'lightblue' => 'add8e6', + 'lightcoral' => 'f08080', + 'lightcyan' => 'e0ffff', + 'lightgoldenrodyellow' => 'fafad2', + 'lightgray' => 'd3d3d3', + 'lightgreen' => '90ee90', + 'lightgrey' => 'd3d3d3', + 'lightpink' => 'ffb6c1', + 'lightsalmon' => 'ffa07a', + 'lightseagreen' => '20b2aa', + 'lightskyblue' => '87cefa', + 'lightslategray' => '778899', + 'lightslategrey' => '778899', + 'lightsteelblue' => 'b0c4de', + 'lightyellow' => 'ffffe0', + 'lime' => '00ff00', + 'limegreen' => '32cd32', + 'linen' => 'faf0e6', + 'magenta' => 'ff00ff', + 'maroon' => '800000', + 'mediumaquamarine' => '66cdaa', + 'mediumblue' => '0000cd', + 'mediumorchid' => 'ba55d3', + 'mediumpurple' => '9370db', + 'mediumseagreen' => '3cb371', + 'mediumslateblue' => '7b68ee', + 'mediumspringgreen' => '00fa9a', + 'mediumturquoise' => '48d1cc', + 'mediumvioletred' => 'c71585', + 'midnightblue' => '191970', + 'mintcream' => 'f5fffa', + 'mistyrose' => 'ffe4e1', + 'moccasin' => 'ffe4b5', + 'navajowhite' => 'ffdead', + 'navy' => '000080', + 'oldlace' => 'fdf5e6', + 'olive' => '808000', + 'olivedrab' => '6b8e23', + 'orange' => 'ffa500', + 'orangered' => 'ff4500', + 'orchid' => 'da70d6', + 'palegoldenrod' => 'eee8aa', + 'palegreen' => '98fb98', + 'paleturquoise' => 'afeeee', + 'palevioletred' => 'db7093', + 'papayawhip' => 'ffefd5', + 'peachpuff' => 'ffdab9', + 'peru' => 'cd853f', + 'pink' => 'ffc0cb', + 'plum' => 'dda0dd', + 'powderblue' => 'b0e0e6', + 'purple' => '800080', + 'rebeccapurple' => '663399', + 'red' => 'ff0000', + 'rosybrown' => 'bc8f8f', + 'royalblue' => '4169e1', + 'saddlebrown' => '8b4513', + 'salmon' => 'fa8072', + 'sandybrown' => 'f4a460', + 'seagreen' => '2e8b57', + 'seashell' => 'fff5ee', + 'sienna' => 'a0522d', + 'silver' => 'c0c0c0', + 'skyblue' => '87ceeb', + 'slateblue' => '6a5acd', + 'slategray' => '708090', + 'slategrey' => '708090', + 'snow' => 'fffafa', + 'springgreen' => '00ff7f', + 'steelblue' => '4682b4', + 'tan' => 'd2b48c', + 'teal' => '008080', + 'thistle' => 'd8bfd8', + 'tomato' => 'ff6347', + // To quote MDN: + // "Technically, transparent is a shortcut for rgba(0,0,0,0)." + 'transparent' => '00000000', + 'turquoise' => '40e0d0', + 'violet' => 'ee82ee', + 'wheat' => 'f5deb3', + 'white' => 'ffffff', + 'whitesmoke' => 'f5f5f5', + 'yellow' => 'ffff00', + 'yellowgreen' => '9acd32', + ); + + public $r = 0; + public $g = 0; + public $b = 0; + public $a = 1.0; + public $variant; + public $implicit_label = true; + public $hints = array('color'); + + public function __construct($value) + { + parent::__construct('Color'); + + $this->contents = $value; + $this->setValues($value); + } + + public function getColor($variant = null) + { + if (!$variant) { + $variant = $this->variant; + } + + switch ($variant) { + case self::COLOR_NAME: + $hex = \sprintf('%02x%02x%02x', $this->r, $this->g, $this->b); + $hex_alpha = \sprintf('%02x%02x%02x%02x', $this->r, $this->g, $this->b, \round($this->a * 0xFF)); + + return \array_search($hex, self::$color_map, true) ?: \array_search($hex_alpha, self::$color_map, true); + case self::COLOR_HEX_3: + if (0 === $this->r % 0x11 && 0 === $this->g % 0x11 && 0 === $this->b % 0x11) { + return \sprintf( + '#%1X%1X%1X', + \round($this->r / 0x11), + \round($this->g / 0x11), + \round($this->b / 0x11) + ); + } + + return false; + case self::COLOR_HEX_6: + return \sprintf('#%02X%02X%02X', $this->r, $this->g, $this->b); + case self::COLOR_RGB: + if (1.0 === $this->a) { + return \sprintf('rgb(%d, %d, %d)', $this->r, $this->g, $this->b); + } + + return \sprintf('rgb(%d, %d, %d, %s)', $this->r, $this->g, $this->b, \round($this->a, 4)); + case self::COLOR_RGBA: + return \sprintf('rgba(%d, %d, %d, %s)', $this->r, $this->g, $this->b, \round($this->a, 4)); + case self::COLOR_HSL: + $val = self::rgbToHsl($this->r, $this->g, $this->b); + if (1.0 === $this->a) { + return \vsprintf('hsl(%d, %d%%, %d%%)', $val); + } + + return \sprintf('hsl(%d, %d%%, %d%%, %s)', $val[0], $val[1], $val[2], \round($this->a, 4)); + case self::COLOR_HSLA: + $val = self::rgbToHsl($this->r, $this->g, $this->b); + + return \sprintf('hsla(%d, %d%%, %d%%, %s)', $val[0], $val[1], $val[2], \round($this->a, 4)); + case self::COLOR_HEX_4: + if (0 === $this->r % 0x11 && 0 === $this->g % 0x11 && 0 === $this->b % 0x11 && 0 === ($this->a * 255) % 0x11) { + return \sprintf( + '#%1X%1X%1X%1X', + \round($this->r / 0x11), + \round($this->g / 0x11), + \round($this->b / 0x11), + \round($this->a * 0xF) + ); + } + + return false; + + case self::COLOR_HEX_8: + return \sprintf('#%02X%02X%02X%02X', $this->r, $this->g, $this->b, \round($this->a * 0xFF)); + } + + return false; + } + + public function hasAlpha($variant = null) + { + if (null === $variant) { + $variant = $this->variant; + } + + switch ($variant) { + case self::COLOR_NAME: + case self::COLOR_RGB: + case self::COLOR_HSL: + return \abs($this->a - 1) >= 0.0001; + case self::COLOR_RGBA: + case self::COLOR_HSLA: + case self::COLOR_HEX_4: + case self::COLOR_HEX_8: + return true; + default: + return false; + } + } + + protected function setValues($value) + { + $value = \strtolower(\trim($value)); + // Find out which variant of color input it is + if (isset(self::$color_map[$value])) { + if (!$this->setValuesFromHex(self::$color_map[$value])) { + return; + } + + $variant = self::COLOR_NAME; + } elseif ('#' === $value[0]) { + $variant = $this->setValuesFromHex(\substr($value, 1)); + + if (!$variant) { + return; + } + } else { + $variant = $this->setValuesFromFunction($value); + + if (!$variant) { + return; + } + } + + // If something has gone horribly wrong + if ($this->r > 0xFF || $this->g > 0xFF || $this->b > 0xFF || $this->a > 1) { + $this->variant = null; // @codeCoverageIgnore + } else { + $this->variant = $variant; + $this->r = (int) $this->r; + $this->g = (int) $this->g; + $this->b = (int) $this->b; + $this->a = (float) $this->a; + } + } + + protected function setValuesFromHex($hex) + { + if (!\ctype_xdigit($hex)) { + return null; + } + + switch (\strlen($hex)) { + case 3: + $variant = self::COLOR_HEX_3; + break; + case 6: + $variant = self::COLOR_HEX_6; + break; + case 4: + $variant = self::COLOR_HEX_4; + break; + case 8: + $variant = self::COLOR_HEX_8; + break; + default: + return null; + } + + switch ($variant) { + case self::COLOR_HEX_4: + $this->a = \hexdec($hex[3]) / 0xF; + // no break + case self::COLOR_HEX_3: + $this->r = \hexdec($hex[0]) * 0x11; + $this->g = \hexdec($hex[1]) * 0x11; + $this->b = \hexdec($hex[2]) * 0x11; + break; + case self::COLOR_HEX_8: + $this->a = \hexdec(\substr($hex, 6, 2)) / 0xFF; + // no break + case self::COLOR_HEX_6: + $hex = \str_split($hex, 2); + $this->r = \hexdec($hex[0]); + $this->g = \hexdec($hex[1]); + $this->b = \hexdec($hex[2]); + break; + } + + return $variant; + } + + protected function setValuesFromFunction($value) + { + if (!\preg_match('/^((?:rgb|hsl)a?)\\s*\\(([0-9\\.%,\\s\\/\\-]+)\\)$/i', $value, $match)) { + return null; + } + + switch (\strtolower($match[1])) { + case 'rgb': + $variant = self::COLOR_RGB; + break; + case 'rgba': + $variant = self::COLOR_RGBA; + break; + case 'hsl': + $variant = self::COLOR_HSL; + break; + case 'hsla': + $variant = self::COLOR_HSLA; + break; + default: + return null; // @codeCoverageIgnore + } + + $params = \preg_replace('/[,\\s\\/]+/', ',', \trim($match[2])); + $params = \explode(',', $params); + $params = \array_map('trim', $params); + + if (\count($params) < 3 || \count($params) > 4) { + return null; + } + + foreach ($params as $i => &$color) { + if (false !== \strpos($color, '%')) { + $color = (float) \str_replace('%', '', $color); + + if (3 === $i) { + $color = $color / 100; + } elseif (\in_array($variant, array(self::COLOR_RGB, self::COLOR_RGBA), true)) { + $color = \round($color / 100 * 0xFF); + } + } + + $color = (float) $color; + + if (0 === $i && \in_array($variant, array(self::COLOR_HSL, self::COLOR_HSLA), true)) { + $color = ($color % 360 + 360) % 360; + } + } + + /** @var float[] Psalm bug workaround */ + $params = \array_map('floatval', $params); + + switch ($variant) { + case self::COLOR_RGBA: + case self::COLOR_RGB: + if (\min($params) < 0 || \max($params) > 0xFF) { + return null; + } + break; + case self::COLOR_HSLA: + case self::COLOR_HSL: + if (\min($params) < 0 || $params[0] > 360 || \max($params[1], $params[2]) > 100) { + return null; + } + break; + } + + if (4 === \count($params)) { + if ($params[3] > 1) { + return null; + } + + $this->a = $params[3]; + } + + if (self::COLOR_HSLA === $variant || self::COLOR_HSL === $variant) { + $params = self::hslToRgb($params[0], $params[1], $params[2]); + } + + list($this->r, $this->g, $this->b) = $params; + + return $variant; + } + + /** + * Turns HSL color to RGB. Black magic. + * + * @param float $h Hue + * @param float $s Saturation + * @param float $l Lightness + * + * @return int[] RGB array + */ + public static function hslToRgb($h, $s, $l) + { + if (\min($h, $s, $l) < 0) { + throw new InvalidArgumentException('The parameters for hslToRgb should be no less than 0'); + } + + if ($h > 360 || \max($s, $l) > 100) { + throw new InvalidArgumentException('The parameters for hslToRgb should be no more than 360, 100, and 100 respectively'); + } + + $h /= 360; + $s /= 100; + $l /= 100; + + $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l * $s; + $m1 = $l * 2 - $m2; + + return array( + (int) \round(self::hueToRgb($m1, $m2, $h + 1 / 3) * 0xFF), + (int) \round(self::hueToRgb($m1, $m2, $h) * 0xFF), + (int) \round(self::hueToRgb($m1, $m2, $h - 1 / 3) * 0xFF), + ); + } + + /** + * Converts RGB to HSL. Color inversion of previous black magic is white magic? + * + * @param float|int $red Red + * @param float|int $green Green + * @param float|int $blue Blue + * + * @return float[] HSL array + */ + public static function rgbToHsl($red, $green, $blue) + { + if (\min($red, $green, $blue) < 0) { + throw new InvalidArgumentException('The parameters for rgbToHsl should be no less than 0'); + } + + if (\max($red, $green, $blue) > 0xFF) { + throw new InvalidArgumentException('The parameters for rgbToHsl should be no more than 255'); + } + + $clrMin = \min($red, $green, $blue); + $clrMax = \max($red, $green, $blue); + $deltaMax = $clrMax - $clrMin; + + $L = ($clrMax + $clrMin) / 510; + + if (0 == $deltaMax) { + $H = 0; + $S = 0; + } else { + if (0.5 > $L) { + $S = $deltaMax / ($clrMax + $clrMin); + } else { + $S = $deltaMax / (510 - $clrMax - $clrMin); + } + + if ($clrMax === $red) { + $H = ($green - $blue) / (6.0 * $deltaMax); + + if (0 > $H) { + $H += 1.0; + } + } elseif ($clrMax === $green) { + $H = 1 / 3 + ($blue - $red) / (6.0 * $deltaMax); + } else { + $H = 2 / 3 + ($red - $green) / (6.0 * $deltaMax); + } + } + + return array( + (float) ($H * 360 % 360), + (float) ($S * 100), + (float) ($L * 100), + ); + } + + /** + * Helper function for hslToRgb. Even blacker magic. + * + * + * @param float $m1 + * @param float $m2 + * @param float $hue + * + * @return float Color value + */ + private static function hueToRgb($m1, $m2, $hue) + { + $hue = ($hue < 0) ? $hue + 1 : (($hue > 1) ? $hue - 1 : $hue); + if ($hue * 6 < 1) { + return $m1 + ($m2 - $m1) * $hue * 6; + } + if ($hue * 2 < 1) { + return $m2; + } + if ($hue * 3 < 2) { + return $m1 + ($m2 - $m1) * (2 / 3 - $hue) * 6; + } + + return $m1; + } +} diff --git a/system/ThirdParty/Kint/Object/Representation/DocstringRepresentation.php b/system/ThirdParty/Kint/Object/Representation/DocstringRepresentation.php new file mode 100644 index 0000000..488d8d6 --- /dev/null +++ b/system/ThirdParty/Kint/Object/Representation/DocstringRepresentation.php @@ -0,0 +1,73 @@ +file = $file; + $this->line = $line; + $this->class = $class; + $this->contents = $docstring; + } + + /** + * Returns the representation's docstring without surrounding comments. + * + * Note that this will not work flawlessly. + * + * On comments with whitespace after the stars the lines will begin with + * whitespace, since we can't accurately guess how much of an indentation + * is required. + * + * And on lines without stars on the left this may eat bullet points. + * + * Long story short: If you want the docstring read the contents. If you + * absolutely must have it without comments (ie renderValueShort) this will + * probably do. + * + * @return null|string Docstring with comments stripped + */ + public function getDocstringWithoutComments() + { + if (!$this->contents) { + return null; + } + + $string = \substr($this->contents, 3, -2); + $string = \preg_replace('/^\\s*\\*\\s*?(\\S|$)/m', '\\1', $string); + + return \trim($string); + } +} diff --git a/system/ThirdParty/Kint/Object/Representation/MicrotimeRepresentation.php b/system/ThirdParty/Kint/Object/Representation/MicrotimeRepresentation.php new file mode 100644 index 0000000..b9f4dac --- /dev/null +++ b/system/ThirdParty/Kint/Object/Representation/MicrotimeRepresentation.php @@ -0,0 +1,71 @@ +seconds = (int) $seconds; + $this->microseconds = (int) $microseconds; + + $this->group = $group; + $this->lap = $lap; + $this->total = $total; + $this->i = $i; + + if ($i) { + $this->avg = $total / $i; + } + + $this->mem = \memory_get_usage(); + $this->mem_real = \memory_get_usage(true); + $this->mem_peak = \memory_get_peak_usage(); + $this->mem_peak_real = \memory_get_peak_usage(true); + } + + public function getDateTime() + { + return DateTime::createFromFormat('U u', $this->seconds.' '.\str_pad($this->microseconds, 6, '0', STR_PAD_LEFT)); + } +} diff --git a/system/ThirdParty/Kint/Object/Representation/Representation.php b/system/ThirdParty/Kint/Object/Representation/Representation.php new file mode 100644 index 0000000..0c911a4 --- /dev/null +++ b/system/ThirdParty/Kint/Object/Representation/Representation.php @@ -0,0 +1,71 @@ +label = $label; + + if (null === $name) { + $name = $label; + } + + $this->setName($name); + } + + public function getLabel() + { + if (\is_array($this->contents) && \count($this->contents) > 1) { + return $this->label.' ('.\count($this->contents).')'; + } + + return $this->label; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = \preg_replace('/[^a-z0-9]+/', '_', \strtolower($name)); + } + + public function labelIsImplicit() + { + return $this->implicit_label; + } +} diff --git a/system/ThirdParty/Kint/Object/Representation/SourceRepresentation.php b/system/ThirdParty/Kint/Object/Representation/SourceRepresentation.php new file mode 100644 index 0000000..c2cf120 --- /dev/null +++ b/system/ThirdParty/Kint/Object/Representation/SourceRepresentation.php @@ -0,0 +1,72 @@ +filename = $filename; + $this->line = $line; + + $start_line = \max($line - $padding, 1); + $length = $line + $padding + 1 - $start_line; + $this->source = self::getSource($filename, $start_line, $length); + if (null !== $this->source) { + $this->contents = \implode("\n", $this->source); + } + } + + /** + * Gets section of source code. + * + * @param string $filename Full path to file + * @param int $start_line The first line to display (1 based) + * @param null|int $length Amount of lines to show + * + * @return null|array + */ + public static function getSource($filename, $start_line = 1, $length = null) + { + if (!$filename || !\file_exists($filename) || !\is_readable($filename)) { + return null; + } + + $source = \preg_split("/\r\n|\n|\r/", \file_get_contents($filename)); + $source = \array_combine(\range(1, \count($source)), $source); + $source = \array_slice($source, $start_line - 1, $length, true); + + return $source; + } +} diff --git a/system/ThirdParty/Kint/Object/Representation/SplFileInfoRepresentation.php b/system/ThirdParty/Kint/Object/Representation/SplFileInfoRepresentation.php new file mode 100644 index 0000000..3df50e6 --- /dev/null +++ b/system/ThirdParty/Kint/Object/Representation/SplFileInfoRepresentation.php @@ -0,0 +1,177 @@ +getRealPath()) { + $this->realpath = $fileInfo->getRealPath(); + $this->perms = $fileInfo->getPerms(); + $this->size = $fileInfo->getSize(); + $this->owner = $fileInfo->getOwner(); + $this->group = $fileInfo->getGroup(); + $this->ctime = $fileInfo->getCTime(); + $this->mtime = $fileInfo->getMTime(); + } + + $this->path = $fileInfo->getPathname(); + + $this->is_dir = $fileInfo->isDir(); + $this->is_file = $fileInfo->isFile(); + $this->is_link = $fileInfo->isLink(); + + if ($this->is_link) { + $this->linktarget = $fileInfo->getLinkTarget(); + } + + switch ($this->perms & 0xF000) { + case 0xC000: + $this->typename = 'Socket'; + $this->typeflag = 's'; + break; + case 0x6000: + $this->typename = 'Block device'; + $this->typeflag = 'b'; + break; + case 0x2000: + $this->typename = 'Character device'; + $this->typeflag = 'c'; + break; + case 0x1000: + $this->typename = 'Named pipe'; + $this->typeflag = 'p'; + break; + default: + if ($this->is_file) { + if ($this->is_link) { + $this->typename = 'File symlink'; + $this->typeflag = 'l'; + } else { + $this->typename = 'File'; + $this->typeflag = '-'; + } + } elseif ($this->is_dir) { + if ($this->is_link) { + $this->typename = 'Directory symlink'; + $this->typeflag = 'l'; + } else { + $this->typename = 'Directory'; + $this->typeflag = 'd'; + } + } + break; + } + + $this->flags = array($this->typeflag); + + // User + $this->flags[] = (($this->perms & 0400) ? 'r' : '-'); + $this->flags[] = (($this->perms & 0200) ? 'w' : '-'); + if ($this->perms & 0100) { + $this->flags[] = ($this->perms & 04000) ? 's' : 'x'; + } else { + $this->flags[] = ($this->perms & 04000) ? 'S' : '-'; + } + + // Group + $this->flags[] = (($this->perms & 0040) ? 'r' : '-'); + $this->flags[] = (($this->perms & 0020) ? 'w' : '-'); + if ($this->perms & 0010) { + $this->flags[] = ($this->perms & 02000) ? 's' : 'x'; + } else { + $this->flags[] = ($this->perms & 02000) ? 'S' : '-'; + } + + // Other + $this->flags[] = (($this->perms & 0004) ? 'r' : '-'); + $this->flags[] = (($this->perms & 0002) ? 'w' : '-'); + if ($this->perms & 0001) { + $this->flags[] = ($this->perms & 01000) ? 's' : 'x'; + } else { + $this->flags[] = ($this->perms & 01000) ? 'S' : '-'; + } + + $this->contents = \implode($this->flags).' '.$this->owner.' '.$this->group; + $this->contents .= ' '.$this->getSize().' '.$this->getMTime().' '; + + if ($this->is_link && $this->linktarget) { + $this->contents .= $this->path.' -> '.$this->linktarget; + } elseif (null !== $this->realpath && \strlen($this->realpath) < \strlen($this->path)) { + $this->contents .= $this->realpath; + } else { + $this->contents .= $this->path; + } + } + + public function getLabel() + { + return $this->typename.' ('.$this->getSize().')'; + } + + public function getSize() + { + if ($this->size) { + $size = Utils::getHumanReadableBytes($this->size); + + return \round($size['value'], 2).$size['unit']; + } + } + + public function getMTime() + { + $year = \date('Y', $this->mtime); + + if ($year !== \date('Y')) { + return \date('M d Y', $this->mtime); + } + + return \date('M d H:i', $this->mtime); + } +} diff --git a/system/ThirdParty/Kint/Object/ResourceObject.php b/system/ThirdParty/Kint/Object/ResourceObject.php new file mode 100644 index 0000000..a43f85d --- /dev/null +++ b/system/ThirdParty/Kint/Object/ResourceObject.php @@ -0,0 +1,49 @@ +resource_type) { + return $this->resource_type.' resource'; + } + + return 'resource'; + } + + public function transplant(BasicObject $old) + { + parent::transplant($old); + + if ($old instanceof self) { + $this->resource_type = $old->resource_type; + } + } +} diff --git a/system/ThirdParty/Kint/Object/StreamObject.php b/system/ThirdParty/Kint/Object/StreamObject.php new file mode 100644 index 0000000..358f274 --- /dev/null +++ b/system/ThirdParty/Kint/Object/StreamObject.php @@ -0,0 +1,54 @@ +stream_meta = $meta; + } + + public function getValueShort() + { + if (empty($this->stream_meta['uri'])) { + return; + } + + $uri = $this->stream_meta['uri']; + + if (\stream_is_local($uri)) { + return Kint::shortenPath($uri); + } + + return $uri; + } +} diff --git a/system/ThirdParty/Kint/Object/ThrowableObject.php b/system/ThirdParty/Kint/Object/ThrowableObject.php new file mode 100644 index 0000000..2a86d57 --- /dev/null +++ b/system/ThirdParty/Kint/Object/ThrowableObject.php @@ -0,0 +1,54 @@ +message = $throw->getMessage(); + } + + public function getValueShort() + { + if (\strlen($this->message)) { + return '"'.$this->message.'"'; + } + } +} diff --git a/system/ThirdParty/Kint/Object/TraceFrameObject.php b/system/ThirdParty/Kint/Object/TraceFrameObject.php new file mode 100644 index 0000000..4259aee --- /dev/null +++ b/system/ThirdParty/Kint/Object/TraceFrameObject.php @@ -0,0 +1,100 @@ +transplant($base); + + $this->trace = array( + 'function' => isset($raw_frame['function']) ? $raw_frame['function'] : null, + 'line' => isset($raw_frame['line']) ? $raw_frame['line'] : null, + 'file' => isset($raw_frame['file']) ? $raw_frame['file'] : null, + 'class' => isset($raw_frame['class']) ? $raw_frame['class'] : null, + 'type' => isset($raw_frame['type']) ? $raw_frame['type'] : null, + 'object' => null, + 'args' => null, + ); + + if ($this->trace['class'] && \method_exists($this->trace['class'], $this->trace['function'])) { + $func = new ReflectionMethod($this->trace['class'], $this->trace['function']); + $this->trace['function'] = new MethodObject($func); + } elseif (!$this->trace['class'] && \function_exists($this->trace['function'])) { + $func = new ReflectionFunction($this->trace['function']); + $this->trace['function'] = new MethodObject($func); + } + + foreach ($this->value->contents as $frame_prop) { + if ('object' === $frame_prop->name) { + $this->trace['object'] = $frame_prop; + $this->trace['object']->name = null; + $this->trace['object']->operator = BasicObject::OPERATOR_NONE; + } + if ('args' === $frame_prop->name) { + $this->trace['args'] = $frame_prop->value->contents; + + if ($this->trace['function'] instanceof MethodObject) { + foreach (\array_values($this->trace['function']->parameters) as $param) { + if (isset($this->trace['args'][$param->position])) { + $this->trace['args'][$param->position]->name = $param->getName(); + } + } + } + } + } + + $this->clearRepresentations(); + + if (isset($this->trace['file'], $this->trace['line']) && \is_readable($this->trace['file'])) { + $this->addRepresentation(new SourceRepresentation($this->trace['file'], $this->trace['line'])); + } + + if ($this->trace['args']) { + $args = new Representation('Arguments'); + $args->contents = $this->trace['args']; + $this->addRepresentation($args); + } + + if ($this->trace['object']) { + $callee = new Representation('object'); + $callee->label = 'Callee object ['.$this->trace['object']->classname.']'; + $callee->contents[] = $this->trace['object']; + $this->addRepresentation($callee); + } + } +} diff --git a/system/ThirdParty/Kint/Object/TraceObject.php b/system/ThirdParty/Kint/Object/TraceObject.php new file mode 100644 index 0000000..a780b08 --- /dev/null +++ b/system/ThirdParty/Kint/Object/TraceObject.php @@ -0,0 +1,45 @@ +size) { + return 'empty'; + } + + return parent::getSize(); + } +} diff --git a/system/ThirdParty/Kint/Parser/ArrayObjectPlugin.php b/system/ThirdParty/Kint/Parser/ArrayObjectPlugin.php new file mode 100644 index 0000000..286d255 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ArrayObjectPlugin.php @@ -0,0 +1,63 @@ +getFlags(); + + if (ArrayObject::STD_PROP_LIST === $flags) { + return; + } + + $var->setFlags(ArrayObject::STD_PROP_LIST); + + $o = $this->parser->parse($var, $o); + + $var->setFlags($flags); + + $this->parser->haltParse(); + } +} diff --git a/system/ThirdParty/Kint/Parser/Base64Plugin.php b/system/ThirdParty/Kint/Parser/Base64Plugin.php new file mode 100644 index 0000000..3d7d6bc --- /dev/null +++ b/system/ThirdParty/Kint/Parser/Base64Plugin.php @@ -0,0 +1,95 @@ +depth = $o->depth + 1; + $base_obj->name = 'base64_decode('.$o->name.')'; + + if ($o->access_path) { + $base_obj->access_path = 'base64_decode('.$o->access_path.')'; + } + + $r = new Representation('Base64'); + $r->contents = $this->parser->parse($data, $base_obj); + + if (\strlen($var) > self::$min_length_soft) { + $o->addRepresentation($r, 0); + } else { + $o->addRepresentation($r); + } + } +} diff --git a/system/ThirdParty/Kint/Parser/BinaryPlugin.php b/system/ThirdParty/Kint/Parser/BinaryPlugin.php new file mode 100644 index 0000000..327c297 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/BinaryPlugin.php @@ -0,0 +1,49 @@ +encoding, array('ASCII', 'UTF-8'), true)) { + $o->value->hints[] = 'binary'; + } + } +} diff --git a/system/ThirdParty/Kint/Parser/BlacklistPlugin.php b/system/ThirdParty/Kint/Parser/BlacklistPlugin.php new file mode 100644 index 0000000..b37e45f --- /dev/null +++ b/system/ThirdParty/Kint/Parser/BlacklistPlugin.php @@ -0,0 +1,143 @@ +parseObject($var, $o); + } + if (\is_array($var)) { + return $this->parseArray($var, $o); + } + } + + protected function parseObject(&$var, BasicObject &$o) + { + foreach (self::$blacklist as $class) { + if ($var instanceof $class) { + return $this->blacklistObject($var, $o); + } + } + + if ($o->depth <= 0) { + return; + } + + foreach (self::$shallow_blacklist as $class) { + if ($var instanceof $class) { + return $this->blacklistObject($var, $o); + } + } + } + + protected function blacklistObject(&$var, BasicObject &$o) + { + $object = new InstanceObject(); + $object->transplant($o); + $object->classname = \get_class($var); + $object->hash = \spl_object_hash($var); + $object->clearRepresentations(); + $object->value = null; + $object->size = null; + $object->hints[] = 'blacklist'; + + $o = $object; + + $this->parser->haltParse(); + } + + protected function parseArray(array &$var, BasicObject &$o) + { + if (\count($var) > self::$array_limit) { + return $this->blacklistArray($var, $o); + } + + if ($o->depth <= 0) { + return; + } + + if (\count($var) > self::$shallow_array_limit) { + return $this->blacklistArray($var, $o); + } + } + + protected function blacklistArray(array &$var, BasicObject &$o) + { + $object = new BasicObject(); + $object->transplant($o); + $object->value = null; + $object->size = \count($var); + $object->hints[] = 'blacklist'; + + $o = $object; + + $this->parser->haltParse(); + } +} diff --git a/system/ThirdParty/Kint/Parser/ClassMethodsPlugin.php b/system/ThirdParty/Kint/Parser/ClassMethodsPlugin.php new file mode 100644 index 0000000..e4c2371 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ClassMethodsPlugin.php @@ -0,0 +1,113 @@ +getMethods() as $method) { + $methods[] = new MethodObject($method); + } + + \usort($methods, array('Kint\\Parser\\ClassMethodsPlugin', 'sort')); + + self::$cache[$class] = $methods; + } + + if (!empty(self::$cache[$class])) { + $rep = new Representation('Available methods', 'methods'); + + // Can't cache access paths + foreach (self::$cache[$class] as $m) { + $method = clone $m; + $method->depth = $o->depth + 1; + + if (!$this->parser->childHasPath($o, $method)) { + $method->access_path = null; + } else { + $method->setAccessPathFrom($o); + } + + if ($method->owner_class !== $class && $ds = $method->getRepresentation('docstring')) { + $ds = clone $ds; + $ds->class = $method->owner_class; + $method->replaceRepresentation($ds); + } + + $rep->contents[] = $method; + } + + $o->addRepresentation($rep); + } + } + + private static function sort(MethodObject $a, MethodObject $b) + { + $sort = ((int) $a->static) - ((int) $b->static); + if ($sort) { + return $sort; + } + + $sort = BasicObject::sortByAccess($a, $b); + if ($sort) { + return $sort; + } + + $sort = InstanceObject::sortByHierarchy($a->owner_class, $b->owner_class); + if ($sort) { + return $sort; + } + + return $a->startline - $b->startline; + } +} diff --git a/system/ThirdParty/Kint/Parser/ClassStaticsPlugin.php b/system/ThirdParty/Kint/Parser/ClassStaticsPlugin.php new file mode 100644 index 0000000..0ba58ca --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ClassStaticsPlugin.php @@ -0,0 +1,122 @@ +getConstants() as $name => $val) { + $const = BasicObject::blank($name, '\\'.$class.'::'.$name); + $const->const = true; + $const->depth = $o->depth + 1; + $const->owner_class = $class; + $const->operator = BasicObject::OPERATOR_STATIC; + $const = $this->parser->parse($val, $const); + + $consts[] = $const; + } + + self::$cache[$class] = $consts; + } + + $statics = new Representation('Static class properties', 'statics'); + $statics->contents = self::$cache[$class]; + + foreach ($reflection->getProperties(ReflectionProperty::IS_STATIC) as $static) { + $prop = new BasicObject(); + $prop->name = '$'.$static->getName(); + $prop->depth = $o->depth + 1; + $prop->static = true; + $prop->operator = BasicObject::OPERATOR_STATIC; + $prop->owner_class = $static->getDeclaringClass()->name; + + $prop->access = BasicObject::ACCESS_PUBLIC; + if ($static->isProtected()) { + $prop->access = BasicObject::ACCESS_PROTECTED; + } elseif ($static->isPrivate()) { + $prop->access = BasicObject::ACCESS_PRIVATE; + } + + if ($this->parser->childHasPath($o, $prop)) { + $prop->access_path = '\\'.$prop->owner_class.'::'.$prop->name; + } + + $static->setAccessible(true); + $static = $static->getValue(); + $statics->contents[] = $this->parser->parse($static, $prop); + } + + if (empty($statics->contents)) { + return; + } + + \usort($statics->contents, array('Kint\\Parser\\ClassStaticsPlugin', 'sort')); + + $o->addRepresentation($statics); + } + + private static function sort(BasicObject $a, BasicObject $b) + { + $sort = ((int) $a->const) - ((int) $b->const); + if ($sort) { + return $sort; + } + + $sort = BasicObject::sortByAccess($a, $b); + if ($sort) { + return $sort; + } + + return InstanceObject::sortByHierarchy($a->owner_class, $b->owner_class); + } +} diff --git a/system/ThirdParty/Kint/Parser/ClosurePlugin.php b/system/ThirdParty/Kint/Parser/ClosurePlugin.php new file mode 100644 index 0000000..73e367b --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ClosurePlugin.php @@ -0,0 +1,94 @@ +transplant($o); + $o = $object; + $object->removeRepresentation('properties'); + + $closure = new ReflectionFunction($var); + + $o->filename = $closure->getFileName(); + $o->startline = $closure->getStartLine(); + + foreach ($closure->getParameters() as $param) { + $o->parameters[] = new ParameterObject($param); + } + + $p = new Representation('Parameters'); + $p->contents = &$o->parameters; + $o->addRepresentation($p, 0); + + $statics = array(); + + if (\method_exists($closure, 'getClosureThis') && $v = $closure->getClosureThis()) { + $statics = array('this' => $v); + } + + if (\count($statics = $statics + $closure->getStaticVariables())) { + $statics_parsed = array(); + + foreach ($statics as $name => &$static) { + $obj = BasicObject::blank('$'.$name); + $obj->depth = $o->depth + 1; + $statics_parsed[$name] = $this->parser->parse($static, $obj); + if (null === $statics_parsed[$name]->value) { + $statics_parsed[$name]->access_path = null; + } + } + + $r = new Representation('Uses'); + $r->contents = $statics_parsed; + $o->addRepresentation($r, 0); + } + } +} diff --git a/system/ThirdParty/Kint/Parser/ColorPlugin.php b/system/ThirdParty/Kint/Parser/ColorPlugin.php new file mode 100644 index 0000000..0d748f2 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ColorPlugin.php @@ -0,0 +1,63 @@ + 32) { + return; + } + + $trimmed = \strtolower(\trim($var)); + + if (!isset(ColorRepresentation::$color_map[$trimmed]) && !\preg_match('/^(?:(?:rgb|hsl)[^\\)]{6,}\\)|#[0-9a-fA-F]{3,8})$/', $trimmed)) { + return; + } + + $rep = new ColorRepresentation($var); + + if ($rep->variant) { + $o->removeRepresentation($o->value); + $o->addRepresentation($rep, 0); + $o->hints[] = 'color'; + } + } +} diff --git a/system/ThirdParty/Kint/Parser/DOMDocumentPlugin.php b/system/ThirdParty/Kint/Parser/DOMDocumentPlugin.php new file mode 100644 index 0000000..ec08d31 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/DOMDocumentPlugin.php @@ -0,0 +1,328 @@ + 'DOMNode', + 'firstChild' => 'DOMNode', + 'lastChild' => 'DOMNode', + 'previousSibling' => 'DOMNode', + 'nextSibling' => 'DOMNode', + 'ownerDocument' => 'DOMDocument', + ); + + /** + * Show all properties and methods. + * + * @var bool + */ + public static $verbose = false; + + public function getTypes() + { + return array('object'); + } + + public function getTriggers() + { + return Parser::TRIGGER_SUCCESS; + } + + public function parse(&$var, BasicObject &$o, $trigger) + { + if (!$o instanceof InstanceObject) { + return; + } + + if ($var instanceof DOMNamedNodeMap || $var instanceof DOMNodeList) { + return $this->parseList($var, $o, $trigger); + } + + if ($var instanceof DOMNode) { + return $this->parseNode($var, $o); + } + } + + protected function parseList(&$var, InstanceObject &$o, $trigger) + { + // Recursion should never happen, should always be stopped at the parent + // DOMNode. Depth limit on the other hand we're going to skip since + // that would show an empty iterator and rather useless. Let the depth + // limit hit the children (DOMNodeList only has DOMNode as children) + if ($trigger & Parser::TRIGGER_RECURSION) { + return; + } + + $o->size = $var->length; + if (0 === $o->size) { + $o->replaceRepresentation(new Representation('Iterator')); + $o->size = null; + + return; + } + + // Depth limit + // Make empty iterator representation since we need it in DOMNode to point out depth limits + if ($this->parser->getDepthLimit() && $o->depth + 1 >= $this->parser->getDepthLimit()) { + $b = new BasicObject(); + $b->name = $o->classname.' Iterator Contents'; + $b->access_path = 'iterator_to_array('.$o->access_path.')'; + $b->depth = $o->depth + 1; + $b->hints[] = 'depth_limit'; + + $r = new Representation('Iterator'); + $r->contents = array($b); + $o->replaceRepresentation($r, 0); + + return; + } + + $data = \iterator_to_array($var); + + $r = new Representation('Iterator'); + $o->replaceRepresentation($r, 0); + + foreach ($data as $key => $item) { + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth + 1; + $base_obj->name = $item->nodeName; + + if ($o->access_path) { + if ($var instanceof DOMNamedNodeMap) { + $base_obj->access_path = $o->access_path.'->getNamedItem('.\var_export($key, true).')'; + } elseif ($var instanceof DOMNodeList) { + $base_obj->access_path = $o->access_path.'->item('.\var_export($key, true).')'; + } else { + $base_obj->access_path = 'iterator_to_array('.$o->access_path.')'; + } + } + + $r->contents[] = $this->parser->parse($item, $base_obj); + } + } + + protected function parseNode(&$var, InstanceObject &$o) + { + // Fill the properties + // They can't be enumerated through reflection or casting, + // so we have to trust the docs and try them one at a time + $known_properties = array( + 'nodeValue', + 'childNodes', + 'attributes', + ); + + if (self::$verbose) { + $known_properties = array( + 'nodeName', + 'nodeValue', + 'nodeType', + 'parentNode', + 'childNodes', + 'firstChild', + 'lastChild', + 'previousSibling', + 'nextSibling', + 'attributes', + 'ownerDocument', + 'namespaceURI', + 'prefix', + 'localName', + 'baseURI', + 'textContent', + ); + } + + $childNodes = array(); + $attributes = array(); + + $rep = $o->value; + + foreach ($known_properties as $prop) { + $prop_obj = $this->parseProperty($o, $prop, $var); + $rep->contents[] = $prop_obj; + + if ('childNodes' === $prop) { + $childNodes = $prop_obj->getRepresentation('iterator'); + } elseif ('attributes' === $prop) { + $attributes = $prop_obj->getRepresentation('iterator'); + } + } + + if (!self::$verbose) { + $o->removeRepresentation('methods'); + $o->removeRepresentation('properties'); + } + + // Attributes and comments and text nodes don't + // need children or attributes of their own + if (\in_array($o->classname, array('DOMAttr', 'DOMText', 'DOMComment'), true)) { + return; + } + + // Set the attributes + if ($attributes) { + $a = new Representation('Attributes'); + foreach ($attributes->contents as $attribute) { + $a->contents[] = self::textualNodeToString($attribute); + } + $o->addRepresentation($a, 0); + } + + // Set the children + if ($childNodes) { + $c = new Representation('Children'); + + if (1 === \count($childNodes->contents) && ($node = \reset($childNodes->contents)) && \in_array('depth_limit', $node->hints, true)) { + $n = new InstanceObject(); + $n->transplant($node); + $n->name = 'childNodes'; + $n->classname = 'DOMNodeList'; + $c->contents = array($n); + } else { + foreach ($childNodes->contents as $index => $node) { + // Shortcircuit text nodes to plain strings + if ('DOMText' === $node->classname || 'DOMComment' === $node->classname) { + $node = self::textualNodeToString($node); + + // And remove them if they're empty + if (\ctype_space($node->value->contents) || '' === $node->value->contents) { + continue; + } + } + + $c->contents[] = $node; + } + } + + $o->addRepresentation($c, 0); + } + + if (isset($c) && \count($c->contents)) { + $o->size = \count($c->contents); + } + + if (!$o->size) { + $o->size = null; + } + } + + protected function parseProperty(InstanceObject $o, $prop, &$var) + { + // Duplicating (And slightly optimizing) the Parser::parseObject() code here + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth + 1; + $base_obj->owner_class = $o->classname; + $base_obj->name = $prop; + $base_obj->operator = BasicObject::OPERATOR_OBJECT; + $base_obj->access = BasicObject::ACCESS_PUBLIC; + + if (null !== $o->access_path) { + $base_obj->access_path = $o->access_path; + + if (\preg_match('/^[A-Za-z0-9_]+$/', $base_obj->name)) { + $base_obj->access_path .= '->'.$base_obj->name; + } else { + $base_obj->access_path .= '->{'.\var_export($base_obj->name, true).'}'; + } + } + + if (!isset($var->{$prop})) { + $base_obj->type = 'null'; + } elseif (isset(self::$blacklist[$prop])) { + $b = new InstanceObject(); + $b->transplant($base_obj); + $base_obj = $b; + + $base_obj->hints[] = 'blacklist'; + $base_obj->classname = self::$blacklist[$prop]; + } elseif ('attributes' === $prop) { + $base_obj = $this->parser->parseDeep($var->{$prop}, $base_obj); + } else { + $base_obj = $this->parser->parse($var->{$prop}, $base_obj); + } + + return $base_obj; + } + + protected static function textualNodeToString(InstanceObject $o) + { + if (empty($o->value) || empty($o->value->contents) || empty($o->classname)) { + return; + } + + if (!\in_array($o->classname, array('DOMText', 'DOMAttr', 'DOMComment'), true)) { + return; + } + + foreach ($o->value->contents as $property) { + if ('nodeValue' === $property->name) { + $ret = clone $property; + $ret->name = $o->name; + + return $ret; + } + } + } +} diff --git a/system/ThirdParty/Kint/Parser/DateTimePlugin.php b/system/ThirdParty/Kint/Parser/DateTimePlugin.php new file mode 100644 index 0000000..f2cebb6 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/DateTimePlugin.php @@ -0,0 +1,55 @@ +transplant($o); + + $o = $object; + } +} diff --git a/system/ThirdParty/Kint/Parser/FsPathPlugin.php b/system/ThirdParty/Kint/Parser/FsPathPlugin.php new file mode 100644 index 0000000..3a8d1e0 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/FsPathPlugin.php @@ -0,0 +1,72 @@ + 2048) { + return; + } + + if (!\preg_match('/[\\/\\'.DIRECTORY_SEPARATOR.']/', $var)) { + return; + } + + if (\preg_match('/[?<>"*|]/', $var)) { + return; + } + + if (!@\file_exists($var)) { + return; + } + + if (\in_array($var, self::$blacklist, true)) { + return; + } + + $r = new SplFileInfoRepresentation(new SplFileInfo($var)); + $r->hints[] = 'fspath'; + $o->addRepresentation($r, 0); + } +} diff --git a/system/ThirdParty/Kint/Parser/IteratorPlugin.php b/system/ThirdParty/Kint/Parser/IteratorPlugin.php new file mode 100644 index 0000000..0487a38 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/IteratorPlugin.php @@ -0,0 +1,110 @@ +name = $class.' Iterator Contents'; + $b->access_path = 'iterator_to_array('.$o->access_path.', true)'; + $b->depth = $o->depth + 1; + $b->hints[] = 'blacklist'; + + $r = new Representation('Iterator'); + $r->contents = array($b); + + $o->addRepresentation($r); + + return; + } + } + + /** @var array|false */ + $data = \iterator_to_array($var); + + if (false === $data) { + return; + } + + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth; + + if ($o->access_path) { + $base_obj->access_path = 'iterator_to_array('.$o->access_path.')'; + } + + $r = new Representation('Iterator'); + $r->contents = $this->parser->parse($data, $base_obj); + $r->contents = $r->contents->value->contents; + + $primary = $o->getRepresentations(); + $primary = \reset($primary); + if ($primary && $primary === $o->value && $primary->contents === array()) { + $o->addRepresentation($r, 0); + } else { + $o->addRepresentation($r); + } + } +} diff --git a/system/ThirdParty/Kint/Parser/JsonPlugin.php b/system/ThirdParty/Kint/Parser/JsonPlugin.php new file mode 100644 index 0000000..84b2519 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/JsonPlugin.php @@ -0,0 +1,73 @@ +depth = $o->depth; + + if ($o->access_path) { + $base_obj->access_path = 'json_decode('.$o->access_path.', true)'; + } + + $r = new Representation('Json'); + $r->contents = $this->parser->parse($json, $base_obj); + + if (!\in_array('depth_limit', $r->contents->hints, true)) { + $r->contents = $r->contents->value->contents; + } + + $o->addRepresentation($r, 0); + } +} diff --git a/system/ThirdParty/Kint/Parser/MicrotimePlugin.php b/system/ThirdParty/Kint/Parser/MicrotimePlugin.php new file mode 100644 index 0000000..5062b59 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/MicrotimePlugin.php @@ -0,0 +1,105 @@ +depth) { + return; + } + + if (\is_string($var)) { + if ('microtime()' !== $o->name || !\preg_match('/^0\\.[0-9]{8} [0-9]{10}$/', $var)) { + return; + } + + $usec = (int) \substr($var, 2, 6); + $sec = (int) \substr($var, 11, 10); + } else { + if ('microtime(...)' !== $o->name) { + return; + } + + $sec = \floor($var); + $usec = $var - $sec; + $usec = \floor($usec * 1000000); + } + + $time = $sec + ($usec / 1000000); + + if (null !== self::$last) { + $last_time = self::$last[0] + (self::$last[1] / 1000000); + $lap = $time - $last_time; + ++self::$times; + } else { + $lap = null; + self::$start = $time; + } + + self::$last = array($sec, $usec); + + if (null !== $lap) { + $total = $time - self::$start; + $r = new MicrotimeRepresentation($sec, $usec, self::$group, $lap, $total, self::$times); + } else { + $r = new MicrotimeRepresentation($sec, $usec, self::$group); + } + $r->contents = $var; + $r->implicit_label = true; + + $o->removeRepresentation($o->value); + $o->addRepresentation($r); + $o->hints[] = 'microtime'; + } + + public static function clean() + { + self::$last = null; + self::$start = null; + self::$times = 0; + ++self::$group; + } +} diff --git a/system/ThirdParty/Kint/Parser/MysqliPlugin.php b/system/ThirdParty/Kint/Parser/MysqliPlugin.php new file mode 100644 index 0000000..265299b --- /dev/null +++ b/system/ThirdParty/Kint/Parser/MysqliPlugin.php @@ -0,0 +1,129 @@ + true, + 'connect_errno' => true, + 'connect_error' => true, + ); + + // These are readable on empty mysqli objects, but not on failed connections + protected $empty_readable = array( + 'client_info' => true, + 'errno' => true, + 'error' => true, + ); + + // These are only readable on connected mysqli objects + protected $connected_readable = array( + 'affected_rows' => true, + 'error_list' => true, + 'field_count' => true, + 'host_info' => true, + 'info' => true, + 'insert_id' => true, + 'server_info' => true, + 'server_version' => true, + 'stat' => true, + 'sqlstate' => true, + 'protocol_version' => true, + 'thread_id' => true, + 'warning_count' => true, + ); + + public function getTypes() + { + return array('object'); + } + + public function getTriggers() + { + return Parser::TRIGGER_COMPLETE; + } + + public function parse(&$var, BasicObject &$o, $trigger) + { + if (!$var instanceof Mysqli) { + return; + } + + $connected = false; + $empty = false; + + if (\is_string(@$var->sqlstate)) { + $connected = true; + } elseif (\is_string(@$var->client_info)) { + $empty = true; + } + + foreach ($o->value->contents as $key => $obj) { + if (isset($this->connected_readable[$obj->name])) { + if (!$connected) { + continue; + } + } elseif (isset($this->empty_readable[$obj->name])) { + if (!$connected && !$empty) { + continue; + } + } elseif (!isset($this->always_readable[$obj->name])) { + continue; + } + + if ('null' !== $obj->type) { + continue; + } + + $param = $var->{$obj->name}; + + if (null === $param) { + continue; + } + + $base = BasicObject::blank($obj->name, $obj->access_path); + + $base->depth = $obj->depth; + $base->owner_class = $obj->owner_class; + $base->operator = $obj->operator; + $base->access = $obj->access; + $base->reference = $obj->reference; + + $o->value->contents[$key] = $this->parser->parse($param, $base); + } + } +} diff --git a/system/ThirdParty/Kint/Parser/Parser.php b/system/ThirdParty/Kint/Parser/Parser.php new file mode 100644 index 0000000..b7f81c6 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/Parser.php @@ -0,0 +1,604 @@ +marker = \uniqid("kint\0", true); + + $this->caller_class = $caller; + + if ($depth_limit) { + $this->depth_limit = $depth_limit; + } + } + + /** + * Set the caller class. + * + * @param null|string $caller Caller class name + */ + public function setCallerClass($caller = null) + { + $this->noRecurseCall(); + + $this->caller_class = $caller; + } + + public function getCallerClass() + { + return $this->caller_class; + } + + /** + * Set the depth limit. + * + * @param false|int $depth_limit Maximum depth to parse data + */ + public function setDepthLimit($depth_limit = false) + { + $this->noRecurseCall(); + + $this->depth_limit = $depth_limit; + } + + public function getDepthLimit() + { + return $this->depth_limit; + } + + /** + * Disables the depth limit and parses a variable. + * + * This should not be used unless you know what you're doing! + * + * @param mixed $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + public function parseDeep(&$var, BasicObject $o) + { + $depth_limit = $this->depth_limit; + $this->depth_limit = false; + + $out = $this->parse($var, $o); + + $this->depth_limit = $depth_limit; + + return $out; + } + + /** + * Parses a variable into a Kint object structure. + * + * @param mixed $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + public function parse(&$var, BasicObject $o) + { + $o->type = \strtolower(\gettype($var)); + + if (!$this->applyPlugins($var, $o, self::TRIGGER_BEGIN)) { + return $o; + } + + switch ($o->type) { + case 'array': + return $this->parseArray($var, $o); + case 'boolean': + case 'double': + case 'integer': + case 'null': + return $this->parseGeneric($var, $o); + case 'object': + return $this->parseObject($var, $o); + case 'resource': + return $this->parseResource($var, $o); + case 'string': + return $this->parseString($var, $o); + default: + return $this->parseUnknown($var, $o); + } + } + + public function addPlugin(Plugin $p) + { + if (!$types = $p->getTypes()) { + return false; + } + + if (!$triggers = $p->getTriggers()) { + return false; + } + + $p->setParser($this); + + foreach ($types as $type) { + if (!isset($this->plugins[$type])) { + $this->plugins[$type] = array( + self::TRIGGER_BEGIN => array(), + self::TRIGGER_SUCCESS => array(), + self::TRIGGER_RECURSION => array(), + self::TRIGGER_DEPTH_LIMIT => array(), + ); + } + + foreach ($this->plugins[$type] as $trigger => &$pool) { + if ($triggers & $trigger) { + $pool[] = $p; + } + } + } + + return true; + } + + public function clearPlugins() + { + $this->plugins = array(); + } + + public function haltParse() + { + $this->parse_break = true; + } + + public function childHasPath(InstanceObject $parent, BasicObject $child) + { + if ('object' === $parent->type && (null !== $parent->access_path || $child->static || $child->const)) { + if (BasicObject::ACCESS_PUBLIC === $child->access) { + return true; + } + + if (BasicObject::ACCESS_PRIVATE === $child->access && $this->caller_class) { + if ($this->caller_class === $child->owner_class) { + return true; + } + } elseif (BasicObject::ACCESS_PROTECTED === $child->access && $this->caller_class) { + if ($this->caller_class === $child->owner_class) { + return true; + } + + if (\is_subclass_of($this->caller_class, $child->owner_class)) { + return true; + } + + if (\is_subclass_of($child->owner_class, $this->caller_class)) { + return true; + } + } + } + + return false; + } + + /** + * Returns an array without the recursion marker in it. + * + * DO NOT pass an array that has had it's marker removed back + * into the parser, it will result in an extra recursion + * + * @param array $array Array potentially containing a recursion marker + * + * @return array Array with recursion marker removed + */ + public function getCleanArray(array $array) + { + unset($array[$this->marker]); + + return $array; + } + + protected function noRecurseCall() + { + $bt = \debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS); + + $caller_frame = array( + 'function' => __FUNCTION__, + ); + + while (isset($bt[0]['object']) && $bt[0]['object'] === $this) { + $caller_frame = \array_shift($bt); + } + + foreach ($bt as $frame) { + if (isset($frame['object']) && $frame['object'] === $this) { + throw new DomainException(__CLASS__.'::'.$caller_frame['function'].' cannot be called from inside a parse'); + } + } + } + + private function parseGeneric(&$var, BasicObject $o) + { + $rep = new Representation('Contents'); + $rep->contents = $var; + $rep->implicit_label = true; + $o->addRepresentation($rep); + $o->value = $rep; + + $this->applyPlugins($var, $o, self::TRIGGER_SUCCESS); + + return $o; + } + + /** + * Parses a string into a Kint BlobObject structure. + * + * @param string $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + private function parseString(&$var, BasicObject $o) + { + $string = new BlobObject(); + $string->transplant($o); + $string->encoding = BlobObject::detectEncoding($var); + $string->size = BlobObject::strlen($var, $string->encoding); + + $rep = new Representation('Contents'); + $rep->contents = $var; + $rep->implicit_label = true; + + $string->addRepresentation($rep); + $string->value = $rep; + + $this->applyPlugins($var, $string, self::TRIGGER_SUCCESS); + + return $string; + } + + /** + * Parses an array into a Kint object structure. + * + * @param array $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + private function parseArray(array &$var, BasicObject $o) + { + $array = new BasicObject(); + $array->transplant($o); + $array->size = \count($var); + + if (isset($var[$this->marker])) { + --$array->size; + $array->hints[] = 'recursion'; + + $this->applyPlugins($var, $array, self::TRIGGER_RECURSION); + + return $array; + } + + $rep = new Representation('Contents'); + $rep->implicit_label = true; + $array->addRepresentation($rep); + $array->value = $rep; + + if (!$array->size) { + $this->applyPlugins($var, $array, self::TRIGGER_SUCCESS); + + return $array; + } + + if ($this->depth_limit && $o->depth >= $this->depth_limit) { + $array->hints[] = 'depth_limit'; + + $this->applyPlugins($var, $array, self::TRIGGER_DEPTH_LIMIT); + + return $array; + } + + $copy = \array_values($var); + + // It's really really hard to access numeric string keys in arrays, + // and it's really really hard to access integer properties in + // objects, so we just use array_values and index by counter to get + // at it reliably for reference testing. This also affects access + // paths since it's pretty much impossible to access these things + // without complicated stuff you should never need to do. + $i = 0; + + // Set the marker for recursion + $var[$this->marker] = $array->depth; + + $refmarker = new stdClass(); + + foreach ($var as $key => &$val) { + if ($key === $this->marker) { + continue; + } + + $child = new BasicObject(); + $child->name = $key; + $child->depth = $array->depth + 1; + $child->access = BasicObject::ACCESS_NONE; + $child->operator = BasicObject::OPERATOR_ARRAY; + + if (null !== $array->access_path) { + if (\is_string($key) && (string) (int) $key === $key) { + $child->access_path = 'array_values('.$array->access_path.')['.$i.']'; // @codeCoverageIgnore + } else { + $child->access_path = $array->access_path.'['.\var_export($key, true).']'; + } + } + + $stash = $val; + $copy[$i] = $refmarker; + if ($val === $refmarker) { + $child->reference = true; + $val = $stash; + } + + $rep->contents[] = $this->parse($val, $child); + ++$i; + } + + $this->applyPlugins($var, $array, self::TRIGGER_SUCCESS); + unset($var[$this->marker]); + + return $array; + } + + /** + * Parses an object into a Kint InstanceObject structure. + * + * @param object $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + private function parseObject(&$var, BasicObject $o) + { + $hash = \spl_object_hash($var); + $values = (array) $var; + + $object = new InstanceObject(); + $object->transplant($o); + $object->classname = \get_class($var); + $object->hash = $hash; + $object->size = \count($values); + + if (isset($this->object_hashes[$hash])) { + $object->hints[] = 'recursion'; + + $this->applyPlugins($var, $object, self::TRIGGER_RECURSION); + + return $object; + } + + $this->object_hashes[$hash] = $object; + + if ($this->depth_limit && $o->depth >= $this->depth_limit) { + $object->hints[] = 'depth_limit'; + + $this->applyPlugins($var, $object, self::TRIGGER_DEPTH_LIMIT); + unset($this->object_hashes[$hash]); + + return $object; + } + + $reflector = new ReflectionObject($var); + + if ($reflector->isUserDefined()) { + $object->filename = $reflector->getFileName(); + $object->startline = $reflector->getStartLine(); + } + + $rep = new Representation('Properties'); + + $copy = \array_values($values); + $refmarker = new stdClass(); + $i = 0; + + // Reflection will not show parent classes private properties, and if a + // property was unset it will happly trigger a notice looking for it. + foreach ($values as $key => &$val) { + // Casting object to array: + // private properties show in the form "\0$owner_class_name\0$property_name"; + // protected properties show in the form "\0*\0$property_name"; + // public properties show in the form "$property_name"; + // http://www.php.net/manual/en/language.types.array.php#language.types.array.casting + + $child = new BasicObject(); + $child->depth = $object->depth + 1; + $child->owner_class = $object->classname; + $child->operator = BasicObject::OPERATOR_OBJECT; + $child->access = BasicObject::ACCESS_PUBLIC; + + $split_key = \explode("\0", $key, 3); + + if (3 === \count($split_key) && '' === $split_key[0]) { + $child->name = $split_key[2]; + if ('*' === $split_key[1]) { + $child->access = BasicObject::ACCESS_PROTECTED; + } else { + $child->access = BasicObject::ACCESS_PRIVATE; + $child->owner_class = $split_key[1]; + } + } elseif (KINT_PHP72) { + $child->name = (string) $key; + } else { + $child->name = $key; // @codeCoverageIgnore + } + + if ($this->childHasPath($object, $child)) { + $child->access_path = $object->access_path; + + if (!KINT_PHP72 && \is_int($child->name)) { + $child->access_path = 'array_values((array) '.$child->access_path.')['.$i.']'; // @codeCoverageIgnore + } elseif (\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $child->name)) { + $child->access_path .= '->'.$child->name; + } else { + $child->access_path .= '->{'.\var_export((string) $child->name, true).'}'; + } + } + + $stash = $val; + $copy[$i] = $refmarker; + if ($val === $refmarker) { + $child->reference = true; + $val = $stash; + } + + $rep->contents[] = $this->parse($val, $child); + ++$i; + } + + $object->addRepresentation($rep); + $object->value = $rep; + $this->applyPlugins($var, $object, self::TRIGGER_SUCCESS); + unset($this->object_hashes[$hash]); + + return $object; + } + + /** + * Parses a resource into a Kint ResourceObject structure. + * + * @param resource $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + private function parseResource(&$var, BasicObject $o) + { + $resource = new ResourceObject(); + $resource->transplant($o); + $resource->resource_type = \get_resource_type($var); + + $this->applyPlugins($var, $resource, self::TRIGGER_SUCCESS); + + return $resource; + } + + /** + * Parses an unknown into a Kint object structure. + * + * @param mixed $var The input variable + * @param BasicObject $o The base object + * + * @return BasicObject + */ + private function parseUnknown(&$var, BasicObject $o) + { + $o->type = 'unknown'; + $this->applyPlugins($var, $o, self::TRIGGER_SUCCESS); + + return $o; + } + + /** + * Applies plugins for an object type. + * + * @param mixed $var variable + * @param BasicObject $o Kint object parsed so far + * @param int $trigger The trigger to check for the plugins + * + * @return bool Continue parsing + */ + private function applyPlugins(&$var, BasicObject &$o, $trigger) + { + $break_stash = $this->parse_break; + + /** @var bool Psalm bug workaround */ + $this->parse_break = false; + + $plugins = array(); + + if (isset($this->plugins[$o->type][$trigger])) { + $plugins = $this->plugins[$o->type][$trigger]; + } + + foreach ($plugins as $plugin) { + try { + $plugin->parse($var, $o, $trigger); + } catch (Exception $e) { + \trigger_error( + 'An exception ('.\get_class($e).') was thrown in '.$e->getFile().' on line '.$e->getLine().' while executing Kint Parser Plugin "'.\get_class($plugin).'". Error message: '.$e->getMessage(), + E_USER_WARNING + ); + } + + if ($this->parse_break) { + $this->parse_break = $break_stash; + + return false; + } + } + + $this->parse_break = $break_stash; + + return true; + } +} diff --git a/system/ThirdParty/Kint/Parser/Plugin.php b/system/ThirdParty/Kint/Parser/Plugin.php new file mode 100644 index 0000000..51d5f0b --- /dev/null +++ b/system/ThirdParty/Kint/Parser/Plugin.php @@ -0,0 +1,55 @@ +parser = $p; + } + + /** + * An array of types (As returned by gettype) for all data this plugin can operate on. + * + * @return array List of types + */ + public function getTypes() + { + return array(); + } + + public function getTriggers() + { + return Parser::TRIGGER_NONE; + } + + abstract public function parse(&$variable, BasicObject &$o, $trigger); +} diff --git a/system/ThirdParty/Kint/Parser/ProxyPlugin.php b/system/ThirdParty/Kint/Parser/ProxyPlugin.php new file mode 100644 index 0000000..3376d3a --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ProxyPlugin.php @@ -0,0 +1,66 @@ +types = $types; + $this->triggers = $triggers; + $this->callback = $callback; + } + + public function getTypes() + { + return $this->types; + } + + public function getTriggers() + { + return $this->triggers; + } + + public function parse(&$var, BasicObject &$o, $trigger) + { + return \call_user_func_array($this->callback, array(&$var, &$o, $trigger, $this->parser)); + } +} diff --git a/system/ThirdParty/Kint/Parser/SerializePlugin.php b/system/ThirdParty/Kint/Parser/SerializePlugin.php new file mode 100644 index 0000000..c5dadb8 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/SerializePlugin.php @@ -0,0 +1,108 @@ + Unserialization can result in code being loaded and executed due to + * > object instantiation and autoloading, and a malicious user may be able + * > to exploit this. + * + * The natural way to stop that from happening is to just refuse to unserialize + * stuff by default. Which is what we're doing for anything that's not scalar. + * + * @var bool + */ + public static $safe_mode = true; + public static $options = array(true); + + public function getTypes() + { + return array('string'); + } + + public function getTriggers() + { + return Parser::TRIGGER_SUCCESS; + } + + public function parse(&$var, BasicObject &$o, $trigger) + { + $trimmed = \rtrim($var); + + if ('N;' !== $trimmed && !\preg_match('/^(?:[COabis]:\\d+[:;]|d:\\d+(?:\\.\\d+);)/', $trimmed)) { + return; + } + + if (!self::$safe_mode || !\in_array($trimmed[0], array('C', 'O', 'a'), true)) { + // Second parameter only supported on PHP 7 + if (KINT_PHP70) { + // Suppress warnings on unserializeable variable + $data = @\unserialize($trimmed, self::$options); + } else { + $data = @\unserialize($trimmed); + } + + if (false === $data && 'b:0;' !== \substr($trimmed, 0, 4)) { + return; + } + } + + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth + 1; + $base_obj->name = 'unserialize('.$o->name.')'; + + if ($o->access_path) { + $base_obj->access_path = 'unserialize('.$o->access_path; + if (!KINT_PHP70 || self::$options === array(true)) { + $base_obj->access_path .= ')'; + } elseif (self::$options === array(false)) { + $base_obj->access_path .= ', false)'; + } else { + $base_obj->access_path .= ', Serialize::$options)'; + } + } + + $r = new Representation('Serialized'); + + if (isset($data)) { + $r->contents = $this->parser->parse($data, $base_obj); + } else { + $base_obj->hints[] = 'blacklist'; + $r->contents = $base_obj; + } + + $o->addRepresentation($r, 0); + } +} diff --git a/system/ThirdParty/Kint/Parser/SimpleXMLElementPlugin.php b/system/ThirdParty/Kint/Parser/SimpleXMLElementPlugin.php new file mode 100644 index 0000000..b90c863 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/SimpleXMLElementPlugin.php @@ -0,0 +1,154 @@ +hints[] = 'simplexml_element'; + + if (!self::$verbose) { + $o->removeRepresentation('properties'); + $o->removeRepresentation('iterator'); + $o->removeRepresentation('methods'); + } + + // Attributes + $a = new Representation('Attributes'); + + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth; + + if ($o->access_path) { + $base_obj->access_path = '(string) '.$o->access_path; + } + + if ($attribs = $var->attributes()) { + $attribs = \iterator_to_array($attribs); + $attribs = \array_map('strval', $attribs); + } else { + $attribs = array(); + } + + // XML attributes are by definition strings and don't have children, + // so up the depth limit in case we're just below the limit since + // there won't be any recursive stuff anyway. + $a->contents = $this->parser->parseDeep($attribs, $base_obj)->value->contents; + + $o->addRepresentation($a, 0); + + // Children + // We need to check children() separately from the values we already parsed because + // text contents won't show up in children() but they will show up in properties. + // + // Why do we still need to check for attributes if we already have an attributes() + // method? Hell if I know! + $children = $var->children(); + + if ($o->value) { + $c = new Representation('Children'); + + foreach ($o->value->contents as $value) { + if ('@attributes' === $value->name) { + continue; + } + + if (isset($children->{$value->name})) { + $i = 0; + + while (isset($children->{$value->name}[$i])) { + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth + 1; + $base_obj->name = $value->name; + if ($value->access_path) { + $base_obj->access_path = $value->access_path.'['.$i.']'; + } + + $value = $this->parser->parse($children->{$value->name}[$i], $base_obj); + + if ($value->access_path && 'string' === $value->type) { + $value->access_path = '(string) '.$value->access_path; + } + + $c->contents[] = $value; + + ++$i; + } + } + } + + $o->size = \count($c->contents); + + if (!$o->size) { + $o->size = null; + + if (\strlen((string) $var)) { + $base_obj = new BlobObject(); + $base_obj->depth = $o->depth + 1; + $base_obj->name = $o->name; + if ($o->access_path) { + $base_obj->access_path = '(string) '.$o->access_path; + } + + $value = (string) $var; + + $c = new Representation('Contents'); + $c->implicit_label = true; + $c->contents = array($this->parser->parseDeep($value, $base_obj)); + } + } + + $o->addRepresentation($c, 0); + } + } +} diff --git a/system/ThirdParty/Kint/Parser/SplFileInfoPlugin.php b/system/ThirdParty/Kint/Parser/SplFileInfoPlugin.php new file mode 100644 index 0000000..8b72193 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/SplFileInfoPlugin.php @@ -0,0 +1,55 @@ +addRepresentation($r, 0); + $o->size = $r->getSize(); + } +} diff --git a/system/ThirdParty/Kint/Parser/SplObjectStoragePlugin.php b/system/ThirdParty/Kint/Parser/SplObjectStoragePlugin.php new file mode 100644 index 0000000..03ff301 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/SplObjectStoragePlugin.php @@ -0,0 +1,54 @@ +getRepresentation('iterator'))) { + return; + } + + $r = $o->getRepresentation('iterator'); + if ($r) { + $o->size = !\is_array($r->contents) ? null : \count($r->contents); + } + } +} diff --git a/system/ThirdParty/Kint/Parser/StreamPlugin.php b/system/ThirdParty/Kint/Parser/StreamPlugin.php new file mode 100644 index 0000000..464a3ff --- /dev/null +++ b/system/ThirdParty/Kint/Parser/StreamPlugin.php @@ -0,0 +1,78 @@ +resource_type) { + return; + } + + if (!$meta = \stream_get_meta_data($var)) { + return; + } + + $rep = new Representation('Stream'); + $rep->implicit_label = true; + + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth; + + if ($o->access_path) { + $base_obj->access_path = 'stream_get_meta_data('.$o->access_path.')'; + } + + $rep->contents = $this->parser->parse($meta, $base_obj); + + if (!\in_array('depth_limit', $rep->contents->hints, true)) { + $rep->contents = $rep->contents->value->contents; + } + + $o->addRepresentation($rep, 0); + $o->value = $rep; + + $stream = new StreamObject($meta); + $stream->transplant($o); + $o = $stream; + } +} diff --git a/system/ThirdParty/Kint/Parser/TablePlugin.php b/system/ThirdParty/Kint/Parser/TablePlugin.php new file mode 100644 index 0000000..510c4ff --- /dev/null +++ b/system/ThirdParty/Kint/Parser/TablePlugin.php @@ -0,0 +1,87 @@ +value->contents)) { + return; + } + + $array = $this->parser->getCleanArray($var); + + if (\count($array) < 2) { + return; + } + + // Ensure this is an array of arrays and that all child arrays have the + // same keys. We don't care about their children - if there's another + // "table" inside we'll just make another one down the value tab + $keys = null; + foreach ($array as $elem) { + if (!\is_array($elem) || \count($elem) < 2) { + return; + } + + if (null === $keys) { + $keys = \array_keys($elem); + } elseif (\array_keys($elem) !== $keys) { + return; + } + } + + // Ensure none of the child arrays are recursion or depth limit. We + // don't care if their children are since they are the table cells + foreach ($o->value->contents as $childarray) { + if (empty($childarray->value->contents)) { + return; + } + } + + // Objects by reference for the win! We can do a copy-paste of the value + // representation contents and just slap a new hint on there and hey + // presto we have our table representation with no extra memory used! + $table = new Representation('Table'); + $table->contents = $o->value->contents; + $table->hints[] = 'table'; + $o->addRepresentation($table, 0); + } +} diff --git a/system/ThirdParty/Kint/Parser/ThrowablePlugin.php b/system/ThirdParty/Kint/Parser/ThrowablePlugin.php new file mode 100644 index 0000000..8490d1d --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ThrowablePlugin.php @@ -0,0 +1,60 @@ +transplant($o); + $r = new SourceRepresentation($var->getFile(), $var->getLine()); + $r->showfilename = true; + $throw->addRepresentation($r, 0); + + $o = $throw; + } +} diff --git a/system/ThirdParty/Kint/Parser/TimestampPlugin.php b/system/ThirdParty/Kint/Parser/TimestampPlugin.php new file mode 100644 index 0000000..72958d6 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/TimestampPlugin.php @@ -0,0 +1,71 @@ +value->label = 'Timestamp'; + $o->value->hints[] = 'timestamp'; + } + } +} diff --git a/system/ThirdParty/Kint/Parser/ToStringPlugin.php b/system/ThirdParty/Kint/Parser/ToStringPlugin.php new file mode 100644 index 0000000..8b7a65f --- /dev/null +++ b/system/ThirdParty/Kint/Parser/ToStringPlugin.php @@ -0,0 +1,67 @@ +hasMethod('__toString')) { + return; + } + + foreach (self::$blacklist as $class) { + if ($var instanceof $class) { + return; + } + } + + $r = new Representation('toString'); + $r->contents = (string) $var; + + $o->addRepresentation($r); + } +} diff --git a/system/ThirdParty/Kint/Parser/TracePlugin.php b/system/ThirdParty/Kint/Parser/TracePlugin.php new file mode 100644 index 0000000..3554993 --- /dev/null +++ b/system/ThirdParty/Kint/Parser/TracePlugin.php @@ -0,0 +1,92 @@ +value) { + return; + } + + $trace = $this->parser->getCleanArray($var); + + if (\count($trace) !== \count($o->value->contents) || !Utils::isTrace($trace)) { + return; + } + + $traceobj = new TraceObject(); + $traceobj->transplant($o); + $rep = $traceobj->value; + + $old_trace = $rep->contents; + + Utils::normalizeAliases(self::$blacklist); + + $rep->contents = array(); + + foreach ($old_trace as $frame) { + $index = $frame->name; + + if (!isset($trace[$index]['function'])) { + // Something's very very wrong here, but it's probably a plugin's fault + continue; + } + + if (Utils::traceFrameIsListed($trace[$index], self::$blacklist)) { + continue; + } + + $rep->contents[$index] = new TraceFrameObject($frame, $trace[$index]); + } + + \ksort($rep->contents); + $rep->contents = \array_values($rep->contents); + + $traceobj->clearRepresentations(); + $traceobj->addRepresentation($rep); + $traceobj->size = \count($rep->contents); + $o = $traceobj; + } +} diff --git a/system/ThirdParty/Kint/Parser/XmlPlugin.php b/system/ThirdParty/Kint/Parser/XmlPlugin.php new file mode 100644 index 0000000..0947e9a --- /dev/null +++ b/system/ThirdParty/Kint/Parser/XmlPlugin.php @@ -0,0 +1,150 @@ +access_path); + + if (empty($xml)) { + return; + } + + list($xml, $access_path, $name) = $xml; + + $base_obj = new BasicObject(); + $base_obj->depth = $o->depth + 1; + $base_obj->name = $name; + $base_obj->access_path = $access_path; + + $r = new Representation('XML'); + $r->contents = $this->parser->parse($xml, $base_obj); + + $o->addRepresentation($r, 0); + } + + protected static function xmlToSimpleXML($var, $parent_path) + { + try { + $errors = \libxml_use_internal_errors(true); + $xml = \simplexml_load_string($var); + \libxml_use_internal_errors($errors); + } catch (Exception $e) { + if (isset($errors)) { + \libxml_use_internal_errors($errors); + } + + return; + } + + if (!$xml) { + return; + } + + if (null === $parent_path) { + $access_path = null; + } else { + $access_path = 'simplexml_load_string('.$parent_path.')'; + } + + $name = $xml->getName(); + + return array($xml, $access_path, $name); + } + + /** + * Get the DOMDocument info. + * + * The documentation of DOMDocument::loadXML() states that while you can + * call it statically, it will give an E_STRICT warning. On my system it + * actually gives an E_DEPRECATED warning, but it works so we'll just add + * an error-silencing '@' to the access path. + * + * If it errors loading then we wouldn't have gotten this far in the first place. + * + * @param string $var The XML string + * @param null|string $parent_path The path to the parent, in this case the XML string + * + * @return null|array The root element DOMNode, the access path, and the root element name + */ + protected static function xmlToDOMDocument($var, $parent_path) + { + // There's no way to check validity in DOMDocument without making errors. For shame! + if (!self::xmlToSimpleXML($var, $parent_path)) { + return null; + } + + $xml = new DOMDocument(); + $xml->loadXML($var); + $xml = $xml->firstChild; + + if (null === $parent_path) { + $access_path = null; + } else { + $access_path = '@\\DOMDocument::loadXML('.$parent_path.')->firstChild'; + } + + $name = $xml->nodeName; + + return array($xml, $access_path, $name); + } +} diff --git a/system/ThirdParty/Kint/Renderer/CliRenderer.php b/system/ThirdParty/Kint/Renderer/CliRenderer.php new file mode 100644 index 0000000..0d0846a --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/CliRenderer.php @@ -0,0 +1,152 @@ +windows_output = KINT_WIN; + } + + if (!self::$terminal_width) { + if (!KINT_WIN && self::$detect_width) { + self::$terminal_width = \exec('tput cols'); + } + + if (self::$terminal_width < self::$min_terminal_width) { + self::$terminal_width = self::$default_width; + } + } + + $this->colors = $this->windows_output ? false : self::$cli_colors; + + $this->header_width = self::$terminal_width; + } + + public function colorValue($string) + { + if (!$this->colors) { + return $string; + } + + return "\x1b[32m".\str_replace("\n", "\x1b[0m\n\x1b[32m", $string)."\x1b[0m"; + } + + public function colorType($string) + { + if (!$this->colors) { + return $string; + } + + return "\x1b[35;1m".\str_replace("\n", "\x1b[0m\n\x1b[35;1m", $string)."\x1b[0m"; + } + + public function colorTitle($string) + { + if (!$this->colors) { + return $string; + } + + return "\x1b[36m".\str_replace("\n", "\x1b[0m\n\x1b[36m", $string)."\x1b[0m"; + } + + public function renderTitle(BasicObject $o) + { + if ($this->windows_output) { + return $this->utf8ToWindows(parent::renderTitle($o)); + } + + return parent::renderTitle($o); + } + + public function preRender() + { + return PHP_EOL; + } + + public function postRender() + { + if ($this->windows_output) { + return $this->utf8ToWindows(parent::postRender()); + } + + return parent::postRender(); + } + + public function escape($string, $encoding = false) + { + return \str_replace("\x1b", '\\x1b', $string); + } + + protected function utf8ToWindows($string) + { + return \str_replace( + array('┌', '═', '┐', '│', '└', '─', '┘'), + array("\xda", "\xdc", "\xbf", "\xb3", "\xc0", "\xc4", "\xd9"), + $string + ); + } +} diff --git a/system/ThirdParty/Kint/Renderer/PlainRenderer.php b/system/ThirdParty/Kint/Renderer/PlainRenderer.php new file mode 100644 index 0000000..493a774 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/PlainRenderer.php @@ -0,0 +1,237 @@ + array( + array('Kint\\Renderer\\PlainRenderer', 'renderJs'), + array('Kint\\Renderer\\Text\\MicrotimePlugin', 'renderJs'), + ), + 'style' => array( + array('Kint\\Renderer\\PlainRenderer', 'renderCss'), + ), + 'raw' => array(), + ); + + /** + * Path to the CSS file to load by default. + * + * @var string + */ + public static $theme = 'plain.css'; + + /** + * Output htmlentities instead of utf8. + * + * @var bool + */ + public static $disable_utf8 = false; + + public static $needs_pre_render = true; + + public static $always_pre_render = false; + + protected $force_pre_render = false; + protected $pre_render; + + public function __construct() + { + parent::__construct(); + + $this->pre_render = self::$needs_pre_render; + + if (self::$always_pre_render) { + $this->setPreRender(true); + } + } + + public function setCallInfo(array $info) + { + parent::setCallInfo($info); + + if (\in_array('@', $this->call_info['modifiers'], true)) { + $this->setPreRender(true); + } + } + + public function setStatics(array $statics) + { + parent::setStatics($statics); + + if (!empty($statics['return'])) { + $this->setPreRender(true); + } + } + + public function setPreRender($pre_render) + { + $this->pre_render = $pre_render; + $this->force_pre_render = true; + } + + public function getPreRender() + { + return $this->pre_render; + } + + public function colorValue($string) + { + return ''.$string.''; + } + + public function colorType($string) + { + return ''.$string.''; + } + + public function colorTitle($string) + { + return ''.$string.''; + } + + public function renderTitle(BasicObject $o) + { + if (self::$disable_utf8) { + return $this->utf8ToHtmlentity(parent::renderTitle($o)); + } + + return parent::renderTitle($o); + } + + public function preRender() + { + $output = ''; + + if ($this->pre_render) { + foreach (self::$pre_render_sources as $type => $values) { + $contents = ''; + foreach ($values as $v) { + $contents .= \call_user_func($v, $this); + } + + if (!\strlen($contents)) { + continue; + } + + switch ($type) { + case 'script': + $output .= ''; + break; + case 'style': + $output .= ''; + break; + default: + $output .= $contents; + } + } + + // Don't pre-render on every dump + if (!$this->force_pre_render) { + self::$needs_pre_render = false; + } + } + + return $output.'
'; + } + + public function postRender() + { + if (self::$disable_utf8) { + return $this->utf8ToHtmlentity(parent::postRender()).'
'; + } + + return parent::postRender().'
'; + } + + public function ideLink($file, $line) + { + $path = $this->escape(Kint::shortenPath($file)).':'.$line; + $ideLink = Kint::getIdeLink($file, $line); + + if (!$ideLink) { + return $path; + } + + $class = ''; + + if (\preg_match('/https?:\\/\\//i', $ideLink)) { + $class = 'class="kint-ide-link" '; + } + + return ''.$path.''; + } + + public function escape($string, $encoding = false) + { + if (false === $encoding) { + $encoding = BlobObject::detectEncoding($string); + } + + $original_encoding = $encoding; + + if (false === $encoding || 'ASCII' === $encoding) { + $encoding = 'UTF-8'; + } + + $string = \htmlspecialchars($string, ENT_NOQUOTES, $encoding); + + // this call converts all non-ASCII characters into numeirc htmlentities + if (\function_exists('mb_encode_numericentity') && 'ASCII' !== $original_encoding) { + $string = \mb_encode_numericentity($string, array(0x80, 0xffff, 0, 0xffff), $encoding); + } + + return $string; + } + + protected function utf8ToHtmlentity($string) + { + return \str_replace( + array('┌', '═', '┐', '│', '└', '─', '┘'), + array('┌', '═', '┐', '│', '└', '─', '┘'), + $string + ); + } + + protected static function renderJs() + { + return \file_get_contents(KINT_DIR.'/resources/compiled/shared.js').\file_get_contents(KINT_DIR.'/resources/compiled/plain.js'); + } + + protected static function renderCss() + { + if (\file_exists(KINT_DIR.'/resources/compiled/'.self::$theme)) { + return \file_get_contents(KINT_DIR.'/resources/compiled/'.self::$theme); + } + + return \file_get_contents(self::$theme); + } +} diff --git a/system/ThirdParty/Kint/Renderer/Renderer.php b/system/ThirdParty/Kint/Renderer/Renderer.php new file mode 100644 index 0000000..cf8b0a7 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Renderer.php @@ -0,0 +1,185 @@ +call_info = array( + 'params' => $info['params'], + 'modifiers' => $info['modifiers'], + 'callee' => $info['callee'], + 'caller' => $info['caller'], + 'trace' => $info['trace'], + ); + } + + public function getCallInfo() + { + return $this->call_info; + } + + public function setStatics(array $statics) + { + $this->statics = $statics; + $this->setShowTrace(!empty($statics['display_called_from'])); + } + + public function getStatics() + { + return $this->statics; + } + + public function setShowTrace($show_trace) + { + $this->show_trace = $show_trace; + } + + public function getShowTrace() + { + return $this->show_trace; + } + + /** + * Returns the first compatible plugin available. + * + * @param array $plugins Array of hints to class strings + * @param array $hints Array of object hints + * + * @return array Array of hints to class strings filtered and sorted by object hints + */ + public function matchPlugins(array $plugins, array $hints) + { + $out = array(); + + foreach ($hints as $key) { + if (isset($plugins[$key])) { + $out[$key] = $plugins[$key]; + } + } + + return $out; + } + + public function filterParserPlugins(array $plugins) + { + return $plugins; + } + + public function preRender() + { + return ''; + } + + public function postRender() + { + return ''; + } + + public static function sortPropertiesFull(BasicObject $a, BasicObject $b) + { + $sort = BasicObject::sortByAccess($a, $b); + if ($sort) { + return $sort; + } + + $sort = BasicObject::sortByName($a, $b); + if ($sort) { + return $sort; + } + + return InstanceObject::sortByHierarchy($a->owner_class, $b->owner_class); + } + + /** + * Sorts an array of BasicObject. + * + * @param BasicObject[] $contents Object properties to sort + * @param int $sort + * + * @return BasicObject[] + */ + public static function sortProperties(array $contents, $sort) + { + switch ($sort) { + case self::SORT_VISIBILITY: + /** @var array Containers to quickly stable sort by type */ + $containers = array( + BasicObject::ACCESS_PUBLIC => array(), + BasicObject::ACCESS_PROTECTED => array(), + BasicObject::ACCESS_PRIVATE => array(), + BasicObject::ACCESS_NONE => array(), + ); + + foreach ($contents as $item) { + $containers[$item->access][] = $item; + } + + return \call_user_func_array('array_merge', $containers); + case self::SORT_FULL: + \usort($contents, array('Kint\\Renderer\\Renderer', 'sortPropertiesFull')); + // no break + default: + return $contents; + } + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/BinaryPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/BinaryPlugin.php new file mode 100644 index 0000000..5b4d613 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/BinaryPlugin.php @@ -0,0 +1,51 @@ +'; + + $chunks = \str_split($r->contents, self::$line_length); + + foreach ($chunks as $index => $chunk) { + $out .= \sprintf('%08X', $index * self::$line_length).":\t"; + $out .= \implode(' ', \str_split(\str_pad(\bin2hex($chunk), 2 * self::$line_length, ' '), self::$chunk_length)); + $out .= "\t".\preg_replace('/[^\\x20-\\x7E]/', '.', $chunk)."\n"; + } + + $out .= ''; + + return $out; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/BlacklistPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/BlacklistPlugin.php new file mode 100644 index 0000000..fcfedc1 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/BlacklistPlugin.php @@ -0,0 +1,36 @@ +'.$this->renderLockedHeader($o, 'Blacklisted').''; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/CallablePlugin.php b/system/ThirdParty/Kint/Renderer/Rich/CallablePlugin.php new file mode 100644 index 0000000..5834017 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/CallablePlugin.php @@ -0,0 +1,174 @@ +renderMethod($o); + } + + if ($o instanceof ClosureObject) { + return $this->renderClosure($o); + } + + return $this->renderCallable($o); + } + + protected function renderClosure(ClosureObject $o) + { + $children = $this->renderer->renderChildren($o); + + $header = ''; + + if (null !== ($s = $o->getModifiers())) { + $header .= ''.$s.' '; + } + + if (null !== ($s = $o->getName())) { + $header .= ''.$this->renderer->escape($s).'('.$this->renderer->escape($o->getParams()).')'; + } + + if (null !== ($s = $o->getValueShort())) { + if (RichRenderer::$strlen_max && BlobObject::strlen($s) > RichRenderer::$strlen_max) { + $s = \substr($s, 0, RichRenderer::$strlen_max).'...'; + } + $header .= ' '.$this->renderer->escape($s); + } + + return '
'.$this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header).$children.'
'; + } + + protected function renderCallable(BasicObject $o) + { + $children = $this->renderer->renderChildren($o); + + $header = ''; + + if (null !== ($s = $o->getModifiers())) { + $header .= ''.$s.' '; + } + + if (null !== ($s = $o->getName())) { + $header .= ''.$this->renderer->escape($s).''; + } + + if (null !== ($s = $o->getValueShort())) { + if (RichRenderer::$strlen_max && BlobObject::strlen($s) > RichRenderer::$strlen_max) { + $s = \substr($s, 0, RichRenderer::$strlen_max).'...'; + } + $header .= ' '.$this->renderer->escape($s); + } + + return '
'.$this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header).$children.'
'; + } + + protected function renderMethod(MethodObject $o) + { + if (!empty(self::$method_cache[$o->owner_class][$o->name])) { + $children = self::$method_cache[$o->owner_class][$o->name]['children']; + + $header = $this->renderer->renderHeaderWrapper( + $o, + (bool) \strlen($children), + self::$method_cache[$o->owner_class][$o->name]['header'] + ); + + return '
'.$header.$children.'
'; + } + + $children = $this->renderer->renderChildren($o); + + $header = ''; + + if (null !== ($s = $o->getModifiers()) || $o->return_reference) { + $header .= ''.$s; + + if ($o->return_reference) { + if ($s) { + $header .= ' '; + } + $header .= $this->renderer->escape('&'); + } + + $header .= ' '; + } + + if (null !== ($s = $o->getName())) { + $function = $this->renderer->escape($s).'('.$this->renderer->escape($o->getParams()).')'; + + if (null !== ($url = $o->getPhpDocUrl())) { + $function = ''.$function.''; + } + + $header .= ''.$function.''; + } + + if (!empty($o->returntype)) { + $header .= ': '; + + if ($o->return_reference) { + $header .= $this->renderer->escape('&'); + } + + $header .= $this->renderer->escape($o->returntype).''; + } elseif ($o->docstring) { + if (\preg_match('/@return\\s+(.*)\\r?\\n/m', $o->docstring, $matches)) { + if (\trim($matches[1])) { + $header .= ': '.$this->renderer->escape(\trim($matches[1])).''; + } + } + } + + if (null !== ($s = $o->getValueShort())) { + if (RichRenderer::$strlen_max && BlobObject::strlen($s) > RichRenderer::$strlen_max) { + $s = \substr($s, 0, RichRenderer::$strlen_max).'...'; + } + $header .= ' '.$this->renderer->escape($s); + } + + if (\strlen($o->owner_class) && \strlen($o->name)) { + self::$method_cache[$o->owner_class][$o->name] = array( + 'header' => $header, + 'children' => $children, + ); + } + + $header = $this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header); + + return '
'.$header.$children.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/ClosurePlugin.php b/system/ThirdParty/Kint/Renderer/Rich/ClosurePlugin.php new file mode 100644 index 0000000..79a9926 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/ClosurePlugin.php @@ -0,0 +1,59 @@ +renderer->renderChildren($o); + + if (!($o instanceof ClosureObject)) { + $header = $this->renderer->renderHeader($o); + } else { + $header = ''; + + if (null !== ($s = $o->getModifiers())) { + $header .= ''.$s.' '; + } + + if (null !== ($s = $o->getName())) { + $header .= ''.$this->renderer->escape($s).'('.$this->renderer->escape($o->getParams()).') '; + } + + $header .= 'Closure '; + $header .= $this->renderer->escape(Kint::shortenPath($o->filename)).':'.(int) $o->startline; + } + + $header = $this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header); + + return '
'.$header.$children.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/ColorPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/ColorPlugin.php new file mode 100644 index 0000000..241a815 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/ColorPlugin.php @@ -0,0 +1,100 @@ +getRepresentation('color'); + + if (!$r instanceof ColorRepresentation) { + return; + } + + $children = $this->renderer->renderChildren($o); + + $header = $this->renderer->renderHeader($o); + $header .= '
'; + + $header = $this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header); + + return '
'.$header.$children.'
'; + } + + public function renderTab(Representation $r) + { + if (!$r instanceof ColorRepresentation) { + return; + } + + $out = ''; + + if ($color = $r->getColor(ColorRepresentation::COLOR_NAME)) { + $out .= ''.$color."\n"; + } + if ($color = $r->getColor(ColorRepresentation::COLOR_HEX_3)) { + $out .= ''.$color."\n"; + } + if ($color = $r->getColor(ColorRepresentation::COLOR_HEX_6)) { + $out .= ''.$color."\n"; + } + + if ($r->hasAlpha()) { + if ($color = $r->getColor(ColorRepresentation::COLOR_HEX_4)) { + $out .= ''.$color."\n"; + } + if ($color = $r->getColor(ColorRepresentation::COLOR_HEX_8)) { + $out .= ''.$color."\n"; + } + if ($color = $r->getColor(ColorRepresentation::COLOR_RGBA)) { + $out .= ''.$color."\n"; + } + if ($color = $r->getColor(ColorRepresentation::COLOR_HSLA)) { + $out .= ''.$color."\n"; + } + } else { + if ($color = $r->getColor(ColorRepresentation::COLOR_RGB)) { + $out .= ''.$color."\n"; + } + if ($color = $r->getColor(ColorRepresentation::COLOR_HSL)) { + $out .= ''.$color."\n"; + } + } + + if (!\strlen($out)) { + return false; + } + + return '
'.$out.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/DepthLimitPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/DepthLimitPlugin.php new file mode 100644 index 0000000..cd92b41 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/DepthLimitPlugin.php @@ -0,0 +1,36 @@ +'.$this->renderLockedHeader($o, 'Depth Limit').''; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/DocstringPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/DocstringPlugin.php new file mode 100644 index 0000000..19c5309 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/DocstringPlugin.php @@ -0,0 +1,70 @@ +contents) as $line) { + $docstring[] = \trim($line); + } + + $docstring = \implode("\n", $docstring); + + $location = array(); + + if ($r->class) { + $location[] = 'Inherited from '.$this->renderer->escape($r->class); + } + if ($r->file && $r->line) { + $location[] = 'Defined in '.$this->renderer->escape(Kint::shortenPath($r->file)).':'.((int) $r->line); + } + + $location = \implode("\n", $location); + + if ($location) { + if (\strlen($docstring)) { + $docstring .= "\n\n"; + } + + $location = ''.$location.''; + } elseif (0 === \strlen($docstring)) { + return ''; + } + + return '
'.$this->renderer->escape($docstring).$location.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/MicrotimePlugin.php b/system/ThirdParty/Kint/Renderer/Rich/MicrotimePlugin.php new file mode 100644 index 0000000..a56bb23 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/MicrotimePlugin.php @@ -0,0 +1,68 @@ +getDateTime()->format('Y-m-d H:i:s.u'); + if (null !== $r->lap) { + $out .= '
SINCE LAST CALL: '.\round($r->lap, 4).'s.'; + } + if (null !== $r->total) { + $out .= '
SINCE START: '.\round($r->total, 4).'s.'; + } + if (null !== $r->avg) { + $out .= '
AVERAGE DURATION: '.\round($r->avg, 4).'s.'; + } + + $bytes = Utils::getHumanReadableBytes($r->mem); + $out .= '
MEMORY USAGE: '.$r->mem.' bytes ('.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + $bytes = Utils::getHumanReadableBytes($r->mem_real); + $out .= ' (real '.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + + $bytes = Utils::getHumanReadableBytes($r->mem_peak); + $out .= '
PEAK MEMORY USAGE: '.$r->mem_peak.' bytes ('.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + $bytes = Utils::getHumanReadableBytes($r->mem_peak_real); + $out .= ' (real '.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + + return '
'.$out.'
'; + } + + public static function renderJs() + { + return \file_get_contents(KINT_DIR.'/resources/compiled/microtime.js'); + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/ObjectPluginInterface.php b/system/ThirdParty/Kint/Renderer/Rich/ObjectPluginInterface.php new file mode 100644 index 0000000..f46aa29 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/ObjectPluginInterface.php @@ -0,0 +1,33 @@ +renderer = $r; + } + + /** + * Renders a locked header. + * + * @param BasicObject $o + * @param string $content + */ + public function renderLockedHeader(BasicObject $o, $content) + { + $header = '
'; + + if (RichRenderer::$access_paths && $o->depth > 0 && $ap = $o->getAccessPath()) { + $header .= ''; + } + + $header .= ''; + + if (null !== ($s = $o->getModifiers())) { + $header .= ''.$s.' '; + } + + if (null !== ($s = $o->getName())) { + $header .= ''.$this->renderer->escape($s).' '; + + if ($s = $o->getOperator()) { + $header .= $this->renderer->escape($s, 'ASCII').' '; + } + } + + if (null !== ($s = $o->getType())) { + $s = $this->renderer->escape($s); + + if ($o->reference) { + $s = '&'.$s; + } + + $header .= ''.$s.' '; + } + + if (null !== ($s = $o->getSize())) { + $header .= '('.$this->renderer->escape($s).') '; + } + + $header .= $content; + + if (!empty($ap)) { + $header .= '
'.$this->renderer->escape($ap).'
'; + } + + return $header.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/PluginInterface.php b/system/ThirdParty/Kint/Renderer/Rich/PluginInterface.php new file mode 100644 index 0000000..79828e7 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/PluginInterface.php @@ -0,0 +1,33 @@ +'.$this->renderLockedHeader($o, 'Recursion').''; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/SimpleXMLElementPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/SimpleXMLElementPlugin.php new file mode 100644 index 0000000..6c18931 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/SimpleXMLElementPlugin.php @@ -0,0 +1,81 @@ +renderer->renderChildren($o); + + $header = ''; + + if (null !== ($s = $o->getModifiers())) { + $header .= ''.$s.' '; + } + + if (null !== ($s = $o->getName())) { + $header .= ''.$this->renderer->escape($s).' '; + + if ($s = $o->getOperator()) { + $header .= $this->renderer->escape($s, 'ASCII').' '; + } + } + + if (null !== ($s = $o->getType())) { + $s = $this->renderer->escape($s); + + if ($o->reference) { + $s = '&'.$s; + } + + $header .= ''.$this->renderer->escape($s).' '; + } + + if (null !== ($s = $o->getSize())) { + $header .= '('.$this->renderer->escape($s).') '; + } + + if (null === $s && $c = $o->getRepresentation('contents')) { + $c = \reset($c->contents); + + if ($c && null !== ($s = $c->getValueShort())) { + if (RichRenderer::$strlen_max && BlobObject::strlen($s) > RichRenderer::$strlen_max) { + $s = \substr($s, 0, RichRenderer::$strlen_max).'...'; + } + $header .= $this->renderer->escape($s); + } + } + + $header = $this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header); + + return '
'.$header.$children.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/SourcePlugin.php b/system/ThirdParty/Kint/Renderer/Rich/SourcePlugin.php new file mode 100644 index 0000000..5443dbf --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/SourcePlugin.php @@ -0,0 +1,79 @@ +source)) { + return false; + } + + $source = $r->source; + + // Trim empty lines from the start and end of the source + foreach ($source as $linenum => $line) { + if (\strlen(\trim($line)) || $linenum === $r->line) { + break; + } + + unset($source[$linenum]); + } + + foreach (\array_reverse($source, true) as $linenum => $line) { + if (\strlen(\trim($line)) || $linenum === $r->line) { + break; + } + + unset($source[$linenum]); + } + + $output = ''; + + foreach ($source as $linenum => $line) { + if ($linenum === $r->line) { + $output .= '
'.$this->renderer->escape($line)."\n".'
'; + } else { + $output .= '
'.$this->renderer->escape($line)."\n".'
'; + } + } + + if ($output) { + \reset($source); + + $data = ''; + if ($r->showfilename) { + $data = ' data-kint-filename="'.$this->renderer->escape($r->filename).'"'; + } + + return '
'.$output.'
'; + } + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/TabPluginInterface.php b/system/ThirdParty/Kint/Renderer/Rich/TabPluginInterface.php new file mode 100644 index 0000000..7cdbde7 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/TabPluginInterface.php @@ -0,0 +1,33 @@ +
MethodRouteHandler
{from}{to}{method}{route}{handler}
'; + + $firstrow = \reset($r->contents); + + foreach ($firstrow->value->contents as $field) { + $out .= ''; + } + + $out .= ''; + + foreach ($r->contents as $row) { + $out .= ''; + + foreach ($row->value->contents as $field) { + $out .= 'getType())) { + $type = $this->renderer->escape($s); + + if ($field->reference) { + $ref = '&'; + $type = $ref.$type; + } + + if (null !== ($s = $field->getSize())) { + $size .= ' ('.$this->renderer->escape($s).')'; + } + } + + if ($type) { + $out .= ' title="'.$type.$size.'"'; + } + + $out .= '>'; + + switch ($field->type) { + case 'boolean': + $out .= $field->value->contents ? ''.$ref.'true' : ''.$ref.'false'; + break; + case 'integer': + case 'double': + $out .= (string) $field->value->contents; + break; + case 'null': + $out .= ''.$ref.'null'; + break; + case 'string': + if ($field->encoding) { + $val = $field->value->contents; + if (RichRenderer::$strlen_max && self::$respect_str_length && BlobObject::strlen($val) > RichRenderer::$strlen_max) { + $val = \substr($val, 0, RichRenderer::$strlen_max).'...'; + } + + $out .= $this->renderer->escape($val); + } else { + $out .= ''.$type.''; + } + break; + case 'array': + $out .= ''.$ref.'array'.$size; + break; + case 'object': + $out .= ''.$ref.$this->renderer->escape($field->classname).''.$size; + break; + case 'resource': + $out .= ''.$ref.'resource'; + break; + default: + $out .= ''.$ref.'unknown'; + break; + } + + if (\in_array('blacklist', $field->hints, true)) { + $out .= ' Blacklisted'; + } elseif (\in_array('recursion', $field->hints, true)) { + $out .= ' Recursion'; + } elseif (\in_array('depth_limit', $field->hints, true)) { + $out .= ' Depth Limit'; + } + + $out .= ''; + } + + $out .= ''; + } + + $out .= '
'.$this->renderer->escape($field->name).'
'; + $out .= $this->renderer->escape($row->name); + $out .= '
'; + + return $out; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/TimestampPlugin.php b/system/ThirdParty/Kint/Renderer/Rich/TimestampPlugin.php new file mode 100644 index 0000000..6e3a2f8 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/TimestampPlugin.php @@ -0,0 +1,42 @@ +contents); + + if ($dt) { + return '
'.$dt->setTimeZone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s T').'
'; + } + } +} diff --git a/system/ThirdParty/Kint/Renderer/Rich/TraceFramePlugin.php b/system/ThirdParty/Kint/Renderer/Rich/TraceFramePlugin.php new file mode 100644 index 0000000..6ca19bb --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Rich/TraceFramePlugin.php @@ -0,0 +1,68 @@ +trace['file']) && !empty($o->trace['line'])) { + $header = ''.$this->renderer->ideLink($o->trace['file'], (int) $o->trace['line']).' '; + } else { + $header = 'PHP internal call '; + } + + if ($o->trace['class']) { + $header .= $this->renderer->escape($o->trace['class'].$o->trace['type']); + } + + if (\is_string($o->trace['function'])) { + $function = $this->renderer->escape($o->trace['function'].'()'); + } else { + $function = $this->renderer->escape( + $o->trace['function']->getName().'('.$o->trace['function']->getParams().')' + ); + + if (null !== ($url = $o->trace['function']->getPhpDocUrl())) { + $function = ''.$function.''; + } + } + + $header .= ''.$function.''; + + $children = $this->renderer->renderChildren($o); + $header = $this->renderer->renderHeaderWrapper($o, (bool) \strlen($children), $header); + + return '
'.$header.$children.'
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/RichRenderer.php b/system/ThirdParty/Kint/Renderer/RichRenderer.php new file mode 100644 index 0000000..dcd39ee --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/RichRenderer.php @@ -0,0 +1,612 @@ + 'Kint\\Renderer\\Rich\\BlacklistPlugin', + 'callable' => 'Kint\\Renderer\\Rich\\CallablePlugin', + 'closure' => 'Kint\\Renderer\\Rich\\ClosurePlugin', + 'color' => 'Kint\\Renderer\\Rich\\ColorPlugin', + 'depth_limit' => 'Kint\\Renderer\\Rich\\DepthLimitPlugin', + 'recursion' => 'Kint\\Renderer\\Rich\\RecursionPlugin', + 'simplexml_element' => 'Kint\\Renderer\\Rich\\SimpleXMLElementPlugin', + 'trace_frame' => 'Kint\\Renderer\\Rich\\TraceFramePlugin', + ); + + /** + * RichRenderer tab plugins should implement Kint\Renderer\Rich\TabPluginInterface. + */ + public static $tab_plugins = array( + 'binary' => 'Kint\\Renderer\\Rich\\BinaryPlugin', + 'color' => 'Kint\\Renderer\\Rich\\ColorPlugin', + 'docstring' => 'Kint\\Renderer\\Rich\\DocstringPlugin', + 'microtime' => 'Kint\\Renderer\\Rich\\MicrotimePlugin', + 'source' => 'Kint\\Renderer\\Rich\\SourcePlugin', + 'table' => 'Kint\\Renderer\\Rich\\TablePlugin', + 'timestamp' => 'Kint\\Renderer\\Rich\\TimestampPlugin', + ); + + public static $pre_render_sources = array( + 'script' => array( + array('Kint\\Renderer\\RichRenderer', 'renderJs'), + array('Kint\\Renderer\\Rich\\MicrotimePlugin', 'renderJs'), + ), + 'style' => array( + array('Kint\\Renderer\\RichRenderer', 'renderCss'), + ), + 'raw' => array(), + ); + + /** + * Whether or not to render access paths. + * + * Access paths can become incredibly heavy with very deep and wide + * structures. Given mostly public variables it will typically make + * up one quarter of the output HTML size. + * + * If this is an unacceptably large amount and your browser is groaning + * under the weight of the access paths - your first order of buisiness + * should be to get a new browser. Failing that, use this to turn them off. + * + * @var bool + */ + public static $access_paths = true; + + /** + * The maximum length of a string before it is truncated. + * + * Falsey to disable + * + * @var int + */ + public static $strlen_max = 80; + + /** + * Path to the CSS file to load by default. + * + * @var string + */ + public static $theme = 'original.css'; + + /** + * Assume types and sizes don't need to be escaped. + * + * Turn this off if you use anything but ascii in your class names, + * but it'll cause a slowdown of around 10% + * + * @var bool + */ + public static $escape_types = false; + + /** + * Move all dumps to a folder at the bottom of the body. + * + * @var bool + */ + public static $folder = true; + + /** + * Sort mode for object properties. + * + * @var int + */ + public static $sort = self::SORT_NONE; + + public static $needs_pre_render = true; + public static $needs_folder_render = true; + + public static $always_pre_render = false; + + protected $plugin_objs = array(); + protected $expand = false; + protected $force_pre_render = false; + protected $pre_render; + protected $use_folder; + + public function __construct() + { + $this->pre_render = self::$needs_pre_render; + $this->use_folder = self::$folder; + + if (self::$always_pre_render) { + $this->setForcePreRender(); + } + } + + public function setCallInfo(array $info) + { + parent::setCallInfo($info); + + if (\in_array('!', $this->call_info['modifiers'], true)) { + $this->setExpand(true); + $this->use_folder = false; + } + + if (\in_array('@', $this->call_info['modifiers'], true)) { + $this->setForcePreRender(); + } + } + + public function setStatics(array $statics) + { + parent::setStatics($statics); + + if (!empty($statics['expanded'])) { + $this->setExpand(true); + } + + if (!empty($statics['return'])) { + $this->setForcePreRender(); + } + } + + public function setExpand($expand) + { + $this->expand = $expand; + } + + public function getExpand() + { + return $this->expand; + } + + public function setForcePreRender() + { + $this->force_pre_render = true; + $this->pre_render = true; + } + + public function setPreRender($pre_render) + { + $this->setForcePreRender(); // TODO: Remove line in next major version + $this->pre_render = $pre_render; + } + + public function getPreRender() + { + return $this->pre_render; + } + + public function setUseFolder($use_folder) + { + $this->use_folder = $use_folder; + } + + public function getUseFolder() + { + return $this->use_folder; + } + + public function render(BasicObject $o) + { + if ($plugin = $this->getPlugin(self::$object_plugins, $o->hints)) { + if (\strlen($output = $plugin->renderObject($o))) { + return $output; + } + } + + $children = $this->renderChildren($o); + $header = $this->renderHeaderWrapper($o, (bool) \strlen($children), $this->renderHeader($o)); + + return '
'.$header.$children.'
'; + } + + public function renderNothing() + { + return '
No argument
'; + } + + public function renderHeaderWrapper(BasicObject $o, $has_children, $contents) + { + $out = 'expand) { + $out .= ' kint-show'; + } + + $out .= '"'; + } + + $out .= '>'; + + if (self::$access_paths && $o->depth > 0 && $ap = $o->getAccessPath()) { + $out .= ''; + } + + if ($has_children) { + $out .= ''; + + if (0 === $o->depth) { + $out .= ''; + $out .= ''; + } + + $out .= ''; + } + + $out .= $contents; + + if (!empty($ap)) { + $out .= '
'.$this->escape($ap).'
'; + } + + return $out.''; + } + + public function renderHeader(BasicObject $o) + { + $output = ''; + + if (null !== ($s = $o->getModifiers())) { + $output .= ''.$s.' '; + } + + if (null !== ($s = $o->getName())) { + $output .= ''.$this->escape($s).' '; + + if ($s = $o->getOperator()) { + $output .= $this->escape($s, 'ASCII').' '; + } + } + + if (null !== ($s = $o->getType())) { + if (self::$escape_types) { + $s = $this->escape($s); + } + + if ($o->reference) { + $s = '&'.$s; + } + + $output .= ''.$s.' '; + } + + if (null !== ($s = $o->getSize())) { + if (self::$escape_types) { + $s = $this->escape($s); + } + $output .= '('.$s.') '; + } + + if (null !== ($s = $o->getValueShort())) { + $s = \preg_replace('/\\s+/', ' ', $s); + + if (self::$strlen_max) { + $s = Utils::truncateString($s, self::$strlen_max); + } + + $output .= $this->escape($s); + } + + return \trim($output); + } + + public function renderChildren(BasicObject $o) + { + $contents = array(); + $tabs = array(); + + foreach ($o->getRepresentations() as $rep) { + $result = $this->renderTab($o, $rep); + if (\strlen($result)) { + $contents[] = $result; + $tabs[] = $rep; + } + } + + if (empty($tabs)) { + return ''; + } + + $output = '
'; + + if (1 === \count($tabs) && $tabs[0]->labelIsImplicit()) { + $output .= \reset($contents); + } else { + $output .= '
    '; + + foreach ($tabs as $i => $tab) { + if (0 === $i) { + $output .= '
  • '; + } else { + $output .= '
  • '; + } + + $output .= $this->escape($tab->getLabel()).'
  • '; + } + + $output .= '
    '; + + foreach ($contents as $tab) { + $output .= '
  • '.$tab.'
  • '; + } + + $output .= '
'; + } + + return $output.'
'; + } + + public function preRender() + { + $output = ''; + + if ($this->pre_render) { + foreach (self::$pre_render_sources as $type => $values) { + $contents = ''; + foreach ($values as $v) { + $contents .= \call_user_func($v, $this); + } + + if (!\strlen($contents)) { + continue; + } + + switch ($type) { + case 'script': + $output .= ''; + break; + case 'style': + $output .= ''; + break; + default: + $output .= $contents; + } + } + + // Don't pre-render on every dump + if (!$this->force_pre_render) { + self::$needs_pre_render = false; + } + } + + $output .= '
'; + + return $output; + } + + public function postRender() + { + if (!$this->show_trace) { + return '
'; + } + + $output = '
'; + $output .= ' '; + + if (!empty($this->call_info['trace']) && \count($this->call_info['trace']) > 1) { + $output .= ''; + } + + if (isset($this->call_info['callee']['file'])) { + $output .= 'Called from '.$this->ideLink( + $this->call_info['callee']['file'], + $this->call_info['callee']['line'] + ); + } + + if (isset($this->call_info['callee']['function']) && ( + !empty($this->call_info['callee']['class']) || + !\in_array( + $this->call_info['callee']['function'], + array('include', 'include_once', 'require', 'require_once'), + true + ) + ) + ) { + $output .= ' ['; + if (isset($this->call_info['callee']['class'])) { + $output .= $this->call_info['callee']['class']; + } + if (isset($this->call_info['callee']['type'])) { + $output .= $this->call_info['callee']['type']; + } + $output .= $this->call_info['callee']['function'].'()]'; + } + + if (!empty($this->call_info['trace']) && \count($this->call_info['trace']) > 1) { + $output .= '
    '; + foreach ($this->call_info['trace'] as $index => $step) { + if (!$index) { + continue; + } + + $output .= '
  1. '.$this->ideLink($step['file'], $step['line']); // closing tag not required + if (isset($step['function']) + && !\in_array($step['function'], array('include', 'include_once', 'require', 'require_once'), true) + ) { + $output .= ' ['; + if (isset($step['class'])) { + $output .= $step['class']; + } + if (isset($step['type'])) { + $output .= $step['type']; + } + $output .= $step['function'].'()]'; + } + } + $output .= '
'; + } + + $output .= '
'; + + return $output; + } + + public function escape($string, $encoding = false) + { + if (false === $encoding) { + $encoding = BlobObject::detectEncoding($string); + } + + $original_encoding = $encoding; + + if (false === $encoding || 'ASCII' === $encoding) { + $encoding = 'UTF-8'; + } + + $string = \htmlspecialchars($string, ENT_NOQUOTES, $encoding); + + // this call converts all non-ASCII characters into numeirc htmlentities + if (\function_exists('mb_encode_numericentity') && 'ASCII' !== $original_encoding) { + $string = \mb_encode_numericentity($string, array(0x80, 0xffff, 0, 0xffff), $encoding); + } + + return $string; + } + + public function ideLink($file, $line) + { + $path = $this->escape(Kint::shortenPath($file)).':'.$line; + $ideLink = Kint::getIdeLink($file, $line); + + if (!$ideLink) { + return $path; + } + + $class = ''; + + if (\preg_match('/https?:\\/\\//i', $ideLink)) { + $class = 'class="kint-ide-link" '; + } + + return ''.$path.''; + } + + protected function renderTab(BasicObject $o, Representation $rep) + { + if ($plugin = $this->getPlugin(self::$tab_plugins, $rep->hints)) { + if (\strlen($output = $plugin->renderTab($rep))) { + return $output; + } + } + + if (\is_array($rep->contents)) { + $output = ''; + + if ($o instanceof InstanceObject && 'properties' === $rep->getName()) { + foreach (self::sortProperties($rep->contents, self::$sort) as $obj) { + $output .= $this->render($obj); + } + } else { + foreach ($rep->contents as $obj) { + $output .= $this->render($obj); + } + } + + return $output; + } + + if (\is_string($rep->contents)) { + $show_contents = false; + + // If it is the value representation of a string and its whitespace + // was truncated in the header, always display the full string + if ('string' !== $o->type || $o->value !== $rep) { + $show_contents = true; + } else { + if (\preg_match('/(:?[\\r\\n\\t\\f\\v]| {2})/', $rep->contents)) { + $show_contents = true; + } elseif (self::$strlen_max && BlobObject::strlen($o->getValueShort()) > self::$strlen_max) { + $show_contents = true; + } + + if (empty($o->encoding)) { + $show_contents = false; + } + } + + if ($show_contents) { + return '
'.$this->escape($rep->contents)."\n
"; + } + } + + if ($rep->contents instanceof BasicObject) { + return $this->render($rep->contents); + } + } + + protected function getPlugin(array $plugins, array $hints) + { + if ($plugins = $this->matchPlugins($plugins, $hints)) { + $plugin = \end($plugins); + + if (!isset($this->plugin_objs[$plugin])) { + $this->plugin_objs[$plugin] = new $plugin($this); + } + + return $this->plugin_objs[$plugin]; + } + } + + protected static function renderJs() + { + return \file_get_contents(KINT_DIR.'/resources/compiled/shared.js').\file_get_contents(KINT_DIR.'/resources/compiled/rich.js'); + } + + protected static function renderCss() + { + if (\file_exists(KINT_DIR.'/resources/compiled/'.self::$theme)) { + return \file_get_contents(KINT_DIR.'/resources/compiled/'.self::$theme); + } + + return \file_get_contents(self::$theme); + } + + protected static function renderFolder() + { + return '
Kint
'; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Text/BlacklistPlugin.php b/system/ThirdParty/Kint/Renderer/Text/BlacklistPlugin.php new file mode 100644 index 0000000..127d32a --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Text/BlacklistPlugin.php @@ -0,0 +1,44 @@ +depth) { + $out .= $this->renderer->colorTitle($this->renderer->renderTitle($o)).PHP_EOL; + } + + $out .= $this->renderer->renderHeader($o).' '.$this->renderer->colorValue('BLACKLISTED').PHP_EOL; + + return $out; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Text/DepthLimitPlugin.php b/system/ThirdParty/Kint/Renderer/Text/DepthLimitPlugin.php new file mode 100644 index 0000000..310b87e --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Text/DepthLimitPlugin.php @@ -0,0 +1,44 @@ +depth) { + $out .= $this->renderer->colorTitle($this->renderer->renderTitle($o)).PHP_EOL; + } + + $out .= $this->renderer->renderHeader($o).' '.$this->renderer->colorValue('DEPTH LIMIT').PHP_EOL; + + return $out; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Text/MicrotimePlugin.php b/system/ThirdParty/Kint/Renderer/Text/MicrotimePlugin.php new file mode 100644 index 0000000..9128032 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Text/MicrotimePlugin.php @@ -0,0 +1,128 @@ +renderer instanceof PlainRenderer) { + $this->useJs = true; + } + } + + public function render(BasicObject $o) + { + $r = $o->getRepresentation('microtime'); + + if (!$r instanceof MicrotimeRepresentation) { + return false; + } + + $out = ''; + + if (0 == $o->depth) { + $out .= $this->renderer->colorTitle($this->renderer->renderTitle($o)).PHP_EOL; + } + + $out .= $this->renderer->renderHeader($o); + $out .= $this->renderer->renderChildren($o).PHP_EOL; + + $indent = \str_repeat(' ', ($o->depth + 1) * $this->renderer->indent_width); + + if ($this->useJs) { + $out .= ''; + } + + $out .= $indent.$this->renderer->colorType('TIME:').' '; + $out .= $this->renderer->colorValue($r->getDateTime()->format('Y-m-d H:i:s.u')).PHP_EOL; + + if (null !== $r->lap) { + $out .= $indent.$this->renderer->colorType('SINCE LAST CALL:').' '; + + $lap = \round($r->lap, 4); + + if ($this->useJs) { + $lap = ''.$lap.''; + } + + $out .= $this->renderer->colorValue($lap.'s').'.'.PHP_EOL; + } + if (null !== $r->total) { + $out .= $indent.$this->renderer->colorType('SINCE START:').' '; + $out .= $this->renderer->colorValue(\round($r->total, 4).'s').'.'.PHP_EOL; + } + if (null !== $r->avg) { + $out .= $indent.$this->renderer->colorType('AVERAGE DURATION:').' '; + + $avg = \round($r->avg, 4); + + if ($this->useJs) { + $avg = ''.$avg.''; + } + + $out .= $this->renderer->colorValue($avg.'s').'.'.PHP_EOL; + } + + $bytes = Utils::getHumanReadableBytes($r->mem); + $mem = $r->mem.' bytes ('.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + $bytes = Utils::getHumanReadableBytes($r->mem_real); + $mem .= ' (real '.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + + $out .= $indent.$this->renderer->colorType('MEMORY USAGE:').' '; + $out .= $this->renderer->colorValue($mem).'.'.PHP_EOL; + + $bytes = Utils::getHumanReadableBytes($r->mem_peak); + $mem = $r->mem_peak.' bytes ('.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + $bytes = Utils::getHumanReadableBytes($r->mem_peak_real); + $mem .= ' (real '.\round($bytes['value'], 3).' '.$bytes['unit'].')'; + + $out .= $indent.$this->renderer->colorType('PEAK MEMORY USAGE:').' '; + $out .= $this->renderer->colorValue($mem).'.'.PHP_EOL; + + if ($this->useJs) { + $out .= ''; + } + + return $out; + } + + public static function renderJs() + { + return RichPlugin::renderJs(); + } +} diff --git a/system/ThirdParty/Kint/Renderer/Text/Plugin.php b/system/ThirdParty/Kint/Renderer/Text/Plugin.php new file mode 100644 index 0000000..9de25c1 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Text/Plugin.php @@ -0,0 +1,41 @@ +renderer = $r; + } + + abstract public function render(BasicObject $o); +} diff --git a/system/ThirdParty/Kint/Renderer/Text/RecursionPlugin.php b/system/ThirdParty/Kint/Renderer/Text/RecursionPlugin.php new file mode 100644 index 0000000..72c2257 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Text/RecursionPlugin.php @@ -0,0 +1,44 @@ +depth) { + $out .= $this->renderer->colorTitle($this->renderer->renderTitle($o)).PHP_EOL; + } + + $out .= $this->renderer->renderHeader($o).' '.$this->renderer->colorValue('RECURSION').PHP_EOL; + + return $out; + } +} diff --git a/system/ThirdParty/Kint/Renderer/Text/TracePlugin.php b/system/ThirdParty/Kint/Renderer/Text/TracePlugin.php new file mode 100644 index 0000000..5833840 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/Text/TracePlugin.php @@ -0,0 +1,111 @@ +depth) { + $out .= $this->renderer->colorTitle($this->renderer->renderTitle($o)).PHP_EOL; + } + + $out .= $this->renderer->renderHeader($o).':'.PHP_EOL; + + $indent = \str_repeat(' ', ($o->depth + 1) * $this->renderer->indent_width); + + $i = 1; + foreach ($o->value->contents as $frame) { + $framedesc = $indent.\str_pad($i.': ', 4, ' '); + + if ($frame->trace['file']) { + $framedesc .= $this->renderer->ideLink($frame->trace['file'], $frame->trace['line']).PHP_EOL; + } else { + $framedesc .= 'PHP internal call'.PHP_EOL; + } + + $framedesc .= $indent.' '; + + if ($frame->trace['class']) { + $framedesc .= $this->renderer->escape($frame->trace['class']); + + if ($frame->trace['object']) { + $framedesc .= $this->renderer->escape('->'); + } else { + $framedesc .= '::'; + } + } + + if (\is_string($frame->trace['function'])) { + $framedesc .= $this->renderer->escape($frame->trace['function']).'(...)'; + } elseif ($frame->trace['function'] instanceof MethodObject) { + $framedesc .= $this->renderer->escape($frame->trace['function']->getName()); + $framedesc .= '('.$this->renderer->escape($frame->trace['function']->getParams()).')'; + } + + $out .= $this->renderer->colorType($framedesc).PHP_EOL.PHP_EOL; + + if ($source = $frame->getRepresentation('source')) { + $line_wanted = $source->line; + $source = $source->source; + + // Trim empty lines from the start and end of the source + foreach ($source as $linenum => $line) { + if (\trim($line) || $linenum === $line_wanted) { + break; + } + + unset($source[$linenum]); + } + + foreach (\array_reverse($source, true) as $linenum => $line) { + if (\trim($line) || $linenum === $line_wanted) { + break; + } + + unset($source[$linenum]); + } + + foreach ($source as $lineno => $line) { + if ($lineno == $line_wanted) { + $out .= $indent.$this->renderer->colorValue($this->renderer->escape($line)).PHP_EOL; + } else { + $out .= $indent.$this->renderer->escape($line).PHP_EOL; + } + } + } + + ++$i; + } + + return $out; + } +} diff --git a/system/ThirdParty/Kint/Renderer/TextRenderer.php b/system/ThirdParty/Kint/Renderer/TextRenderer.php new file mode 100644 index 0000000..43b6c40 --- /dev/null +++ b/system/ThirdParty/Kint/Renderer/TextRenderer.php @@ -0,0 +1,346 @@ + 'Kint\\Renderer\\Text\\BlacklistPlugin', + 'depth_limit' => 'Kint\\Renderer\\Text\\DepthLimitPlugin', + 'microtime' => 'Kint\\Renderer\\Text\\MicrotimePlugin', + 'recursion' => 'Kint\\Renderer\\Text\\RecursionPlugin', + 'trace' => 'Kint\\Renderer\\Text\\TracePlugin', + ); + + /** + * Parser plugins must be instanceof one of these or + * it will be removed for performance reasons. + */ + public static $parser_plugin_whitelist = array( + 'Kint\\Parser\\BlacklistPlugin', + 'Kint\\Parser\\MicrotimePlugin', + 'Kint\\Parser\\StreamPlugin', + 'Kint\\Parser\\TracePlugin', + ); + + /** + * The maximum length of a string before it is truncated. + * + * Falsey to disable + * + * @var int + */ + public static $strlen_max = 0; + + /** + * The default width of the terminal for headers. + * + * @var int + */ + public static $default_width = 80; + + /** + * Indentation width. + * + * @var int + */ + public static $default_indent = 4; + + /** + * Decorate the header and footer. + * + * @var bool + */ + public static $decorations = true; + + /** + * Sort mode for object properties. + * + * @var int + */ + public static $sort = self::SORT_NONE; + + public $header_width = 80; + public $indent_width = 4; + + protected $plugin_objs = array(); + + public function __construct() + { + $this->header_width = self::$default_width; + $this->indent_width = self::$default_indent; + } + + public function render(BasicObject $o) + { + if ($plugin = $this->getPlugin(self::$plugins, $o->hints)) { + if (\strlen($output = $plugin->render($o))) { + return $output; + } + } + + $out = ''; + + if (0 == $o->depth) { + $out .= $this->colorTitle($this->renderTitle($o)).PHP_EOL; + } + + $out .= $this->renderHeader($o); + $out .= $this->renderChildren($o).PHP_EOL; + + return $out; + } + + public function renderNothing() + { + if (self::$decorations) { + return $this->colorTitle( + $this->boxText('No argument', $this->header_width) + ).PHP_EOL; + } + + return $this->colorTitle('No argument').PHP_EOL; + } + + public function boxText($text, $width) + { + $out = '┌'.\str_repeat('─', $width - 2).'┐'.PHP_EOL; + + if (\strlen($text)) { + $text = Utils::truncateString($text, $width - 4); + $text = \str_pad($text, $width - 4); + + $out .= '│ '.$this->escape($text).' │'.PHP_EOL; + } + + $out .= '└'.\str_repeat('─', $width - 2).'┘'; + + return $out; + } + + public function renderTitle(BasicObject $o) + { + $name = (string) $o->getName(); + + if (self::$decorations) { + return $this->boxText($name, $this->header_width); + } + + return Utils::truncateString($name, $this->header_width); + } + + public function renderHeader(BasicObject $o) + { + $output = array(); + + if ($o->depth) { + if (null !== ($s = $o->getModifiers())) { + $output[] = $s; + } + + if (null !== $o->name) { + $output[] = $this->escape(\var_export($o->name, true)); + + if (null !== ($s = $o->getOperator())) { + $output[] = $this->escape($s); + } + } + } + + if (null !== ($s = $o->getType())) { + if ($o->reference) { + $s = '&'.$s; + } + + $output[] = $this->colorType($this->escape($s)); + } + + if (null !== ($s = $o->getSize())) { + $output[] = '('.$this->escape($s).')'; + } + + if (null !== ($s = $o->getValueShort())) { + if (self::$strlen_max) { + $s = Utils::truncateString($s, self::$strlen_max); + } + $output[] = $this->colorValue($this->escape($s)); + } + + return \str_repeat(' ', $o->depth * $this->indent_width).\implode(' ', $output); + } + + public function renderChildren(BasicObject $o) + { + if ('array' === $o->type) { + $output = ' ['; + } elseif ('object' === $o->type) { + $output = ' ('; + } else { + return ''; + } + + $children = ''; + + if ($o->value && \is_array($o->value->contents)) { + if ($o instanceof InstanceObject && 'properties' === $o->value->getName()) { + foreach (self::sortProperties($o->value->contents, self::$sort) as $obj) { + $children .= $this->render($obj); + } + } else { + foreach ($o->value->contents as $child) { + $children .= $this->render($child); + } + } + } + + if ($children) { + $output .= PHP_EOL.$children; + $output .= \str_repeat(' ', $o->depth * $this->indent_width); + } + + if ('array' === $o->type) { + $output .= ']'; + } else { + $output .= ')'; + } + + return $output; + } + + public function colorValue($string) + { + return $string; + } + + public function colorType($string) + { + return $string; + } + + public function colorTitle($string) + { + return $string; + } + + public function postRender() + { + if (self::$decorations) { + $output = \str_repeat('═', $this->header_width); + } else { + $output = ''; + } + + if (!$this->show_trace) { + return $this->colorTitle($output); + } + + if ($output) { + $output .= PHP_EOL; + } + + return $this->colorTitle($output.$this->calledFrom().PHP_EOL); + } + + public function filterParserPlugins(array $plugins) + { + $return = array(); + + foreach ($plugins as $index => $plugin) { + foreach (self::$parser_plugin_whitelist as $whitelist) { + if ($plugin instanceof $whitelist) { + $return[] = $plugin; + continue 2; + } + } + } + + return $return; + } + + public function ideLink($file, $line) + { + return $this->escape(Kint::shortenPath($file)).':'.$line; + } + + public function escape($string, $encoding = false) + { + return $string; + } + + protected function calledFrom() + { + $output = ''; + + if (isset($this->call_info['callee']['file'])) { + $output .= 'Called from '.$this->ideLink( + $this->call_info['callee']['file'], + $this->call_info['callee']['line'] + ); + } + + if (isset($this->call_info['callee']['function']) && ( + !empty($this->call_info['callee']['class']) || + !\in_array( + $this->call_info['callee']['function'], + array('include', 'include_once', 'require', 'require_once'), + true + ) + ) + ) { + $output .= ' ['; + if (isset($this->call_info['callee']['class'])) { + $output .= $this->call_info['callee']['class']; + } + if (isset($this->call_info['callee']['type'])) { + $output .= $this->call_info['callee']['type']; + } + $output .= $this->call_info['callee']['function'].'()]'; + } + + return $output; + } + + protected function getPlugin(array $plugins, array $hints) + { + if ($plugins = $this->matchPlugins($plugins, $hints)) { + $plugin = \end($plugins); + + if (!isset($this->plugin_objs[$plugin])) { + $this->plugin_objs[$plugin] = new $plugin($this); + } + + return $this->plugin_objs[$plugin]; + } + } +} diff --git a/system/ThirdParty/Kint/Utils.php b/system/ThirdParty/Kint/Utils.php new file mode 100644 index 0000000..27a2491 --- /dev/null +++ b/system/ThirdParty/Kint/Utils.php @@ -0,0 +1,240 @@ + (float) ($value / \pow(1024, $i)), + 'unit' => $unit[$i], + ); + } + + public static function isSequential(array $array) + { + return \array_keys($array) === \range(0, \count($array) - 1); + } + + public static function composerGetExtras($key = 'kint') + { + $extras = array(); + + if (0 === \strpos(KINT_DIR, 'phar://')) { + // Only run inside phar file, so skip for code coverage + return $extras; // @codeCoverageIgnore + } + + $folder = KINT_DIR.'/vendor'; + + for ($i = 0; $i < 4; ++$i) { + $installed = $folder.'/composer/installed.json'; + + if (\file_exists($installed) && \is_readable($installed)) { + $packages = \json_decode(\file_get_contents($installed), true); + + foreach ($packages as $package) { + if (isset($package['extra'][$key]) && \is_array($package['extra'][$key])) { + $extras = \array_replace($extras, $package['extra'][$key]); + } + } + + $folder = \dirname($folder); + + if (\file_exists($folder.'/composer.json') && \is_readable($folder.'/composer.json')) { + $composer = \json_decode(\file_get_contents($folder.'/composer.json'), true); + + if (isset($composer['extra'][$key]) && \is_array($composer['extra'][$key])) { + $extras = \array_replace($extras, $composer['extra'][$key]); + } + } + + break; + } + + $folder = \dirname($folder); + } + + return $extras; + } + + /** + * @codeCoverageIgnore + */ + public static function composerSkipFlags() + { + $extras = self::composerGetExtras(); + + if (!empty($extras['disable-facade']) && !\defined('KINT_SKIP_FACADE')) { + \define('KINT_SKIP_FACADE', true); + } + + if (!empty($extras['disable-helpers']) && !\defined('KINT_SKIP_HELPERS')) { + \define('KINT_SKIP_HELPERS', true); + } + } + + public static function isTrace(array $trace) + { + if (!self::isSequential($trace)) { + return false; + } + + static $bt_structure = array( + 'function' => 'string', + 'line' => 'integer', + 'file' => 'string', + 'class' => 'string', + 'object' => 'object', + 'type' => 'string', + 'args' => 'array', + ); + + $file_found = false; + + foreach ($trace as $frame) { + if (!\is_array($frame) || !isset($frame['function'])) { + return false; + } + + foreach ($frame as $key => $val) { + if (!isset($bt_structure[$key])) { + return false; + } + + if (\gettype($val) !== $bt_structure[$key]) { + return false; + } + + if ('file' === $key) { + $file_found = true; + } + } + } + + return $file_found; + } + + public static function traceFrameIsListed(array $frame, array $matches) + { + if (isset($frame['class'])) { + $called = array(\strtolower($frame['class']), \strtolower($frame['function'])); + } else { + $called = \strtolower($frame['function']); + } + + return \in_array($called, $matches, true); + } + + public static function normalizeAliases(array &$aliases) + { + static $name_regex = '[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*'; + + foreach ($aliases as $index => &$alias) { + if (\is_array($alias) && 2 === \count($alias)) { + $alias = \array_values(\array_filter($alias, 'is_string')); + + if (2 === \count($alias) && + \preg_match('/^'.$name_regex.'$/', $alias[1]) && + \preg_match('/^\\\\?('.$name_regex.'\\\\)*'.$name_regex.'$/', $alias[0]) + ) { + $alias = array( + \strtolower(\ltrim($alias[0], '\\')), + \strtolower($alias[1]), + ); + } else { + unset($aliases[$index]); + continue; + } + } elseif (\is_string($alias)) { + if (\preg_match('/^\\\\?('.$name_regex.'\\\\)*'.$name_regex.'$/', $alias)) { + $alias = \explode('\\', \strtolower($alias)); + $alias = \end($alias); + } else { + unset($aliases[$index]); + continue; + } + } else { + unset($aliases[$index]); + } + } + + $aliases = \array_values($aliases); + } + + public static function truncateString($input, $length = PHP_INT_MAX, $end = '...', $encoding = false) + { + $length = (int) $length; + $endlength = BlobObject::strlen($end); + + if ($endlength >= $length) { + throw new InvalidArgumentException('Can\'t truncate a string to '.$length.' characters if ending with string '.$endlength.' characters long'); + } + + if (BlobObject::strlen($input, $encoding) > $length) { + return BlobObject::substr($input, 0, $length - $endlength, $encoding).$end; + } + + return $input; + } + + public static function getTypeString(ReflectionType $type) + { + if ($type instanceof ReflectionNamedType) { + return $type->getName(); + } + + return (string) $type; // @codeCoverageIgnore + } +} diff --git a/system/ThirdParty/Kint/init.php b/system/ThirdParty/Kint/init.php new file mode 100644 index 0000000..952e041 --- /dev/null +++ b/system/ThirdParty/Kint/init.php @@ -0,0 +1,62 @@ += 0)); +\define('KINT_PHP70', (\version_compare(PHP_VERSION, '7.0') >= 0)); +\define('KINT_PHP72', (\version_compare(PHP_VERSION, '7.2') >= 0)); +\define('KINT_PHP73', (\version_compare(PHP_VERSION, '7.3') >= 0)); +\define('KINT_PHP74', (\version_compare(PHP_VERSION, '7.4') >= 0)); + +// Dynamic default settings +Kint::$file_link_format = \ini_get('xdebug.file_link_format'); +if (isset($_SERVER['DOCUMENT_ROOT'])) { + Kint::$app_root_dirs = array( + $_SERVER['DOCUMENT_ROOT'] => '', + \realpath($_SERVER['DOCUMENT_ROOT']) => '', + ); +} + +Utils::composerSkipFlags(); + +if ((!\defined('KINT_SKIP_FACADE') || !KINT_SKIP_FACADE) && !\class_exists('Kint')) { + \class_alias('Kint\\Kint', 'Kint'); +} + +if (!\defined('KINT_SKIP_HELPERS') || !KINT_SKIP_HELPERS) { + require_once __DIR__.'/init_helpers.php'; +} diff --git a/system/ThirdParty/Kint/init_helpers.php b/system/ThirdParty/Kint/init_helpers.php new file mode 100644 index 0000000..b961d67 --- /dev/null +++ b/system/ThirdParty/Kint/init_helpers.php @@ -0,0 +1,84 @@ +dl dl{padding:0 0 0 12px}.kint-rich dt.kint-parent>nav,.kint-rich>footer>nav{background:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMCAxNTAiPjxwYXRoIGQ9Ik02IDdoMThsLTkgMTV6bTAgMzBoMThsLTkgMTV6bTAgNDVoMThsLTktMTV6bTAgMzBoMThsLTktMTV6bTAgMTJsMTggMThtLTE4IDBsMTgtMTgiIGZpbGw9IiM1NTUiLz48cGF0aCBkPSJNNiAxMjZsMTggMThtLTE4IDBsMTgtMTgiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSIjNTU1Ii8+PC9zdmc+") no-repeat scroll 0 0/15px 75px transparent;cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}.kint-rich dt.kint-parent:hover>nav,.kint-rich>footer>nav:hover{background-position:0 25%}.kint-rich dt.kint-parent.kint-show>nav,.kint-rich>footer.kint-show>nav{background-position:0 50%}.kint-rich dt.kint-parent.kint-show:hover>nav,.kint-rich>footer.kint-show>nav:hover{background-position:0 75%}.kint-rich dt.kint-parent.kint-locked>nav{background-position:0 100%}.kint-rich dt.kint-parent+dd{display:none;border-left:1px dashed #d7d7d7}.kint-rich dt.kint-parent.kint-show+dd{display:block}.kint-rich var,.kint-rich var a{color:#06f;font-style:normal}.kint-rich dt:hover var,.kint-rich dt:hover var a{color:red}.kint-rich dfn{font-style:normal;font-family:monospace;color:#1d1e1e}.kint-rich pre{color:#1d1e1e;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #d7d7d7;background:#f8f8f8;display:block;word-break:normal}.kint-rich .kint-popup-trigger,.kint-rich .kint-access-path-trigger,.kint-rich .kint-search-trigger{background:rgba(29,30,30,0.8);border-radius:3px;height:16px;font-size:16px;margin-left:5px;font-weight:bold;width:16px;text-align:center;float:right !important;cursor:pointer;color:#f8f8f8;position:relative;overflow:hidden;line-height:17.6px}.kint-rich .kint-popup-trigger:hover,.kint-rich .kint-access-path-trigger:hover,.kint-rich .kint-search-trigger:hover{color:#1d1e1e;background:#f8f8f8}.kint-rich dt.kint-parent>.kint-popup-trigger{line-height:19.2px}.kint-rich .kint-search-trigger{font-size:20px}.kint-rich input.kint-search{display:none;border:1px solid #d7d7d7;border-top-width:0;border-bottom-width:0;padding:4px;float:right !important;margin:-4px 0;color:#1d1e1e;background:#f8f8f8;height:24px;width:160px;position:relative;z-index:100}.kint-rich input.kint-search.kint-show{display:block}.kint-rich .kint-search-root ul.kint-tabs>li:not(.kint-search-match){background:#f8f8f8;opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match){opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match)>dt{background:#f8f8f8}.kint-rich .kint-search-root dl:not(.kint-search-match) dl,.kint-rich .kint-search-root dl:not(.kint-search-match) ul.kint-tabs>li:not(.kint-search-match){opacity:1}.kint-rich div.access-path{background:#f8f8f8;display:none;margin-top:5px;padding:4px;white-space:pre}.kint-rich div.access-path.kint-show{display:block}.kint-rich footer{padding:0 3px 3px;font-size:9px;background:transparent}.kint-rich footer>.kint-popup-trigger{background:transparent;color:#1d1e1e}.kint-rich footer nav{height:10px;width:10px;background-size:10px 50px}.kint-rich footer>ol{display:none;margin-left:32px}.kint-rich footer.kint-show>ol{display:block}.kint-rich a{color:#1d1e1e;text-shadow:none;text-decoration:underline}.kint-rich a:hover{color:#1d1e1e;border-bottom:1px dotted #1d1e1e}.kint-rich ul{list-style:none;padding-left:12px}.kint-rich ul:not(.kint-tabs) li{border-left:1px dashed #d7d7d7}.kint-rich ul:not(.kint-tabs) li>dl{border-left:none}.kint-rich ul.kint-tabs{margin:0 0 0 12px;padding-left:0;background:#f8f8f8;border:1px solid #d7d7d7;border-top:0}.kint-rich ul.kint-tabs>li{background:#f8f8f8;border:1px solid #d7d7d7;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}.kint-rich ul.kint-tabs>li:hover,.kint-rich ul.kint-tabs>li.kint-active-tab:hover{border-color:#aaa;color:red}.kint-rich ul.kint-tabs>li.kint-active-tab{background:#f8f8f8;border-top:0;margin-top:-1px;height:27px;line-height:24px}.kint-rich ul.kint-tabs>li:not(.kint-active-tab){line-height:20px}.kint-rich ul.kint-tabs li+li{margin-left:0}.kint-rich ul:not(.kint-tabs)>li:not(:first-child){display:none}.kint-rich dt:hover+dd>ul>li.kint-active-tab{border-color:#aaa;color:red}.kint-rich dt>.kint-color-preview{width:16px;height:16px;display:inline-block;vertical-align:middle;margin-left:10px;border:1px solid #d7d7d7;background-color:#ccc;background-image:url('data:image/svg+xml;utf8,');background-size:100%}.kint-rich dt>.kint-color-preview:hover{border-color:#aaa}.kint-rich dt>.kint-color-preview>div{width:100%;height:100%}.kint-rich table{border-collapse:collapse;empty-cells:show;border-spacing:0}.kint-rich table *{font-size:12px}.kint-rich table dt{background:none;padding:2px}.kint-rich table dt .kint-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kint-rich table td,.kint-rich table th{border:1px solid #d7d7d7;padding:2px;vertical-align:center}.kint-rich table th{cursor:alias}.kint-rich table td:first-child,.kint-rich table th{font-weight:bold;background:#f8f8f8;color:#1d1e1e}.kint-rich table td{background:#f8f8f8;white-space:pre}.kint-rich table td>dl{padding:0}.kint-rich table pre{border-top:0;border-right:0}.kint-rich table thead th:first-child{background:none;border:0}.kint-rich table tr:hover>td{box-shadow:0 0 1px 0 #aaa inset}.kint-rich table tr:hover var{color:red}.kint-rich table ul.kint-tabs li.kint-active-tab{height:20px;line-height:17px}.kint-rich pre.kint-source{margin-left:-1px}.kint-rich pre.kint-source[data-kint-filename]:before{display:block;content:attr(data-kint-filename);margin-bottom:4px;padding-bottom:4px;border-bottom:1px solid #f8f8f8}.kint-rich pre.kint-source>div:before{display:inline-block;content:counter(kint-l);counter-increment:kint-l;border-right:1px solid #aaa;padding-right:8px;margin-right:8px}.kint-rich pre.kint-source>div.kint-highlight{background:#f8f8f8}.kint-rich .kint-microtime-lap{text-shadow:-1px 0 #aaa,0 1px #aaa,1px 0 #aaa,0 -1px #aaa;color:#f8f8f8;font-weight:bold}.kint-rich .kint-focused{box-shadow:0 0 3px 2px red}.kint-rich dt{font-weight:normal}.kint-rich dt.kint-parent{margin-top:4px}.kint-rich dl dl{margin-top:4px;padding-left:25px;border-left:none}.kint-rich>dl>dt{background:#f8f8f8}.kint-rich ul{margin:0;padding-left:0}.kint-rich ul:not(.kint-tabs)>li{border-left:0}.kint-rich ul.kint-tabs{background:#f8f8f8;border:1px solid #d7d7d7;border-width:0 1px 1px 1px;padding:4px 0 0 12px;margin-left:-1px;margin-top:-1px}.kint-rich ul.kint-tabs li,.kint-rich ul.kint-tabs li+li{margin:0 0 0 4px}.kint-rich ul.kint-tabs li{border-bottom-width:0;height:25px}.kint-rich ul.kint-tabs li:first-child{margin-left:0}.kint-rich ul.kint-tabs li.kint-active-tab{border-top:1px solid #d7d7d7;background:#fff;font-weight:bold;padding-top:0;border-bottom:1px solid #fff !important;margin-bottom:-1px}.kint-rich ul.kint-tabs li.kint-active-tab:hover{border-bottom:1px solid #fff}.kint-rich ul>li>pre{border:1px solid #d7d7d7}.kint-rich dt:hover+dd>ul{border-color:#aaa}.kint-rich pre{background:#fff;margin-top:4px;margin-left:25px}.kint-rich .kint-source{margin-left:-1px}.kint-rich .kint-source .kint-highlight{background:#cfc}.kint-rich .kint-parent.kint-show>.kint-search{border-bottom-width:1px}.kint-rich table td{background:#fff}.kint-rich table td>dl{padding:0;margin:0}.kint-rich table td>dl>dt.kint-parent{margin:0}.kint-rich table td:first-child,.kint-rich table td,.kint-rich table th{padding:2px 4px}.kint-rich table dd,.kint-rich table dt{background:#fff}.kint-rich table tr:hover>td{box-shadow:none;background:#cfc} diff --git a/system/ThirdParty/Kint/resources/compiled/microtime.js b/system/ThirdParty/Kint/resources/compiled/microtime.js new file mode 100644 index 0000000..20e3445 --- /dev/null +++ b/system/ThirdParty/Kint/resources/compiled/microtime.js @@ -0,0 +1 @@ +void 0===window.kintMicrotimeInitialized&&(window.kintMicrotimeInitialized=1,window.addEventListener("load",function(){"use strict";var c={},i=Array.prototype.slice.call(document.querySelectorAll("[data-kint-microtime-group]"),0);i.forEach(function(i){if(i.querySelector(".kint-microtime-lap")){var t=i.getAttribute("data-kint-microtime-group"),e=parseFloat(i.querySelector(".kint-microtime-lap").innerHTML),r=parseFloat(i.querySelector(".kint-microtime-avg").innerHTML);void 0===c[t]&&(c[t]={}),(void 0===c[t].min||c[t].min>e)&&(c[t].min=e),(void 0===c[t].max||c[t].maxdl dl{padding:0 0 0 12px}.kint-rich dt.kint-parent>nav,.kint-rich>footer>nav{background:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMCAxNTAiPjxnIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0iI0ZGRiI+PHBhdGggZD0iTTEgMWgyOHYyOEgxem01IDE0aDE4bS05IDlWNk0xIDYxaDI4djI4SDF6bTUgMTRoMTgiIHN0cm9rZT0iIzM3OSIvPjxwYXRoIGQ9Ik0xIDMxaDI4djI4SDF6bTUgMTRoMThtLTkgOVYzNk0xIDkxaDI4djI4SDF6bTUgMTRoMTgiIHN0cm9rZT0iIzVBMyIvPjxwYXRoIGQ9Ik0xIDEyMWgyOHYyOEgxem01IDVsMTggMThtLTE4IDBsMTgtMTgiIHN0cm9rZT0iI0NDQyIvPjwvZz48L3N2Zz4=") no-repeat scroll 0 0/15px 75px transparent;cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}.kint-rich dt.kint-parent:hover>nav,.kint-rich>footer>nav:hover{background-position:0 25%}.kint-rich dt.kint-parent.kint-show>nav,.kint-rich>footer.kint-show>nav{background-position:0 50%}.kint-rich dt.kint-parent.kint-show:hover>nav,.kint-rich>footer.kint-show>nav:hover{background-position:0 75%}.kint-rich dt.kint-parent.kint-locked>nav{background-position:0 100%}.kint-rich dt.kint-parent+dd{display:none;border-left:1px dashed #b6cedb}.kint-rich dt.kint-parent.kint-show+dd{display:block}.kint-rich var,.kint-rich var a{color:#0092db;font-style:normal}.kint-rich dt:hover var,.kint-rich dt:hover var a{color:#5cb730}.kint-rich dfn{font-style:normal;font-family:monospace;color:#1d1e1e}.kint-rich pre{color:#1d1e1e;margin:0 0 0 12px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #b6cedb;background:#e0eaef;display:block;word-break:normal}.kint-rich .kint-popup-trigger,.kint-rich .kint-access-path-trigger,.kint-rich .kint-search-trigger{background:rgba(29,30,30,0.8);border-radius:3px;height:16px;font-size:16px;margin-left:5px;font-weight:bold;width:16px;text-align:center;float:right !important;cursor:pointer;color:#e0eaef;position:relative;overflow:hidden;line-height:17.6px}.kint-rich .kint-popup-trigger:hover,.kint-rich .kint-access-path-trigger:hover,.kint-rich .kint-search-trigger:hover{color:#1d1e1e;background:#e0eaef}.kint-rich dt.kint-parent>.kint-popup-trigger{line-height:19.2px}.kint-rich .kint-search-trigger{font-size:20px}.kint-rich input.kint-search{display:none;border:1px solid #b6cedb;border-top-width:0;border-bottom-width:0;padding:4px;float:right !important;margin:-4px 0;color:#1d1e1e;background:#c1d4df;height:24px;width:160px;position:relative;z-index:100}.kint-rich input.kint-search.kint-show{display:block}.kint-rich .kint-search-root ul.kint-tabs>li:not(.kint-search-match){background:#d0d0d0;opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match){opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match)>dt{background:#e8e8e8}.kint-rich .kint-search-root dl:not(.kint-search-match) dl,.kint-rich .kint-search-root dl:not(.kint-search-match) ul.kint-tabs>li:not(.kint-search-match){opacity:1}.kint-rich div.access-path{background:#c1d4df;display:none;margin-top:5px;padding:4px;white-space:pre}.kint-rich div.access-path.kint-show{display:block}.kint-rich footer{padding:0 3px 3px;font-size:9px;background:transparent}.kint-rich footer>.kint-popup-trigger{background:transparent;color:#1d1e1e}.kint-rich footer nav{height:10px;width:10px;background-size:10px 50px}.kint-rich footer>ol{display:none;margin-left:32px}.kint-rich footer.kint-show>ol{display:block}.kint-rich a{color:#1d1e1e;text-shadow:none;text-decoration:underline}.kint-rich a:hover{color:#1d1e1e;border-bottom:1px dotted #1d1e1e}.kint-rich ul{list-style:none;padding-left:12px}.kint-rich ul:not(.kint-tabs) li{border-left:1px dashed #b6cedb}.kint-rich ul:not(.kint-tabs) li>dl{border-left:none}.kint-rich ul.kint-tabs{margin:0 0 0 12px;padding-left:0;background:#e0eaef;border:1px solid #b6cedb;border-top:0}.kint-rich ul.kint-tabs>li{background:#c1d4df;border:1px solid #b6cedb;cursor:pointer;display:inline-block;height:24px;margin:2px;padding:0 12px;vertical-align:top}.kint-rich ul.kint-tabs>li:hover,.kint-rich ul.kint-tabs>li.kint-active-tab:hover{border-color:#0092db;color:#5cb730}.kint-rich ul.kint-tabs>li.kint-active-tab{background:#e0eaef;border-top:0;margin-top:-1px;height:27px;line-height:24px}.kint-rich ul.kint-tabs>li:not(.kint-active-tab){line-height:20px}.kint-rich ul.kint-tabs li+li{margin-left:0}.kint-rich ul:not(.kint-tabs)>li:not(:first-child){display:none}.kint-rich dt:hover+dd>ul>li.kint-active-tab{border-color:#0092db;color:#5cb730}.kint-rich dt>.kint-color-preview{width:16px;height:16px;display:inline-block;vertical-align:middle;margin-left:10px;border:1px solid #b6cedb;background-color:#ccc;background-image:url('data:image/svg+xml;utf8,');background-size:100%}.kint-rich dt>.kint-color-preview:hover{border-color:#0092db}.kint-rich dt>.kint-color-preview>div{width:100%;height:100%}.kint-rich table{border-collapse:collapse;empty-cells:show;border-spacing:0}.kint-rich table *{font-size:12px}.kint-rich table dt{background:none;padding:2px}.kint-rich table dt .kint-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kint-rich table td,.kint-rich table th{border:1px solid #b6cedb;padding:2px;vertical-align:center}.kint-rich table th{cursor:alias}.kint-rich table td:first-child,.kint-rich table th{font-weight:bold;background:#c1d4df;color:#1d1e1e}.kint-rich table td{background:#e0eaef;white-space:pre}.kint-rich table td>dl{padding:0}.kint-rich table pre{border-top:0;border-right:0}.kint-rich table thead th:first-child{background:none;border:0}.kint-rich table tr:hover>td{box-shadow:0 0 1px 0 #0092db inset}.kint-rich table tr:hover var{color:#5cb730}.kint-rich table ul.kint-tabs li.kint-active-tab{height:20px;line-height:17px}.kint-rich pre.kint-source{margin-left:-1px}.kint-rich pre.kint-source[data-kint-filename]:before{display:block;content:attr(data-kint-filename);margin-bottom:4px;padding-bottom:4px;border-bottom:1px solid #c1d4df}.kint-rich pre.kint-source>div:before{display:inline-block;content:counter(kint-l);counter-increment:kint-l;border-right:1px solid #0092db;padding-right:8px;margin-right:8px}.kint-rich pre.kint-source>div.kint-highlight{background:#c1d4df}.kint-rich .kint-microtime-lap{text-shadow:-1px 0 #0092db,0 1px #0092db,1px 0 #0092db,0 -1px #0092db;color:#e0eaef;font-weight:bold}.kint-rich>dl>dt{background:linear-gradient(to bottom, #e3ecf0 0, #c0d4df 100%)}.kint-rich ul.kint-tabs{background:linear-gradient(to bottom, #9dbed0 0px, #b2ccda 100%)}.kint-rich>dl:not(.kint-trace)>dd>ul.kint-tabs li{background:#e0eaef}.kint-rich>dl:not(.kint-trace)>dd>ul.kint-tabs li.kint-active-tab{background:#c1d4df}.kint-rich>dl.kint-trace>dt{background:linear-gradient(to bottom, #c0d4df 0px, #e3ecf0 100%)}.kint-rich .kint-source .kint-highlight{background:#f0eb96} diff --git a/system/ThirdParty/Kint/resources/compiled/plain.css b/system/ThirdParty/Kint/resources/compiled/plain.css new file mode 100644 index 0000000..ba1eba0 --- /dev/null +++ b/system/ThirdParty/Kint/resources/compiled/plain.css @@ -0,0 +1 @@ +.kint-plain{background:rgba(255,255,255,0.9);white-space:pre;display:block;font-family:monospace;color:#222}.kint-plain i{color:#d00;font-style:normal}.kint-plain u{color:#030;text-decoration:none;font-weight:bold}.kint-plain .kint-microtime-lap{font-weight:bold;text-shadow:1px 0 #fff, 0 1px #fff, -1px 0 #fff, 0 -1px #fff} diff --git a/system/ThirdParty/Kint/resources/compiled/plain.js b/system/ThirdParty/Kint/resources/compiled/plain.js new file mode 100644 index 0000000..9791fc9 --- /dev/null +++ b/system/ThirdParty/Kint/resources/compiled/plain.js @@ -0,0 +1 @@ +void 0===window.kintPlain&&(window.kintPlain=function(){"use strict";var i={initLoad:function(){i.style=window.kintShared.dedupe("style.kint-plain-style",i.style),i.script=window.kintShared.dedupe("script.kint-plain-script",i.script)},style:null,script:null};return i}()),window.kintShared.runOnce(window.kintPlain.initLoad); diff --git a/system/ThirdParty/Kint/resources/compiled/rich.js b/system/ThirdParty/Kint/resources/compiled/rich.js new file mode 100644 index 0000000..18fb072 --- /dev/null +++ b/system/ThirdParty/Kint/resources/compiled/rich.js @@ -0,0 +1 @@ +void 0===window.kintRich&&(window.kintRich=function(){"use strict";var n={selectText:function(e){var t=window.getSelection(),a=document.createRange();a.selectNodeContents(e),t.removeAllRanges(),t.addRange(a)},each:function(e,t){Array.prototype.slice.call(document.querySelectorAll(e),0).forEach(t)},hasClass:function(e,t){return!!e.classList&&(void 0===t&&(t="kint-show"),e.classList.contains(t))},addClass:function(e,t){void 0===t&&(t="kint-show"),e.classList.add(t)},removeClass:function(e,t){return void 0===t&&(t="kint-show"),e.classList.remove(t),e},toggle:function(e,t){var a=n.getChildren(e);a&&(void 0===t&&(t=n.hasClass(e)),t?n.removeClass(e):n.addClass(e),1===a.childNodes.length&&(a=a.childNodes[0].childNodes[0])&&n.hasClass(a,"kint-parent")&&n.toggle(a,t))},toggleChildren:function(e,t){var a=n.getChildren(e);if(a){var r=a.getElementsByClassName("kint-parent"),o=r.length;for(void 0===t&&(t=!n.hasClass(e));o--;)n.toggle(r[o],t)}},toggleAll:function(e){for(var t=document.getElementsByClassName("kint-parent"),a=t.length,r=!n.hasClass(e.parentNode);a--;)n.toggle(t[a],r)},switchTab:function(e){var t,a=e.previousSibling,r=0;for(n.removeClass(e.parentNode.getElementsByClassName("kint-active-tab")[0],"kint-active-tab"),n.addClass(e,"kint-active-tab");a;)1===a.nodeType&&r++,a=a.previousSibling;t=e.parentNode.nextSibling.childNodes;for(var o=0;o"},openInNewWindow:function(e){var t=window.open();t&&(t.document.open(),t.document.write(n.mktag("html")+n.mktag("head")+n.mktag("title")+"Kint ("+(new Date).toISOString()+")"+n.mktag("/title")+n.mktag('meta charset="utf-8"')+document.getElementsByClassName("kint-rich-script")[0].outerHTML+document.getElementsByClassName("kint-rich-style")[0].outerHTML+n.mktag("/head")+n.mktag("body")+'
'+e.parentNode.outerHTML+"
"+n.mktag("/body")),t.document.close())},sortTable:function(e,a){var t=e.tBodies[0];[].slice.call(e.tBodies[0].rows).sort(function(e,t){if(e=e.cells[a].textContent.trim().toLocaleLowerCase(),t=t.cells[a].textContent.trim().toLocaleLowerCase(),isNaN(e)||isNaN(t)){if(isNaN(e)&&!isNaN(t))return 1;if(isNaN(t)&&!isNaN(e))return-1}else e=parseFloat(e),t=parseFloat(t);return eli:not(.kint-active-tab)",function(e){0===e.offsetWidth&&0===e.offsetHeight||n.keyboardNav.targets.push(e)})},sync:function(e){var t=document.querySelector(".kint-focused");if(t&&n.removeClass(t,"kint-focused"),n.keyboardNav.active){var a=n.keyboardNav.targets[n.keyboardNav.target];n.addClass(a,"kint-focused"),e||n.keyboardNav.scroll(a)}},scroll:function(e){var t=function(e){return e.offsetTop+(e.offsetParent?t(e.offsetParent):0)},a=t(e);if(n.folder){var r=n.folder.querySelector("dd.kint-folder");r.scrollTo(0,a-r.clientHeight/2)}else window.scrollTo(0,a-window.innerHeight/2)},moveCursor:function(e){for(n.keyboardNav.target+=e;n.keyboardNav.target<0;)n.keyboardNav.target+=n.keyboardNav.targets.length;for(;n.keyboardNav.target>=n.keyboardNav.targets.length;)n.keyboardNav.target-=n.keyboardNav.targets.length;n.keyboardNav.sync()},setCursor:function(e){n.keyboardNav.fetchTargets();for(var t=0;tdl dl{padding:0 0 0 15px}.kint-rich dt.kint-parent>nav,.kint-rich>footer>nav{background:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMzAgMTUwIj48ZGVmcz48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNCAzYTI0IDMyIDAgMCAxIDAgMjQgNDAgMjAtMTAgMCAxIDIzLTEyQTQwIDIwIDEwIDAgMSA0IDN6IiBpZD0iYSIvPjwvZGVmcz48ZyBmaWxsPSIjOTNhMWExIiBzdHJva2U9IiM5M2ExYTEiPjx1c2UgeGxpbms6aHJlZj0iI2EiLz48dXNlIHhsaW5rOmhyZWY9IiNhIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAtMTUgNDUpIi8+PC9nPjxnIGZpbGw9IiM1ODZlNzUiIHN0cm9rZT0iIzU4NmU3NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAzMCkiPjx1c2UgeGxpbms6aHJlZj0iI2EiLz48dXNlIHhsaW5rOmhyZWY9IiNhIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAtMTUgNDUpIi8+PC9nPjxwYXRoIGQ9Ik02IDEyNmwxOCAxOG0tMTggMGwxOC0xOCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiM1ODZlNzUiLz48L3N2Zz4=") no-repeat scroll 0 0/15px 75px transparent;cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}.kint-rich dt.kint-parent:hover>nav,.kint-rich>footer>nav:hover{background-position:0 25%}.kint-rich dt.kint-parent.kint-show>nav,.kint-rich>footer.kint-show>nav{background-position:0 50%}.kint-rich dt.kint-parent.kint-show:hover>nav,.kint-rich>footer.kint-show>nav:hover{background-position:0 75%}.kint-rich dt.kint-parent.kint-locked>nav{background-position:0 100%}.kint-rich dt.kint-parent+dd{display:none;border-left:1px dashed #586e75}.kint-rich dt.kint-parent.kint-show+dd{display:block}.kint-rich var,.kint-rich var a{color:#268bd2;font-style:normal}.kint-rich dt:hover var,.kint-rich dt:hover var a{color:#2aa198}.kint-rich dfn{font-style:normal;font-family:monospace;color:#93a1a1}.kint-rich pre{color:#839496;margin:0 0 0 15px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #586e75;background:#002b36;display:block;word-break:normal}.kint-rich .kint-popup-trigger,.kint-rich .kint-access-path-trigger,.kint-rich .kint-search-trigger{background:rgba(131,148,150,0.8);border-radius:3px;height:16px;font-size:16px;margin-left:5px;font-weight:bold;width:16px;text-align:center;float:right !important;cursor:pointer;color:#002b36;position:relative;overflow:hidden;line-height:17.6px}.kint-rich .kint-popup-trigger:hover,.kint-rich .kint-access-path-trigger:hover,.kint-rich .kint-search-trigger:hover{color:#839496;background:#002b36}.kint-rich dt.kint-parent>.kint-popup-trigger{line-height:19.2px}.kint-rich .kint-search-trigger{font-size:20px}.kint-rich input.kint-search{display:none;border:1px solid #586e75;border-top-width:0;border-bottom-width:0;padding:5px;float:right !important;margin:-5px 0;color:#93a1a1;background:#073642;height:26px;width:160px;position:relative;z-index:100}.kint-rich input.kint-search.kint-show{display:block}.kint-rich .kint-search-root ul.kint-tabs>li:not(.kint-search-match){background:#252525;opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match){opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match)>dt{background:#1b1b1b}.kint-rich .kint-search-root dl:not(.kint-search-match) dl,.kint-rich .kint-search-root dl:not(.kint-search-match) ul.kint-tabs>li:not(.kint-search-match){opacity:1}.kint-rich div.access-path{background:#073642;display:none;margin-top:5px;padding:4px;white-space:pre}.kint-rich div.access-path.kint-show{display:block}.kint-rich footer{padding:0 3px 3px;font-size:9px;background:transparent}.kint-rich footer>.kint-popup-trigger{background:transparent;color:#839496}.kint-rich footer nav{height:10px;width:10px;background-size:10px 50px}.kint-rich footer>ol{display:none;margin-left:32px}.kint-rich footer.kint-show>ol{display:block}.kint-rich a{color:#839496;text-shadow:none;text-decoration:underline}.kint-rich a:hover{color:#93a1a1;border-bottom:1px dotted #93a1a1}.kint-rich ul{list-style:none;padding-left:15px}.kint-rich ul:not(.kint-tabs) li{border-left:1px dashed #586e75}.kint-rich ul:not(.kint-tabs) li>dl{border-left:none}.kint-rich ul.kint-tabs{margin:0 0 0 15px;padding-left:0;background:#002b36;border:1px solid #586e75;border-top:0}.kint-rich ul.kint-tabs>li{background:#073642;border:1px solid #586e75;cursor:pointer;display:inline-block;height:30px;margin:3px;padding:0 15px;vertical-align:top}.kint-rich ul.kint-tabs>li:hover,.kint-rich ul.kint-tabs>li.kint-active-tab:hover{border-color:#268bd2;color:#2aa198}.kint-rich ul.kint-tabs>li.kint-active-tab{background:#002b36;border-top:0;margin-top:-1px;height:27px;line-height:24px}.kint-rich ul.kint-tabs>li:not(.kint-active-tab){line-height:25px}.kint-rich ul.kint-tabs li+li{margin-left:0}.kint-rich ul:not(.kint-tabs)>li:not(:first-child){display:none}.kint-rich dt:hover+dd>ul>li.kint-active-tab{border-color:#268bd2;color:#2aa198}.kint-rich dt>.kint-color-preview{width:16px;height:16px;display:inline-block;vertical-align:middle;margin-left:10px;border:1px solid #586e75;background-color:#ccc;background-image:url('data:image/svg+xml;utf8,');background-size:100%}.kint-rich dt>.kint-color-preview:hover{border-color:#268bd2}.kint-rich dt>.kint-color-preview>div{width:100%;height:100%}.kint-rich table{border-collapse:collapse;empty-cells:show;border-spacing:0}.kint-rich table *{font-size:12px}.kint-rich table dt{background:none;padding:2.5px}.kint-rich table dt .kint-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kint-rich table td,.kint-rich table th{border:1px solid #586e75;padding:2.5px;vertical-align:center}.kint-rich table th{cursor:alias}.kint-rich table td:first-child,.kint-rich table th{font-weight:bold;background:#073642;color:#93a1a1}.kint-rich table td{background:#002b36;white-space:pre}.kint-rich table td>dl{padding:0}.kint-rich table pre{border-top:0;border-right:0}.kint-rich table thead th:first-child{background:none;border:0}.kint-rich table tr:hover>td{box-shadow:0 0 1px 0 #268bd2 inset}.kint-rich table tr:hover var{color:#2aa198}.kint-rich table ul.kint-tabs li.kint-active-tab{height:20px;line-height:17px}.kint-rich pre.kint-source{margin-left:-1px}.kint-rich pre.kint-source[data-kint-filename]:before{display:block;content:attr(data-kint-filename);margin-bottom:5px;padding-bottom:5px;border-bottom:1px solid #073642}.kint-rich pre.kint-source>div:before{display:inline-block;content:counter(kint-l);counter-increment:kint-l;border-right:1px solid #268bd2;padding-right:10px;margin-right:10px}.kint-rich pre.kint-source>div.kint-highlight{background:#073642}.kint-rich .kint-microtime-lap{text-shadow:-1px 0 #268bd2,0 1px #268bd2,1px 0 #268bd2,0 -1px #268bd2;color:#002b36;font-weight:bold}body{background:#073642;color:#fff}.kint-rich{box-shadow:0 0 5px 3px #073642}.kint-rich .kint-focused{box-shadow:0 0 3px 2px #859900 inset;border-radius:7px}.kint-rich>dl>dt,.kint-rich ul.kint-tabs{box-shadow:4px 0 2px -3px #268bd2 inset}.kint-rich ul.kint-tabs li.kint-active-tab{padding-top:7px;height:34px}.kint-rich footer li{color:#ddd} diff --git a/system/ThirdParty/Kint/resources/compiled/solarized.css b/system/ThirdParty/Kint/resources/compiled/solarized.css new file mode 100644 index 0000000..db5da0d --- /dev/null +++ b/system/ThirdParty/Kint/resources/compiled/solarized.css @@ -0,0 +1 @@ +.kint-rich{font-size:13px;overflow-x:auto;white-space:nowrap;background:rgba(255,255,255,0.9)}.kint-rich.kint-folder{position:fixed;bottom:0;left:0;right:0;z-index:999999;width:100%;margin:0;display:none}.kint-rich.kint-folder.kint-show{display:block}.kint-rich.kint-folder dd.kint-folder{max-height:calc(100vh - 100px);padding-right:10px;overflow-y:scroll}.kint-rich::selection,.kint-rich::-moz-selection,.kint-rich::-webkit-selection{background:#268bd2;color:#657b83}.kint-rich .kint-focused{box-shadow:0 0 3px 2px #2aa198}.kint-rich,.kint-rich::before,.kint-rich::after,.kint-rich *,.kint-rich *::before,.kint-rich *::after{box-sizing:border-box;border-radius:0;color:#657b83;float:none !important;font-family:Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;line-height:15px;margin:0;padding:0;text-align:left}.kint-rich{margin:10px 0}.kint-rich dt,.kint-rich dl{width:auto}.kint-rich dt,.kint-rich div.access-path{background:#fdf6e3;border:1px solid #93a1a1;color:#657b83;display:block;font-weight:bold;list-style:none outside none;overflow:auto;padding:5px}.kint-rich dt:hover,.kint-rich div.access-path:hover{border-color:#268bd2}.kint-rich>dl dl{padding:0 0 0 15px}.kint-rich dt.kint-parent>nav,.kint-rich>footer>nav{background:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMzAgMTUwIj48ZGVmcz48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNCAzYTI0IDMyIDAgMCAxIDAgMjQgNDAgMjAtMTAgMCAxIDIzLTEyQTQwIDIwIDEwIDAgMSA0IDN6IiBpZD0iYSIvPjwvZGVmcz48ZyBmaWxsPSIjOTNhMWExIiBzdHJva2U9IiM5M2ExYTEiPjx1c2UgeGxpbms6aHJlZj0iI2EiLz48dXNlIHhsaW5rOmhyZWY9IiNhIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAtMTUgNDUpIi8+PC9nPjxnIGZpbGw9IiM1ODZlNzUiIHN0cm9rZT0iIzU4NmU3NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAzMCkiPjx1c2UgeGxpbms6aHJlZj0iI2EiLz48dXNlIHhsaW5rOmhyZWY9IiNhIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAtMTUgNDUpIi8+PC9nPjxwYXRoIGQ9Ik02IDEyNmwxOCAxOG0tMTggMGwxOC0xOCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiM1ODZlNzUiLz48L3N2Zz4=") no-repeat scroll 0 0/15px 75px transparent;cursor:pointer;display:inline-block;height:15px;width:15px;margin-right:3px;vertical-align:middle}.kint-rich dt.kint-parent:hover>nav,.kint-rich>footer>nav:hover{background-position:0 25%}.kint-rich dt.kint-parent.kint-show>nav,.kint-rich>footer.kint-show>nav{background-position:0 50%}.kint-rich dt.kint-parent.kint-show:hover>nav,.kint-rich>footer.kint-show>nav:hover{background-position:0 75%}.kint-rich dt.kint-parent.kint-locked>nav{background-position:0 100%}.kint-rich dt.kint-parent+dd{display:none;border-left:1px dashed #93a1a1}.kint-rich dt.kint-parent.kint-show+dd{display:block}.kint-rich var,.kint-rich var a{color:#268bd2;font-style:normal}.kint-rich dt:hover var,.kint-rich dt:hover var a{color:#2aa198}.kint-rich dfn{font-style:normal;font-family:monospace;color:#586e75}.kint-rich pre{color:#657b83;margin:0 0 0 15px;padding:5px;overflow-y:hidden;border-top:0;border:1px solid #93a1a1;background:#fdf6e3;display:block;word-break:normal}.kint-rich .kint-popup-trigger,.kint-rich .kint-access-path-trigger,.kint-rich .kint-search-trigger{background:rgba(101,123,131,0.8);border-radius:3px;height:16px;font-size:16px;margin-left:5px;font-weight:bold;width:16px;text-align:center;float:right !important;cursor:pointer;color:#fdf6e3;position:relative;overflow:hidden;line-height:17.6px}.kint-rich .kint-popup-trigger:hover,.kint-rich .kint-access-path-trigger:hover,.kint-rich .kint-search-trigger:hover{color:#657b83;background:#fdf6e3}.kint-rich dt.kint-parent>.kint-popup-trigger{line-height:19.2px}.kint-rich .kint-search-trigger{font-size:20px}.kint-rich input.kint-search{display:none;border:1px solid #93a1a1;border-top-width:0;border-bottom-width:0;padding:5px;float:right !important;margin:-5px 0;color:#586e75;background:#eee8d5;height:26px;width:160px;position:relative;z-index:100}.kint-rich input.kint-search.kint-show{display:block}.kint-rich .kint-search-root ul.kint-tabs>li:not(.kint-search-match){background:#e2e2e2;opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match){opacity:0.5}.kint-rich .kint-search-root dl:not(.kint-search-match)>dt{background:#f0f0f0}.kint-rich .kint-search-root dl:not(.kint-search-match) dl,.kint-rich .kint-search-root dl:not(.kint-search-match) ul.kint-tabs>li:not(.kint-search-match){opacity:1}.kint-rich div.access-path{background:#eee8d5;display:none;margin-top:5px;padding:4px;white-space:pre}.kint-rich div.access-path.kint-show{display:block}.kint-rich footer{padding:0 3px 3px;font-size:9px;background:transparent}.kint-rich footer>.kint-popup-trigger{background:transparent;color:#657b83}.kint-rich footer nav{height:10px;width:10px;background-size:10px 50px}.kint-rich footer>ol{display:none;margin-left:32px}.kint-rich footer.kint-show>ol{display:block}.kint-rich a{color:#657b83;text-shadow:none;text-decoration:underline}.kint-rich a:hover{color:#586e75;border-bottom:1px dotted #586e75}.kint-rich ul{list-style:none;padding-left:15px}.kint-rich ul:not(.kint-tabs) li{border-left:1px dashed #93a1a1}.kint-rich ul:not(.kint-tabs) li>dl{border-left:none}.kint-rich ul.kint-tabs{margin:0 0 0 15px;padding-left:0;background:#fdf6e3;border:1px solid #93a1a1;border-top:0}.kint-rich ul.kint-tabs>li{background:#eee8d5;border:1px solid #93a1a1;cursor:pointer;display:inline-block;height:30px;margin:3px;padding:0 15px;vertical-align:top}.kint-rich ul.kint-tabs>li:hover,.kint-rich ul.kint-tabs>li.kint-active-tab:hover{border-color:#268bd2;color:#2aa198}.kint-rich ul.kint-tabs>li.kint-active-tab{background:#fdf6e3;border-top:0;margin-top:-1px;height:27px;line-height:24px}.kint-rich ul.kint-tabs>li:not(.kint-active-tab){line-height:25px}.kint-rich ul.kint-tabs li+li{margin-left:0}.kint-rich ul:not(.kint-tabs)>li:not(:first-child){display:none}.kint-rich dt:hover+dd>ul>li.kint-active-tab{border-color:#268bd2;color:#2aa198}.kint-rich dt>.kint-color-preview{width:16px;height:16px;display:inline-block;vertical-align:middle;margin-left:10px;border:1px solid #93a1a1;background-color:#ccc;background-image:url('data:image/svg+xml;utf8,');background-size:100%}.kint-rich dt>.kint-color-preview:hover{border-color:#268bd2}.kint-rich dt>.kint-color-preview>div{width:100%;height:100%}.kint-rich table{border-collapse:collapse;empty-cells:show;border-spacing:0}.kint-rich table *{font-size:12px}.kint-rich table dt{background:none;padding:2.5px}.kint-rich table dt .kint-parent{min-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kint-rich table td,.kint-rich table th{border:1px solid #93a1a1;padding:2.5px;vertical-align:center}.kint-rich table th{cursor:alias}.kint-rich table td:first-child,.kint-rich table th{font-weight:bold;background:#eee8d5;color:#586e75}.kint-rich table td{background:#fdf6e3;white-space:pre}.kint-rich table td>dl{padding:0}.kint-rich table pre{border-top:0;border-right:0}.kint-rich table thead th:first-child{background:none;border:0}.kint-rich table tr:hover>td{box-shadow:0 0 1px 0 #268bd2 inset}.kint-rich table tr:hover var{color:#2aa198}.kint-rich table ul.kint-tabs li.kint-active-tab{height:20px;line-height:17px}.kint-rich pre.kint-source{margin-left:-1px}.kint-rich pre.kint-source[data-kint-filename]:before{display:block;content:attr(data-kint-filename);margin-bottom:5px;padding-bottom:5px;border-bottom:1px solid #eee8d5}.kint-rich pre.kint-source>div:before{display:inline-block;content:counter(kint-l);counter-increment:kint-l;border-right:1px solid #268bd2;padding-right:10px;margin-right:10px}.kint-rich pre.kint-source>div.kint-highlight{background:#eee8d5}.kint-rich .kint-microtime-lap{text-shadow:-1px 0 #268bd2,0 1px #268bd2,1px 0 #268bd2,0 -1px #268bd2;color:#fdf6e3;font-weight:bold}.kint-rich .kint-focused{box-shadow:0 0 3px 2px #859900 inset;border-radius:7px}.kint-rich>dl>dt,.kint-rich ul.kint-tabs{box-shadow:4px 0 2px -3px #268bd2 inset}.kint-rich ul.kint-tabs li.kint-active-tab{padding-top:7px;height:34px} diff --git a/system/Validation/FormatRules.php b/system/Validation/FormatRules.php index c8572b0..d143c6f 100644 --- a/system/Validation/FormatRules.php +++ b/system/Validation/FormatRules.php @@ -59,8 +59,6 @@ return ctype_alpha($str); } - //-------------------------------------------------------------------- - /** * Alpha with spaces. * @@ -78,10 +76,8 @@ return (bool) preg_match('/^[A-Z ]+$/i', $value); } - //-------------------------------------------------------------------- - /** - * Alpha-numeric with underscores and dashes + * Alphanumeric with underscores and dashes * * @param string $str * @@ -89,13 +85,27 @@ */ public function alpha_dash(string $str = null): bool { - return (bool) preg_match('/^[a-z0-9_-]+$/i', $str); + return (bool) preg_match('/^[a-z0-9_-]+$/i', $str); } - //-------------------------------------------------------------------- + /** + * Alphanumeric, spaces, and a limited set of punctuation characters. + * Accepted punctuation characters are: ~ tilde, ! exclamation, + * # number, $ dollar, % percent, & ampersand, * asterisk, - dash, + * _ underscore, + plus, = equals, | vertical bar, : colon, . period + * ~ ! # $ % & * - _ + = | : . + * + * @param string $str + * + * @return boolean + */ + public function alpha_numeric_punct($str) + { + return (bool) preg_match('/^[A-Z0-9 ~!#$%\&\*\-_+=|:.]+$/i', $str); + } /** - * Alpha-numeric + * Alphanumeric * * @param string $str * @@ -106,10 +116,8 @@ return ctype_alnum($str); } - //-------------------------------------------------------------------- - /** - * Alpha-numeric w/ spaces + * Alphanumeric w/ spaces * * @param string $str * @@ -120,8 +128,6 @@ return (bool) preg_match('/^[A-Z0-9 ]+$/i', $str); } - //-------------------------------------------------------------------- - /** * Any type of string * @@ -137,8 +143,6 @@ return is_string($str); } - //-------------------------------------------------------------------- - /** * Decimal number * @@ -151,8 +155,6 @@ return (bool) preg_match('/^[\-+]?[0-9]+(|\.[0-9]+)$/', $str); } - //-------------------------------------------------------------------- - /** * String of hexidecimal characters * @@ -165,8 +167,6 @@ return ctype_xdigit($str); } - //-------------------------------------------------------------------- - /** * Integer * @@ -179,8 +179,6 @@ return (bool) preg_match('/^[\-+]?[0-9]+$/', $str); } - //-------------------------------------------------------------------- - /** * Is a Natural number (0,1,2,3, etc.) * @@ -192,8 +190,6 @@ return ctype_digit($str); } - //-------------------------------------------------------------------- - /** * Is a Natural number, but not a zero (1,2,3, etc.) * @@ -205,8 +201,6 @@ return ($str !== '0' && ctype_digit($str)); } - //-------------------------------------------------------------------- - /** * Numeric * @@ -219,8 +213,6 @@ return (bool) preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); } - //-------------------------------------------------------------------- - /** * Compares value against a regular expression pattern. * @@ -240,8 +232,6 @@ return (bool) preg_match($pattern, $str); } - //-------------------------------------------------------------------- - /** * Validates that the string is a valid timezone as per the * timezone_identifiers_list function. @@ -257,8 +247,6 @@ return in_array($str, timezone_identifiers_list()); } - //-------------------------------------------------------------------- - /** * Valid Base64 * @@ -273,8 +261,6 @@ return (base64_encode(base64_decode($str)) === $str); } - //-------------------------------------------------------------------- - /** * Valid JSON * @@ -288,8 +274,6 @@ return json_last_error() === JSON_ERROR_NONE; } - //-------------------------------------------------------------------- - /** * Checks for a correctly formatted email address * @@ -307,8 +291,6 @@ return (bool) filter_var($str, FILTER_VALIDATE_EMAIL); } - //-------------------------------------------------------------------- - /** * Validate a comma-separated list of email addresses. * @@ -338,19 +320,20 @@ return true; } - //-------------------------------------------------------------------- - /** - * Validate an IP address + * Validate an IP address (human readable format or binary string - inet_pton) * * @param string $ip IP Address * @param string $which IP protocol: 'ipv4' or 'ipv6' - * @param array $data * * @return boolean */ - public function valid_ip(string $ip = null, string $which = null, array $data): bool - { + public function valid_ip(string $ip = null, string $which = null): bool + { + if(empty($ip)) + { + return false; + } switch (strtolower($which)) { case 'ipv4': @@ -364,11 +347,9 @@ break; } - return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which); + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which) || (!ctype_print($ip) && (bool) filter_var(inet_ntop($ip), FILTER_VALIDATE_IP, $which)); } - //-------------------------------------------------------------------- - /** * Checks a URL to ensure it's formed correctly. * @@ -397,8 +378,6 @@ return (filter_var($str, FILTER_VALIDATE_URL) !== false); } - //-------------------------------------------------------------------- - /** * Checks for a valid date and matches a given date format * @@ -419,5 +398,4 @@ return (bool) $date && \DateTime::getLastErrors()['warning_count'] === 0 && \DateTime::getLastErrors()['error_count'] === 0; } - //-------------------------------------------------------------------- } diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index 959194c..484b1e6 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -208,11 +208,11 @@ * the error to $this->errors and moves on to the next, * so that we can collect all of the first errors. * - * @param string $field - * @param string|null $label - * @param string $value - * @param array|null $rules - * @param array $data // All of the fields to check. + * @param string $field + * @param string|null $label + * @param string|array $value Value to be validated, can be a string or an array + * @param array|null $rules + * @param array $data // All of the fields to check. * * @return boolean */ @@ -291,7 +291,14 @@ // Set the error message if we didn't survive. if ($passed === false) { - $this->errors[$field] = is_null($error) ? $this->getErrorMessage($rule, $field, $label, $param) : $error; + // if the $value is an array, convert it to as string representation + if (is_array($value)) + { + $value = '[' . implode(', ', $value) . ']'; + } + + $this->errors[$field] = is_null($error) ? $this->getErrorMessage($rule, $field, $label, $param, $value) + : $error; return false; } @@ -682,10 +689,11 @@ * @param string $field * @param string|null $label * @param string $param + * @param string $value The value that caused the validation to fail. * * @return string */ - protected function getErrorMessage(string $rule, string $field, string $label = null, string $param = null): string + protected function getErrorMessage(string $rule, string $field, string $label = null, string $param = null, string $value = null): string { // Check if custom message has been defined by user if (isset($this->customErrors[$field][$rule])) @@ -702,6 +710,7 @@ $message = str_replace('{field}', $label ?? $field, $message); $message = str_replace('{param}', $this->rules[$param]['label'] ?? $param, $message); + $message = str_replace('{value}', $value, $message); return $message; } diff --git a/system/View/View.php b/system/View/View.php index b78a5ae..01241dd 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -498,7 +498,7 @@ * * @return string */ - public function include(string $view, array $options = null, $saveData = null): string + public function include(string $view, array $options = null, $saveData = true): string { return $this->render($view, $options, $saveData); } diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index b980a80..f0f7eea 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -34,7 +34,7 @@ 'type' => 'DATETIME', 'null' => true, ], - 'deleted_at' => [ + 'deleted_at' => [ 'type' => 'DATETIME', 'null' => true, ], @@ -67,7 +67,7 @@ 'constraint' => 11, 'null' => true, ], - 'deleted_at' => [ + 'deleted_at' => [ 'type' => 'INTEGER', 'constraint' => 11, 'null' => true, diff --git a/tests/_support/Database/Seeds/AnotherSeeder.php b/tests/_support/Database/Seeds/AnotherSeeder.php new file mode 100644 index 0000000..5254d4d --- /dev/null +++ b/tests/_support/Database/Seeds/AnotherSeeder.php @@ -0,0 +1,15 @@ + 'Jerome Lohan', + 'email' => 'jlo@lohanenterprises.com', + 'country' => 'UK', + ]; + + $this->db->table('user')->insert($row); + } +}