';
}
}
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 ($elements !== [] && $elements[array_key_first($elements)]['end'] <= $element['end']) {
$element['children'][] = array_shift($elements);
}
// Make sure our children know whether they have children, too
if (isset($element['children'])) {
$element['children'] = $this->structureTimelineData($element['children']);
}
// If we have no younger siblings, we can return
if ($elements === []) {
return [$element];
}
// Make sure our younger siblings know their relatives, too
return array_merge([$element], $this->structureTimelineData($elements));
}
/**
* Returns an array of data from all of the modules
* that should be displayed in the 'Vars' tab.
*/
protected function collectVarData(): array
{
if (! ($this->config->collectVarData ?? true)) {
return [];
}
$data = [];
foreach ($this->collectors as $collector) {
if (! $collector->hasVarData()) {
continue;
}
$data = array_merge($data, $collector->getVarData());
}
return $data;
}
/**
* Rounds a number to the nearest incremental value.
*/
protected function roundTo(float $number, int $increments = 5): float
{
$increments = 1 / $increments;
return ceil($number * $increments) / $increments;
}
/**
* Prepare for debugging..
*
* @return void
*/
public function prepare(?RequestInterface $request = null, ?ResponseInterface $response = null)
{
/**
* @var IncomingRequest|null $request
*/
if (CI_DEBUG && ! is_cli()) {
$app = Services::codeigniter();
$request ??= Services::request();
$response ??= Services::response();
// Disable the toolbar for downloads
if ($response instanceof DownloadResponse) {
return;
}
$toolbar = Services::toolbar(config(ToolbarConfig::class));
$stats = $app->getPerformanceStats();
$data = $toolbar->run(
$stats['startTime'],
$stats['totalTime'],
$request,
$response
);
helper('filesystem');
// Updated to microtime() so we can get history
$time = sprintf('%.6f', Time::now()->format('U.u'));
if (! is_dir(WRITEPATH . 'debugbar')) {
mkdir(WRITEPATH . 'debugbar', 0777);
}
write_file(WRITEPATH . 'debugbar/debugbar_' . $time . '.json', $data, 'w+');
$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}"));
return;
}
$oldKintMode = Kint::$mode_default;
Kint::$mode_default = Kint::MODE_RICH;
$kintScript = @Kint::dump('');
Kint::$mode_default = $oldKintMode;
$kintScript = substr($kintScript, 0, strpos($kintScript, '') + 8);
$kintScript = ($kintScript === '0') ? '' : $kintScript;
$script = PHP_EOL
. ''
. ''
. ''
. $kintScript
. PHP_EOL;
if (strpos($response->getBody(), '') !== false) {
$response->setBody(
preg_replace(
'//',
'' . $script,
$response->getBody(),
1
)
);
return;
}
$response->appendBody($script);
}
}
/**
* Inject debug toolbar into the response.
*
* @codeCoverageIgnore
*
* @return void
*/
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 && preg_match('/\d+\.\d{6}/s', (string) Services::request()->getGet('debugbar_time'), $debugbarTime)) {
$history = new History();
$history->setFiles(
$debugbarTime[0],
$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;
}
}