diff --git a/application/Config/App.php b/application/Config/App.php index ef0ebee97507..15b20003c850 100644 --- a/application/Config/App.php +++ b/application/Config/App.php @@ -264,30 +264,6 @@ class App extends BaseConfig */ public $CSPEnabled = false; - /* - |-------------------------------------------------------------------------- - | Debug Toolbar - |-------------------------------------------------------------------------- - | The Debug Toolbar provides a way to see information about the performance - | and state of your application during that page display. By default it will - | NOT be displayed under production environments, and will only display if - | CI_DEBUG is true, since if it's not, there's not much to display anyway. - | - | toolbarMaxHistory = Number of history files, 0 for none or -1 for unlimited - | - */ - public $toolbarCollectors = [ - 'CodeIgniter\Debug\Toolbar\Collectors\Timers', - 'CodeIgniter\Debug\Toolbar\Collectors\Database', - 'CodeIgniter\Debug\Toolbar\Collectors\Logs', - 'CodeIgniter\Debug\Toolbar\Collectors\Views', - // 'CodeIgniter\Debug\Toolbar\Collectors\Cache', - 'CodeIgniter\Debug\Toolbar\Collectors\Files', - 'CodeIgniter\Debug\Toolbar\Collectors\Routes', - 'CodeIgniter\Debug\Toolbar\Collectors\Events', - ]; - public $toolbarMaxHistory = 20; - /* |-------------------------------------------------------------------------- | Application Salt diff --git a/application/Config/Events.php b/application/Config/Events.php index 7ea37d329934..8b2fcd4a4e89 100644 --- a/application/Config/Events.php +++ b/application/Config/Events.php @@ -29,6 +29,7 @@ { Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); - // Handles the display of the toolbar itself. MUST remain here for toolbar to be displayed. - Events::on('pre_system', 'CodeIgniter\Debug\Toolbar::eventHandler'); + Events::on('pre_system', function () { + Services::toolbar()->respond(); + }); } diff --git a/application/Config/Toolbar.php b/application/Config/Toolbar.php new file mode 100644 index 000000000000..7b59691527c1 --- /dev/null +++ b/application/Config/Toolbar.php @@ -0,0 +1,31 @@ +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, $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) - { - return $response->setHeader('Debugbar-Time', (string)$time) - ->setHeader('Debugbar-Link', site_url("?debugbar_time={$time}")) - ->getBody(); - } - - $script = PHP_EOL - . '' - . '' - . '' - . PHP_EOL; - - if (strpos($response->getBody(), '') !== false) - { - return $response->setBody(str_replace('', $script . '', - $response->getBody())); - } - - return $response->appendBody($script); - } + Services::toolbar()->prepare(); } //-------------------------------------------------------------------- diff --git a/system/Config/Services.php b/system/Config/Services.php index 7f724037b7b9..456dda6402c4 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -742,12 +742,12 @@ public static function timer($getShared = true) //-------------------------------------------------------------------- /** - * @param \Config\App $config - * @param boolean $getShared + * @param \Config\Toolbar $config + * @param boolean $getShared * * @return \CodeIgniter\Debug\Toolbar */ - public static function toolbar(\Config\App $config = null, bool $getShared = true) + public static function toolbar(\Config\Toolbar $config = null, bool $getShared = true) { if ($getShared) { @@ -756,7 +756,7 @@ public static function toolbar(\Config\App $config = null, bool $getShared = tru if (! is_object($config)) { - $config = config(App::class); + $config = config(\Config\Toolbar::class); } return new \CodeIgniter\Debug\Toolbar($config); diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index 041e860ebafe..b4ecf54ba2c6 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -36,7 +36,8 @@ * @filesource */ -use Config\App; +use CodeIgniter\Debug\Toolbar\Collectors\History; +use CodeIgniter\Format\JSONFormatter; use Config\Services; use CodeIgniter\Config\BaseConfig; use CodeIgniter\Format\XMLFormatter; @@ -52,20 +53,17 @@ */ class Toolbar { - /** - * Collectors to be used and displayed. - * - * @var array + * @var BaseConfig */ - protected $collectors = []; + protected $config; /** - * Incoming Request + * Collectors to be used and displayed. * - * @var \CodeIgniter\HTTP\IncomingRequest + * @var \CodeIgniter\Debug\Toolbar\Collectors\BaseCollector[] */ - protected static $request; + protected $collectors = []; //-------------------------------------------------------------------- @@ -76,12 +74,14 @@ class Toolbar */ public function __construct(BaseConfig $config) { - foreach ($config->toolbarCollectors as $collector) + $this->config = $config; + + foreach ($config->collectors as $collector) { if (! class_exists($collector)) { log_message('critical', 'Toolbar collector does not exists(' . $collector . ').' . - 'please check $toolbarCollectors in the Config\App.php file.'); + 'please check $collectors in the Config\Toolbar.php file.'); continue; } @@ -117,19 +117,7 @@ public function run($startTime, $totalTime, $request, $response): string foreach ($this->collectors as $collector) { - $data['collectors'][] = [ - 'title' => $collector->getTitle(), - 'titleSafe' => $collector->getTitle(true), - 'titleDetails' => $collector->getTitleDetails(), - 'display' => $collector->display(), - 'badgeValue' => $collector->getBadgeValue(), - 'isEmpty' => $collector->isEmpty(), - 'hasTabContent' => $collector->hasTabContent(), - 'hasLabel' => $collector->hasLabel(), - 'icon' => $collector->icon(), - 'hasTimelineData' => $collector->hasTimelineData(), - 'timelineData' => $collector->timelineData(), - ]; + $data['collectors'][] = $collector->getAsArray(); } foreach ($this->collectVarData() as $heading => $items) @@ -208,98 +196,6 @@ public function run($startTime, $totalTime, $request, $response): string //-------------------------------------------------------------------- - /** - * Format output - * - * @param string $data JSON encoded Toolbar data - * @param string $format html, json, xml - * - * @return string - */ - protected static function format(string $data, string $format = 'html') - { - $data = json_decode($data, true); - - // History must be loaded on the fly - $filenames = glob(WRITEPATH . 'debugbar/debugbar_*'); - $total = count($filenames); - rsort($filenames); - - $files = []; - - $current = static::$request->getGet('debugbar_time'); - $app = config(App::class); - - for ($i = 0; $i < $total; $i++) - { - // Oldest files will be deleted - if ($app->toolbarMaxHistory >= 0 && $i + 1 > $app->toolbarMaxHistory) - { - unlink($filenames[$i]); - continue; - } - - // Get the contents of this specific history request - ob_start(); - include($filenames[$i]); - $contents = ob_get_contents(); - ob_end_clean(); - - $file = json_decode($contents, true); - - // Debugbar files shown in History Collector - $files[] = [ - 'time' => (int)$time = substr($filenames[$i], -10), - 'datetime' => date('Y-m-d H:i:s', $time), - 'active' => (int)($time === $current), - 'status' => $file['vars']['response']['statusCode'], - 'method' => $file['method'], - 'url' => $file['url'], - 'isAJAX' => $file['isAJAX'] ? 'Yes' : 'No', - 'contentType' => $file['vars']['response']['contentType'], - ]; - } - - // Set the History here. Class is not necessary - $data['collectors'][] = [ - 'title' => 'History', - 'titleSafe' => 'history', - 'titleDetails' => '', - 'display' => ['files' => $files], - 'badgeValue' => $count = count($files), - 'isEmpty' => ! (bool)$count, - 'hasTabContent' => true, - 'hasLabel' => true, - 'icon' => '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==', - 'hasTimelineData' => false, - 'timelineData' => [], - ]; - - $output = ''; - - switch ($format) - { - case 'html': - $data['styles'] = []; - extract($data); - $parser = Services::parser(BASEPATH . 'Debug/Toolbar/Views/', null, false); - ob_start(); - include(__DIR__ . '/Toolbar/Views/toolbar.tpl.php'); - $output = ob_get_contents(); - ob_end_clean(); - break; - case 'json': - $output = json_encode($data); - break; - case 'xml': - $formatter = new XMLFormatter; - $output = $formatter->format($data); - break; - } - - return $output; - } - //-------------------------------------------------------------------- /** @@ -312,10 +208,10 @@ protected static function format(string $data, string $format = 'html') * * @return string */ - protected static function renderTimeline(array $collectors, $startTime, int $segmentCount, int $segmentDuration, array& $styles): string + protected function renderTimeline(array $collectors, $startTime, int $segmentCount, int $segmentDuration, array& $styles): string { $displayTime = $segmentCount * $segmentDuration; - $rows = static::collectTimelineData($collectors); + $rows = $this->collectTimelineData($collectors); $output = ''; $styleCount = 0; @@ -349,7 +245,7 @@ protected static function renderTimeline(array $collectors, $startTime, int $seg * * @return array */ - protected static function collectTimelineData($collectors): array + protected function collectTimelineData($collectors): array { $data = []; @@ -413,60 +309,174 @@ protected function roundTo($number, $increments = 5) //-------------------------------------------------------------------- + public function prepare() + { + if (CI_DEBUG && ! is_cli()) + { + global $app; + + $request = Services::request(); + $response = Services::response(); + + $toolbar = Services::toolbar(config(Toolbar::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; + } + + $script = PHP_EOL + . '' + . '' + . '' + . PHP_EOL; + + if (strpos($response->getBody(), '') !== false) + { + $response->setBody( + str_replace('', $script . '', $response->getBody()) + ); + + return; + } + + $response->appendBody($script); + } + } + + //-------------------------------------------------------------------- + /** * */ - public static function eventHandler() + public function respond() { - static::$request = Services::request(); - if (ENVIRONMENT === 'testing') { return; } + $request = Services::request(); + // If the request contains '?debugbar then we're // simply returning the loading script - if (static::$request->getGet('debugbar') !== null) + if ($request->getGet('debugbar') !== null) { // Let the browser know that we are sending javascript header('Content-Type: application/javascript'); ob_start(); - include(BASEPATH . 'Debug/Toolbar/toolbarloader.js.php'); - $output = ob_get_contents(); - @ob_end_clean(); + include($this->config->viewsPath . 'toolbarloader.js.php'); + $output = ob_get_clean(); exit($output); } // Otherwise, if it includes ?debugbar_time, then // we should return the entire debugbar. - if (static::$request->getGet('debugbar_time')) + if ($request->getGet('debugbar_time')) { helper('security'); // Negotiate the content-type to format the output - $format = static::$request->negotiate('media', [ + $format = $request->negotiate('media', [ 'text/html', 'application/json', 'application/xml', ]); $format = explode('/', $format)[1]; - $file = sanitize_filename('debugbar_' . static::$request->getGet('debugbar_time')); - $filename = WRITEPATH . 'debugbar/' . $file; + $file = sanitize_filename('debugbar_' . $request->getGet('debugbar_time')); + $filename = WRITEPATH . 'debugbar/' . $file . '.json'; // Show the toolbar if (is_file($filename)) { - $contents = static::format(file_get_contents($filename), $format); + $contents = $this->format(file_get_contents($filename), $format); exit($contents); } // File was not written or do not exists http_response_code(404); - exit(); // Exit here is needed to avoid load the index page + exit; // Exit here is needed to avoid load the index page } } + + /** + * Format output + * + * @param string $data JSON encoded Toolbar data + * @param string $format html, json, xml + * + * @return string + */ + protected function format(string $data, string $format = 'html') + { + $data = json_decode($data, true); + + if ($this->config->maxHistory !== 0) + { + $history = new History(); + $history->setFiles( + 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; + } } diff --git a/system/Debug/Toolbar/Collectors/BaseCollector.php b/system/Debug/Toolbar/Collectors/BaseCollector.php index a203599fdc6c..0495f2eab67c 100644 --- a/system/Debug/Toolbar/Collectors/BaseCollector.php +++ b/system/Debug/Toolbar/Collectors/BaseCollector.php @@ -302,4 +302,21 @@ public function icon(): string return ''; } + public function getAsArray() + { + 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(), + ]; + } + } diff --git a/system/Debug/Toolbar/Collectors/History.php b/system/Debug/Toolbar/Collectors/History.php new file mode 100644 index 000000000000..146ca71b6578 --- /dev/null +++ b/system/Debug/Toolbar/Collectors/History.php @@ -0,0 +1,170 @@ += 0 && $counter > $limit) + { + unlink($filename); + continue; + } + + // Get the contents of this specific history request + $contents = file_get_contents($filename); + $contents = json_decode($contents); + + \preg_match_all('/\d+/', $filename, $time); + $time = (int)$time[0][1]; + + // 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 + * + * @return array + */ + public function display(): array + { + return ['files' => $this->files]; + } + + //-------------------------------------------------------------------- + + /** + * Displays the number of included files as a badge in the tab button. + * + * @return integer + */ + public function getBadgeValue() + { + return count($this->files); + } + + public function isEmpty() + { + return empty($this->files); + } + + //-------------------------------------------------------------------- + + /** + * Display the icon. + * + * Icon from https://icons8.com - 1em package + * + * @return string + */ + 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=='; + } +} diff --git a/system/Debug/Toolbar/Views/toolbar.tpl.php b/system/Debug/Toolbar/Views/toolbar.tpl.php index d6b387a63b24..2494a834496e 100644 --- a/system/Debug/Toolbar/Views/toolbar.tpl.php +++ b/system/Debug/Toolbar/Views/toolbar.tpl.php @@ -1,3 +1,24 @@ + @@ -11,7 +32,7 @@ width="155.000000px" height="200.000000px" viewBox="0 0 155.000000 200.000000" preserveAspectRatio="xMidYMid meet"> - - + @@ -60,7 +81,7 @@

- + @@ -92,17 +113,18 @@
- - - - - - - - + + + + + + + + - + renderTimeline($collectors, $startTime, $segmentCount, $segmentDuration, + $styles) ?>
NAMECOMPONENTDURATION ms
NAMECOMPONENTDURATION ms
@@ -110,7 +132,7 @@ - +

@@ -124,10 +146,11 @@
- + $items) : ?> - +

@@ -218,10 +241,10 @@ $value) : ?> - - - - + + + +
@@ -237,14 +260,16 @@ $value) : ?> - + -

Response ( )

+

Response + ( ) +

@@ -276,5 +301,6 @@ . { } + diff --git a/system/Debug/Toolbar/toolbarloader.js.php b/system/Debug/Toolbar/Views/toolbarloader.js.php similarity index 100% rename from system/Debug/Toolbar/toolbarloader.js.php rename to system/Debug/Toolbar/Views/toolbarloader.js.php diff --git a/system/View/View.php b/system/View/View.php index 7df71925957a..f38976245e7c 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -215,26 +215,23 @@ public function render(string $view, array $options = null, $saveData = null): s if (CI_DEBUG && (! isset($options['debug']) || $options['debug'] === true)) { - $after = (new \Config\Filters())->globals['after']; - if (in_array('toolbar', $after) || array_key_exists('toolbar', $after)) + $toolbarCollectors = config(\Config\Toolbar::class)->collectors; + + if (in_array(\CodeIgniter\Debug\Toolbar\Collectors\Views::class, $toolbarCollectors)) { - $toolbarCollectors = (config(\Config\App::class))->toolbarCollectors; - if (in_array('CodeIgniter\Debug\Toolbar\Collectors\Views', $toolbarCollectors) || array_key_exists('CodeIgniter\Debug\Toolbar\Collectors\Views', $toolbarCollectors)) + // Clean up our path names to make them a little cleaner + foreach (['APPPATH', 'BASEPATH', 'ROOTPATH'] as $path) { - // Clean up our path names to make them a little cleaner - foreach (['APPPATH', 'BASEPATH', 'ROOTPATH'] as $path) + if (strpos($this->renderVars['file'], constant($path)) === 0) { - if (strpos($this->renderVars['file'], constant($path)) === 0) - { - $this->renderVars['file'] = str_replace(constant($path), $path . '/', $this->renderVars['file']); - break; - } + $this->renderVars['file'] = str_replace(constant($path), $path . '/', $this->renderVars['file']); + break; } - $this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file']; - $output = '' . PHP_EOL - . $output . PHP_EOL - . '' . PHP_EOL; } + $this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file']; + $output = '' . PHP_EOL + . $output . PHP_EOL + . '' . PHP_EOL; } }