This commit is contained in:
Markus
2022-04-28 09:40:10 +02:00
commit 795794f992
9586 changed files with 1146991 additions and 0 deletions

433
system/Debug/Exceptions.php Normal file
View File

@@ -0,0 +1,433 @@
<?php
/**
* 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\API\ResponseTrait;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\Response;
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Throwable;
/**
* Exceptions manager
*/
class Exceptions
{
use ResponseTrait;
/**
* Nesting level of the output buffering mechanism
*
* @var int
*/
public $ob_level;
/**
* The path to the directory containing the
* cli and html error view directories.
*
* @var string
*/
protected $viewPath;
/**
* Config for debug exceptions.
*
* @var ExceptionsConfig
*/
protected $config;
/**
* The incoming request.
*
* @var IncomingRequest
*/
protected $request;
/**
* The outgoing response.
*
* @var Response
*/
protected $response;
public function __construct(ExceptionsConfig $config, IncomingRequest $request, Response $response)
{
$this->ob_level = ob_get_level();
$this->viewPath = rtrim($config->errorViewPath, '\\/ ') . DIRECTORY_SEPARATOR;
$this->config = $config;
$this->request = $request;
$this->response = $response;
// workaround for upgraded users
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
}
/**
* Responsible for registering the error, exception and shutdown
* handling of our application.
*
* @codeCoverageIgnore
*/
public function initialize()
{
set_exception_handler([$this, 'exceptionHandler']);
set_error_handler([$this, 'errorHandler']);
register_shutdown_function([$this, 'shutdownHandler']);
}
/**
* 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.
*
* @codeCoverageIgnore
*/
public function exceptionHandler(Throwable $exception)
{
[$statusCode, $exitCode] = $this->determineCodes($exception);
if ($this->config->log === true && ! in_array($statusCode, $this->config->ignoreCodes, true)) {
log_message('critical', $exception->getMessage() . "\n{trace}", [
'trace' => $exception->getTraceAsString(),
]);
}
if (! is_cli()) {
$this->response->setStatusCode($statusCode);
header(sprintf('HTTP/%s %s %s', $this->request->getProtocolVersion(), $this->response->getStatusCode(), $this->response->getReasonPhrase()), true, $statusCode);
if (strpos($this->request->getHeaderLine('accept'), 'text/html') === false) {
$this->respond(ENVIRONMENT === 'development' ? $this->collectVars($exception, $statusCode) : '', $statusCode)->send();
exit($exitCode);
}
}
$this->render($exception, $statusCode);
exit($exitCode);
}
/**
* Even in PHP7, some errors make it through to the errorHandler, so
* convert these to Exceptions and let the exception handler log it and
* display it.
*
* This seems to be primarily when a user triggers it with trigger_error().
*
* @throws ErrorException
*
* @codeCoverageIgnore
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if (! (error_reporting() & $severity)) {
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
/**
* Checks to see if any errors have happened during shutdown that
* need to be caught and handle them.
*
* @codeCoverageIgnore
*/
public function shutdownHandler()
{
$error = error_get_last();
if ($error === null) {
return;
}
['type' => $type, 'message' => $message, 'file' => $file, 'line' => $line] = $error;
if (in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) {
$this->exceptionHandler(new ErrorException($message, $type, 0, $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
*/
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 (str_ireplace(['off', 'none', 'no', 'false', 'null'], '', ini_get('display_errors'))) {
$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.
*/
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);
}
if (ob_get_level() > $this->ob_level + 1) {
ob_end_clean();
}
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.
*/
protected function collectVars(Throwable $exception, int $statusCode): array
{
$trace = $exception->getTrace();
if ($this->config->sensitiveDataInTrace !== []) {
$this->maskSensitiveData($trace, $this->config->sensitiveDataInTrace);
}
return [
'title' => get_class($exception),
'type' => get_class($exception),
'code' => $statusCode,
'message' => $exception->getMessage() ?? '(null)',
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $trace,
];
}
/**
* Mask sensitive data in the trace.
*
* @param array|object $trace
*/
protected function maskSensitiveData(&$trace, array $keysToMask, string $path = '')
{
foreach ($keysToMask as $keyToMask) {
$explode = explode('/', $keyToMask);
$index = end($explode);
if (strpos(strrev($path . '/' . $index), strrev($keyToMask)) === 0) {
if (is_array($trace) && array_key_exists($index, $trace)) {
$trace[$index] = '******************';
} elseif (is_object($trace) && property_exists($trace, $index) && isset($trace->{$index})) {
$trace->{$index} = '******************';
}
}
}
if (is_object($trace)) {
$trace = get_object_vars($trace);
}
if (is_array($trace)) {
foreach ($trace as $pathKey => $subarray) {
$this->maskSensitiveData($subarray, $keysToMask, $path . '/' . $pathKey);
}
}
}
/**
* Determines the HTTP status code and the exit status code for this request.
*/
protected function determineCodes(Throwable $exception): array
{
$statusCode = abs($exception->getCode());
if ($statusCode < 100 || $statusCode > 599) {
$exitStatus = $statusCode + EXIT__AUTO_MIN;
if ($exitStatus > EXIT__AUTO_MAX) {
$exitStatus = EXIT_ERROR;
}
$statusCode = 500;
} else {
$exitStatus = EXIT_ERROR;
}
return [$statusCode, $exitStatus];
}
//--------------------------------------------------------------------
// Display Methods
//--------------------------------------------------------------------
/**
* This makes nicer looking paths for the error output.
*/
public static function cleanPath(string $file): string
{
switch (true) {
case strpos($file, APPPATH) === 0:
$file = 'APPPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(APPPATH));
break;
case strpos($file, SYSTEMPATH) === 0:
$file = 'SYSTEMPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(SYSTEMPATH));
break;
case strpos($file, FCPATH) === 0:
$file = 'FCPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(FCPATH));
break;
case defined('VENDORPATH') && strpos($file, VENDORPATH) === 0:
$file = 'VENDORPATH' . DIRECTORY_SEPARATOR . substr($file, strlen(VENDORPATH));
break;
}
return $file;
}
/**
* Describes memory usage in real-world units. Intended for use
* with memory_get_usage, etc.
*/
public static function describeMemory(int $bytes): string
{
if ($bytes < 1024) {
return $bytes . 'B';
}
if ($bytes < 1048576) {
return round($bytes / 1024, 2) . 'KB';
}
return round($bytes / 1048576, 2) . 'MB';
}
/**
* Creates a syntax-highlighted version of a PHP file.
*
* @return bool|string
*/
public static function highlightFile(string $file, int $lineNumber, int $lines = 15)
{
if (empty($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 $e) {
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>';
}
}

130
system/Debug/Iterator.php Normal file
View File

@@ -0,0 +1,130 @@
<?php
/**
* 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.
*
* @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 (empty($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/>';
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* 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\Kint;
use Kint\Renderer\RichRenderer as KintRichRenderer;
/**
* Overrides RichRenderer::preRender() for CSP
*/
class RichRenderer extends KintRichRenderer
{
public function preRender()
{
$output = '';
if ($this->pre_render) {
foreach (self::$pre_render_sources as $type => $values) {
$contents = '';
foreach ($values as $v) {
$contents .= $v($this);
}
if (! \strlen($contents)) {
continue;
}
switch ($type) {
case 'script':
$output .= '<script {csp-script-nonce} class="kint-rich-script">' . $contents . '</script>';
break;
case 'style':
$output .= '<style {csp-style-nonce} class="kint-rich-style">' . $contents . '</style>';
break;
default:
$output .= $contents;
}
}
// Don't pre-render on every dump
if (! $this->force_pre_render) {
self::$needs_pre_render = false;
}
}
$output .= '<div class="kint-rich';
if ($this->use_folder) {
$output .= ' kint-file';
if (self::$needs_folder_render || $this->force_pre_render) {
$output = $this->renderFolder() . $output;
if (! $this->force_pre_render) {
self::$needs_folder_render = false;
}
}
}
$output .= '">';
return $output;
}
}

129
system/Debug/Timer.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
/**
* 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 RuntimeException;
/**
* Class Timer
*
* Provides a simple way to measure the amount of time
* that elapses between two points.
*/
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 $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 exists 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);
}
}

518
system/Debug/Toolbar.php Normal file
View File

@@ -0,0 +1,518 @@
<?php
/**
* 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\CodeIgniter;
use CodeIgniter\Debug\Toolbar\Collectors\BaseCollector;
use CodeIgniter\Debug\Toolbar\Collectors\Config;
use CodeIgniter\Debug\Toolbar\Collectors\History;
use CodeIgniter\Format\JSONFormatter;
use CodeIgniter\Format\XMLFormatter;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Config\Toolbar as ToolbarConfig;
use Kint\Kint;
/**
* 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 BaseCollector[]
*/
protected $collectors = [];
public function __construct(ToolbarConfig $config)
{
$this->config = $config;
foreach ($config->collectors as $collector) {
if (! class_exists($collector)) {
log_message('critical', 'Toolbar collector does not exists(' . $collector . ').' .
'please check $collectors in the Config\Toolbar.php file.');
continue;
}
$this->collectors[] = new $collector();
}
}
/**
* Returns all the data required by Debug Bar
*
* @param float $startTime App start time
* @param IncomingRequest $request
* @param Response $response
*
* @return string JSON encoded data
*/
public function run(float $startTime, float $totalTime, RequestInterface $request, ResponseInterface $response): string
{
// Data items used within the view.
$data['url'] = current_url();
$data['method'] = $request->getMethod(true);
$data['isAJAX'] = $request->isAJAX();
$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'] = CodeIgniter::CI_VERSION;
$data['collectors'] = [];
foreach ($this->collectors as $collector) {
$data['collectors'][] = $collector->getAsArray();
}
foreach ($this->collectVarData() as $heading => $items) {
$varData = [];
if (is_array($items)) {
foreach ($items as $key => $value) {
if (is_string($value)) {
$varData[esc($key)] = esc($value);
} else {
$oldKintMode = Kint::$mode_default;
$oldKintCalledFrom = Kint::$display_called_from;
Kint::$mode_default = Kint::MODE_RICH;
Kint::$display_called_from = false;
$kint = @Kint::dump($value);
$kint = substr($kint, strpos($kint, '</style>') + 8);
Kint::$mode_default = $oldKintMode;
Kint::$display_called_from = $oldKintCalledFrom;
$varData[esc($key)] = $kint;
}
}
}
$data['vars']['varData'][esc($heading)] = $varData;
}
if (! empty($_SESSION)) {
foreach ($_SESSION as $key => $value) {
// Replace the binary data with string to avoid json_encode failure.
if (is_string($value) && preg_match('~[^\x20-\x7E\t\r\n]~', $value)) {
$value = 'binary data';
}
$data['vars']['session'][esc($key)] = is_string($value) ? esc($value) : '<pre>' . esc(print_r($value, true)) . '</pre>';
}
}
foreach ($request->getGet() as $name => $value) {
$data['vars']['get'][esc($name)] = is_array($value) ? '<pre>' . esc(print_r($value, true)) . '</pre>' : esc($value);
}
foreach ($request->getPost() as $name => $value) {
$data['vars']['post'][esc($name)] = is_array($value) ? '<pre>' . esc(print_r($value, true)) . '</pre>' : esc($value);
}
foreach ($request->headers() as $header) {
$data['vars']['headers'][esc($header->getName())] = esc($header->getValueLine());
}
foreach ($request->getCookie() as $name => $value) {
$data['vars']['cookies'][esc($name)] = esc($value);
}
$data['vars']['request'] = ($request->isSecure() ? 'HTTPS' : 'HTTP') . '/' . $request->getProtocolVersion();
$data['vars']['response'] = [
'statusCode' => $response->getStatusCode(),
'reason' => esc($response->getReasonPhrase()),
'contentType' => esc($response->getHeaderLine('content-type')),
];
$data['config'] = Config::display();
if ($response->CSP !== null) {
$response->CSP->addImageSrc('data:');
}
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" onclick="ciDebugBar.toggleChildRows(\'timeline-' . $styleCount . '\');">';
} else {
$output .= '<tr>';
}
$output .= '<td class="' . ($isChild ? 'debug-bar-width30' : '') . '" style="--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" id="timeline-' . ($styleCount - 1) . '_children" style="' . ($open ? '' : 'display: none;') . '">';
$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" style="--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) {
$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 (! empty($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 (empty($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..
*
* @param RequestInterface $request
* @param ResponseInterface $response
*
* @global \CodeIgniter\CodeIgniter $app
*/
public function prepare(?RequestInterface $request = null, ?ResponseInterface $response = null)
{
/**
* @var IncomingRequest $request
* @var Response $response
*/
if (CI_DEBUG && ! is_cli()) {
global $app;
$request = $request ?? Services::request();
$response = $response ?? Services::response();
// Disable the toolbar for downloads
if ($response instanceof DownloadResponse) {
return;
}
$toolbar = Services::toolbar(config(self::class));
$stats = $app->getPerformanceStats();
$data = $toolbar->run(
$stats['startTime'],
$stats['totalTime'],
$request,
$response
);
helper('filesystem');
// Updated to time() so we can get history
$time = time();
if (! is_dir(WRITEPATH . 'debugbar')) {
mkdir(WRITEPATH . 'debugbar', 0777);
}
write_file(WRITEPATH . 'debugbar/' . 'debugbar_' . $time . '.json', $data, 'w+');
$format = $response->getHeaderLine('content-type');
// Non-HTML formats should not include the debugbar
// then we send headers saying where to find the debug data
// for this response
if ($request->isAJAX() || strpos($format, 'html') === false) {
$response->setHeader('Debugbar-Time', "{$time}")
->setHeader('Debugbar-Link', site_url("?debugbar_time={$time}"))
->getBody();
return;
}
$oldKintMode = Kint::$mode_default;
Kint::$mode_default = Kint::MODE_RICH;
$kintScript = @Kint::dump('');
Kint::$mode_default = $oldKintMode;
$kintScript = substr($kintScript, 0, strpos($kintScript, '</style>') + 8);
$script = PHP_EOL
. '<script type="text/javascript" {csp-script-nonce} id="debugbar_loader" '
. 'data-time="' . $time . '" '
. 'src="' . site_url() . '?debugbar"></script>'
. '<script type="text/javascript" {csp-script-nonce} id="debugbar_dynamic_script"></script>'
. '<style type="text/css" {csp-style-nonce} id="debugbar_dynamic_style"></style>'
. $kintScript
. PHP_EOL;
if (strpos($response->getBody(), '<head>') !== false) {
$response->setBody(preg_replace('/<head>/', '<head>' . $script, $response->getBody(), 1));
return;
}
$response->appendBody($script);
}
}
/**
* Inject debug toolbar into the response.
*
* @codeCoverageIgnore
*/
public function respond()
{
if (ENVIRONMENT === 'testing') {
return;
}
$request = Services::request();
// If the request contains '?debugbar then we're
// simply returning the loading script
if ($request->getGet('debugbar') !== null) {
header('Content-Type: application/javascript');
ob_start();
include $this->config->viewsPath . 'toolbarloader.js';
$output = ob_get_clean();
$output = str_replace('{url}', rtrim(site_url(), '/'), $output);
echo $output;
exit;
}
// Otherwise, if it includes ?debugbar_time, then
// we should return the entire debugbar.
if ($request->getGet('debugbar_time')) {
helper('security');
// Negotiate the content-type to format the output
$format = $request->negotiate('media', ['text/html', 'application/json', 'application/xml']);
$format = explode('/', $format)[1];
$filename = sanitize_filename('debugbar_' . $request->getGet('debugbar_time'));
$filename = WRITEPATH . 'debugbar/' . $filename . '.json';
if (is_file($filename)) {
// Show the toolbar if it exists
echo $this->format(file_get_contents($filename), $format);
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);
if ($this->config->maxHistory !== 0) {
$history = new History();
$history->setFiles(
(int) Services::request()->getGet('debugbar_time'),
$this->config->maxHistory
);
$data['collectors'][] = $history->getAsArray();
}
$output = '';
switch ($format) {
case 'html':
$data['styles'] = [];
extract($data);
$parser = Services::parser($this->config->viewsPath, null, false);
ob_start();
include $this->config->viewsPath . 'toolbar.tpl.php';
$output = ob_get_clean();
break;
case 'json':
$formatter = new JSONFormatter();
$output = $formatter->format($data);
break;
case 'xml':
$formatter = new XMLFormatter();
$output = $formatter->format($data);
break;
}
return $output;
}
}

View File

@@ -0,0 +1,234 @@
<?php
/**
* 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\Debug\Exceptions;
/**
* 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'
* ],
* ];
*/
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 [];
}
/**
* Clean Path
*
* This makes nicer looking paths for the error output.
*/
public function cleanPath(string $file): string
{
return Exceptions::cleanPath($file);
}
/**
* Gets the "badge" value for the button.
*/
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(),
];
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* 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\CodeIgniter;
use Config\App;
use Config\Services;
/**
* Debug toolbar configuration
*/
class Config
{
/**
* Return toolbar config values as an array.
*/
public static function display(): array
{
$config = config(App::class);
return [
'ciVersion' => CodeIgniter::CI_VERSION,
'phpVersion' => PHP_VERSION,
'phpSAPI' => PHP_SAPI,
'environment' => ENVIRONMENT,
'baseURL' => $config->baseURL,
'timezone' => app_timezone(),
'locale' => Services::request()->getLocale(),
'cspEnabled' => $config->CSPEnabled,
];
}
}

View File

@@ -0,0 +1,253 @@
<?php
/**
* 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;
/**
* Collector for the Database tab of the Debug Toolbar.
*/
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 param $ array \CodeIgniter\Database\Query
*/
public static function collect(Query $query)
{
$config = config('Toolbar');
// 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['queries'] = array_map(static function (array $query) {
$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 === '' && strpos($line['file'], 'SYSTEMPATH') === false) {
$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'] . microtime()),
];
}, 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, static 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 empty(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()
{
$this->connections = \Config\Database::getConnections();
}
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* 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\View\RendererInterface;
use Config\Services;
/**
* Views collector
*/
class Events 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;
/**
* 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 = 'Events';
/**
* Instance of the Renderer service
*
* @var RendererInterface
*/
protected $viewer;
/**
* Constructor.
*/
public function __construct()
{
$this->viewer = Services::renderer();
}
/**
* Child classes should implement this to return the timeline data
* formatted for correct usage.
*/
protected function formatTimelineData(): array
{
$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;
}
/**
* Returns the data of this collector to be formatted in the toolbar
*/
public function display(): array
{
$data = [
'events' => [],
];
foreach (\CodeIgniter\Events\Events::getPerformanceLogs() as $row) {
$key = $row['event'];
if (! array_key_exists($key, $data['events'])) {
$data['events'][$key] = [
'event' => $key,
'duration' => ($row['end'] - $row['start']) * 1000,
'count' => 1,
];
continue;
}
$data['events'][$key]['duration'] += ($row['end'] - $row['start']) * 1000;
$data['events'][$key]['count']++;
}
foreach ($data['events'] as &$row) {
$row['duration'] = number_format($row['duration'], 2);
}
return $data;
}
/**
* Gets the "badge" value for the button.
*/
public function getBadgeValue(): int
{
return count(\CodeIgniter\Events\Events::getPerformanceLogs());
}
/**
* Display the icon.
*
* Icon from https://icons8.com - 1em package
*/
public function icon(): string
{
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEASURBVEhL7ZXNDcIwDIVTsRBH1uDQDdquUA6IM1xgCA6MwJUN2hk6AQzAz0vl0ETUxC5VT3zSU5w81/mRMGZysixbFEVR0jSKNt8geQU9aRpFmp/keX6AbjZ5oB74vsaN5lSzA4tLSjpBFxsjeSuRy4d2mDdQTWU7YLbXTNN05mKyovj5KL6B7q3hoy3KwdZxBlT+Ipz+jPHrBqOIynZgcZonoukb/0ckiTHqNvDXtXEAaygRbaB9FvUTjRUHsIYS0QaSp+Dw6wT4hiTmYHOcYZsdLQ2CbXa4ftuuYR4x9vYZgdb4vsFYUdmABMYeukK9/SUme3KMFQ77+Yfzh8eYF8+orDuDWU5LAAAAAElFTkSuQmCC';
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* 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 = $this->cleanPath($file);
if (strpos($path, 'SYSTEMPATH') !== false) {
$coreFiles[] = [
'name' => basename($file),
'path' => $path,
];
} else {
$userFiles[] = [
'name' => basename($file),
'path' => $path,
];
}
}
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';
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* 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;
/**
* History collector
*/
class History 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;
/**
* Whether this collector needs to display
* a label or not.
*
* @var bool
*/
protected $hasLabel = true;
/**
* The 'title' of this Collector.
* Used to name things in the toolbar HTML.
*
* @var string
*/
protected $title = 'History';
/**
* @var array History files
*/
protected $files = [];
/**
* Specify time limit & file count for debug history.
*
* @param int $current Current history time
* @param int $limit Max history files
*/
public function setFiles(int $current, int $limit = 20)
{
$filenames = glob(WRITEPATH . 'debugbar/debugbar_*.json');
$files = [];
$counter = 0;
foreach (array_reverse($filenames) as $filename) {
$counter++;
// Oldest files will be deleted
if ($limit >= 0 && $counter > $limit) {
unlink($filename);
continue;
}
// Get the contents of this specific history request
$contents = file_get_contents($filename);
$contents = @json_decode($contents);
if (json_last_error() === JSON_ERROR_NONE) {
preg_match_all('/\d+/', $filename, $time);
$time = (int) end($time[0]);
// Debugbar files shown in History Collector
$files[] = [
'time' => $time,
'datetime' => date('Y-m-d H:i:s', $time),
'active' => $time === $current,
'status' => $contents->vars->response->statusCode,
'method' => $contents->method,
'url' => $contents->url,
'isAJAX' => $contents->isAJAX ? 'Yes' : 'No',
'contentType' => $contents->vars->response->contentType,
];
}
}
$this->files = $files;
}
/**
* Returns the data of this collector to be formatted in the toolbar
*/
public function display(): array
{
return ['files' => $this->files];
}
/**
* Displays the number of included files as a badge in the tab button.
*/
public function getBadgeValue(): int
{
return count($this->files);
}
/**
* Return true if there are no history files.
*/
public function isEmpty(): bool
{
return empty($this->files);
}
/**
* Display the icon.
*
* Icon from https://icons8.com - 1em package
*/
public function icon(): string
{
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJySURBVEhL3ZU7aJNhGIVTpV6i4qCIgkIHxcXLErS4FBwUFNwiCKGhuTYJGaIgnRoo4qRu6iCiiIuIXXTTIkIpuqoFwaGgonUQlC5KafU5ycmNP0lTdPLA4fu+8573/a4/f6hXpFKpwUwmc9fDfweKbk+n07fgEv33TLSbtt/hvwNFT1PsG/zdTE0Gp+GFfD6/2fbVIxqNrqPIRbjg4t/hY8aztcngfDabHXbKyiiXy2vcrcPH8oDCry2FKDrA+Ar6L01E/ypyXzXaARjDGGcoeNxSDZXE0dHRA5VRE5LJ5CFy5jzJuOX2wHRHRnjbklZ6isQ3tIctBaAd4vlK3jLtkOVWqABBXd47jGHLmjTmSScttQV5J+SjfcUweFQEbsjAas5aqoCLXutJl7vtQsAzpRowYqkBinyCC8Vicb2lOih8zoldd0F8RD7qTFiqAnGrAy8stUAvi/hbqDM+YzkAFrLPdR5ZqoLXsd+Bh5YCIH7JniVdquUWxOPxDfboHhrI5XJ7HHhiqQXox+APe/Qk64+gGYVCYZs8cMpSFQj9JOoFzVqqo7k4HIvFYpscCoAjOmLffUsNUGRaQUwDlmofUa34ecsdgXdcXo4wbakBgiUFafXJV8A4DJ/2UrxUKm3E95H8RbjLcgOJRGILhnmCP+FBy5XvwN2uIPcy1AJvWgqC4xm2aU4Xb3lF4I+Tpyf8hRe5w3J7YLymSeA8Z3nSclv4WLRyFdfOjzrUFX0klJUEtZtntCNc+F69cz/FiDzEPtjzmcUMOr83kDQEX6pAJxJfpL3OX22n01YN7SZCoQnaSdoZ+Jz+PZihH3wt/xlCoT9M6nEtmRSPCQAAAABJRU5ErkJggg==';
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* 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 Config\Services;
/**
* Loags collector
*/
class Logs 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 = 'Logs';
/**
* Our collected data.
*
* @var array
*/
protected $data;
/**
* Returns the data of this collector to be formatted in the toolbar
*/
public function display(): array
{
return [
'logs' => $this->collectLogs(),
];
}
/**
* Does this collector actually have any data to display?
*/
public function isEmpty(): bool
{
$this->collectLogs();
return empty($this->data);
}
/**
* Display the icon.
*
* Icon from https://icons8.com - 1em package
*/
public function icon(): string
{
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACYSURBVEhLYxgFJIHU1FSjtLS0i0D8AYj7gEKMEBkqAaAFF4D4ERCvAFrwH4gDoFIMKSkpFkB+OTEYqgUTACXfA/GqjIwMQyD9H2hRHlQKJFcBEiMGQ7VgAqCBvUgK32dmZspCpagGGNPT0/1BLqeF4bQHQJePpiIwhmrBBEADR1MRfgB0+WgqAmOoFkwANHA0FY0CUgEDAwCQ0PUpNB3kqwAAAABJRU5ErkJggg==';
}
/**
* Ensures the data has been collected.
*/
protected function collectLogs()
{
if (! empty($this->data)) {
return $this->data;
}
return $this->data = Services::logger(true)->logCache ?? [];
}
}

View File

@@ -0,0 +1,153 @@
<?php
/**
* 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 Config\Services;
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
*
* @throws ReflectionException
*/
public function display(): array
{
$rawRoutes = Services::routes(true);
$router = Services::router(null, null, true);
// Matched Route
$route = $router->getMatchedRoute();
// Get our parameters
// Closure routes
if (is_callable($router->controllerName())) {
$method = new ReflectionFunction($router->controllerName());
} else {
try {
$method = new ReflectionMethod($router->controllerName(), $router->methodName());
} catch (ReflectionException $e) {
// If we're here, the method doesn't exist
// and is likely calculated in _remap.
$method = new ReflectionMethod($router->controllerName(), '_remap');
}
}
$rawParams = $method->getParameters();
$params = [];
foreach ($rawParams as $key => $param) {
$params[] = [
'name' => $param->getName(),
'value' => $router->params()[$key] ??
('&lt;empty&gt;&nbsp| default: ' . var_export($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, true)),
];
}
$matchedRoute = [
[
'directory' => $router->directory(),
'controller' => $router->controllerName(),
'method' => $router->methodName(),
'paramCount' => count($router->params()),
'truePCount' => count($params),
'params' => $params,
],
];
// Defined Routes
$routes = [];
$methods = [
'get',
'head',
'post',
'patch',
'put',
'delete',
'options',
'trace',
'connect',
'cli',
];
foreach ($methods as $method) {
$raw = $rawRoutes->getRoutes($method);
foreach ($raw as $route => $handler) {
// filter for strings, as callbacks aren't displayable
if (is_string($handler)) {
$routes[] = [
'method' => strtoupper($method),
'route' => $route,
'handler' => $handler,
];
}
}
}
return [
'matchedRoute' => $matchedRoute,
'routes' => $routes,
];
}
/**
* Returns a count of all the routes in the system.
*/
public function getBadgeValue(): int
{
$rawRoutes = Services::routes(true);
return count($rawRoutes->getRoutes());
}
/**
* 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==';
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* 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 Config\Services;
/**
* Timers collector
*/
class Timers 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;
/**
* The 'title' of this Collector.
* Used to name things in the toolbar HTML.
*
* @var string
*/
protected $title = 'Timers';
/**
* Child classes should implement this to return the timeline data
* formatted for correct usage.
*/
protected function formatTimelineData(): array
{
$data = [];
$benchmark = Services::timer(true);
$rows = $benchmark->getTimers(6);
foreach ($rows as $name => $info) {
if ($name === 'total_execution') {
continue;
}
$data[] = [
'name' => ucwords(str_replace('_', ' ', $name)),
'component' => 'Timer',
'start' => $info['start'],
'duration' => $info['end'] - $info['start'],
];
}
return $data;
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* 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\View\RendererInterface;
use Config\Services;
/**
* 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 Renderer service
*
* @var RendererInterface
*/
protected $viewer;
/**
* Views counter
*
* @var array
*/
protected $views = [];
/**
* Constructor.
*/
public function __construct()
{
$this->viewer = Services::renderer();
}
/**
* Child classes should implement this to return the timeline data
* formatted for correct usage.
*/
protected function formatTimelineData(): array
{
$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
{
return [
'View Data' => $this->viewer->getData(),
];
}
/**
* Returns a count of all views.
*/
public function getBadgeValue(): int
{
return count($this->viewer->getPerformanceData());
}
/**
* 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=';
}
}

View File

@@ -0,0 +1,48 @@
<p class="debug-bar-alignRight">
<a href="https://codeigniter4.github.io/CodeIgniter4/index.html" target="_blank" >Read the CodeIgniter docs...</a>
</p>
<table>
<tbody>
<tr>
<td>CodeIgniter Version:</td>
<td>{ ciVersion }</td>
</tr>
<tr>
<td>PHP Version:</td>
<td>{ phpVersion }</td>
</tr>
<tr>
<td>PHP SAPI:</td>
<td>{ phpSAPI }</td>
</tr>
<tr>
<td>Environment:</td>
<td>{ environment }</td>
</tr>
<tr>
<td>Base URL:</td>
<td>
{ if $baseURL == '' }
<div class="warning">
The $baseURL should always be set manually to prevent possible URL personification from external parties.
</div>
{ else }
{ baseURL }
{ endif }
</td>
</tr>
<tr>
<td>Timezone:</td>
<td>{ timezone }</td>
</tr>
<tr>
<td>Locale:</td>
<td>{ locale }</td>
</tr>
<tr>
<td>Content Security Policy Enabled:</td>
<td>{ if $cspEnabled } Yes { else } No { endif }</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,26 @@
<table>
<thead>
<tr>
<th class="debug-bar-width6r">Time</th>
<th>Query String</th>
</tr>
</thead>
<tbody>
{queries}
<tr class="{class}" title="{hover}" data-toggle="{qid}-trace">
<td class="narrow">{duration}</td>
<td>{! sql !}</td>
<td style="text-align: right"><strong>{trace-file}</strong></td>
</tr>
<tr class="muted" id="{qid}-trace" style="display:none">
<td></td>
<td colspan="2">
{trace}
{index}<strong>{file}</strong><br/>
{function}<br/><br/>
{/trace}
</td>
</tr>
{/queries}
</tbody>
</table>

View File

@@ -0,0 +1,18 @@
<table>
<thead>
<tr>
<th class="debug-bar-width6r">Time</th>
<th>Event Name</th>
<th>Times Called</th>
</tr>
</thead>
<tbody>
{events}
<tr>
<td class="narrow">{ duration } ms</td>
<td>{event}</td>
<td>{count}</td>
</tr>
{/events}
</tbody>
</table>

View File

@@ -0,0 +1,16 @@
<table>
<tbody>
{userFiles}
<tr>
<td>{name}</td>
<td>{path}</td>
</tr>
{/userFiles}
{coreFiles}
<tr class="muted">
<td class="debug-bar-width20e">{name}</td>
<td>{path}</td>
</tr>
{/coreFiles}
</tbody>
</table>

View File

@@ -0,0 +1,28 @@
<table>
<thead>
<tr>
<th>Action</th>
<th>Datetime</th>
<th>Status</th>
<th>Method</th>
<th>URL</th>
<th>Content-Type</th>
<th>Is AJAX?</th>
</tr>
</thead>
<tbody>
{files}
<tr data-active="{active}">
<td class="debug-bar-width70p">
<button class="ci-history-load" data-time="{time}">Load</button>
</td>
<td class="debug-bar-width140p">{datetime}</td>
<td>{status}</td>
<td>{method}</td>
<td>{url}</td>
<td>{contentType}</td>
<td>{isAJAX}</td>
</tr>
{/files}
</tbody>
</table>

View File

@@ -0,0 +1,20 @@
{ if $logs == [] }
<p>Nothing was logged. If you were expecting logged items, ensure that LoggerConfig file has the correct threshold set.</p>
{ else }
<table>
<thead>
<tr>
<th>Severity</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{logs}
<tr>
<td>{level}</td>
<td>{msg}</td>
</tr>
{/logs}
</tbody>
</table>
{ endif }

View File

@@ -0,0 +1,52 @@
<h3>Matched Route</h3>
<table>
<tbody>
{matchedRoute}
<tr>
<td>Directory:</td>
<td>{directory}</td>
</tr>
<tr>
<td>Controller:</td>
<td>{controller}</td>
</tr>
<tr>
<td>Method:</td>
<td>{method}</td>
</tr>
<tr>
<td>Params:</td>
<td>{paramCount} / {truePCount}</td>
</tr>
{params}
<tr class="route-params-item">
<td>{name}</td>
<td>{value}</td>
</tr>
{/params}
{/matchedRoute}
</tbody>
</table>
<h3>Defined Routes</h3>
<table>
<thead>
<tr>
<th>Method</th>
<th>Route</th>
<th>Handler</th>
</tr>
</thead>
<tbody>
{routes}
<tr>
<td>{method}</td>
<td data-debugbar-route="{method}">{route}</td>
<td>{handler}</td>
</tr>
{/routes}
</tbody>
</table>

View File

@@ -0,0 +1,653 @@
/**
* 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: 0px;
padding: 0px;
clear: both;
text-align: center; }
#debug-icon a svg {
margin: 8px;
max-width: 20px;
max-height: 20px; }
#debug-icon.fixed-top {
bottom: auto;
top: 0; }
#debug-icon .debug-bar-ndisplay {
display: none; }
#debug-bar {
bottom: 0;
left: 0;
position: fixed;
right: 0;
z-index: 10000;
height: 36px;
line-height: 36px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-size: 16px;
font-weight: 400; }
#debug-bar h1 {
bottom: 0;
display: inline-block;
font-size: 14px;
font-weight: normal;
margin: 0 16px 0 0;
padding: 0;
position: absolute;
right: 30px;
text-align: left;
top: 0; }
#debug-bar h1 svg {
width: 16px;
margin-right: 5px; }
#debug-bar h2 {
font-size: 16px;
margin: 0;
padding: 5px 0 10px 0; }
#debug-bar h2 span {
font-size: 13px; }
#debug-bar h3 {
font-size: 12px;
font-weight: 200;
margin: 0 0 0 10px;
padding: 0;
text-transform: uppercase; }
#debug-bar p {
font-size: 12px;
margin: 0 0 0 15px;
padding: 0; }
#debug-bar a {
text-decoration: none; }
#debug-bar a:hover {
text-decoration: underline; }
#debug-bar button {
border: 1px solid;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
cursor: pointer;
line-height: 15px; }
#debug-bar button:hover {
text-decoration: underline; }
#debug-bar table {
border-collapse: collapse;
font-size: 14px;
line-height: normal;
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.fixed-top {
bottom: auto;
top: 0; }
#debug-bar.fixed-top .tab {
bottom: auto;
top: 36px; }
#debug-bar #toolbar-position a,
#debug-bar #toolbar-theme a {
padding: 0 6px;
display: inline-flex;
vertical-align: top; }
#debug-bar #toolbar-position a:hover,
#debug-bar #toolbar-theme a:hover {
text-decoration: none; }
#debug-bar #debug-bar-link {
bottom: 0;
display: inline-block;
font-size: 16px;
line-height: 36px;
padding: 6px;
position: absolute;
right: 10px;
top: 0;
width: 24px; }
#debug-bar .ci-label {
display: inline-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 {
bottom: 35px;
display: none;
left: 0;
max-height: 62%;
overflow: hidden;
overflow-y: auto;
padding: 1em 2em;
position: fixed;
right: 0;
z-index: 9999; }
#debug-bar .timeline {
margin-left: 0;
width: 100%; }
#debug-bar .timeline th {
border-left: 1px solid;
font-size: 12px;
font-weight: 200;
padding: 5px 5px 10px 5px;
position: relative;
text-align: left; }
#debug-bar .timeline th:first-child {
border-left: 0; }
#debug-bar .timeline td {
border-left: 1px solid;
padding: 5px;
position: relative; }
#debug-bar .timeline td:first-child {
border-left: 0;
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-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; } }
#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: #5BC0DE;
color: #FFFFFF; }
#debug-bar .tab {
background-color: #FFFFFF;
box-shadow: 0 -1px 4px #DFDFDF;
-moz-box-shadow: 0 -1px 4px #DFDFDF;
-webkit-box-shadow: 0 -1px 4px #DFDFDF; }
#debug-bar .timeline th,
#debug-bar .timeline td {
border-color: #DFDFDF; }
#debug-bar .timeline .timer {
background-color: #DD8615; }
.debug-view.show-view {
border-color: #DD8615; }
#debug-bar tr[data-toggle] {
cursor: pointer; }
.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: #797979; }
#debug-bar .muted:hover td {
color: #DFDFDF; }
#debug-bar #toolbar-position,
#debug-bar #toolbar-theme {
filter: brightness(0) invert(0.6); }
#debug-bar .ci-label.active {
background-color: #252525; }
#debug-bar .ci-label:hover {
background-color: #252525; }
#debug-bar .ci-label .badge {
background-color: #5BC0DE;
color: #DFDFDF; }
#debug-bar .tab {
background-color: #252525;
box-shadow: 0 -1px 4px #434343;
-moz-box-shadow: 0 -1px 4px #434343;
-webkit-box-shadow: 0 -1px 4px #434343; }
#debug-bar .timeline th,
#debug-bar .timeline td {
border-color: #434343; }
#debug-bar .timeline .timer {
background-color: #DD8615; }
.debug-view.show-view {
border-color: #DD8615; }
.debug-view-path {
background-color: #FDC894;
color: #434343; } }
#toolbarContainer.dark #debug-icon {
background-color: #252525;
box-shadow: 0 0 4px #DFDFDF;
-moz-box-shadow: 0 0 4px #DFDFDF;
-webkit-box-shadow: 0 0 4px #DFDFDF; }
#toolbarContainer.dark #debug-icon a:active,
#toolbarContainer.dark #debug-icon a:link,
#toolbarContainer.dark #debug-icon a:visited {
color: #DD8615; }
#toolbarContainer.dark #debug-bar {
background-color: #252525;
color: #DFDFDF; }
#toolbarContainer.dark #debug-bar h1,
#toolbarContainer.dark #debug-bar h2,
#toolbarContainer.dark #debug-bar h3,
#toolbarContainer.dark #debug-bar p,
#toolbarContainer.dark #debug-bar a,
#toolbarContainer.dark #debug-bar button,
#toolbarContainer.dark #debug-bar table,
#toolbarContainer.dark #debug-bar thead,
#toolbarContainer.dark #debug-bar tr,
#toolbarContainer.dark #debug-bar td,
#toolbarContainer.dark #debug-bar button,
#toolbarContainer.dark #debug-bar .toolbar {
background-color: transparent;
color: #DFDFDF; }
#toolbarContainer.dark #debug-bar button {
background-color: #252525; }
#toolbarContainer.dark #debug-bar table strong {
color: #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 #ci-database table tbody tr.duplicate {
background-color: #434343;}
#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: #797979; }
#toolbarContainer.dark #debug-bar .muted:hover td {
color: #DFDFDF; }
#toolbarContainer.dark #debug-bar #toolbar-position,
#toolbarContainer.dark #debug-bar #toolbar-theme {
filter: brightness(0) invert(0.6); }
#toolbarContainer.dark #debug-bar .ci-label.active {
background-color: #252525; }
#toolbarContainer.dark #debug-bar .ci-label:hover {
background-color: #252525; }
#toolbarContainer.dark #debug-bar .ci-label .badge {
background-color: #5BC0DE;
color: #DFDFDF; }
#toolbarContainer.dark #debug-bar .tab {
background-color: #252525;
box-shadow: 0 -1px 4px #434343;
-moz-box-shadow: 0 -1px 4px #434343;
-webkit-box-shadow: 0 -1px 4px #434343; }
#toolbarContainer.dark #debug-bar .timeline th,
#toolbarContainer.dark #debug-bar .timeline td {
border-color: #434343; }
#toolbarContainer.dark #debug-bar .timeline .timer {
background-color: #DD8615; }
#toolbarContainer.dark .debug-view.show-view {
border-color: #DD8615; }
#toolbarContainer.dark .debug-view-path {
background-color: #FDC894;
color: #434343; }
#toolbarContainer.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 #ci-database table tbody tr.duplicate {
background-color: #DFDFDF;}
#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: #797979; }
#toolbarContainer.light #debug-bar .muted td {
color: #797979; }
#toolbarContainer.light #debug-bar .muted:hover td {
color: #434343; }
#toolbarContainer.light #debug-bar #toolbar-position,
#toolbarContainer.light #debug-bar #toolbar-theme {
filter: brightness(0) invert(0.6); }
#toolbarContainer.light #debug-bar .ci-label.active {
background-color: #DFDFDF; }
#toolbarContainer.light #debug-bar .ci-label:hover {
background-color: #DFDFDF; }
#toolbarContainer.light #debug-bar .ci-label .badge {
background-color: #5BC0DE;
color: #FFFFFF; }
#toolbarContainer.light #debug-bar .tab {
background-color: #FFFFFF;
box-shadow: 0 -1px 4px #DFDFDF;
-moz-box-shadow: 0 -1px 4px #DFDFDF;
-webkit-box-shadow: 0 -1px 4px #DFDFDF; }
#toolbarContainer.light #debug-bar .timeline th,
#toolbarContainer.light #debug-bar .timeline td {
border-color: #DFDFDF; }
#toolbarContainer.light #debug-bar .timeline .timer {
background-color: #DD8615; }
#toolbarContainer.light .debug-view.show-view {
border-color: #DD8615; }
#toolbarContainer.light .debug-view-path {
background-color: #FDC894;
color: #434343; }
.debug-bar-width30 {
width: 30%; }
.debug-bar-width10 {
width: 10%; }
.debug-bar-width70p {
width: 70px; }
.debug-bar-width140p {
width: 140px; }
.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; }

View File

@@ -0,0 +1,687 @@
/*
* 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();
document.getElementById('debug-bar-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
document.getElementById('debug-icon-link').addEventListener('click', ciDebugBar.toggleToolbar, true);
// Allows to highlight the row of the current history request
var btn = this.toolbar.querySelector('button[data-time="' + localStorage.getItem('debugbar-time') + '"]');
ciDebugBar.addClass(btn.parentNode.parentNode, 'current');
historyLoad = this.toolbar.getElementsByClassName('ci-history-load');
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);
el.style.display = 'block';
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++)
{
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.style.display;
// Hide all tabs
var tabs = document.querySelectorAll('#debug-bar .tab');
for (var i = 0; i < tabs.length; i++)
{
tabs[i].style.display = 'none';
}
// 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 != 'block')
{
tab.style.display = 'block';
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'), ' ');
}
},
/**
* 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'));
target.style.display = target.style.display === 'none' ? 'table-row' : 'none';
}
},
/**
* Toggle display of a data table
*
* @param obj
*/
toggleDataTable : function (obj) {
if (typeof obj == 'string')
{
obj = document.getElementById(obj + '_table');
}
if (obj)
{
obj.style.display = obj.style.display === 'none' ? 'block' : 'none';
}
},
/**
* 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)
{
obj.style.display = obj.style.display === 'none' ? '' : 'none';
par.classList.toggle('timeline-parent-open');
}
},
//--------------------------------------------------------------------
/**
* Toggle tool bar from full to icon and icon to full
*/
toggleToolbar : function () {
var open = ciDebugBar.toolbar.style.display != 'none';
ciDebugBar.icon.style.display = open == true ? 'inline-block' : 'none';
ciDebugBar.toolbar.style.display = open == false ? 'inline-block' : 'none';
// 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');
ciDebugBar.icon.style.display = open != 'open' ? 'inline-block' : 'none';
ciDebugBar.toolbar.style.display = open == 'open' ? 'inline-block' : 'none';
},
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);
},
/**
* 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))
{
row.style = 'cursor: pointer;';
row.setAttribute('title', location.origin + '/' + ciDebugBar.trimSlash(row.innerText));
row.addEventListener('click', function (ev) {
_location = location.origin + '/' + ciDebugBar.trimSlash(ev.target.innerText);
var redirectWindow = window.open(_location, '_blank');
redirectWindow.location;
});
}
else
{
row.innerHTML = '<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" style="margin-left: 4px;">'
+ '</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;
}
})
}
}
};

View File

@@ -0,0 +1,271 @@
<?php
/**
* @var \CodeIgniter\Debug\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 \CodeIgniter\View\Parser $parser
*/
?>
<style type="text/css">
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . '/toolbar.css')) ?>
</style>
<script id="toolbar_js" type="text/javascript">
<?= file_get_contents(__DIR__ . '/toolbar.js') ?>
</script>
<div id="debug-icon" class="debug-bar-ndisplay">
<a id="debug-icon-link" href="javascript:void(0)">
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 155 200"><defs/><path fill="#dd4814" 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"><a href="javascript: void(0)">&#8597;</a></span>
<span id="toolbar-theme"><a href="javascript: void(0)">&#128261;</a></span>
<span class="ci-label">
<a href="javascript: void(0)" 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 &nbsp; <?= $totalMemory ?> MB</span>
</a>
</span>
<?php foreach ($collectors as $c) : ?>
<?php if (! $c['isEmpty'] && ($c['hasTabContent'] || $c['hasLabel'])) : ?>
<span class="ci-label">
<a href="javascript: void(0)" 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 ?>
<span class="ci-label">
<a href="javascript: void(0)" data-tab="ci-vars">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACLSURBVEhLYxgFJIHU1NSraWlp/6H4T0pKSjRUijoAyXAwBlrYDpViAFpmARQrJwZDtWACoCROC4D8CnR5XBiqBRMADfyNprgRKkUdAApzoCUdUNwE5MtApYYIALp6NBWBMVQLJgAaOJqK8AOgq+mSio6DggjEBtLUT0UwQ5HZIADkj6aiUTAggIEBANAEDa/lkCRlAAAAAElFTkSuQmCC">
<span class="hide-sm">Vars</span>
</a>
</span>
<h1>
<span class="ci-label">
<a href="javascript: void(0)" data-tab="ci-config">
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 155 200"><defs/><path fill="#dd4814" 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>
<?= $CI_VERSION ?>
</a>
</span>
</h1>
<!-- Open/Close Toggle -->
<a id="debug-bar-link" href="javascript:void(0)" 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'] : $parser->setData($c['display'])->render("_{$c['titleSafe']}.tpl") ?>
</div>
<?php endif ?>
<?php endif ?>
<?php endforeach ?>
<!-- In & Out -->
<div id="ci-vars" class="tab">
<!-- VarData from Collectors -->
<?php if (isset($vars['varData'])) : ?>
<?php foreach ($vars['varData'] as $heading => $items) : ?>
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('<?= strtolower(str_replace(' ', '-', $heading)) ?>'); return false;">
<h2><?= $heading ?></h2>
</a>
<?php if (is_array($items)) : ?>
<table id="<?= strtolower(str_replace(' ', '-', $heading . '_table')) ?>">
<tbody>
<?php foreach ($items as $key => $value) : ?>
<tr>
<td><?= $key ?></td>
<td><?= $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php else: ?>
<p class="muted">No data to display.</p>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
<!-- Session -->
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('session'); return false;">
<h2>Session User Data</h2>
</a>
<?php if (isset($vars['session'])) : ?>
<?php if (! empty($vars['session'])) : ?>
<table id="session_table">
<tbody>
<?php foreach ($vars['session'] as $key => $value) : ?>
<tr>
<td><?= $key ?></td>
<td><?= $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php else : ?>
<p class="muted">No data to display.</p>
<?php endif ?>
<?php else : ?>
<p class="muted">Session doesn't seem to be active.</p>
<?php endif ?>
<h2>Request <span>( <?= $vars['request'] ?> )</span></h2>
<?php if (isset($vars['get']) && $get = $vars['get']) : ?>
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('get'); return false;">
<h3>$_GET</h3>
</a>
<table id="get_table">
<tbody>
<?php foreach ($get as $name => $value) : ?>
<tr>
<td><?= $name ?></td>
<td><?= $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php if (isset($vars['post']) && $post = $vars['post']) : ?>
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('post'); return false;">
<h3>$_POST</h3>
</a>
<table id="post_table">
<tbody>
<?php foreach ($post as $name => $value) : ?>
<tr>
<td><?= $name ?></td>
<td><?= $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php if (isset($vars['headers']) && $headers = $vars['headers']) : ?>
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('request_headers'); return false;">
<h3>Headers</h3>
</a>
<table id="request_headers_table">
<tbody>
<?php foreach ($headers as $header => $value) : ?>
<tr>
<td><?= $header ?></td>
<td><?= $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php if (isset($vars['cookies']) && $cookies = $vars['cookies']) : ?>
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('cookie'); return false;">
<h3>Cookies</h3>
</a>
<table id="cookie_table">
<tbody>
<?php foreach ($cookies as $name => $value) : ?>
<tr>
<td><?= $name ?></td>
<td><?= is_array($value) ? print_r($value, true) : $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<h2>Response
<span>( <?= $vars['response']['statusCode'] . ' - ' . $vars['response']['reason'] ?> )</span>
</h2>
<?php if (isset($vars['headers']) && $headers = $vars['headers']) : ?>
<a href="javascript:void(0)" onclick="ciDebugBar.toggleDataTable('response_headers'); return false;">
<h3>Headers</h3>
</a>
<table id="response_headers_table">
<tbody>
<?php foreach ($headers as $header => $value) : ?>
<tr>
<td><?= $header ?></td>
<td><?= $value ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
</div>
<!-- Config Values -->
<div id="ci-config" class="tab">
<h2>System Configuration</h2>
<?= $parser->setData($config)->render('_config.tpl') ?>
</div>
</div>
<style type="text/css">
<?php foreach ($styles as $name => $style): ?>
<?= sprintf(".%s { %s }\n", $name, $style) ?>
<?php endforeach ?>
</style>

View File

@@ -0,0 +1,87 @@
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 onclick="loadDoc(' + debugbarTime + ')">Update</button>';
document.querySelector('a[data-tab="ci-history"] > span > .badge').className += ' active';
}
}
}
}
}, false);
return realXHR;
}
window.XMLHttpRequest = newXHR;