Initial project files: BESCMS full source
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Exceptions as ExceptionsConfig;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Provides common functions for exception handlers,
|
||||
* especially around displaying the output.
|
||||
*/
|
||||
abstract class BaseExceptionHandler
|
||||
{
|
||||
/**
|
||||
* Config for debug exceptions.
|
||||
*/
|
||||
protected ExceptionsConfig $config;
|
||||
|
||||
/**
|
||||
* Nesting level of the output buffering mechanism
|
||||
*/
|
||||
protected int $obLevel;
|
||||
|
||||
/**
|
||||
* The path to the directory containing the
|
||||
* cli and html error view directories.
|
||||
*/
|
||||
protected ?string $viewPath = null;
|
||||
|
||||
public function __construct(ExceptionsConfig $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
$this->obLevel = ob_get_level();
|
||||
|
||||
if ($this->viewPath === null) {
|
||||
$this->viewPath = rtrim($this->config->errorViewPath, '\\/ ') . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main entry point into the handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function handle(
|
||||
Throwable $exception,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
int $statusCode,
|
||||
int $exitCode,
|
||||
);
|
||||
|
||||
/**
|
||||
* Gathers the variables that will be made available to the view.
|
||||
*/
|
||||
protected function collectVars(Throwable $exception, int $statusCode): array
|
||||
{
|
||||
// Get the first exception.
|
||||
$firstException = $exception;
|
||||
|
||||
while ($prevException = $firstException->getPrevious()) {
|
||||
$firstException = $prevException;
|
||||
}
|
||||
|
||||
$trace = $firstException->getTrace();
|
||||
|
||||
if ($this->config->sensitiveDataInTrace !== []) {
|
||||
$trace = $this->maskSensitiveData($trace, $this->config->sensitiveDataInTrace);
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $exception::class,
|
||||
'type' => $exception::class,
|
||||
'code' => $statusCode,
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $trace,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask sensitive data in the trace.
|
||||
*/
|
||||
protected function maskSensitiveData(array $trace, array $keysToMask, string $path = ''): array
|
||||
{
|
||||
foreach ($trace as $i => $line) {
|
||||
$trace[$i]['args'] = $this->maskData($line['args'], $keysToMask);
|
||||
}
|
||||
|
||||
return $trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|object $args
|
||||
*
|
||||
* @return array|object
|
||||
*/
|
||||
private function maskData($args, array $keysToMask, string $path = '')
|
||||
{
|
||||
foreach ($keysToMask as $keyToMask) {
|
||||
$explode = explode('/', $keyToMask);
|
||||
$index = end($explode);
|
||||
|
||||
if (str_starts_with(strrev($path . '/' . $index), strrev($keyToMask))) {
|
||||
if (is_array($args) && array_key_exists($index, $args)) {
|
||||
$args[$index] = '******************';
|
||||
} elseif (
|
||||
is_object($args) && property_exists($args, $index)
|
||||
&& isset($args->{$index}) && is_scalar($args->{$index})
|
||||
) {
|
||||
$args->{$index} = '******************';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($args)) {
|
||||
foreach ($args as $pathKey => $subarray) {
|
||||
$args[$pathKey] = $this->maskData($subarray, $keysToMask, $path . '/' . $pathKey);
|
||||
}
|
||||
} elseif (is_object($args)) {
|
||||
foreach ($args as $pathKey => $subarray) {
|
||||
$args->{$pathKey} = $this->maskData($subarray, $keysToMask, $path . '/' . $pathKey);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes memory usage in real-world units. Intended for use
|
||||
* with memory_get_usage, etc.
|
||||
*
|
||||
* @used-by app/Views/errors/html/error_exception.php
|
||||
*/
|
||||
protected static function describeMemory(int $bytes): string
|
||||
{
|
||||
helper('number');
|
||||
|
||||
return number_to_size($bytes, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a syntax-highlighted version of a PHP file.
|
||||
*
|
||||
* @used-by app/Views/errors/html/error_exception.php
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected static function highlightFile(string $file, int $lineNumber, int $lines = 15)
|
||||
{
|
||||
if ($file === '' || ! is_readable($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set our highlight colors:
|
||||
if (function_exists('ini_set')) {
|
||||
ini_set('highlight.comment', '#767a7e; font-style: italic');
|
||||
ini_set('highlight.default', '#c7c7c7');
|
||||
ini_set('highlight.html', '#06B');
|
||||
ini_set('highlight.keyword', '#f1ce61;');
|
||||
ini_set('highlight.string', '#869d6a');
|
||||
}
|
||||
|
||||
try {
|
||||
$source = file_get_contents($file);
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = str_replace(["\r\n", "\r"], "\n", $source);
|
||||
$source = explode("\n", highlight_string($source, true));
|
||||
|
||||
if (PHP_VERSION_ID < 80300) {
|
||||
$source = str_replace('<br />', "\n", $source[1]);
|
||||
$source = explode("\n", str_replace("\r\n", "\n", $source));
|
||||
} else {
|
||||
// We have to remove these tags since we're preparing the result
|
||||
// ourselves and these tags are added manually at the end.
|
||||
$source = str_replace(['<pre><code>', '</code></pre>'], '', $source);
|
||||
}
|
||||
|
||||
// Get just the part to show
|
||||
$start = max($lineNumber - (int) round($lines / 2), 0);
|
||||
|
||||
// Get just the lines we need to display, while keeping line numbers...
|
||||
$source = array_splice($source, $start, $lines, true);
|
||||
|
||||
// Used to format the line number in the source
|
||||
$format = '% ' . strlen((string) ($start + $lines)) . 'd';
|
||||
|
||||
$out = '';
|
||||
// Because the highlighting may have an uneven number
|
||||
// of open and close span tags on one line, we need
|
||||
// to ensure we can close them all to get the lines
|
||||
// showing correctly.
|
||||
$spans = 0;
|
||||
|
||||
foreach ($source as $n => $row) {
|
||||
$spans += substr_count($row, '<span') - substr_count($row, '</span');
|
||||
$row = str_replace(["\r", "\n"], ['', ''], $row);
|
||||
|
||||
if (($n + $start + 1) === $lineNumber) {
|
||||
preg_match_all('#<[^>]+>#', $row, $tags);
|
||||
|
||||
$out .= sprintf(
|
||||
"<span class='line highlight'><span class='number'>{$format}</span> %s\n</span>%s",
|
||||
$n + $start + 1,
|
||||
strip_tags($row),
|
||||
implode('', $tags[0]),
|
||||
);
|
||||
} else {
|
||||
$out .= sprintf('<span class="line"><span class="number">' . $format . '</span> %s', $n + $start + 1, $row) . "\n";
|
||||
// We're closing only one span tag we added manually line before,
|
||||
// so we have to increment $spans count to close this tag later.
|
||||
$spans++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($spans > 0) {
|
||||
$out .= str_repeat('</span>', $spans);
|
||||
}
|
||||
|
||||
return '<pre><code>' . $out . '</code></pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an exception and status code will display the error to the client.
|
||||
*
|
||||
* @param string|null $viewFile
|
||||
*/
|
||||
protected function render(Throwable $exception, int $statusCode, $viewFile = null): void
|
||||
{
|
||||
if ($viewFile === null) {
|
||||
echo 'The error view file was not specified. Cannot display error view.';
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (! is_file($viewFile)) {
|
||||
echo 'The error view file "' . $viewFile . '" was not found. Cannot display error view.';
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo (function () use ($exception, $statusCode, $viewFile): string {
|
||||
$vars = $this->collectVars($exception, $statusCode);
|
||||
extract($vars, EXTR_SKIP);
|
||||
|
||||
// CLI error views output to STDERR/STDOUT, so ob_start() does not work.
|
||||
ob_clean(); //屏蔽之前输出
|
||||
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
|
||||
return ob_get_clean();
|
||||
})();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @see \CodeIgniter\Debug\ExceptionHandlerTest
|
||||
*/
|
||||
final class ExceptionHandler extends BaseExceptionHandler implements ExceptionHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* ResponseTrait needs this.
|
||||
*/
|
||||
private $request = null;
|
||||
|
||||
/**
|
||||
* ResponseTrait needs this.
|
||||
*/
|
||||
private $response = null;
|
||||
|
||||
/**
|
||||
* Determines the correct way to display the error.
|
||||
*/
|
||||
public function handle(
|
||||
$exception,
|
||||
$request,
|
||||
$response,
|
||||
int $statusCode,
|
||||
int $exitCode,
|
||||
): void {
|
||||
// ResponseTrait needs these properties.
|
||||
|
||||
// Determine possible directories of error views
|
||||
$addPath = (is_cli() ? 'cli' : 'html') . DIRECTORY_SEPARATOR;
|
||||
$path = $this->viewPath . $addPath;
|
||||
// Determine the views
|
||||
$view = $this->determineView($exception, $path, $statusCode);
|
||||
|
||||
// Check if the view exists
|
||||
$viewFile = null;
|
||||
if (is_file($path . $view)) {
|
||||
$viewFile = $path . $view;
|
||||
}
|
||||
|
||||
// Displays the HTML or CLI error code.
|
||||
$this->render($exception, $statusCode, $viewFile);
|
||||
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the view to display based on the exception thrown, HTTP status
|
||||
* code, whether an HTTP or CLI request, etc.
|
||||
*
|
||||
* @return string The filename of the view file to use
|
||||
*/
|
||||
protected function determineView(
|
||||
Throwable $exception,
|
||||
string $templatePath,
|
||||
int $statusCode = 500,
|
||||
): string {
|
||||
// Production environments should have a custom exception file.
|
||||
$view = 'production.php';
|
||||
|
||||
if ($this->isDisplayErrorsEnabled()) {
|
||||
// If display_errors is enabled, shows the error details.
|
||||
$view = 'error_exception.php';
|
||||
}
|
||||
|
||||
// 404 Errors
|
||||
if ($exception instanceof PageNotFoundException) {
|
||||
return 'error_404.php';
|
||||
}
|
||||
|
||||
$templatePath = rtrim($templatePath, '\\/ ') . DIRECTORY_SEPARATOR;
|
||||
|
||||
// Allow for custom views based upon the status code
|
||||
if (is_file($templatePath . 'error_' . $statusCode . '.php')) {
|
||||
return 'error_' . $statusCode . '.php';
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function isDisplayErrorsEnabled(): bool
|
||||
{
|
||||
return in_array(
|
||||
strtolower(ini_get('display_errors')),
|
||||
['1', 'true', 'on', 'yes'],
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
interface ExceptionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Determines the correct way to display the error.
|
||||
*/
|
||||
public function handle(
|
||||
Throwable $exception,
|
||||
$request,
|
||||
$response,
|
||||
int $statusCode,
|
||||
int $exitCode,
|
||||
): void;
|
||||
}
|
||||
@@ -0,0 +1,776 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use CodeIgniter\Exceptions\HasExitCodeInterface;
|
||||
use CodeIgniter\Exceptions\HTTPExceptionInterface;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use Config\Exceptions as ExceptionsConfig;
|
||||
use ErrorException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Exceptions manager
|
||||
*
|
||||
* @see \CodeIgniter\Debug\ExceptionsTest
|
||||
*/
|
||||
class Exceptions
|
||||
{
|
||||
|
||||
/**
|
||||
* Nesting level of the output buffering mechanism
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
public $ob_level;
|
||||
|
||||
/**
|
||||
* The path to the directory containing the
|
||||
* cli and html error view directories.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
protected $viewPath;
|
||||
|
||||
/**
|
||||
* Config for debug exceptions.
|
||||
*
|
||||
* @var ExceptionsConfig
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The request.
|
||||
*
|
||||
* @var RequestInterface|null
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The outgoing response.
|
||||
*
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
private ?Throwable $exceptionCaughtByExceptionHandler = null;
|
||||
|
||||
public function __construct(ExceptionsConfig $config)
|
||||
{
|
||||
// For backward compatibility
|
||||
$this->ob_level = ob_get_level();
|
||||
$this->viewPath = rtrim($config->errorViewPath, '\\/ ') . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->config = $config;
|
||||
|
||||
// workaround for upgraded users
|
||||
// This causes "Deprecated: Creation of dynamic property" in PHP 8.2.
|
||||
// @TODO remove this after dropping PHP 8.1 support.
|
||||
if (! isset($this->config->sensitiveDataInTrace)) {
|
||||
$this->config->sensitiveDataInTrace = [];
|
||||
}
|
||||
if (! isset($this->config->logDeprecations, $this->config->deprecationLogLevel)) {
|
||||
$this->config->logDeprecations = false;
|
||||
$this->config->deprecationLogLevel = 'warning';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for registering the error, exception and shutdown
|
||||
* handling of our application.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
set_exception_handler([$this, 'exceptionHandler']);
|
||||
set_error_handler([$this, 'errorHandler']);
|
||||
//set_exception_handler($this->exceptionHandler(...));
|
||||
//set_error_handler($this->errorHandler(...));
|
||||
register_shutdown_function([$this, 'shutdownHandler']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文翻译输出的错误信息
|
||||
*/
|
||||
private function _cn_msg($message) {
|
||||
|
||||
if (!$message) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
if (strpos($message, 'Unable to connect to the database') !== false) {
|
||||
$message.= '<br>无法连接到数据库,检查数据库是否启动或者数据库配置文件不对,config/database.php';
|
||||
} elseif (strpos($message, 'Unclosed \'{\'') !== false) {
|
||||
$message.= '<br>循环体或者if语句,缺少结束语句,{ }没有成对出现';
|
||||
} elseif (strpos($message, 'Cannot access offset of type string on string') !== false) {
|
||||
$message.= '<br>此变量是字符串,不能使用数组的方式调用他,检查下代码语法';
|
||||
} elseif (strpos($message, 'Call to undefined function') !== false) {
|
||||
$message.= '<br>'.str_replace('Call to undefined function', '函数没有定义', $message);
|
||||
} elseif (strpos($message, 'open_basedir restriction in effect') !== false) {
|
||||
$message.= '<br>目录被限制读取,需要设置.users.ini文件中的目录白名单';
|
||||
} elseif (strpos($message, 'Undefined constant') !== false) {
|
||||
$message.= '<br>'.str_replace('Undefined constant', '变量或者常量没有定义', $message);
|
||||
} elseif (preg_match("/Table '(.+)' doesn't exist/", $message, $mt)) {
|
||||
$message.= '<br>数据库表'.$mt[1].'不存在,表丢失或者表没有创建成功';
|
||||
} elseif (preg_match("/Unknown column '(.+)' in 'field list'/", $message, $mt)) {
|
||||
$message.= '<br>表中没有字段'.$mt[1].',字段没有被创建';
|
||||
} elseif (preg_match("/Access level to (.+) must be protected \(as in class (.+)\) or weaker/U", $message, $mt)) {
|
||||
$message.= '<br>'.$mt[1].'在类'.$mt[2].'中已经被定义过更高级别的权限,请删除本文件的定义代码';
|
||||
} elseif (preg_match("/Creation of dynamic property (.+) is deprecated/", $message, $mt)) {
|
||||
$message.= '<br>动态属性被废除'.$mt[1].',请预先定义';
|
||||
} elseif (preg_match("/Failed opening required '(.+)'/", $message, $mt)) {
|
||||
$message.= '<br>文件'.$mt[1].'不存在,文件丢失或者文件没有创建成功';
|
||||
} elseif (preg_match("/syntax error, unexpected token (.+)/", $message, $mt)) {
|
||||
$message.= '<br>PHP语法错误 或者 模板标签语法错误,检查上下行代码是否写对';
|
||||
} elseif (preg_match("/Cannot declare class (.+), because the name is already in use/", $message, $mt)) {
|
||||
$message.= '<br>类名'.$mt[1].'重复,全文搜索下哪个地方被重复命名了';
|
||||
} elseif (preg_match("/Controller method is not found: (.+)/", $message, $mt)) {
|
||||
$message.= '<br>检查此文件中是否有'.$mt[1].'方法名:'.$this->_get_file();
|
||||
} elseif (preg_match("/Controller or its method is not found:(.+)/", $message, $mt)) {
|
||||
$message.= '<br>检查此文件是否存在:'.$this->_get_file().',检查地址是否正确,注意控制器文件首字母要大写';
|
||||
} elseif (preg_match("/count\(\): Argument #1 \((.+)\) must be of type Countable\|array/", $message, $mt)) {
|
||||
$message.= '<br>需要将count函数改为dr_count';
|
||||
} elseif (IS_XRDEV) {
|
||||
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 替换模板文件显示完整路径
|
||||
*/
|
||||
private function _rp_file($file) {
|
||||
|
||||
if (strpos((string)$file, '.cache.php') !== false && strpos((string)$file, '_DS_') !== false) {
|
||||
$file = str_replace([WRITEPATH.'template/', '_DS_', '.cache.php'], ['', '/', ''], $file);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取控制器地址
|
||||
*/
|
||||
private function _get_file() {
|
||||
$file = APPPATH;
|
||||
if ($file == FRAMEPATH) {
|
||||
$file = CMSPATH.'Control';
|
||||
} else {
|
||||
$file.= 'Controllers';
|
||||
}
|
||||
|
||||
if (IS_ADMIN) {
|
||||
$file.= '/Admin';
|
||||
} elseif (IS_MEMBER) {
|
||||
$file.= '/Member';
|
||||
} elseif (IS_API) {
|
||||
$file.= '/Api';
|
||||
}
|
||||
|
||||
return $file.'/'.ucfirst(\Phpcmf\Service::L('Router')->class).'.php';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Catches any uncaught errors and exceptions, including most Fatal errors
|
||||
* (Yay PHP7!). Will log the error, display it if display_errors is on,
|
||||
* and fire an event that allows custom actions to be taken at this point.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function exceptionHandler(Throwable $exception)
|
||||
{
|
||||
|
||||
$message = $this->_cn_msg($exception->getMessage());
|
||||
// ajax 返回
|
||||
if (IS_AJAX || IS_API) {
|
||||
// 调试模式不屏蔽敏感信息
|
||||
$file = $exception->getFile();
|
||||
if (strpos($file, WRITEPATH.'template') !== false) {
|
||||
$file = $this->_rp_file($file);
|
||||
$message = '模板标签写法错误:'.$message;
|
||||
$arr = \Phpcmf\Service::V()->get_view_files();
|
||||
if ($arr) {
|
||||
$one = current($arr);
|
||||
$message.= '('.CI_DEBUG ? $one['path'] : basename($one['path']).')';
|
||||
}
|
||||
}
|
||||
if (CI_DEBUG) {
|
||||
$message.= '<br>错误文件:'.$file.'('.$exception->getLine().')';
|
||||
$message.= '<br>访问地址:'.\Phpcmf\Service::V()->now_php_url();
|
||||
$trace = $exception->getTrace();
|
||||
if ($trace) {
|
||||
foreach ($trace as $t) {
|
||||
if ($t['file'] && strpos((string)$t['file'], FRAMEPATH) === false) {
|
||||
$message.= '<br>'.$t['function'].':'.$t['file'].'('.$t['line'].')';
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = str_replace([FCPATH, WEBPATH], ['/', '/'], $message);
|
||||
}
|
||||
dr_exit_msg(0, $message);
|
||||
}
|
||||
|
||||
|
||||
$this->exceptionCaughtByExceptionHandler = null;
|
||||
|
||||
$this->exceptionCaughtByExceptionHandler = $exception;
|
||||
|
||||
[$statusCode, $exitCode] = $this->determineCodes($exception);
|
||||
|
||||
if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes, true)) {
|
||||
$uri = '';
|
||||
$routeInfo = '';
|
||||
|
||||
log_message('critical', $exception::class . ": {message}\n{routeInfo}\nin {exFile} on line {exLine}.\n{trace}", [
|
||||
'message' => $exception->getMessage(),
|
||||
'routeInfo' => $routeInfo,
|
||||
'exFile' => clean_path($exception->getFile()), // {file} refers to THIS file
|
||||
'exLine' => $exception->getLine(), // {line} refers to THIS line
|
||||
'trace' => self::renderBacktrace($exception->getTrace()),
|
||||
]);
|
||||
|
||||
// Get the first exception.
|
||||
$last = $exception;
|
||||
|
||||
while ($prevException = $last->getPrevious()) {
|
||||
$last = $prevException;
|
||||
|
||||
log_message('critical', '[Caused by] ' . $prevException::class . ": {message}\nin {exFile} on line {exLine}.\n{trace}", [
|
||||
'message' => $prevException->getMessage(),
|
||||
'exFile' => clean_path($prevException->getFile()), // {file} refers to THIS file
|
||||
'exLine' => $prevException->getLine(), // {line} refers to THIS line
|
||||
'trace' => self::renderBacktrace($prevException->getTrace()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (method_exists($this->config, 'handler')) {
|
||||
// Use new ExceptionHandler
|
||||
$handler = $this->config->handler($statusCode, $exception);
|
||||
$handler->handle(
|
||||
$exception,
|
||||
null,
|
||||
null,
|
||||
$statusCode,
|
||||
$exitCode,
|
||||
);
|
||||
|
||||
return;
|
||||
} else {
|
||||
exit($exception->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback to be registered to `set_error_handler()`.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws ErrorException
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
|
||||
{
|
||||
if ($this->isDeprecationError($severity)) {
|
||||
if ($this->isSessionSidDeprecationError($message, $file, $line)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isImplicitNullableDeprecationError($message, $file, $line)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $this->config->logDeprecations || (bool) env('CODEIGNITER_SCREAM_DEPRECATIONS')) {
|
||||
throw new ErrorException($message, 0, $severity, $file, $line);
|
||||
}
|
||||
|
||||
return $this->handleDeprecationError($message, $file, $line);
|
||||
}
|
||||
|
||||
if ((error_reporting() & $severity) !== 0) {
|
||||
throw new ErrorException($message, 0, $severity, $file, $line);
|
||||
}
|
||||
|
||||
return false; // return false to propagate the error to PHP standard error handler
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles session.sid_length and session.sid_bits_per_character deprecations
|
||||
* in PHP 8.4.
|
||||
*/
|
||||
private function isSessionSidDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
|
||||
{
|
||||
if (
|
||||
PHP_VERSION_ID >= 80400
|
||||
&& str_contains($message, 'session.sid_')
|
||||
) {
|
||||
log_message(
|
||||
'wranting',
|
||||
'[DEPRECATED] {message} in {errFile} on line {errLine}.',
|
||||
[
|
||||
'message' => $message,
|
||||
'errFile' => clean_path($file ?? ''),
|
||||
'errLine' => $line ?? 0,
|
||||
],
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround to implicit nullable deprecation errors in PHP 8.4.
|
||||
*
|
||||
* "Implicitly marking parameter $xxx as nullable is deprecated,
|
||||
* the explicit nullable type must be used instead"
|
||||
*
|
||||
* @TODO remove this before v4.6.0 release
|
||||
*/
|
||||
private function isImplicitNullableDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
|
||||
{
|
||||
if (
|
||||
PHP_VERSION_ID >= 80400
|
||||
&& str_contains($message, 'the explicit nullable type must be used instead')
|
||||
// Only Kint and Faker, which cause this error, are logged.
|
||||
&& (str_starts_with($message, 'Kint\\') || str_starts_with($message, 'Faker\\'))
|
||||
) {
|
||||
log_message(
|
||||
'wranting',
|
||||
'[DEPRECATED] {message} in {errFile} on line {errLine}.',
|
||||
[
|
||||
'message' => $message,
|
||||
'errFile' => clean_path($file ?? ''),
|
||||
'errLine' => $line ?? 0,
|
||||
],
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if any errors have happened during shutdown that
|
||||
* need to be caught and handle them.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function shutdownHandler()
|
||||
{
|
||||
$error = error_get_last();
|
||||
|
||||
if ($error === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
['type' => $type, 'message' => $message, 'file' => $file, 'line' => $line] = $error;
|
||||
|
||||
if ($this->exceptionCaughtByExceptionHandler instanceof Throwable) {
|
||||
$message .= "\n【Previous Exception】\n"
|
||||
. $this->exceptionCaughtByExceptionHandler::class . "\n"
|
||||
. $this->exceptionCaughtByExceptionHandler->getMessage() . "\n"
|
||||
. $this->exceptionCaughtByExceptionHandler->getTraceAsString();
|
||||
}
|
||||
|
||||
if (in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) {
|
||||
$this->exceptionHandler(new ErrorException($message, 0, $type, $file, $line));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the view to display based on the exception thrown,
|
||||
* whether an HTTP or CLI request, etc.
|
||||
*
|
||||
* @return string The path and filename of the view file to use
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to ExceptionHandler.
|
||||
*/
|
||||
protected function determineView(Throwable $exception, string $templatePath): string
|
||||
{
|
||||
// Production environments should have a custom exception file.
|
||||
$view = 'production.php';
|
||||
$templatePath = rtrim($templatePath, '\\/ ') . DIRECTORY_SEPARATOR;
|
||||
|
||||
if (
|
||||
in_array(
|
||||
strtolower(ini_get('display_errors')),
|
||||
['1', 'true', 'on', 'yes'],
|
||||
true,
|
||||
)
|
||||
) {
|
||||
$view = 'error_exception.php';
|
||||
}
|
||||
|
||||
// 404 Errors
|
||||
if ($exception instanceof PageNotFoundException) {
|
||||
return 'error_404.php';
|
||||
}
|
||||
|
||||
// Allow for custom views based upon the status code
|
||||
if (is_file($templatePath . 'error_' . $exception->getCode() . '.php')) {
|
||||
return 'error_' . $exception->getCode() . '.php';
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an exception and status code will display the error to the client.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
protected function render(Throwable $exception, int $statusCode)
|
||||
{
|
||||
// Determine possible directories of error views
|
||||
$path = $this->viewPath;
|
||||
$altPath = rtrim((new Paths())->viewDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$path .= (is_cli() ? 'cli' : 'html') . DIRECTORY_SEPARATOR;
|
||||
$altPath .= (is_cli() ? 'cli' : 'html') . DIRECTORY_SEPARATOR;
|
||||
|
||||
// Determine the views
|
||||
$view = $this->determineView($exception, $path);
|
||||
$altView = $this->determineView($exception, $altPath);
|
||||
|
||||
// Check if the view exists
|
||||
if (is_file($path . $view)) {
|
||||
$viewFile = $path . $view;
|
||||
} elseif (is_file($altPath . $altView)) {
|
||||
$viewFile = $altPath . $altView;
|
||||
}
|
||||
|
||||
if (! isset($viewFile)) {
|
||||
echo 'The error view files were not found. Cannot render exception trace.';
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo (function () use ($exception, $statusCode, $viewFile): string {
|
||||
$vars = $this->collectVars($exception, $statusCode);
|
||||
extract($vars, EXTR_SKIP);
|
||||
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
|
||||
return ob_get_clean();
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers the variables that will be made available to the view.
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
protected function collectVars(Throwable $exception, int $statusCode): array
|
||||
{
|
||||
// Get the first exception.
|
||||
$firstException = $exception;
|
||||
|
||||
while ($prevException = $firstException->getPrevious()) {
|
||||
$firstException = $prevException;
|
||||
}
|
||||
|
||||
$trace = $firstException->getTrace();
|
||||
|
||||
if ($this->config->sensitiveDataInTrace !== []) {
|
||||
$trace = $this->maskSensitiveData($trace, $this->config->sensitiveDataInTrace);
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $exception::class,
|
||||
'type' => $exception::class,
|
||||
'code' => $statusCode,
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $trace,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask sensitive data in the trace.
|
||||
*
|
||||
* @param array $trace
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
protected function maskSensitiveData($trace, array $keysToMask, string $path = '')
|
||||
{
|
||||
foreach ($trace as $i => $line) {
|
||||
$trace[$i]['args'] = $this->maskData($line['args'], $keysToMask);
|
||||
}
|
||||
|
||||
return $trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|object $args
|
||||
*
|
||||
* @return array|object
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
private function maskData($args, array $keysToMask, string $path = '')
|
||||
{
|
||||
foreach ($keysToMask as $keyToMask) {
|
||||
$explode = explode('/', $keyToMask);
|
||||
$index = end($explode);
|
||||
|
||||
if (str_starts_with(strrev($path . '/' . $index), strrev($keyToMask))) {
|
||||
if (is_array($args) && array_key_exists($index, $args)) {
|
||||
$args[$index] = '******************';
|
||||
} elseif (
|
||||
is_object($args) && property_exists($args, $index)
|
||||
&& isset($args->{$index}) && is_scalar($args->{$index})
|
||||
) {
|
||||
$args->{$index} = '******************';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($args)) {
|
||||
foreach ($args as $pathKey => $subarray) {
|
||||
$args[$pathKey] = $this->maskData($subarray, $keysToMask, $path . '/' . $pathKey);
|
||||
}
|
||||
} elseif (is_object($args)) {
|
||||
foreach ($args as $pathKey => $subarray) {
|
||||
$args->{$pathKey} = $this->maskData($subarray, $keysToMask, $path . '/' . $pathKey);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the HTTP status code and the exit status code for this request.
|
||||
*/
|
||||
protected function determineCodes(Throwable $exception): array
|
||||
{
|
||||
$statusCode = 500;
|
||||
$exitStatus = EXIT_ERROR;
|
||||
|
||||
if ($exception instanceof HTTPExceptionInterface) {
|
||||
$statusCode = $exception->getCode();
|
||||
}
|
||||
|
||||
if ($exception instanceof HasExitCodeInterface) {
|
||||
$exitStatus = $exception->getExitCode();
|
||||
}
|
||||
|
||||
return [$statusCode, $exitStatus];
|
||||
}
|
||||
|
||||
private function isDeprecationError(int $error): bool
|
||||
{
|
||||
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;
|
||||
|
||||
return ($error & $deprecations) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true
|
||||
*/
|
||||
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
|
||||
{
|
||||
// Remove the trace of the error handler.
|
||||
$trace = array_slice(debug_backtrace(), 2);
|
||||
|
||||
log_message(
|
||||
$this->config->deprecationLogLevel,
|
||||
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
|
||||
[
|
||||
'message' => $message,
|
||||
'errFile' => clean_path($file ?? ''),
|
||||
'errLine' => $line ?? 0,
|
||||
'trace' => self::renderBacktrace($trace),
|
||||
],
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Display Methods
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This makes nicer looking paths for the error output.
|
||||
*
|
||||
* @deprecated Use dedicated `clean_path()` function.
|
||||
*/
|
||||
public static function cleanPath(string $file): string
|
||||
{
|
||||
return CI_DEBUG ? $file : match (true) {
|
||||
str_starts_with($file, APPPATH) => 'APPPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(APPPATH)),
|
||||
str_starts_with($file, SYSTEMPATH) => 'SYSTEMPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(SYSTEMPATH)),
|
||||
str_starts_with($file, FCPATH) => 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH)),
|
||||
defined('VENDORPATH') && str_starts_with($file, VENDORPATH) => 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(VENDORPATH)),
|
||||
default => $file,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes memory usage in real-world units. Intended for use
|
||||
* with memory_get_usage, etc.
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
public static function describeMemory(int $bytes): string
|
||||
{
|
||||
if ($bytes < 1024) {
|
||||
return $bytes . 'B';
|
||||
}
|
||||
|
||||
if ($bytes < 1_048_576) {
|
||||
return round($bytes / 1024, 2) . 'KB';
|
||||
}
|
||||
|
||||
return round($bytes / 1_048_576, 2) . 'MB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a syntax-highlighted version of a PHP file.
|
||||
*
|
||||
* @return bool|string
|
||||
*
|
||||
* @deprecated 4.4.0 No longer used. Moved to BaseExceptionHandler.
|
||||
*/
|
||||
public static function highlightFile(string $file, int $lineNumber, int $lines = 15)
|
||||
{
|
||||
if ($file === '' || ! is_readable($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set our highlight colors:
|
||||
if (function_exists('ini_set')) {
|
||||
ini_set('highlight.comment', '#767a7e; font-style: italic');
|
||||
ini_set('highlight.default', '#c7c7c7');
|
||||
ini_set('highlight.html', '#06B');
|
||||
ini_set('highlight.keyword', '#f1ce61;');
|
||||
ini_set('highlight.string', '#869d6a');
|
||||
}
|
||||
|
||||
try {
|
||||
$source = file_get_contents($file);
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = str_replace(["\r\n", "\r"], "\n", $source);
|
||||
$source = explode("\n", highlight_string($source, true));
|
||||
$source = str_replace('<br />', "\n", $source[1]);
|
||||
$source = explode("\n", str_replace("\r\n", "\n", $source));
|
||||
|
||||
// Get just the part to show
|
||||
$start = max($lineNumber - (int) round($lines / 2), 0);
|
||||
|
||||
// Get just the lines we need to display, while keeping line numbers...
|
||||
$source = array_splice($source, $start, $lines, true);
|
||||
|
||||
// Used to format the line number in the source
|
||||
$format = '% ' . strlen((string) ($start + $lines)) . 'd';
|
||||
|
||||
$out = '';
|
||||
// Because the highlighting may have an uneven number
|
||||
// of open and close span tags on one line, we need
|
||||
// to ensure we can close them all to get the lines
|
||||
// showing correctly.
|
||||
$spans = 1;
|
||||
|
||||
foreach ($source as $n => $row) {
|
||||
$spans += substr_count($row, '<span') - substr_count($row, '</span');
|
||||
$row = str_replace(["\r", "\n"], ['', ''], $row);
|
||||
|
||||
if (($n + $start + 1) === $lineNumber) {
|
||||
preg_match_all('#<[^>]+>#', $row, $tags);
|
||||
|
||||
$out .= sprintf(
|
||||
"<span class='line highlight'><span class='number'>{$format}</span> %s\n</span>%s",
|
||||
$n + $start + 1,
|
||||
strip_tags($row),
|
||||
implode('', $tags[0]),
|
||||
);
|
||||
} else {
|
||||
$out .= sprintf('<span class="line"><span class="number">' . $format . '</span> %s', $n + $start + 1, $row) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($spans > 0) {
|
||||
$out .= str_repeat('</span>', $spans);
|
||||
}
|
||||
|
||||
return '<pre><code>' . $out . '</code></pre>';
|
||||
}
|
||||
|
||||
private static function renderBacktrace(array $backtrace): string
|
||||
{
|
||||
$backtraces = [];
|
||||
|
||||
foreach ($backtrace as $index => $trace) {
|
||||
$frame = $trace + ['file' => '[internal function]', 'line' => '', 'class' => '', 'type' => '', 'args' => []];
|
||||
|
||||
if ($frame['file'] !== '[internal function]') {
|
||||
$frame['file'] = sprintf('%s(%s)', $frame['file'], $frame['line']);
|
||||
}
|
||||
|
||||
unset($frame['line']);
|
||||
$idx = $index;
|
||||
$idx = str_pad((string) ++$idx, 2, ' ', STR_PAD_LEFT);
|
||||
|
||||
$args = implode(', ', array_map(static fn ($value): string => match (true) {
|
||||
is_object($value) => sprintf('Object(%s)', $value::class),
|
||||
is_array($value) => $value !== [] ? '[...]' : '[]',
|
||||
$value === null => 'null',
|
||||
is_resource($value) => sprintf('resource (%s)', get_resource_type($value)),
|
||||
default => var_export($value, true),
|
||||
}, $frame['args']));
|
||||
|
||||
$backtraces[] = sprintf(
|
||||
'%s %s: %s%s%s(%s)',
|
||||
$idx,
|
||||
clean_path($frame['file']),
|
||||
$frame['class'],
|
||||
$frame['type'],
|
||||
$frame['function'],
|
||||
$args,
|
||||
);
|
||||
}
|
||||
|
||||
return implode("\n", $backtraces);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Iterator for debugging.
|
||||
*/
|
||||
class Iterator
|
||||
{
|
||||
/**
|
||||
* Stores the tests that we are to run.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tests = [];
|
||||
|
||||
/**
|
||||
* Stores the results of each of the tests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $results = [];
|
||||
|
||||
/**
|
||||
* Adds a test to run.
|
||||
*
|
||||
* Tests are simply closures that the user can define any sequence of
|
||||
* things to happen during the test.
|
||||
*
|
||||
* @param Closure(): mixed $closure
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function add(string $name, Closure $closure)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
$this->tests[$name] = $closure;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs through all of the tests that have been added, recording
|
||||
* time to execute the desired number of iterations, and the approximate
|
||||
* memory usage used during those iterations.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function run(int $iterations = 1000, bool $output = true)
|
||||
{
|
||||
foreach ($this->tests as $name => $test) {
|
||||
// clear memory before start
|
||||
gc_collect_cycles();
|
||||
|
||||
$start = microtime(true);
|
||||
$startMem = $maxMemory = memory_get_usage(true);
|
||||
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$result = $test();
|
||||
$maxMemory = max($maxMemory, memory_get_usage(true));
|
||||
|
||||
unset($result);
|
||||
}
|
||||
|
||||
$this->results[$name] = [
|
||||
'time' => microtime(true) - $start,
|
||||
'memory' => $maxMemory - $startMem,
|
||||
'n' => $iterations,
|
||||
];
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
return $this->getReport();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get results.
|
||||
*/
|
||||
public function getReport(): string
|
||||
{
|
||||
if ($this->results === []) {
|
||||
return 'No results to display.';
|
||||
}
|
||||
|
||||
helper('number');
|
||||
|
||||
// Template
|
||||
$tpl = '<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Test</td>
|
||||
<td>Time</td>
|
||||
<td>Memory</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>';
|
||||
|
||||
$rows = '';
|
||||
|
||||
foreach ($this->results as $name => $result) {
|
||||
$memory = number_to_size($result['memory'], 4);
|
||||
|
||||
$rows .= "<tr>
|
||||
<td>{$name}</td>
|
||||
<td>" . number_format($result['time'], 4) . "</td>
|
||||
<td>{$memory}</td>
|
||||
</tr>";
|
||||
}
|
||||
|
||||
$tpl = str_replace('{rows}', $rows, $tpl);
|
||||
|
||||
return $tpl . '<br/>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class Timer
|
||||
*
|
||||
* Provides a simple way to measure the amount of time
|
||||
* that elapses between two points.
|
||||
*
|
||||
* @see \CodeIgniter\Debug\TimerTest
|
||||
*/
|
||||
class Timer
|
||||
{
|
||||
/**
|
||||
* List of all timers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $timers = [];
|
||||
|
||||
/**
|
||||
* Starts a timer running.
|
||||
*
|
||||
* Multiple calls can be made to this method so that several
|
||||
* execution points can be measured.
|
||||
*
|
||||
* @param string $name The name of this timer.
|
||||
* @param float|null $time Allows user to provide time.
|
||||
*
|
||||
* @return Timer
|
||||
*/
|
||||
public function start(string $name, ?float $time = null)
|
||||
{
|
||||
$this->timers[strtolower($name)] = [
|
||||
'start' => ! empty($time) ? $time : microtime(true),
|
||||
'end' => null,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a running timer.
|
||||
*
|
||||
* If the timer is not stopped before the timers() method is called,
|
||||
* it will be automatically stopped at that point.
|
||||
*
|
||||
* @param string $name The name of this timer.
|
||||
*
|
||||
* @return Timer
|
||||
*/
|
||||
public function stop(string $name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
if (empty($this->timers[$name])) {
|
||||
throw new RuntimeException('Cannot stop timer: invalid name given.');
|
||||
}
|
||||
|
||||
$this->timers[$name]['end'] = microtime(true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of a recorded timer.
|
||||
*
|
||||
* @param string $name The name of the timer.
|
||||
* @param int $decimals Number of decimal places.
|
||||
*
|
||||
* @return float|null Returns null if timer does not exist by that name.
|
||||
* Returns a float representing the number of
|
||||
* seconds elapsed while that timer was running.
|
||||
*/
|
||||
public function getElapsedTime(string $name, int $decimals = 4)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
if (empty($this->timers[$name])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$timer = $this->timers[$name];
|
||||
|
||||
if (empty($timer['end'])) {
|
||||
$timer['end'] = microtime(true);
|
||||
}
|
||||
|
||||
return (float) number_format($timer['end'] - $timer['start'], $decimals, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of timers, with the duration pre-calculated for you.
|
||||
*
|
||||
* @param int $decimals Number of decimal places
|
||||
*/
|
||||
public function getTimers(int $decimals = 4): array
|
||||
{
|
||||
$timers = $this->timers;
|
||||
|
||||
foreach ($timers as &$timer) {
|
||||
if (empty($timer['end'])) {
|
||||
$timer['end'] = microtime(true);
|
||||
}
|
||||
|
||||
$timer['duration'] = (float) number_format($timer['end'] - $timer['start'], $decimals);
|
||||
}
|
||||
|
||||
return $timers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a timer with the specified name exists.
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return array_key_exists(strtolower($name), $this->timers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes callable and measures its time.
|
||||
* Returns its return value if any.
|
||||
*
|
||||
* @param string $name The name of the timer
|
||||
* @param callable(): mixed $callable callable to be executed
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function record(string $name, callable $callable)
|
||||
{
|
||||
$this->start($name);
|
||||
$returnValue = $callable();
|
||||
$this->stop($name);
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug;
|
||||
|
||||
use CodeIgniter\Debug\Toolbar\Collectors\BaseCollector;
|
||||
use CodeIgniter\Debug\Toolbar\Collectors\Config;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Toolbar as ToolbarConfig;
|
||||
|
||||
/**
|
||||
* Displays a toolbar with bits of stats to aid a developer in debugging.
|
||||
*
|
||||
* Inspiration: http://prophiler.fabfuel.de
|
||||
*/
|
||||
class Toolbar
|
||||
{
|
||||
/**
|
||||
* Toolbar configuration settings.
|
||||
*
|
||||
* @var ToolbarConfig
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Collectors to be used and displayed.
|
||||
*
|
||||
* @var list<BaseCollector>
|
||||
*/
|
||||
protected $collectors = [];
|
||||
|
||||
public function __construct(ToolbarConfig $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
foreach ($config->collectors as $collector) {
|
||||
|
||||
$this->collectors[] = new $collector();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the data required by Debug Bar
|
||||
*
|
||||
* @param float $startTime App start time
|
||||
* @param IncomingRequest $request
|
||||
*
|
||||
* @return string JSON encoded data
|
||||
*/
|
||||
public function run(float $startTime, float $totalTime): string
|
||||
{
|
||||
$data = [];
|
||||
// Data items used within the view.
|
||||
$data['url'] = dr_now_url();
|
||||
$data['method'] = 1;
|
||||
$data['isAJAX'] = 2;
|
||||
$data['startTime'] = $startTime;
|
||||
$data['totalTime'] = $totalTime * 1000;
|
||||
$data['totalMemory'] = number_format(memory_get_peak_usage() / 1024 / 1024, 3);
|
||||
$data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7);
|
||||
$data['segmentCount'] = (int) ceil($data['totalTime'] / $data['segmentDuration']);
|
||||
$data['CI_VERSION'] = FRAME_VERSION;
|
||||
$data['collectors'] = [];
|
||||
|
||||
foreach ($this->collectors as $collector) {
|
||||
$data['collectors'][] = $collector->getAsArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
$data['vars']['response'] = [
|
||||
'headers' => [],
|
||||
];
|
||||
|
||||
|
||||
|
||||
$data['config'] = Config::display();
|
||||
|
||||
|
||||
return json_encode($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called within the view to display the timeline itself.
|
||||
*/
|
||||
protected function renderTimeline(array $collectors, float $startTime, int $segmentCount, int $segmentDuration, array &$styles): string
|
||||
{
|
||||
$rows = $this->collectTimelineData($collectors);
|
||||
$styleCount = 0;
|
||||
|
||||
// Use recursive render function
|
||||
return $this->renderTimelineRecursive($rows, $startTime, $segmentCount, $segmentDuration, $styles, $styleCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively renders timeline elements and their children.
|
||||
*/
|
||||
protected function renderTimelineRecursive(array $rows, float $startTime, int $segmentCount, int $segmentDuration, array &$styles, int &$styleCount, int $level = 0, bool $isChild = false): string
|
||||
{
|
||||
$displayTime = $segmentCount * $segmentDuration;
|
||||
|
||||
$output = '';
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$hasChildren = isset($row['children']) && ! empty($row['children']);
|
||||
$isQuery = isset($row['query']) && ! empty($row['query']);
|
||||
|
||||
// Open controller timeline by default
|
||||
$open = $row['name'] === 'Controller';
|
||||
|
||||
if ($hasChildren || $isQuery) {
|
||||
$output .= '<tr class="timeline-parent' . ($open ? ' timeline-parent-open' : '') . '" id="timeline-' . $styleCount . '_parent" data-toggle="childrows" data-child="timeline-' . $styleCount . '">';
|
||||
} else {
|
||||
$output .= '<tr>';
|
||||
}
|
||||
|
||||
$output .= '<td class="' . ($isChild ? 'debug-bar-width30' : '') . ' debug-bar-level-' . $level . '" >' . ($hasChildren || $isQuery ? '<nav></nav>' : '') . $row['name'] . '</td>';
|
||||
$output .= '<td class="' . ($isChild ? 'debug-bar-width10' : '') . '">' . $row['component'] . '</td>';
|
||||
$output .= '<td class="' . ($isChild ? 'debug-bar-width10 ' : '') . 'debug-bar-alignRight">' . number_format($row['duration'] * 1000, 2) . ' ms</td>';
|
||||
$output .= "<td class='debug-bar-noverflow' colspan='{$segmentCount}'>";
|
||||
|
||||
$offset = ((((float) $row['start'] - $startTime) * 1000) / $displayTime) * 100;
|
||||
$length = (((float) $row['duration'] * 1000) / $displayTime) * 100;
|
||||
|
||||
$styles['debug-bar-timeline-' . $styleCount] = "left: {$offset}%; width: {$length}%;";
|
||||
|
||||
$output .= "<span class='timer debug-bar-timeline-{$styleCount}' title='" . number_format($length, 2) . "%'></span>";
|
||||
$output .= '</td>';
|
||||
$output .= '</tr>';
|
||||
|
||||
$styleCount++;
|
||||
|
||||
// Add children if any
|
||||
if ($hasChildren || $isQuery) {
|
||||
$output .= '<tr class="child-row ' . ($open ? '' : ' debug-bar-ndisplay') . '" id="timeline-' . ($styleCount - 1) . '_children" >';
|
||||
$output .= '<td colspan="' . ($segmentCount + 3) . '" class="child-container">';
|
||||
$output .= '<table class="timeline">';
|
||||
$output .= '<tbody>';
|
||||
|
||||
if ($isQuery) {
|
||||
// Output query string if query
|
||||
$output .= '<tr>';
|
||||
$output .= '<td class="query-container debug-bar-level-' . ($level + 1) . '" >' . $row['query'] . '</td>';
|
||||
$output .= '</tr>';
|
||||
} else {
|
||||
// Recursively render children
|
||||
$output .= $this->renderTimelineRecursive($row['children'], $startTime, $segmentCount, $segmentDuration, $styles, $styleCount, $level + 1, true);
|
||||
}
|
||||
|
||||
$output .= '</tbody>';
|
||||
$output .= '</table>';
|
||||
$output .= '</td>';
|
||||
$output .= '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted array of timeline data arrays from the collectors.
|
||||
*
|
||||
* @param array $collectors
|
||||
*/
|
||||
protected function collectTimelineData($collectors): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
// Collect it
|
||||
foreach ($collectors as $collector) {
|
||||
if (! $collector['hasTimelineData']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = array_merge($data, $collector['timelineData']);
|
||||
}
|
||||
|
||||
// Sort it
|
||||
$sortArray = [
|
||||
array_column($data, 'start'), SORT_NUMERIC, SORT_ASC,
|
||||
array_column($data, 'duration'), SORT_NUMERIC, SORT_DESC,
|
||||
&$data,
|
||||
];
|
||||
|
||||
array_multisort(...$sortArray);
|
||||
|
||||
// Add end time to each element
|
||||
array_walk($data, static function (&$row): void {
|
||||
$row['end'] = $row['start'] + $row['duration'];
|
||||
});
|
||||
|
||||
// Group it
|
||||
$data = $this->structureTimelineData($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges the already sorted timeline data into a parent => child structure.
|
||||
*/
|
||||
protected function structureTimelineData(array $elements): array
|
||||
{
|
||||
// We define ourselves as the first element of the array
|
||||
$element = array_shift($elements);
|
||||
|
||||
// If we have children behind us, collect and attach them to us
|
||||
while ($elements !== [] && $elements[array_key_first($elements)]['end'] <= $element['end']) {
|
||||
$element['children'][] = array_shift($elements);
|
||||
}
|
||||
|
||||
// Make sure our children know whether they have children, too
|
||||
if (isset($element['children'])) {
|
||||
$element['children'] = $this->structureTimelineData($element['children']);
|
||||
}
|
||||
|
||||
// If we have no younger siblings, we can return
|
||||
if ($elements === []) {
|
||||
return [$element];
|
||||
}
|
||||
|
||||
// Make sure our younger siblings know their relatives, too
|
||||
return array_merge([$element], $this->structureTimelineData($elements));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of data from all of the modules
|
||||
* that should be displayed in the 'Vars' tab.
|
||||
*/
|
||||
protected function collectVarData(): array
|
||||
{
|
||||
if (! ($this->config->collectVarData ?? true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($this->collectors as $collector) {
|
||||
if (! $collector->hasVarData()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = array_merge($data, $collector->getVarData());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a number to the nearest incremental value.
|
||||
*/
|
||||
protected function roundTo(float $number, int $increments = 5): float
|
||||
{
|
||||
$increments = 1 / $increments;
|
||||
|
||||
return ceil($number * $increments) / $increments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare for debugging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepare($app)
|
||||
{
|
||||
|
||||
|
||||
if (IS_POST) {
|
||||
return;
|
||||
} elseif (IS_API) {
|
||||
return;
|
||||
} elseif (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
|
||||
return;
|
||||
}
|
||||
|
||||
//ajax请求
|
||||
|
||||
/**
|
||||
* @var IncomingRequest|null $request
|
||||
*/
|
||||
if (CI_DEBUG && ! is_cli()) {
|
||||
|
||||
|
||||
|
||||
$stats = $app->getPerformanceStats();
|
||||
$data = $this->run( $stats['startTime'], $stats['totalTime']);
|
||||
|
||||
helper('filesystem');
|
||||
|
||||
// Updated to microtime() so we can get history
|
||||
$time = sprintf('%.6f', Time::now()->format('U.u'));
|
||||
|
||||
if (! is_dir(WRITEPATH . 'debugbar')) {
|
||||
mkdir(WRITEPATH . 'debugbar', 0777);
|
||||
}
|
||||
|
||||
write_file(WRITEPATH . 'debugbar/debugbar_' . $time . '.json', $data, 'w+');
|
||||
|
||||
|
||||
$kintScript = file_get_contents($this->config->viewsPath.'script.js');
|
||||
|
||||
$kintScript = substr($kintScript, 0, strpos($kintScript, '</style>') + 8);
|
||||
$kintScript = ($kintScript === '0') ? '' : $kintScript;
|
||||
|
||||
|
||||
$script = PHP_EOL
|
||||
. '<script id="debugbar_loader" '
|
||||
. 'data-time="' . $time . '" '
|
||||
. 'src="' . WEB_DIR . SELF . '?debugbar"></script>'
|
||||
. '<script id="debugbar_dynamic_script"></script>'
|
||||
. '<style id="debugbar_dynamic_style"></style>'
|
||||
. $kintScript
|
||||
. PHP_EOL;
|
||||
|
||||
echo $script;
|
||||
return ;
|
||||
|
||||
if (str_contains((string) $response->getBody(), '<head>')) {
|
||||
$response->setBody(
|
||||
preg_replace(
|
||||
'/<head>/',
|
||||
'<head>' . $script,
|
||||
$response->getBody(),
|
||||
1
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$response->appendBody($script);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inject debug toolbar into the response.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function respond()
|
||||
{
|
||||
|
||||
|
||||
$debugbar = isset($_GET['debugbar']) ? 1 : '';
|
||||
$debugbar_time = isset($_GET['debugbar_time']) && $_GET['debugbar_time'] ? $_GET['debugbar_time'] : '';
|
||||
|
||||
// If the request contains '?debugbar then we're
|
||||
// simply returning the loading script
|
||||
if ($debugbar) {
|
||||
header('Content-Type: application/javascript');
|
||||
|
||||
ob_start();
|
||||
include $this->config->viewsPath . 'toolbarloader.js';
|
||||
$output = ob_get_clean();
|
||||
$output = str_replace('{url}', WEB_DIR.SELF, $output);
|
||||
echo $output;
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// Otherwise, if it includes ?debugbar_time, then
|
||||
// we should return the entire debugbar.
|
||||
if ($debugbar_time) {
|
||||
helper('security');
|
||||
|
||||
|
||||
$filename = sanitize_filename('debugbar_' . $debugbar_time);
|
||||
$filename = WRITEPATH . 'debugbar/' . $filename . '.json';
|
||||
|
||||
if (is_file($filename)) {
|
||||
// Show the toolbar if it exists
|
||||
echo $this->format(file_get_contents($filename), 'html');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// Filename not found
|
||||
http_response_code(404);
|
||||
|
||||
exit; // Exit here is needed to avoid loading the index page
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format output
|
||||
*/
|
||||
protected function format(string $data, string $format = 'html'): string
|
||||
{
|
||||
$data = json_decode($data, true);
|
||||
|
||||
|
||||
|
||||
$output = '';
|
||||
|
||||
|
||||
$data['styles'] = [];
|
||||
extract($data);
|
||||
ob_start();
|
||||
include $this->config->viewsPath . 'toolbar.tpl.php';
|
||||
$output = ob_get_clean();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function render($name, $data)
|
||||
{
|
||||
extract($data);
|
||||
ob_start();
|
||||
include $this->config->viewsPath . $name;
|
||||
$output = ob_get_clean();
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
/**
|
||||
* Base Toolbar collector
|
||||
*/
|
||||
class BaseCollector
|
||||
{
|
||||
/**
|
||||
* Whether this collector has data that can
|
||||
* be displayed in the Timeline.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTimeline = false;
|
||||
|
||||
/**
|
||||
* Whether this collector needs to display
|
||||
* content in a tab or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTabContent = false;
|
||||
|
||||
/**
|
||||
* Whether this collector needs to display
|
||||
* a label or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasLabel = false;
|
||||
|
||||
/**
|
||||
* Whether this collector has data that
|
||||
* should be shown in the Vars tab.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasVarData = false;
|
||||
|
||||
/**
|
||||
* The 'title' of this Collector.
|
||||
* Used to name things in the toolbar HTML.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* Gets the Collector's title.
|
||||
*/
|
||||
public function getTitle(bool $safe = false): string
|
||||
{
|
||||
if ($safe) {
|
||||
return str_replace(' ', '-', strtolower($this->title));
|
||||
}
|
||||
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any information that should be shown next to the title.
|
||||
*/
|
||||
public function getTitleDetails(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this collector need it's own tab?
|
||||
*/
|
||||
public function hasTabContent(): bool
|
||||
{
|
||||
return (bool) $this->hasTabContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this collector have a label?
|
||||
*/
|
||||
public function hasLabel(): bool
|
||||
{
|
||||
return (bool) $this->hasLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this collector have information for the timeline?
|
||||
*/
|
||||
public function hasTimelineData(): bool
|
||||
{
|
||||
return (bool) $this->hasTimeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the data for the timeline, properly formatted,
|
||||
* or returns an empty array.
|
||||
*/
|
||||
public function timelineData(): array
|
||||
{
|
||||
if (! $this->hasTimeline) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->formatTimelineData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this Collector have data that should be shown in the
|
||||
* 'Vars' tab?
|
||||
*/
|
||||
public function hasVarData(): bool
|
||||
{
|
||||
return (bool) $this->hasVarData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection of data that should be shown in the 'Vars' tab.
|
||||
* The format is an array of sections, each with their own array
|
||||
* of key/value pairs:
|
||||
*
|
||||
* $data = [
|
||||
* 'section 1' => [
|
||||
* 'foo' => 'bar,
|
||||
* 'bar' => 'baz'
|
||||
* ],
|
||||
* 'section 2' => [
|
||||
* 'foo' => 'bar,
|
||||
* 'bar' => 'baz'
|
||||
* ],
|
||||
* ];
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getVarData()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Child classes should implement this to return the timeline data
|
||||
* formatted for correct usage.
|
||||
*
|
||||
* Timeline data should be formatted into arrays that look like:
|
||||
*
|
||||
* [
|
||||
* 'name' => 'Database::Query',
|
||||
* 'component' => 'Database',
|
||||
* 'start' => 10 // milliseconds
|
||||
* 'duration' => 15 // milliseconds
|
||||
* ]
|
||||
*/
|
||||
protected function formatTimelineData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of this collector to be formatted in the toolbar
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function display()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This makes nicer looking paths for the error output.
|
||||
*
|
||||
* @deprecated Use the dedicated `clean_path()` function.
|
||||
*/
|
||||
public function cleanPath(string $file): string
|
||||
{
|
||||
return clean_path($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the "badge" value for the button.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getBadgeValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this collector have any data collected?
|
||||
*
|
||||
* If not, then the toolbar button won't get shown.
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML to display the icon. Should either
|
||||
* be SVG, or a base-64 encoded.
|
||||
*
|
||||
* Recommended dimensions are 24px x 24px
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return settings as an array.
|
||||
*/
|
||||
public function getAsArray(): array
|
||||
{
|
||||
return [
|
||||
'title' => $this->getTitle(),
|
||||
'titleSafe' => $this->getTitle(true),
|
||||
'titleDetails' => $this->getTitleDetails(),
|
||||
'display' => $this->display(),
|
||||
'badgeValue' => $this->getBadgeValue(),
|
||||
'isEmpty' => $this->isEmpty(),
|
||||
'hasTabContent' => $this->hasTabContent(),
|
||||
'hasLabel' => $this->hasLabel(),
|
||||
'icon' => $this->icon(),
|
||||
'hasTimelineData' => $this->hasTimelineData(),
|
||||
'timelineData' => $this->timelineData(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
/**
|
||||
* Debug toolbar configuration
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* Return toolbar config values as an array.
|
||||
*/
|
||||
public static function display(): array
|
||||
{
|
||||
|
||||
return [
|
||||
'ciVersion' => FRAME_VERSION,
|
||||
'phpVersion' => PHP_VERSION,
|
||||
'phpSAPI' => PHP_SAPI,
|
||||
'environment' => ENVIRONMENT,
|
||||
'baseURL' => dr_now_url(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
use CodeIgniter\Database\Query;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Toolbar;
|
||||
|
||||
/**
|
||||
* Collector for the Database tab of the Debug Toolbar.
|
||||
*
|
||||
* @see \CodeIgniter\Debug\Toolbar\Collectors\DatabaseTest
|
||||
*/
|
||||
class Database extends BaseCollector
|
||||
{
|
||||
/**
|
||||
* Whether this collector has timeline data.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTimeline = true;
|
||||
|
||||
/**
|
||||
* Whether this collector should display its own tab.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTabContent = true;
|
||||
|
||||
/**
|
||||
* Whether this collector has data for the Vars tab.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasVarData = false;
|
||||
|
||||
/**
|
||||
* The name used to reference this collector in the toolbar.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Database';
|
||||
|
||||
/**
|
||||
* Array of database connections.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $connections;
|
||||
|
||||
/**
|
||||
* The query instances that have been collected
|
||||
* through the DBQuery Event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $queries = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* The static method used during Events to collect
|
||||
* data.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function collect(Query $query)
|
||||
{
|
||||
$config = config(Toolbar::class);
|
||||
|
||||
// Provide default in case it's not set
|
||||
$max = $config->maxQueries ?: 100;
|
||||
|
||||
if (count(static::$queries) < $max) {
|
||||
$queryString = $query->getQuery();
|
||||
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
if (! is_cli()) {
|
||||
// when called in the browser, the first two trace arrays
|
||||
// are from the DB event trigger, which are unneeded
|
||||
$backtrace = array_slice($backtrace, 2);
|
||||
}
|
||||
|
||||
static::$queries[] = [
|
||||
'query' => $query,
|
||||
'string' => $queryString,
|
||||
'duplicate' => in_array($queryString, array_column(static::$queries, 'string', null), true),
|
||||
'trace' => $backtrace,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns timeline data formatted for the toolbar.
|
||||
*
|
||||
* @return array The formatted data or an empty array.
|
||||
*/
|
||||
protected function formatTimelineData(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->connections as $alias => $connection) {
|
||||
// Connection Time
|
||||
$data[] = [
|
||||
'name' => 'Connecting to Database: "' . $alias . '"',
|
||||
'component' => 'Database',
|
||||
'start' => $connection->getConnectStart(),
|
||||
'duration' => $connection->getConnectDuration(),
|
||||
];
|
||||
}
|
||||
|
||||
foreach (static::$queries as $query) {
|
||||
$data[] = [
|
||||
'name' => 'Query',
|
||||
'component' => 'Database',
|
||||
'start' => $query['query']->getStartTime(true),
|
||||
'duration' => $query['query']->getDuration(),
|
||||
'query' => $query['query']->debugToolbarDisplay(),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of this collector to be formatted in the toolbar
|
||||
*/
|
||||
public function display(): array
|
||||
{
|
||||
$data = [];
|
||||
$data['queries'] = array_map(static function (array $query): array {
|
||||
$isDuplicate = $query['duplicate'] === true;
|
||||
|
||||
$firstNonSystemLine = '';
|
||||
|
||||
foreach ($query['trace'] as $index => &$line) {
|
||||
// simplify file and line
|
||||
if (isset($line['file'])) {
|
||||
$line['file'] = clean_path($line['file']) . ':' . $line['line'];
|
||||
unset($line['line']);
|
||||
} else {
|
||||
$line['file'] = '[internal function]';
|
||||
}
|
||||
|
||||
// find the first trace line that does not originate from `system/`
|
||||
if ($firstNonSystemLine === '' && ! str_contains($line['file'], 'SYSTEMPATH')) {
|
||||
$firstNonSystemLine = $line['file'];
|
||||
}
|
||||
|
||||
// simplify function call
|
||||
if (isset($line['class'])) {
|
||||
$line['function'] = $line['class'] . $line['type'] . $line['function'];
|
||||
unset($line['class'], $line['type']);
|
||||
}
|
||||
|
||||
if (strrpos($line['function'], '{closure}') === false) {
|
||||
$line['function'] .= '()';
|
||||
}
|
||||
|
||||
$line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function'];
|
||||
|
||||
// add index numbering padded with nonbreaking space
|
||||
$indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT);
|
||||
$indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded);
|
||||
|
||||
$line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4);
|
||||
}
|
||||
|
||||
return [
|
||||
'hover' => $isDuplicate ? 'This query was called more than once.' : '',
|
||||
'class' => $isDuplicate ? 'duplicate' : '',
|
||||
'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms',
|
||||
'sql' => $query['query']->debugToolbarDisplay(),
|
||||
'trace' => $query['trace'],
|
||||
'trace-file' => $firstNonSystemLine,
|
||||
'qid' => md5($query['query'] . Time::now()->format('0.u00 U')),
|
||||
];
|
||||
}, static::$queries);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the "badge" value for the button.
|
||||
*/
|
||||
public function getBadgeValue(): int
|
||||
{
|
||||
return count(static::$queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Information to be displayed next to the title.
|
||||
*
|
||||
* @return string The number of queries (in parentheses) or an empty string.
|
||||
*/
|
||||
public function getTitleDetails(): string
|
||||
{
|
||||
$this->getConnections();
|
||||
|
||||
$queryCount = count(static::$queries);
|
||||
$uniqueCount = count(array_filter(static::$queries, function($query) {
|
||||
return $query['duplicate'] === false;
|
||||
}));
|
||||
$connectionCount = count($this->connections);
|
||||
|
||||
return sprintf(
|
||||
'(%d total Quer%s, %d %s unique across %d Connection%s)',
|
||||
$queryCount,
|
||||
$queryCount > 1 ? 'ies' : 'y',
|
||||
$uniqueCount,
|
||||
$uniqueCount > 1 ? 'of them' : '',
|
||||
$connectionCount,
|
||||
$connectionCount > 1 ? 's' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this collector have any data collected?
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return static::$queries === [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon.
|
||||
*
|
||||
* Icon from https://icons8.com - 1em package
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADMSURBVEhLY6A3YExLSwsA4nIycQDIDIhRWEBqamo/UNF/SjDQjF6ocZgAKPkRiFeEhoYyQ4WIBiA9QAuWAPEHqBAmgLqgHcolGQD1V4DMgHIxwbCxYD+QBqcKINseKo6eWrBioPrtQBq/BcgY5ht0cUIYbBg2AJKkRxCNWkDQgtFUNJwtABr+F6igE8olGQD114HMgHIxAVDyAhA/AlpSA8RYUwoeXAPVex5qHCbIyMgwBCkAuQJIY00huDBUz/mUlBQDqHGjgBjAwAAACexpph6oHSQAAAAASUVORK5CYII=';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connections from the database config
|
||||
*/
|
||||
private function getConnections(): void
|
||||
{
|
||||
$this->connections = \Config\Database::getConnections();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
/**
|
||||
* Files collector
|
||||
*/
|
||||
class Files extends BaseCollector
|
||||
{
|
||||
/**
|
||||
* Whether this collector has data that can
|
||||
* be displayed in the Timeline.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTimeline = false;
|
||||
|
||||
/**
|
||||
* Whether this collector needs to display
|
||||
* content in a tab or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTabContent = true;
|
||||
|
||||
/**
|
||||
* The 'title' of this Collector.
|
||||
* Used to name things in the toolbar HTML.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Files';
|
||||
|
||||
/**
|
||||
* Returns any information that should be shown next to the title.
|
||||
*/
|
||||
public function getTitleDetails(): string
|
||||
{
|
||||
return '( ' . count(get_included_files()) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of this collector to be formatted in the toolbar
|
||||
*/
|
||||
public function display(): array
|
||||
{
|
||||
$rawFiles = get_included_files();
|
||||
$coreFiles = [];
|
||||
$userFiles = [];
|
||||
|
||||
foreach ($rawFiles as $file) {
|
||||
$path = clean_path($file);
|
||||
|
||||
if (str_contains($path, 'SYSTEMPATH')) {
|
||||
$coreFiles[] = [
|
||||
'path' => $path,
|
||||
'name' => basename($file),
|
||||
];
|
||||
} else {
|
||||
$userFiles[] = [
|
||||
'path' => $path,
|
||||
'name' => basename($file),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
sort($userFiles);
|
||||
sort($coreFiles);
|
||||
|
||||
return [
|
||||
'coreFiles' => $coreFiles,
|
||||
'userFiles' => $userFiles,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the number of included files as a badge in the tab button.
|
||||
*/
|
||||
public function getBadgeValue(): int
|
||||
{
|
||||
return count(get_included_files());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon.
|
||||
*
|
||||
* Icon from https://icons8.com - 1em package
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGBSURBVEhL7ZQ9S8NQGIVTBQUncfMfCO4uLgoKbuKQOWg+OkXERRE1IAXrIHbVDrqIDuLiJgj+gro7S3dnpfq88b1FMTE3VZx64HBzzvvZWxKnj15QCcPwCD5HUfSWR+JtzgmtsUcQBEva5IIm9SwSu+95CAWbUuy67qBa32ByZEDpIaZYZSZMjjQuPcQUq8yEyYEb8FSerYeQVGbAFzJkX1PyQWLhgCz0BxTCekC1Wp0hsa6yokzhed4oje6Iz6rlJEkyIKfUEFtITVtQdAibn5rMyaYsMS+a5wTv8qeXMhcU16QZbKgl3hbs+L4/pnpdc87MElZgq10p5DxGdq8I7xrvUWUKvG3NbSK7ubngYzdJwSsF7TiOh9VOgfcEz1UayNe3JUPM1RWC5GXYgTfc75B4NBmXJnAtTfpABX0iPvEd9ezALwkplCFXcr9styiNOKc1RRZpaPM9tcqBwlWzGY1qPL9wjqRBgF5BH6j8HWh2S7MHlX8PrmbK+k/8PzjOOzx1D3i1pKTTAAAAAElFTkSuQmCC';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Routes collector
|
||||
*/
|
||||
class Routes extends BaseCollector
|
||||
{
|
||||
/**
|
||||
* Whether this collector has data that can
|
||||
* be displayed in the Timeline.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTimeline = false;
|
||||
|
||||
/**
|
||||
* Whether this collector needs to display
|
||||
* content in a tab or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTabContent = true;
|
||||
|
||||
/**
|
||||
* The 'title' of this Collector.
|
||||
* Used to name things in the toolbar HTML.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Routes';
|
||||
|
||||
/**
|
||||
* Returns the data of this collector to be formatted in the toolbar
|
||||
*
|
||||
* @return array{
|
||||
* matchedRoute: list<array{
|
||||
* directory: string,
|
||||
* controller: string,
|
||||
* method: string,
|
||||
* paramCount: int,
|
||||
* truePCount: int,
|
||||
* params: list<array{
|
||||
* name: string,
|
||||
* value: mixed
|
||||
* }>
|
||||
* }>,
|
||||
* routes: list<array{
|
||||
* method: string,
|
||||
* route: string,
|
||||
* handler: string
|
||||
* }>
|
||||
* }
|
||||
*
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function display(): array
|
||||
{
|
||||
|
||||
$file = APPPATH;
|
||||
if ($file == FRAMEPATH) {
|
||||
$file = CMSPATH.'Control';
|
||||
} else {
|
||||
$file.= 'Controllers';
|
||||
}
|
||||
|
||||
if (IS_ADMIN) {
|
||||
$file.= '/Admin';
|
||||
} elseif (IS_MEMBER) {
|
||||
$file.= '/Member';
|
||||
} elseif (IS_API) {
|
||||
$file.= '/Api';
|
||||
}
|
||||
|
||||
return [
|
||||
'matchedRoute' => [
|
||||
'uri' => \Phpcmf\Service::L('Router')->uri(),
|
||||
'url' => dr_now_url(),
|
||||
'app' => APP_DIR ? APP_DIR : '/',
|
||||
'controller' => \Phpcmf\Service::L('Router')->class,
|
||||
'method' => \Phpcmf\Service::L('Router')->method,
|
||||
'file' => $file.'/'.ucfirst(\Phpcmf\Service::L('Router')->class).'.php',
|
||||
],
|
||||
'get' => $_GET,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of all the routes in the system.
|
||||
*/
|
||||
public function getBadgeValue(): int
|
||||
{
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon.
|
||||
*
|
||||
* Icon from https://icons8.com - 1em package
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFDSURBVEhL7ZRNSsNQFIUjVXSiOFEcuQIHDpzpxC0IGYeE/BEInbWlCHEDLsSiuANdhKDjgm6ggtSJ+l25ldrmmTwIgtgDh/t37r1J+16cX0dRFMtpmu5pWAkrvYjjOB7AETzStBFW+inxu3KUJMmhludQpoflS1zXban4LYqiO224h6VLTHr8Z+z8EpIHFF9gG78nDVmW7UgTHKjsCyY98QP+pcq+g8Ku2s8G8X3f3/I8b038WZTp+bO38zxfFd+I6YY6sNUvFlSDk9CRhiAI1jX1I9Cfw7GG1UB8LAuwbU0ZwQnbRDeEN5qqBxZMLtE1ti9LtbREnMIuOXnyIf5rGIb7Wq8HmlZgwYBH7ORTcKH5E4mpjeGt9fBZcHE2GCQ3Vt7oTNPNg+FXLHnSsHkw/FR+Gg2bB8Ptzrst/v6C/wrH+QB+duli6MYJdQAAAABJRU5ErkJggg==';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* Views collector
|
||||
*/
|
||||
class Views extends BaseCollector
|
||||
{
|
||||
/**
|
||||
* Whether this collector has data that can
|
||||
* be displayed in the Timeline.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTimeline = true;
|
||||
|
||||
/**
|
||||
* Whether this collector needs to display
|
||||
* content in a tab or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTabContent = false;
|
||||
|
||||
/**
|
||||
* Whether this collector needs to display
|
||||
* a label or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasLabel = true;
|
||||
|
||||
/**
|
||||
* Whether this collector has data that
|
||||
* should be shown in the Vars tab.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasVarData = true;
|
||||
|
||||
/**
|
||||
* The 'title' of this Collector.
|
||||
* Used to name things in the toolbar HTML.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Views';
|
||||
|
||||
/**
|
||||
* Instance of the shared Renderer service
|
||||
*
|
||||
* @var RendererInterface|null
|
||||
*/
|
||||
protected $viewer;
|
||||
|
||||
/**
|
||||
* Views counter
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $views = [];
|
||||
|
||||
|
||||
/**
|
||||
* 把CI模板类改成PHPCMF模板类用于debug.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->viewer = \Phpcmf\Service::V();
|
||||
$this->hasTabContent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of this collector to be formatted in the toolbar
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function display(): array
|
||||
{
|
||||
|
||||
$vars = [];
|
||||
$tpl_var = $this->viewer->get_data();
|
||||
if ($tpl_var) {
|
||||
foreach ($tpl_var as $key => $value) {
|
||||
if (in_array($key, ['member', 'admin'])) {
|
||||
continue;
|
||||
}
|
||||
$vars[] = [
|
||||
'name' => $key,
|
||||
'value' => var_export($value, true),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'vars' => $vars,
|
||||
'tips' => $this->viewer->get_load_tips(),
|
||||
'times' => [['tpl' => $this->viewer->get_view_time()]],
|
||||
'files' => $this->viewer->get_view_files(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any information that should be shown next to the title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBadgeValue(): int
|
||||
{
|
||||
return dr_count($this->viewer->get_view_files());
|
||||
}
|
||||
|
||||
|
||||
public function setData($data = [], $context = null)
|
||||
{
|
||||
|
||||
if (! empty($context))
|
||||
{
|
||||
foreach ($data as $key => &$value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
foreach ($value as &$obj)
|
||||
{
|
||||
$obj = $this->objectToArray($obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = $this->objectToArray($value);
|
||||
}
|
||||
|
||||
$this->dataContexts[$key] = $context;
|
||||
}
|
||||
}
|
||||
|
||||
$this->tempData = $this->tempData ?? $this->data;
|
||||
$this->tempData = array_merge($this->tempData, $data);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
private function initViewer(): void
|
||||
{
|
||||
$this->viewer = \Phpcmf\Service::V();
|
||||
}
|
||||
|
||||
/**
|
||||
* Child classes should implement this to return the timeline data
|
||||
* formatted for correct usage.
|
||||
*/
|
||||
protected function formatTimelineData(): array
|
||||
{
|
||||
$this->initViewer();
|
||||
|
||||
$data = [];
|
||||
|
||||
$rows = $this->viewer->getPerformanceData();
|
||||
|
||||
foreach ($rows as $info) {
|
||||
$data[] = [
|
||||
'name' => 'View: ' . $info['view'],
|
||||
'component' => 'Views',
|
||||
'start' => $info['start'],
|
||||
'duration' => $info['end'] - $info['start'],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection of data that should be shown in the 'Vars' tab.
|
||||
* The format is an array of sections, each with their own array
|
||||
* of key/value pairs:
|
||||
*
|
||||
* $data = [
|
||||
* 'section 1' => [
|
||||
* 'foo' => 'bar,
|
||||
* 'bar' => 'baz'
|
||||
* ],
|
||||
* 'section 2' => [
|
||||
* 'foo' => 'bar,
|
||||
* 'bar' => 'baz'
|
||||
* ],
|
||||
* ];
|
||||
*/
|
||||
public function getVarData(): array
|
||||
{
|
||||
$this->initViewer();
|
||||
|
||||
return [
|
||||
'View Data' => $this->viewer->getData(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the icon.
|
||||
*
|
||||
* Icon from https://icons8.com - 1em package
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADeSURBVEhL7ZSxDcIwEEWNYA0YgGmgyAaJLTcUaaBzQQEVjMEabBQxAdw53zTHiThEovGTfnE/9rsoRUxhKLOmaa6Uh7X2+UvguLCzVxN1XW9x4EYHzik033Hp3X0LO+DaQG8MDQcuq6qao4qkHuMgQggLvkPLjqh00ZgFDBacMJYFkuwFlH1mshdkZ5JPJERA9JpI6xNCBESvibQ+IURA9JpI6xNCBESvibQ+IURA9DTsuHTOrVFFxixgB/eUFlU8uKJ0eDBFOu/9EvoeKnlJS2/08Tc8NOwQ8sIfMeYFjqKDjdU2sp4AAAAASUVORK5CYII=';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="debug-bar-width6r">Time</th>
|
||||
<th>Query String</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($queries) { foreach ($queries as $t) { ?>
|
||||
<tr class="<?php echo $t['class'];?>" title="<?php echo $t['hover'];?>" data-toggle="<?php echo $t['qid'];?>-trace">
|
||||
<td class="narrow"><?php echo $t['duration'];?></td>
|
||||
<td><?php echo $t['sql'];?></td>
|
||||
<td class="debug-bar-alignRight"><strong><?php echo $t['trace-file'];?></strong></td>
|
||||
</tr>
|
||||
<tr class="muted debug-bar-ndisplay" id="<?php echo $t['qid'];?>-trace">
|
||||
<td></td>
|
||||
<td colspan="2">
|
||||
<?php foreach ($t['trace'] as $tt) { ?>
|
||||
<?php echo $tt['index'];?>
|
||||
<strong><?php echo $tt['file'];?></strong><br/>
|
||||
<?php echo $tt['function'];?><br/><br/>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,17 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<?php foreach ($userFiles as $t) { ?>
|
||||
<tr>
|
||||
<td> <?php echo $t['name'];?></td>
|
||||
<td> <?php echo $t['path'];?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php foreach ($coreFiles as $t) { ?>
|
||||
|
||||
<tr class="muted">
|
||||
<td class="debug-bar-width20e"> <?php echo $t['name'];?> </td>
|
||||
<td> <?php echo $t['path'];?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,32 @@
|
||||
<h3>Matched Route</h3>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<?php if ($matchedRoute) { foreach ($matchedRoute as $key => $tt) { ?>
|
||||
<tr>
|
||||
<td width=200><?php echo $key;?></td>
|
||||
<td><?php echo $tt;?></td>
|
||||
</tr>
|
||||
<?php } } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<h3>GET</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>key</th>
|
||||
<th>value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($get) { foreach ($get as $key => $tt) { ?>
|
||||
<tr>
|
||||
<td width=200><?php echo $key;?></td>
|
||||
<td><?php echo $tt;?></td>
|
||||
</tr>
|
||||
<?php } } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
<?php if ($times) { foreach ($times as $t) { ?>
|
||||
<h3>运行时间:<?php echo $t['tpl'];?>ms</h3>
|
||||
<?php } } ?>
|
||||
|
||||
|
||||
<br>
|
||||
<h2>模板文件</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width=200>模板</th>
|
||||
<th>路径</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($files) { foreach ($files as $t) { ?>
|
||||
<tr>
|
||||
<td><?php echo $t['name'];?></td>
|
||||
<td><?php echo $t['path'];?></td>
|
||||
</tr>
|
||||
<?php } } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<h2>引用提示</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width=200>模板</th>
|
||||
<th>提示</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($tips) { foreach ($tips as $t) { ?>
|
||||
<tr>
|
||||
<td><?php echo $t['name'];?></td>
|
||||
<td><?php echo $t['tips'];?></td>
|
||||
</tr>
|
||||
<?php } } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>模板变量</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<?php if ($vars) { foreach ($vars as $t) { ?>
|
||||
<tr>
|
||||
<td width=200><?php echo $t['name'];?></td>
|
||||
<td><pre><?php echo $t['value'];?></pre></td>
|
||||
</tr>
|
||||
<?php } } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,887 @@
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
#debug-icon {
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
z-index: 10000;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
#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-vars {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#debug-bar {
|
||||
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 {
|
||||
display: flex;
|
||||
font-weight: normal;
|
||||
margin: 0 0 0 auto;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
}
|
||||
#debug-bar h1 svg {
|
||||
width: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
#debug-bar h2 {
|
||||
font-weight: bold;
|
||||
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;
|
||||
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 td[data-debugbar-route] form {
|
||||
display: none;
|
||||
}
|
||||
#debug-bar td[data-debugbar-route]:hover form {
|
||||
display: block;
|
||||
}
|
||||
#debug-bar td[data-debugbar-route]:hover > div {
|
||||
display: none;
|
||||
}
|
||||
#debug-bar td[data-debugbar-route] input[type=text] {
|
||||
padding: 2px;
|
||||
}
|
||||
#debug-bar .toolbar {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 12px 0 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 10000;
|
||||
}
|
||||
#debug-bar .toolbar .rotate {
|
||||
animation: toolbar-rotate 9s linear infinite;
|
||||
}
|
||||
@keyframes toolbar-rotate {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
#debug-bar.fixed-top {
|
||||
bottom: auto;
|
||||
top: 0;
|
||||
}
|
||||
#debug-bar.fixed-top .tab {
|
||||
bottom: auto;
|
||||
top: 36px;
|
||||
}
|
||||
#debug-bar #toolbar-position,
|
||||
#debug-bar #toolbar-theme {
|
||||
padding: 0 6px;
|
||||
display: inline-flex;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
}
|
||||
#debug-bar #toolbar-position:hover,
|
||||
#debug-bar #toolbar-theme:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
#debug-bar #debug-bar-link {
|
||||
display: flex;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#debug-bar .ci-label {
|
||||
display: inline-flex;
|
||||
font-size: 14px;
|
||||
}
|
||||
#debug-bar .ci-label:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
#debug-bar .ci-label a {
|
||||
color: inherit;
|
||||
display: flex;
|
||||
letter-spacing: normal;
|
||||
padding: 0 10px;
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
}
|
||||
#debug-bar .ci-label img {
|
||||
margin: 6px 3px 6px 0;
|
||||
width: 16px !important;
|
||||
}
|
||||
#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 {
|
||||
height: fit-content;
|
||||
text-align: left;
|
||||
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 {
|
||||
position: static;
|
||||
display: table;
|
||||
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;
|
||||
max-width: none;
|
||||
}
|
||||
#debug-bar .timeline td.child-container {
|
||||
padding: 0px;
|
||||
}
|
||||
#debug-bar .timeline td.child-container .timeline {
|
||||
margin: 0px;
|
||||
}
|
||||
#debug-bar .timeline td.child-container .timeline td:first-child:not(.child-container) {
|
||||
padding-left: calc(5px + 10px * var(--level));
|
||||
}
|
||||
#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 .timeline .timeline-parent {
|
||||
cursor: pointer;
|
||||
}
|
||||
#debug-bar .timeline .timeline-parent td:first-child nav {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMCAxNTAiPjxwYXRoIGQ9Ik02IDdoMThsLTkgMTV6bTAgMzBoMThsLTkgMTV6bTAgNDVoMThsLTktMTV6bTAgMzBoMThsLTktMTV6bTAgMTJsMTggMThtLTE4IDBsMTgtMTgiIGZpbGw9IiM1NTUiLz48cGF0aCBkPSJNNiAxMjZsMTggMThtLTE4IDBsMTgtMTgiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSIjNTU1Ii8+PC9zdmc+") no-repeat scroll 0 0/15px 75px transparent;
|
||||
background-position: 0 25%;
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-right: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#debug-bar .timeline .timeline-parent-open {
|
||||
background-color: #DFDFDF;
|
||||
}
|
||||
#debug-bar .timeline .timeline-parent-open td:first-child nav {
|
||||
background-position: 0 75%;
|
||||
}
|
||||
#debug-bar .timeline .child-row:hover {
|
||||
background: transparent;
|
||||
}
|
||||
#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-bar > .debug-bar-dblock {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.debug-view.show-view {
|
||||
border: 1px solid;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.debug-view-path {
|
||||
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;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
#debug-bar .ci-label img {
|
||||
margin: unset;
|
||||
}
|
||||
.hide-sm {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
#debug-bar table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
margin: 5px 5px 10px 5px;
|
||||
}
|
||||
#debug-bar table td,
|
||||
#debug-bar table th {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
#debug-bar .timeline {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
#debug-bar .toolbar {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
#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: #DD8615;
|
||||
}
|
||||
#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: #DD4814;
|
||||
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: #DD8615;
|
||||
}
|
||||
#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: #DD4814;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
#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: #DD8615;
|
||||
}
|
||||
#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: #DD4814;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
#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.dark td[data-debugbar-route] input[type=text] {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#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: #DD8615;
|
||||
}
|
||||
#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: #DD4814;
|
||||
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%;
|
||||
}
|
||||
|
||||
.debug-bar-width10 {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.debug-bar-width70p {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.debug-bar-width190p {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.debug-bar-width20e {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.debug-bar-width6r {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.debug-bar-ndisplay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.debug-bar-alignRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.debug-bar-alignLeft {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.debug-bar-noverflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.debug-bar-dtableRow {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.debug-bar-dinlineBlock {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.debug-bar-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.debug-bar-mleft4 {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.debug-bar-level-0 {
|
||||
--level: 0;
|
||||
}
|
||||
|
||||
.debug-bar-level-1 {
|
||||
--level: 1;
|
||||
}
|
||||
|
||||
.debug-bar-level-2 {
|
||||
--level: 2;
|
||||
}
|
||||
|
||||
.debug-bar-level-3 {
|
||||
--level: 3;
|
||||
}
|
||||
|
||||
.debug-bar-level-4 {
|
||||
--level: 4;
|
||||
}
|
||||
|
||||
.debug-bar-level-5 {
|
||||
--level: 5;
|
||||
}
|
||||
|
||||
.debug-bar-level-6 {
|
||||
--level: 6;
|
||||
}
|
||||
@@ -0,0 +1,817 @@
|
||||
/*
|
||||
* Functionality for the CodeIgniter Debug Toolbar.
|
||||
*/
|
||||
|
||||
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();
|
||||
ciDebugBar.routerLink();
|
||||
ciDebugBar.setHotReloadState();
|
||||
|
||||
document
|
||||
.getElementById("debug-bar-link")
|
||||
.addEventListener("click", ciDebugBar.toggleToolbar, true);
|
||||
document
|
||||
.getElementById("debug-icon-link")
|
||||
.addEventListener("click", ciDebugBar.toggleToolbar, true);
|
||||
|
||||
historyLoad = this.toolbar.getElementsByClassName("ci-history-load");
|
||||
|
||||
if (historyLoad.length) {
|
||||
// Allows highlighting the row of the current history request
|
||||
var btn = this.toolbar.querySelector(
|
||||
'button[data-time="' + localStorage.getItem("debugbar-time-new") + '"]'
|
||||
);
|
||||
ciDebugBar.addClass(btn.parentNode.parentNode, "current");
|
||||
|
||||
|
||||
for (var i = 0; i < historyLoad.length; i++) {
|
||||
historyLoad[i].addEventListener(
|
||||
"click",
|
||||
function () {
|
||||
loadDoc(this.getAttribute("data-time"));
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Display the active Tab on page load
|
||||
var tab = ciDebugBar.readCookie("debug-bar-tab");
|
||||
if (document.getElementById(tab)) {
|
||||
var el = document.getElementById(tab);
|
||||
ciDebugBar.switchClass(el, "debug-bar-ndisplay", "debug-bar-dblock");
|
||||
ciDebugBar.addClass(el, "active");
|
||||
tab = document.querySelector("[data-tab=" + tab + "]");
|
||||
if (tab) {
|
||||
ciDebugBar.addClass(tab.parentNode, "active");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
createListeners: function () {
|
||||
var buttons = [].slice.call(
|
||||
this.toolbar.querySelectorAll(".ci-label a")
|
||||
);
|
||||
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttons[i].addEventListener("click", ciDebugBar.showTab, true);
|
||||
}
|
||||
|
||||
// Hook up generic toggle via data attributes `data-toggle="foo"`
|
||||
var links = this.toolbar.querySelectorAll("[data-toggle]");
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
let toggleData = links[i].getAttribute("data-toggle");
|
||||
if (toggleData === "datatable") {
|
||||
|
||||
let datatable = links[i].getAttribute("data-table");
|
||||
links[i].addEventListener("click", function() {
|
||||
ciDebugBar.toggleDataTable(datatable)
|
||||
}, true);
|
||||
|
||||
} else if (toggleData === "childrows") {
|
||||
|
||||
let child = links[i].getAttribute("data-child");
|
||||
links[i].addEventListener("click", function() {
|
||||
ciDebugBar.toggleChildRows(child)
|
||||
}, true);
|
||||
|
||||
} else {
|
||||
links[i].addEventListener("click", ciDebugBar.toggleRows, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showTab: function () {
|
||||
// Get the target tab, if any
|
||||
var tab = document.getElementById(this.getAttribute("data-tab"));
|
||||
|
||||
// If the label have not a tab stops here
|
||||
if (! tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove debug-bar-tab cookie
|
||||
ciDebugBar.createCookie("debug-bar-tab", "", -1);
|
||||
|
||||
// Check our current state.
|
||||
var state = tab.classList.contains("debug-bar-dblock");
|
||||
|
||||
// Hide all tabs
|
||||
var tabs = document.querySelectorAll("#debug-bar .tab");
|
||||
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
ciDebugBar.switchClass(tabs[i], "debug-bar-dblock", "debug-bar-ndisplay");
|
||||
}
|
||||
|
||||
// Mark all labels as inactive
|
||||
var labels = document.querySelectorAll("#debug-bar .ci-label");
|
||||
|
||||
for (var i = 0; i < labels.length; i++) {
|
||||
ciDebugBar.removeClass(labels[i], "active");
|
||||
}
|
||||
|
||||
// Show/hide the selected tab
|
||||
if (! state) {
|
||||
ciDebugBar.switchClass(tab, "debug-bar-ndisplay", "debug-bar-dblock");
|
||||
ciDebugBar.addClass(this.parentNode, "active");
|
||||
// Create debug-bar-tab cookie to persistent state
|
||||
ciDebugBar.createCookie(
|
||||
"debug-bar-tab",
|
||||
this.getAttribute("data-tab"),
|
||||
365
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
addClass: function (el, className) {
|
||||
if (el.classList) {
|
||||
el.classList.add(className);
|
||||
} else {
|
||||
el.className += " " + className;
|
||||
}
|
||||
},
|
||||
|
||||
removeClass: function (el, className) {
|
||||
if (el.classList) {
|
||||
el.classList.remove(className);
|
||||
} else {
|
||||
el.className = el.className.replace(
|
||||
new RegExp(
|
||||
"(^|\\b)" + className.split(" ").join("|") + "(\\b|$)",
|
||||
"gi"
|
||||
),
|
||||
" "
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
switchClass : function(el, classFrom, classTo) {
|
||||
ciDebugBar.removeClass(el, classFrom);
|
||||
ciDebugBar.addClass(el, classTo);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle display of another object based on
|
||||
* the data-toggle value of this object
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
toggleRows: function (event) {
|
||||
if (event.target) {
|
||||
let row = event.target.closest("tr");
|
||||
let target = document.getElementById(
|
||||
row.getAttribute("data-toggle")
|
||||
);
|
||||
|
||||
if (target.classList.contains("debug-bar-ndisplay")) {
|
||||
ciDebugBar.switchClass(target, "debug-bar-ndisplay", "debug-bar-dtableRow");
|
||||
} else {
|
||||
ciDebugBar.switchClass(target, "debug-bar-dtableRow", "debug-bar-ndisplay");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle display of a data table
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
toggleDataTable: function (obj) {
|
||||
if (typeof obj == "string") {
|
||||
obj = document.getElementById(obj + "_table");
|
||||
}
|
||||
|
||||
if (obj) {
|
||||
if (obj.classList.contains("debug-bar-ndisplay")) {
|
||||
ciDebugBar.switchClass(obj, "debug-bar-ndisplay", "debug-bar-dblock");
|
||||
} else {
|
||||
ciDebugBar.switchClass(obj, "debug-bar-dblock", "debug-bar-ndisplay");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle display of timeline child elements
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
toggleChildRows: function (obj) {
|
||||
if (typeof obj == "string") {
|
||||
par = document.getElementById(obj + "_parent");
|
||||
obj = document.getElementById(obj + "_children");
|
||||
}
|
||||
|
||||
if (par && obj) {
|
||||
|
||||
if (obj.classList.contains("debug-bar-ndisplay")) {
|
||||
ciDebugBar.removeClass(obj, "debug-bar-ndisplay");
|
||||
} else {
|
||||
ciDebugBar.addClass(obj, "debug-bar-ndisplay");
|
||||
}
|
||||
|
||||
par.classList.toggle("timeline-parent-open");
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Toggle tool bar from full to icon and icon to full
|
||||
*/
|
||||
toggleToolbar: function () {
|
||||
var open = ! ciDebugBar.toolbar.classList.contains("debug-bar-ndisplay");
|
||||
|
||||
if (open) {
|
||||
ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-ndisplay", "debug-bar-dinlineBlock");
|
||||
ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-dinlineBlock", "debug-bar-ndisplay");
|
||||
} else {
|
||||
ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-dinlineBlock", "debug-bar-ndisplay");
|
||||
ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-ndisplay", "debug-bar-dinlineBlock");
|
||||
}
|
||||
|
||||
// Remember it for other page loads on this site
|
||||
ciDebugBar.createCookie("debug-bar-state", "", -1);
|
||||
ciDebugBar.createCookie(
|
||||
"debug-bar-state",
|
||||
open == true ? "minimized" : "open",
|
||||
365
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the initial state of the toolbar (open or minimized) when
|
||||
* the page is first loaded to allow it to remember the state between refreshes.
|
||||
*/
|
||||
setToolbarState: function () {
|
||||
var open = ciDebugBar.readCookie("debug-bar-state");
|
||||
|
||||
if (open != "open") {
|
||||
ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-ndisplay", "debug-bar-dinlineBlock");
|
||||
ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-dinlineBlock", "debug-bar-ndisplay");
|
||||
} else {
|
||||
ciDebugBar.switchClass(ciDebugBar.icon, "debug-bar-dinlineBlock", "debug-bar-ndisplay");
|
||||
ciDebugBar.switchClass(ciDebugBar.toolbar, "debug-bar-ndisplay", "debug-bar-dinlineBlock");
|
||||
}
|
||||
},
|
||||
|
||||
toggleViewsHints: function () {
|
||||
// Avoid toggle hints on history requests that are not the initial
|
||||
if (
|
||||
localStorage.getItem("debugbar-time") !=
|
||||
localStorage.getItem("debugbar-time-new")
|
||||
) {
|
||||
var a = document.querySelector('a[data-tab="ci-views"]');
|
||||
a.href = "#";
|
||||
return;
|
||||
}
|
||||
|
||||
var nodeList = []; // [ Element, NewElement( 1 )/OldElement( 0 ) ]
|
||||
var sortedComments = [];
|
||||
var comments = [];
|
||||
|
||||
var getComments = function () {
|
||||
var nodes = [];
|
||||
var result = [];
|
||||
var xpathResults = document.evaluate(
|
||||
"//comment()[starts-with(., ' DEBUG-VIEW')]",
|
||||
document,
|
||||
null,
|
||||
XPathResult.ANY_TYPE,
|
||||
null
|
||||
);
|
||||
var nextNode = xpathResults.iterateNext();
|
||||
while (nextNode) {
|
||||
nodes.push(nextNode);
|
||||
nextNode = xpathResults.iterateNext();
|
||||
}
|
||||
|
||||
// sort comment by opening and closing tags
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
// get file path + name to use as key
|
||||
var path = nodes[i].nodeValue.substring(
|
||||
18,
|
||||
nodes[i].nodeValue.length - 1
|
||||
);
|
||||
|
||||
if (nodes[i].nodeValue[12] === "S") {
|
||||
// simple check for start comment
|
||||
// create new entry
|
||||
result[path] = [nodes[i], null];
|
||||
} else if (result[path]) {
|
||||
// add to existing entry
|
||||
result[path][1] = nodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// find node that has TargetNode as parentNode
|
||||
var getParentNode = function (node, targetNode) {
|
||||
if (node.parentNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.parentNode !== targetNode) {
|
||||
return getParentNode(node.parentNode, targetNode);
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
// define invalid & outer ( also invalid ) elements
|
||||
const INVALID_ELEMENTS = ["NOSCRIPT", "SCRIPT", "STYLE"];
|
||||
const OUTER_ELEMENTS = ["HTML", "BODY", "HEAD"];
|
||||
|
||||
var getValidElementInner = function (node, reverse) {
|
||||
// handle invalid tags
|
||||
if (OUTER_ELEMENTS.indexOf(node.nodeName) !== -1) {
|
||||
for (var i = 0; i < document.body.children.length; ++i) {
|
||||
var index = reverse
|
||||
? document.body.children.length - (i + 1)
|
||||
: i;
|
||||
var element = document.body.children[index];
|
||||
|
||||
// skip invalid tags
|
||||
if (INVALID_ELEMENTS.indexOf(element.nodeName) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return [element, reverse];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// get to next valid element
|
||||
while (
|
||||
node !== null &&
|
||||
INVALID_ELEMENTS.indexOf(node.nodeName) !== -1
|
||||
) {
|
||||
node = reverse
|
||||
? node.previousElementSibling
|
||||
: node.nextElementSibling;
|
||||
}
|
||||
|
||||
// return non array if we couldnt find something
|
||||
if (node === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [node, reverse];
|
||||
};
|
||||
|
||||
// get next valid element ( to be safe to add divs )
|
||||
// @return [ element, skip element ] or null if we couldnt find a valid place
|
||||
var getValidElement = function (nodeElement) {
|
||||
if (nodeElement) {
|
||||
if (nodeElement.nextElementSibling !== null) {
|
||||
return (
|
||||
getValidElementInner(
|
||||
nodeElement.nextElementSibling,
|
||||
false
|
||||
) ||
|
||||
getValidElementInner(
|
||||
nodeElement.previousElementSibling,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
if (nodeElement.previousElementSibling !== null) {
|
||||
return getValidElementInner(
|
||||
nodeElement.previousElementSibling,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// something went wrong! -> element is not in DOM
|
||||
return null;
|
||||
};
|
||||
|
||||
function showHints() {
|
||||
// Had AJAX? Reset view blocks
|
||||
sortedComments = getComments();
|
||||
|
||||
for (var key in sortedComments) {
|
||||
var startElement = getValidElement(sortedComments[key][0]);
|
||||
var endElement = getValidElement(sortedComments[key][1]);
|
||||
|
||||
// skip if we couldnt get a valid element
|
||||
if (startElement === null || endElement === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find element which has same parent as startelement
|
||||
var jointParent = getParentNode(
|
||||
endElement[0],
|
||||
startElement[0].parentNode
|
||||
);
|
||||
if (jointParent === null) {
|
||||
// find element which has same parent as endelement
|
||||
jointParent = getParentNode(
|
||||
startElement[0],
|
||||
endElement[0].parentNode
|
||||
);
|
||||
if (jointParent === null) {
|
||||
// both tries failed
|
||||
continue;
|
||||
} else {
|
||||
startElement[0] = jointParent;
|
||||
}
|
||||
} else {
|
||||
endElement[0] = jointParent;
|
||||
}
|
||||
|
||||
var debugDiv = document.createElement("div"); // holder
|
||||
var debugPath = document.createElement("div"); // path
|
||||
var childArray = startElement[0].parentNode.childNodes; // target child array
|
||||
var parent = startElement[0].parentNode;
|
||||
var start, end;
|
||||
|
||||
// setup container
|
||||
debugDiv.classList.add("debug-view");
|
||||
debugDiv.classList.add("show-view");
|
||||
debugPath.classList.add("debug-view-path");
|
||||
debugPath.innerText = key;
|
||||
debugDiv.appendChild(debugPath);
|
||||
|
||||
// calc distance between them
|
||||
// start
|
||||
for (var i = 0; i < childArray.length; ++i) {
|
||||
// check for comment ( start & end ) -> if its before valid start element
|
||||
if (
|
||||
childArray[i] === sortedComments[key][1] ||
|
||||
childArray[i] === sortedComments[key][0] ||
|
||||
childArray[i] === startElement[0]
|
||||
) {
|
||||
start = i;
|
||||
if (childArray[i] === sortedComments[key][0]) {
|
||||
start++; // increase to skip the start comment
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// adjust if we want to skip the start element
|
||||
if (startElement[1]) {
|
||||
start++;
|
||||
}
|
||||
|
||||
// end
|
||||
for (var i = start; i < childArray.length; ++i) {
|
||||
if (childArray[i] === endElement[0]) {
|
||||
end = i;
|
||||
// dont break to check for end comment after end valid element
|
||||
} else if (childArray[i] === sortedComments[key][1]) {
|
||||
// if we found the end comment, we can break
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// move elements
|
||||
var number = end - start;
|
||||
if (endElement[1]) {
|
||||
number++;
|
||||
}
|
||||
for (var i = 0; i < number; ++i) {
|
||||
if (INVALID_ELEMENTS.indexOf(childArray[start]) !== -1) {
|
||||
// skip invalid childs that can cause problems if moved
|
||||
start++;
|
||||
continue;
|
||||
}
|
||||
debugDiv.appendChild(childArray[start]);
|
||||
}
|
||||
|
||||
// add container to DOM
|
||||
nodeList.push(parent.insertBefore(debugDiv, childArray[start]));
|
||||
}
|
||||
|
||||
ciDebugBar.createCookie("debug-view", "show", 365);
|
||||
ciDebugBar.addClass(btn, "active");
|
||||
}
|
||||
|
||||
function hideHints() {
|
||||
for (var i = 0; i < nodeList.length; ++i) {
|
||||
var index;
|
||||
|
||||
// find index
|
||||
for (
|
||||
var j = 0;
|
||||
j < nodeList[i].parentNode.childNodes.length;
|
||||
++j
|
||||
) {
|
||||
if (nodeList[i].parentNode.childNodes[j] === nodeList[i]) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// move child back
|
||||
while (nodeList[i].childNodes.length !== 1) {
|
||||
nodeList[i].parentNode.insertBefore(
|
||||
nodeList[i].childNodes[1],
|
||||
nodeList[i].parentNode.childNodes[index].nextSibling
|
||||
);
|
||||
index++;
|
||||
}
|
||||
|
||||
nodeList[i].parentNode.removeChild(nodeList[i]);
|
||||
}
|
||||
nodeList.length = 0;
|
||||
|
||||
ciDebugBar.createCookie("debug-view", "", -1);
|
||||
ciDebugBar.removeClass(btn, "active");
|
||||
}
|
||||
|
||||
var btn = document.querySelector("[data-tab=ci-views]");
|
||||
|
||||
// If the Views Collector is inactive stops here
|
||||
if (! btn) {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.parentNode.onclick = function () {
|
||||
if (ciDebugBar.readCookie("debug-view")) {
|
||||
hideHints();
|
||||
} else {
|
||||
showHints();
|
||||
}
|
||||
};
|
||||
|
||||
// Determine Hints state on page load
|
||||
if (ciDebugBar.readCookie("debug-view")) {
|
||||
showHints();
|
||||
}
|
||||
},
|
||||
|
||||
setToolbarPosition: function () {
|
||||
var btnPosition = this.toolbar.querySelector("#toolbar-position");
|
||||
|
||||
if (ciDebugBar.readCookie("debug-bar-position") === "top") {
|
||||
ciDebugBar.addClass(ciDebugBar.icon, "fixed-top");
|
||||
ciDebugBar.addClass(ciDebugBar.toolbar, "fixed-top");
|
||||
}
|
||||
|
||||
btnPosition.addEventListener(
|
||||
"click",
|
||||
function () {
|
||||
var position = ciDebugBar.readCookie("debug-bar-position");
|
||||
|
||||
ciDebugBar.createCookie("debug-bar-position", "", -1);
|
||||
|
||||
if (! position || position === "bottom") {
|
||||
ciDebugBar.createCookie("debug-bar-position", "top", 365);
|
||||
ciDebugBar.addClass(ciDebugBar.icon, "fixed-top");
|
||||
ciDebugBar.addClass(ciDebugBar.toolbar, "fixed-top");
|
||||
} else {
|
||||
ciDebugBar.createCookie(
|
||||
"debug-bar-position",
|
||||
"bottom",
|
||||
365
|
||||
);
|
||||
ciDebugBar.removeClass(ciDebugBar.icon, "fixed-top");
|
||||
ciDebugBar.removeClass(ciDebugBar.toolbar, "fixed-top");
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
setToolbarTheme: function () {
|
||||
var btnTheme = this.toolbar.querySelector("#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
|
||||
);
|
||||
},
|
||||
|
||||
setHotReloadState: function () {
|
||||
var btn = document.getElementById("debug-hot-reload").parentNode;
|
||||
var btnImg = btn.getElementsByTagName("img")[0];
|
||||
var eventSource;
|
||||
|
||||
// If the Hot Reload Collector is inactive stops here
|
||||
if (! btn) {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.onclick = function () {
|
||||
if (ciDebugBar.readCookie("debug-hot-reload")) {
|
||||
ciDebugBar.createCookie("debug-hot-reload", "", -1);
|
||||
ciDebugBar.removeClass(btn, "active");
|
||||
ciDebugBar.removeClass(btnImg, "rotate");
|
||||
|
||||
// Close the EventSource connection if it exists
|
||||
if (typeof eventSource !== "undefined") {
|
||||
eventSource.close();
|
||||
eventSource = void 0; // Undefine the variable
|
||||
}
|
||||
} else {
|
||||
ciDebugBar.createCookie("debug-hot-reload", "show", 365);
|
||||
ciDebugBar.addClass(btn, "active");
|
||||
ciDebugBar.addClass(btnImg, "rotate");
|
||||
|
||||
eventSource = ciDebugBar.hotReloadConnect();
|
||||
}
|
||||
};
|
||||
|
||||
// Determine Hot Reload state on page load
|
||||
if (ciDebugBar.readCookie("debug-hot-reload")) {
|
||||
ciDebugBar.addClass(btn, "active");
|
||||
ciDebugBar.addClass(btnImg, "rotate");
|
||||
eventSource = ciDebugBar.hotReloadConnect();
|
||||
}
|
||||
},
|
||||
|
||||
hotReloadConnect: function () {
|
||||
return;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to create a cookie.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param days
|
||||
*/
|
||||
createCookie: function (name, value, days) {
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
|
||||
var expires = "; expires=" + date.toGMTString();
|
||||
} else {
|
||||
var expires = "";
|
||||
}
|
||||
|
||||
document.cookie =
|
||||
name + "=" + value + expires + "; path=/; samesite=Lax";
|
||||
},
|
||||
|
||||
readCookie: function (name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(";");
|
||||
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == " ") {
|
||||
c = c.substring(1, c.length);
|
||||
}
|
||||
if (c.indexOf(nameEQ) == 0) {
|
||||
return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
trimSlash: function (text) {
|
||||
return text.replace(/^\/|\/$/g, "");
|
||||
},
|
||||
|
||||
routerLink: function () {
|
||||
var row, _location;
|
||||
var rowGet = this.toolbar.querySelectorAll(
|
||||
'td[data-debugbar-route="GET"]'
|
||||
);
|
||||
var patt = /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/;
|
||||
|
||||
for (var i = 0; i < rowGet.length; i++) {
|
||||
row = rowGet[i];
|
||||
if (!/\/\(.+?\)/.test(rowGet[i].innerText)) {
|
||||
ciDebugBar.addClass(row, "debug-bar-pointer");
|
||||
row.setAttribute(
|
||||
"title",
|
||||
location.origin + "/" + ciDebugBar.trimSlash(row.innerText)
|
||||
);
|
||||
row.addEventListener("click", function (ev) {
|
||||
_location =
|
||||
location.origin +
|
||||
"/" +
|
||||
ciDebugBar.trimSlash(ev.target.innerText);
|
||||
var redirectWindow = window.open(_location, "_blank");
|
||||
redirectWindow.location;
|
||||
});
|
||||
} else {
|
||||
row.innerHTML =
|
||||
"<div>" +
|
||||
row.innerText +
|
||||
"</div>" +
|
||||
'<form data-debugbar-route-tpl="' +
|
||||
ciDebugBar.trimSlash(row.innerText.replace(patt, "?")) +
|
||||
'">' +
|
||||
row.innerText.replace(
|
||||
patt,
|
||||
'<input type="text" placeholder="$1">'
|
||||
) +
|
||||
'<input type="submit" value="Go" class="debug-bar-mleft4">' +
|
||||
"</form>";
|
||||
}
|
||||
}
|
||||
|
||||
rowGet = this.toolbar.querySelectorAll(
|
||||
'td[data-debugbar-route="GET"] form'
|
||||
);
|
||||
for (var i = 0; i < rowGet.length; i++) {
|
||||
row = rowGet[i];
|
||||
|
||||
row.addEventListener("submit", function (event) {
|
||||
event.preventDefault();
|
||||
var inputArray = [],
|
||||
t = 0;
|
||||
var input = event.target.querySelectorAll("input[type=text]");
|
||||
var tpl = event.target.getAttribute("data-debugbar-route-tpl");
|
||||
|
||||
for (var n = 0; n < input.length; n++) {
|
||||
if (input[n].value.length > 0) {
|
||||
inputArray.push(input[n].value);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputArray.length > 0) {
|
||||
_location =
|
||||
location.origin +
|
||||
"/" +
|
||||
tpl.replace(/\?/g, function () {
|
||||
return inputArray[t++];
|
||||
});
|
||||
|
||||
var redirectWindow = window.open(_location, "_blank");
|
||||
redirectWindow.location;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php declare(strict_types=1);
|
||||
use CodeIgniter\Debug\Toolbar;
|
||||
use CodeIgniter\View\Parser;
|
||||
|
||||
/**
|
||||
* @var Toolbar $this
|
||||
* @var int $totalTime
|
||||
* @var int $totalMemory
|
||||
* @var string $url
|
||||
* @var string $method
|
||||
* @var bool $isAJAX
|
||||
* @var int $startTime
|
||||
* @var int $totalTime
|
||||
* @var int $totalMemory
|
||||
* @var float $segmentDuration
|
||||
* @var int $segmentCount
|
||||
* @var string $CI_VERSION
|
||||
* @var array $collectors
|
||||
* @var array $vars
|
||||
* @var array $styles
|
||||
* @var Parser $parser
|
||||
*/
|
||||
?>
|
||||
<style>
|
||||
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . '/toolbar.css')) ?>
|
||||
</style>
|
||||
|
||||
<script id="toolbar_js">
|
||||
var ciSiteURL = "<?= rtrim(site_url(), '/') ?>"
|
||||
<?= file_get_contents(__DIR__ . '/toolbar.js') ?>
|
||||
</script>
|
||||
<div id="debug-icon" class="debug-bar-ndisplay">
|
||||
<a id="debug-icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 155 200"><defs/><path fill="#2890ff" d="M73.7 3.7c2.2 7.9-.7 18.5-7.8 29-1.8 2.6-10.7 12.2-19.7 21.3-23.9 24-33.6 37.1-40.3 54.4-7.9 20.6-7.8 40.8.5 58.2C12.8 180 27.6 193 42.5 198l6 2-3-2.2c-21-15.2-22.9-38.7-4.8-58.8 2.5-2.7 4.8-5 5.1-5 .4 0 .7 2.7.7 6.1 0 5.7.2 6.2 3.7 9.5 3 2.7 4.6 3.4 7.8 3.4 5.6 0 9.9-2.4 11.6-6.5 2.9-6.9 1.6-12-5-20.5-10.5-13.4-11.7-23.3-4.3-34.7l3.1-4.8.7 4.7c1.3 8.2 5.8 12.9 25 25.8 20.9 14.1 30.6 26.1 32.8 40.5 1.1 7.2-.1 16.1-3.1 21.8-2.7 5.3-11.2 14.3-16.5 17.4-2.4 1.4-4.3 2.6-4.3 2.8 0 .2 2.4-.4 5.3-1.4 24.1-8.3 42.7-27.1 48.2-48.6 1.9-7.6 1.9-20.2-.1-28.5-3.5-15.2-14.6-30.5-29.9-41.2l-7-4.9-.6 3.3c-.8 4.8-2.6 7.6-5.9 9.3-4.5 2.3-10.3 1.9-13.8-1-6.7-5.7-7.8-14.6-3.7-30.5 3-11.6 3.2-20.6.5-29.1C88.3 18 80.6 6.3 74.8 2.2 73.1.9 73 1 73.7 3.7z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div id="debug-bar">
|
||||
<div class="toolbar">
|
||||
<span id="toolbar-position">↕</span>
|
||||
<span id="toolbar-theme">🔅</span>
|
||||
<span id="hot-reload-btn" class="ci-label">
|
||||
<a id="debug-hot-reload" title="Toggle Hot Reload">
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAABNklEQVR4nN2US04CQRCGv/DaiBxEvYWuBRPDKSCIXsCdcg0ULqTI8xIGN7JwTCU/ScV5tTO64Us6maSq/7+nuqvgkLgHopTl+QAWwBToAg3+wMTzM7YBrihp4jkCToEB8OJyRkCFAB5yDDxVoAd8OpNMOkrcAeMAgz3nzsQ0EqkDayXZqXy5Qugrdy2tGNdKeNWv40xCqGpvJK0YEwXt8ooylMZzUnCh4EkJgzNpmFaMrYLNEgbH0thmGVhSUVrSeE8KLv+7RBMFb0oY3EnDeihGN+WZhmJ7ZlnPtKHB5RvtNwy0d5XWaGgqRmp7a/9QLjRevoDLvOSRM+nnlKumk++0xwZlLhVnEulOhnohTS37vnU1t5M/ho7rPR03/LKW1bxNQep6ETZb5mpGW2/Ak2KpF3oYfAPX9Xpc671kqwAAAABJRU5ErkJggg==" />
|
||||
</a>
|
||||
</span>
|
||||
<span class="ci-label">
|
||||
<a data-tab="ci-timeline">
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAD7SURBVEhLY6ArSEtLK09NTbWHcvGC9PR0BaDaQiAdUl9fzwQVxg+AFvwHamqHcnGCpKQkeaDa9yD1UD09UCn8AKaBWJySkmIApFehi0ONwwRQBceBLurAh4FqFoHUAtkrgPgREN+ByYEw1DhMANVEMIhAYQ5U1wtU/wmILwLZRlAp/IBYC8gGw88CaFj3A/FnIL4ETDXGUCnyANSC/UC6HIpnQMXAqQXIvo0khxNDjcMEQEmU9AzDuNI7Lgw1DhOAJIEuhQcRKMcC+e+QNHdDpcgD6BaAANSSQqBcENFlDi6AzQKqgkFlwWhxjVI8o2OgmkFaXI8CTMDAAAAxd1O4FzLMaAAAAABJRU5ErkJggg==">
|
||||
<span class="hide-sm"><?= $totalTime ?> ms <?= $totalMemory ?> MB</span>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<?php foreach ($collectors as $c) : ?>
|
||||
<?php if (! $c['isEmpty'] && ($c['hasTabContent'] || $c['hasLabel'])) : ?>
|
||||
<span class="ci-label">
|
||||
<a data-tab="ci-<?= $c['titleSafe'] ?>">
|
||||
<img src="<?= $c['icon'] ?>">
|
||||
<span class="hide-sm">
|
||||
<?= $c['title'] ?>
|
||||
<?php if ($c['badgeValue'] !== null) : ?>
|
||||
<span class="badge"><?= $c['badgeValue'] ?></span>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
|
||||
|
||||
|
||||
<h1>
|
||||
<span class="ci-label">
|
||||
<a data-tab="ci-config">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 155 200"><defs/><path fill="#2890ff" d="M73.7 3.7c2.2 7.9-.7 18.5-7.8 29-1.8 2.6-10.7 12.2-19.7 21.3-23.9 24-33.6 37.1-40.3 54.4-7.9 20.6-7.8 40.8.5 58.2C12.8 180 27.6 193 42.5 198l6 2-3-2.2c-21-15.2-22.9-38.7-4.8-58.8 2.5-2.7 4.8-5 5.1-5 .4 0 .7 2.7.7 6.1 0 5.7.2 6.2 3.7 9.5 3 2.7 4.6 3.4 7.8 3.4 5.6 0 9.9-2.4 11.6-6.5 2.9-6.9 1.6-12-5-20.5-10.5-13.4-11.7-23.3-4.3-34.7l3.1-4.8.7 4.7c1.3 8.2 5.8 12.9 25 25.8 20.9 14.1 30.6 26.1 32.8 40.5 1.1 7.2-.1 16.1-3.1 21.8-2.7 5.3-11.2 14.3-16.5 17.4-2.4 1.4-4.3 2.6-4.3 2.8 0 .2 2.4-.4 5.3-1.4 24.1-8.3 42.7-27.1 48.2-48.6 1.9-7.6 1.9-20.2-.1-28.5-3.5-15.2-14.6-30.5-29.9-41.2l-7-4.9-.6 3.3c-.8 4.8-2.6 7.6-5.9 9.3-4.5 2.3-10.3 1.9-13.8-1-6.7-5.7-7.8-14.6-3.7-30.5 3-11.6 3.2-20.6.5-29.1C88.3 18 80.6 6.3 74.8 2.2 73.1.9 73 1 73.7 3.7z"/></svg>
|
||||
|
||||
</a>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<!-- Open/Close Toggle -->
|
||||
<a id="debug-bar-link" role="button" title="Open/Close">
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEPSURBVEhL7ZVLDoJAEEThRuoGDwSEG+jCuFU34s3AK3APP1VDDSGMqI1xx0s6M/2rnlHEaMZElmWrPM+vsDvsYbQ7+us0TReSC2EBrEHxCevRYuppYLXkQpC8sVCuGfTvqSE3hFdFwUGuGfRvqSE35NUAfKZrbQNQm2jrMA+gOK+M+FmhDsRL5voHMA8gFGecq0JOXLWlQg7E7AMIxZnjOiZOEJ82gFCcedUE4gS56QP8yf8ywItz7e+RituKlkkDBoIOH4Nd4HZD4NsGYJ/Abn1xEVOcuZ8f0zc/tHiYmzTAwscBvDIK/veyQ9K/rnewjdF26q0kF1IUxZIFPAVW98x/a+qp8L2M/+HMhETRE6S8TxpZ7KGXAAAAAElFTkSuQmCC">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Timeline -->
|
||||
<div id="ci-timeline" class="tab">
|
||||
<table class="timeline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="debug-bar-width30">NAME</th>
|
||||
<th class="debug-bar-width10">COMPONENT</th>
|
||||
<th class="debug-bar-width10">DURATION</th>
|
||||
<?php for ($i = 0; $i < $segmentCount; $i++) : ?>
|
||||
<th><?= $i * $segmentDuration ?> ms</th>
|
||||
<?php endfor ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?= $this->renderTimeline($collectors, $startTime, $segmentCount, $segmentDuration, $styles) ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Collector-provided Tabs -->
|
||||
<?php foreach ($collectors as $c) : ?>
|
||||
<?php if (! $c['isEmpty']) : ?>
|
||||
<?php if ($c['hasTabContent']) : ?>
|
||||
<div id="ci-<?= $c['titleSafe'] ?>" class="tab">
|
||||
<h2><?= $c['title'] ?> <span><?= $c['titleDetails'] ?></span></h2>
|
||||
|
||||
<?= is_string($c['display']) ? $c['display'] : $this->render("_{$c['titleSafe']}.tpl", $c['display']) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
|
||||
|
||||
</div>
|
||||
<style>
|
||||
<?php foreach ($styles as $name => $style): ?>
|
||||
<?= sprintf(".%s { %s }\n", $name, $style) ?>
|
||||
<?php endforeach ?>
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
document.addEventListener('DOMContentLoaded', loadDoc, false);
|
||||
|
||||
function loadDoc(time) {
|
||||
if (isNaN(time)) {
|
||||
time = document.getElementById("debugbar_loader").getAttribute("data-time");
|
||||
localStorage.setItem('debugbar-time', time);
|
||||
}
|
||||
|
||||
localStorage.setItem('debugbar-time-new', time);
|
||||
|
||||
let url = '{url}';
|
||||
let xhttp = new XMLHttpRequest();
|
||||
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
let toolbar = document.getElementById("toolbarContainer");
|
||||
|
||||
if (! toolbar) {
|
||||
toolbar = document.createElement('div');
|
||||
toolbar.setAttribute('id', 'toolbarContainer');
|
||||
document.body.appendChild(toolbar);
|
||||
}
|
||||
|
||||
let responseText = this.responseText;
|
||||
let dynamicStyle = document.getElementById('debugbar_dynamic_style');
|
||||
let dynamicScript = document.getElementById('debugbar_dynamic_script');
|
||||
|
||||
// get the first style block, copy contents to dynamic_style, then remove here
|
||||
let start = responseText.indexOf('>', responseText.indexOf('<style')) + 1;
|
||||
let end = responseText.indexOf('</style>', start);
|
||||
dynamicStyle.innerHTML = responseText.substr(start, end - start);
|
||||
responseText = responseText.substr(end + 8);
|
||||
|
||||
// get the first script after the first style, copy contents to dynamic_script, then remove here
|
||||
start = responseText.indexOf('>', responseText.indexOf('<script')) + 1;
|
||||
end = responseText.indexOf('\<\/script>', start);
|
||||
dynamicScript.innerHTML = responseText.substr(start, end - start);
|
||||
responseText = responseText.substr(end + 9);
|
||||
|
||||
// check for last style block, append contents to dynamic_style, then remove here
|
||||
start = responseText.indexOf('>', responseText.indexOf('<style')) + 1;
|
||||
end = responseText.indexOf('</style>', start);
|
||||
dynamicStyle.innerHTML += responseText.substr(start, end - start);
|
||||
responseText = responseText.substr(0, start - 8);
|
||||
|
||||
toolbar.innerHTML = responseText;
|
||||
|
||||
if (typeof ciDebugBar === 'object') {
|
||||
ciDebugBar.init();
|
||||
}
|
||||
} else if (this.readyState === 4 && this.status === 404) {
|
||||
console.log('CodeIgniter DebugBar: File "WRITEPATH/debugbar/debugbar_' + time + '" not found.');
|
||||
}
|
||||
};
|
||||
|
||||
xhttp.open("GET", url + "?debugbar_time=" + time, true);
|
||||
xhttp.send();
|
||||
}
|
||||
|
||||
window.oldXHR = window.ActiveXObject
|
||||
? new ActiveXObject('Microsoft.XMLHTTP')
|
||||
: window.XMLHttpRequest;
|
||||
|
||||
function newXHR() {
|
||||
const realXHR = new window.oldXHR();
|
||||
|
||||
realXHR.addEventListener("readystatechange", function() {
|
||||
// Only success responses and URLs that do not contains "debugbar_time" are tracked
|
||||
if (realXHR.readyState === 4 && realXHR.status.toString()[0] === '2' && realXHR.responseURL.indexOf('debugbar_time') === -1) {
|
||||
if (realXHR.getAllResponseHeaders().indexOf("Debugbar-Time") >= 0) {
|
||||
let debugbarTime = realXHR.getResponseHeader('Debugbar-Time');
|
||||
|
||||
if (debugbarTime) {
|
||||
let h2 = document.querySelector('#ci-history > h2');
|
||||
|
||||
if (h2) {
|
||||
h2.innerHTML = 'History <small>You have new debug data.</small> <button id="ci-history-update">Update</button>';
|
||||
document.querySelector('a[data-tab="ci-history"] > span > .badge').className += ' active';
|
||||
document.getElementById('ci-history-update').addEventListener('click', function () {
|
||||
loadDoc(debugbarTime);
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
return realXHR;
|
||||
}
|
||||
|
||||
window.XMLHttpRequest = newXHR;
|
||||
Reference in New Issue
Block a user