diff --git a/spec/support/application/app/Config/toolbar.php b/spec/support/application/app/Config/toolbar.php index 2122a9f8..1ee59934 100644 --- a/spec/support/application/app/Config/toolbar.php +++ b/spec/support/application/app/Config/toolbar.php @@ -10,10 +10,12 @@ */ return [ - 'collectors' => [], - 'collect_var_data' => true, - 'max_history' => 20, - 'view_path' => SYST_PATH . 'Debug' . DS . 'Toolbar' . DS . 'Views', - 'max_queries' => 100, - 'show_debugbar' => true, + 'collectors' => [], + 'collect_var_data' => true, + 'max_history' => 20, + 'view_path' => SYST_PATH . 'Debug' . DS . 'Toolbar' . DS . 'Views', + 'max_queries' => 100, + 'show_debugbar' => true, + 'watched_directories' => ['app'], + 'watched_extensions' => ['php', 'css', 'js', 'html', 'svg', 'json', 'env'], ]; diff --git a/spec/system/framework/HotReloader/DirectoryHasher.spec.php b/spec/system/framework/HotReloader/DirectoryHasher.spec.php new file mode 100644 index 00000000..f67939c6 --- /dev/null +++ b/spec/system/framework/HotReloader/DirectoryHasher.spec.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +use BlitzPHP\Exceptions\FrameworkException; +use BlitzPHP\HotReloader\DirectoryHasher; + +use function Kahlan\expect; + +describe('HotReloader / DirectoryHasher', function (): void { + beforeAll(function (): void { + $this->hasher = new DirectoryHasher(); + }); + + it('hashApp', function (): void { + $results = $this->hasher->hashApp(); + + expect($results)->toBeA('array'); + expect($results)->toContainKey('app'); + }); + + it('Leve une exception si on essai de hasher un dossier invalide', function (): void { + $path = $path = APP_PATH . 'Foo'; + + expect(fn() => $this->hasher->hashDirectory($path)) + ->toThrow(FrameworkException::invalidDirectory($path)); + }); + + it('Chaque dossier a un hash unique', function (): void { + $hash1 = $this->hasher->hashDirectory(APP_PATH); + $hash2 = $this->hasher->hashDirectory(SYST_PATH); + + expect($hash1)->not->toBe($hash2); + }); + + it('Un meme dossier produira le meme hash', function (): void { + $hash1 = $this->hasher->hashDirectory(APP_PATH); + $hash2 = $this->hasher->hashDirectory(APP_PATH); + + expect($hash1)->toBe($hash2); + }); + + it ('hash', function (): void { + $expected = md5(implode('', $this->hasher->hashApp())); + + expect($expected)->toBe($this->hasher->hash()); + }); +}); diff --git a/src/Debug/Toolbar.php b/src/Debug/Toolbar.php index 64ba82c4..b422a35b 100644 --- a/src/Debug/Toolbar.php +++ b/src/Debug/Toolbar.php @@ -18,12 +18,12 @@ use BlitzPHP\Formatter\JsonFormatter; use BlitzPHP\Formatter\XmlFormatter; use BlitzPHP\Http\Request; +use BlitzPHP\Http\Response; use BlitzPHP\Utilities\Date; use BlitzPHP\View\Parser; use Exception; use GuzzleHttp\Psr7\Utils; use Kint\Kint; -use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use stdClass; @@ -66,8 +66,8 @@ public function __construct(?stdClass $config = null) foreach ($this->config->collectors as $collector) { if (! class_exists($collector)) { logger()->critical( - 'Toolbar collector does not exist (' . $collector . ').' - . ' Please check $collectors in the app/Config/toolbar.php file.' + 'Le collecteur de la barre d\'outils n\'existe pas (' . $collector . ').' + . ' Veuillez vérifier $collectors dans le fichier app/Config/toolbar.php.' ); continue; @@ -80,7 +80,8 @@ public function __construct(?stdClass $config = null) /** * Renvoie toutes les données requises par la barre de débogage * - * @param float $startTime Heure de début de l'application + * @param float $startTime Heure de début de l'application + * @param Request $request * * @return string Données encodées en JSON */ @@ -88,8 +89,8 @@ public function run(float $startTime, float $totalTime, ServerRequestInterface $ { // Éléments de données utilisés dans la vue. $data['url'] = current_url(); - $data['method'] = strtoupper($request->getMethod()); - $data['isAJAX'] = service('request')->ajax(); + $data['method'] = $request->getMethod(); + $data['isAJAX'] = $request->ajax(); $data['startTime'] = $startTime; $data['totalTime'] = $totalTime * 1000; $data['totalMemory'] = number_format(memory_get_peak_usage() / 1024 / 1024, 3); @@ -134,7 +135,7 @@ public function run(float $startTime, float $totalTime, ServerRequestInterface $ foreach ($_SESSION as $key => $value) { // Remplacez les données binaires par une chaîne pour éviter l'échec de json_encode. if (is_string($value) && preg_match('~[^\x20-\x7E\t\r\n]~', $value)) { - $value = 'binary data'; + $value = 'donnée binaire'; } $data['vars']['session'][esc($key)] = is_string($value) ? esc($value) : '
' . esc(print_r($value, true)) . '
'; @@ -160,7 +161,7 @@ public function run(float $startTime, float $totalTime, ServerRequestInterface $ $data['vars']['cookies'][esc($name)] = esc($value); } - $data['vars']['request'] = (service('request')->is('ssl') ? 'HTTPS' : 'HTTP') . '/' . $request->getProtocolVersion(); + $data['vars']['request'] = ($request->is('ssl') ? 'HTTPS' : 'HTTP') . '/' . $request->getProtocolVersion(); $data['vars']['response'] = [ 'statusCode' => $response->getStatusCode(), @@ -210,12 +211,12 @@ protected function renderTimelineRecursive(array $rows, float $startTime, int $s $open = $row['name'] === 'Controller'; if ($hasChildren || $isQuery) { - $output .= ''; + $output .= ''; } else { $output .= ''; } - $output .= '' . ($hasChildren || $isQuery ? '' : '') . $row['name'] . ''; + $output .= '' . ($hasChildren || $isQuery ? '' : '') . $row['name'] . ''; $output .= '' . $row['component'] . ''; $output .= '' . number_format($row['duration'] * 1000, 2) . ' ms'; $output .= ""; @@ -233,7 +234,7 @@ protected function renderTimelineRecursive(array $rows, float $startTime, int $s // Ajouter des enfants le cas échéant if ($hasChildren || $isQuery) { - $output .= ''; + $output .= ''; $output .= ''; $output .= ''; $output .= ''; @@ -241,7 +242,7 @@ protected function renderTimelineRecursive(array $rows, float $startTime, int $s if ($isQuery) { // Sortie de la chaîne de requête si requête $output .= ''; - $output .= ''; + $output .= ''; $output .= ''; } else { // Rendre récursivement les enfants @@ -356,13 +357,34 @@ protected function roundTo(float $number, int $increments = 5): float return ceil($number * $increments) / $increments; } + /** + * Traite la barre d'outils de débogage pour la requête en cours. + * + * Cette méthode détermine s'il faut afficher la barre d'outils de débogage ou la préparer pour une utilisation ultérieure. + * + * @param array $stats Un tableau contenant des statistiques de performances. + * @param Request $request La requête serveur en cours. + * @param ResponseInterface $response La réponse en cours. + * + * @return ResponseInterface La réponse traitée, avec la barre d'outils de débogage injectée ou préparée pour une utilisation ultérieure. + */ + public function process(array $stats, ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + if ($request->hasAny('blitzphp-debugbar', 'debugbar_time')) { + return $this->respond($request); + } + + return $this->prepare($stats, $request, $response); + } + /** * Préparez-vous au débogage.. */ - public function prepare(array $stats, ?RequestInterface $request = null, ?ResponseInterface $response = null): ResponseInterface + public function prepare(array $stats, ?ServerRequestInterface $request = null, ?ResponseInterface $response = null): ResponseInterface { /** @var Request $request */ $request ??= service('request'); + /** @var Response $response */ $response ??= service('response'); // Si on est en CLI ou en prod, pas la peine de continuer car la debugbar n'est pas utilisable dans ces environnements @@ -402,95 +424,88 @@ public function prepare(array $stats, ?RequestInterface $request = null, ?Respon ->withHeader('Debugbar-Link', site_url("?debugbar_time={$time}")); } - $_SESSION['_blitz_debugbar_'] = array_merge($_SESSION['_blitz_debugbar_'] ?? [], compact('time')); - - $debugRenderer = $this->respond(); - - // Extract css - preg_match('/') + 8); + $kintScript = ($kintScript === '0') ? '' : $kintScript; + + $script = PHP_EOL + . '' + . '' + . '' + . $kintScript + . PHP_EOL; + + if (str_contains($responseContent = (string) $response->getBody(), '')) { + $responseContent = preg_replace( + '//', + '' . $script, + $responseContent, + 1, + ); } else { - $responseContent .= '
' . trim(preg_replace('/\s+/', ' ', $debugRenderer)) . '
' . $js . ''; + $responseContent .= $script; } - return $response->withBody( - Utils::streamFor($responseContent) - ); + return $response->withBody(Utils::streamFor($responseContent)); } /** * Injectez la barre d'outils de débogage dans la réponse. * - * @return string + * @param Request $request * * @codeCoverageIgnore */ - public function respond() + public function respond(ServerRequestInterface $request): Response { + $response = new Response(); + if (on_test()) { - return ''; + return $response; } - $request = service('request'); - - // Si la requête contient '?debugbar alors nous sommes + // Si la requête contient '?blitzphp-debugbar alors nous sommes // renvoie simplement le script de chargement - if ($request->getQuery('debugbar') !== null) { - header('Content-Type: application/javascript'); + if ($request->getQuery('blitzphp-debugbar') !== null) { + $response = $response->withType('application/javascript'); ob_start(); - include $this->config->view_path . 'toolbarloader.js'; + include $this->config->view_path . DS . 'toolbarloader.js'; $output = ob_get_clean(); - return str_replace('{url}', rtrim(site_url(), '/'), $output); + return $response->withStringBody(str_replace('{url}', rtrim(site_url(), '/'), $output)); } // Sinon, s'il inclut ?debugbar_time, alors // nous devrions retourner la barre de débogage entière. - $debugbarTime = $_SESSION['_blitz_debugbar_']['time'] ?? $request->getQuery('debugbar_time'); - if ($debugbarTime) { + if (null !== $debugbarTime = $request->getQuery('debugbar_time')) { // Négociation du type de contenu pour formater la sortie - $format = $request->negotiate('media', ['text/html', 'application/json', 'application/xml']); - $format = explode('/', $format)[1]; + $format = $request->negotiate('media', ['text/html', 'application/json', 'application/xml']); + $response = $response->withType($format); + $format = explode('/', $format)[1]; $filename = 'debugbar_' . $debugbarTime; $filename = $this->debugPath . DS . $filename . '.json'; if (is_file($filename)) { // Affiche la barre d'outils si elle existe - return $this->format($debugbarTime, file_get_contents($filename), $format); + return $response->withStringBody($this->format($debugbarTime, file_get_contents($filename), $format)); } - - // Nom de fichier introuvable - http_response_code(404); - - exit; // Quitter ici est nécessaire pour éviter de charger la page d'index } - return ''; + // Nom de fichier introuvable + return $response->withStatus(404); } /** * Formatte la sortie * - * @param float $debugbar_time + * @param mixed $debugbar_time */ protected function format($debugbar_time, string $data, string $format = 'html'): string { diff --git a/src/Debug/Toolbar/Collectors/RoutesCollector.php b/src/Debug/Toolbar/Collectors/RoutesCollector.php index 9e483b56..c4cd23b3 100644 --- a/src/Debug/Toolbar/Collectors/RoutesCollector.php +++ b/src/Debug/Toolbar/Collectors/RoutesCollector.php @@ -54,6 +54,25 @@ public function __construct() /** * {@inheritDoc} * + * @return array{ + * matchedRoute: array + * }>, + * routes: list + * } + * * @throws ReflectionException */ public function display(): array @@ -91,8 +110,8 @@ public function display(): array $matchedRoute = [ [ 'directory' => $this->router->directory(), - 'controller' => $this->router->controllerName(), - 'method' => $this->router->methodName(), + 'controller' => is_string($controller = $this->router->controllerName()) ? $controller : 'Non défini', + 'method' => is_string($controller) ? $this->router->methodName() : 'Non définie', 'paramCount' => count($this->router->params()), 'truePCount' => count($params), 'params' => $params, diff --git a/src/Debug/Toolbar/Views/_database.tpl b/src/Debug/Toolbar/Views/_database.tpl index 0b556125..e2dbc95f 100644 --- a/src/Debug/Toolbar/Views/_database.tpl +++ b/src/Debug/Toolbar/Views/_database.tpl @@ -8,10 +8,20 @@ {queries} - + + + + + + {/queries} diff --git a/src/Debug/Toolbar/Views/_history.tpl b/src/Debug/Toolbar/Views/_history.tpl index a2fee0b0..c658c0af 100644 --- a/src/Debug/Toolbar/Views/_history.tpl +++ b/src/Debug/Toolbar/Views/_history.tpl @@ -13,8 +13,8 @@ {files} - diff --git a/src/Debug/Toolbar/Views/_routes.tpl b/src/Debug/Toolbar/Views/_routes.tpl index f7f63e41..f5cf2266 100644 --- a/src/Debug/Toolbar/Views/_routes.tpl +++ b/src/Debug/Toolbar/Views/_routes.tpl @@ -9,7 +9,7 @@
' . $row['query'] . '' . $row['query'] . '
{duration} {! sql !} {affected_rows}{trace-file}
+ {trace} + {index}{file}
+ {function}

+ {/trace} +
- + + {datetime} {status}
-

Route assortis

+

Route adaptée

diff --git a/src/Debug/Toolbar/Views/toolbar-min.js b/src/Debug/Toolbar/Views/toolbar-min.js index 1b4156e3..83116a47 100644 --- a/src/Debug/Toolbar/Views/toolbar-min.js +++ b/src/Debug/Toolbar/Views/toolbar-min.js @@ -1 +1 @@ -var blitzphpDebugBar={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"),blitzphpDebugBar.createListeners(),blitzphpDebugBar.setToolbarState(),blitzphpDebugBar.setToolbarPosition(),blitzphpDebugBar.setToolbarTheme(),blitzphpDebugBar.toggleViewsHints(),blitzphpDebugBar.routerLink(),document.getElementById("debug-bar-link").addEventListener("click",blitzphpDebugBar.toggleToolbar,!0),document.getElementById("debug-icon-link").addEventListener("click",blitzphpDebugBar.toggleToolbar,!0);var e=this.toolbar.querySelector('button[data-time="'+localStorage.getItem("debugbar-time")+'"]');blitzphpDebugBar.addClass(e.parentNode.parentNode,"current"),historyLoad=this.toolbar.getElementsByClassName("blitzphp-history-load");for(var t=0;t"+e.innerText+'
'+e.innerText.replace(r,'')+'':(e.style="cursor: pointer;",e.setAttribute("title",location.origin+"/"+blitzphpDebugBar.trimSlash(e.innerText)),e.addEventListener("click",(function(e){t=location.origin+"/"+blitzphpDebugBar.trimSlash(e.target.innerText),window.open(t,"_blank").location})));a=this.toolbar.querySelectorAll('td[data-debugbar-route="GET"] form');for(l=0;l0&&a.push(l[i].value);a.length>0&&(t=location.origin+"/"+o.replace(/\?/g,(function(){return a[r++]})),window.open(t,"_blank").location)}))}}; \ No newline at end of file +var blitzphpDebugBar={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"),blitzphpDebugBar.createListeners(),blitzphpDebugBar.setToolbarState(),blitzphpDebugBar.setToolbarPosition(),blitzphpDebugBar.setToolbarTheme(),blitzphpDebugBar.toggleViewsHints(),blitzphpDebugBar.routerLink(),blitzphpDebugBar.setHotReloadState(),document.getElementById("debug-bar-link").addEventListener("click",blitzphpDebugBar.toggleToolbar,!0),document.getElementById("debug-icon-link").addEventListener("click",blitzphpDebugBar.toggleToolbar,!0);var e=this.toolbar.querySelector('button[data-time="'+localStorage.getItem("debugbar-time")+'"]');blitzphpDebugBar.addClass(e.parentNode.parentNode,"current"),historyLoad=this.toolbar.getElementsByClassName("blitzphp-history-load");for(var t=0;t{},e},createCookie:function(e,t,a){if(a){var r=new Date;r.setTime(r.getTime()+24*a*60*60*1e3);var i="; expires="+r.toGMTString()}else i="";document.cookie=e+"="+t+i+"; path=/; samesite=Lax"},readCookie:function(e){for(var t=e+"=",a=document.cookie.split(";"),r=0;r"+e.innerText+'
'+e.innerText.replace(r,'')+'':(blitzphpDebugBar.addClass(e,"debug-bar-pointer"),e.setAttribute("title",location.origin+"/"+blitzphpDebugBar.trimSlash(e.innerText)),e.addEventListener("click",(function(e){t=location.origin+"/"+blitzphpDebugBar.trimSlash(e.target.innerText),window.open(t,"_blank").location})));a=this.toolbar.querySelectorAll('td[data-debugbar-route="GET"] form');for(i=0;i0&&a.push(i[o].value);a.length>0&&(t=location.origin+"/"+l.replace(/\?/g,(function(){return a[r++]})),window.open(t,"_blank").location)}))}}; \ No newline at end of file diff --git a/src/Debug/Toolbar/Views/toolbar.css b/src/Debug/Toolbar/Views/toolbar.css index 92f261fe..bf68e7b3 100644 --- a/src/Debug/Toolbar/Views/toolbar.css +++ b/src/Debug/Toolbar/Views/toolbar.css @@ -5,10 +5,11 @@ z-index: 10000; height: 36px; width: 36px; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; clear: both; text-align: center; + cursor: pointer; } #debug-icon a svg { margin: 8px; @@ -23,6 +24,10 @@ display: none; } + #debug-bar .debug-bar-vars { + cursor: pointer; + } + #debug-bar { bottom: 0; left: 0; @@ -39,12 +44,15 @@ display: flex; font-weight: normal; margin: 0 0 0 auto; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; } #debug-bar h1 svg { width: 16px; margin-right: 5px; } #debug-bar h2 { + font-weight: bold; font-size: 16px; margin: 0; padding: 5px 0 10px 0; @@ -87,6 +95,7 @@ line-height: normal; margin: 5px 10px 15px 10px; width: calc(100% - 10px); + overflow-y: auto; } #debug-bar table strong { font-weight: 500; @@ -148,19 +157,21 @@ bottom: auto; top: 36px; } - #debug-bar #toolbar-position a, - #debug-bar #toolbar-theme a { + #debug-bar #toolbar-position, + #debug-bar #toolbar-theme { padding: 0 6px; display: inline-flex; vertical-align: top; + cursor: pointer; } - #debug-bar #toolbar-position a:hover, - #debug-bar #toolbar-theme a:hover { + #debug-bar #toolbar-position:hover, + #debug-bar #toolbar-theme:hover { text-decoration: none; } #debug-bar #debug-bar-link { display: flex; padding: 6px; + cursor: pointer; } #debug-bar .blitzphp-label { display: inline-flex; @@ -196,6 +207,8 @@ white-space: nowrap; } #debug-bar .tab { + height: fit-content; + text-align: left; bottom: 35px; display: none; left: 0; @@ -208,6 +221,8 @@ z-index: 9999; } #debug-bar .timeline { + position: static; + display: table; margin-left: 0; width: 100%; } @@ -280,6 +295,9 @@ padding-left: 1em; text-align: right; } + #debug-bar > .debug-bar-dblock { + display: block; + } .debug-view.show-view { border: 1px solid; @@ -307,6 +325,25 @@ display: none !important; } } + @media screen and (max-width: 768px) { + #debug-bar table { + display: block; + font-size: 12px; + margin: 5px 5px 10px 5px; + } + #debug-bar table td, + #debug-bar table th { + padding: 4px 6px; + } + #debug-bar .timeline { + display: block; + white-space: nowrap; + font-size: 12px; + } + #debug-bar .toolbar { + overflow-x: auto; + } + } #debug-icon { background-color: #FFFFFF; box-shadow: 0 0 4px #DFDFDF; @@ -524,9 +561,6 @@ #debug-bar .timeline .timer { background-color: #c2bb44; } - #debug-bar .timeline .timeline-parent-open td { - color: #252525; - } .debug-view.show-view { border-color: #c2bb44; } @@ -639,9 +673,6 @@ #toolbarContainer.dark #debug-bar .timeline .timer { background-color: #c2bb44; } - #toolbarContainer.dark #debug-bar .timeline .timeline-parent-open td { - color: #252525; - } #toolbarContainer.dark .debug-view.show-view { border-color: #c2bb44; } @@ -802,4 +833,47 @@ .debug-bar-noverflow { overflow: hidden; } - \ No newline at end of file + + .debug-bar-dtableRow { + display: table-row; + } + + .debug-bar-dinlineBlock { + display: inline-block; + } + + .debug-bar-pointer { + cursor: pointer; + } + + .debug-bar-mleft4 { + margin-left: 4px; + } + + .debug-bar-level-0 { + --level: 0; + } + + .debug-bar-level-1 { + --level: 1; + } + + .debug-bar-level-2 { + --level: 2; + } + + .debug-bar-level-3 { + --level: 3; + } + + .debug-bar-level-4 { + --level: 4; + } + + .debug-bar-level-5 { + --level: 5; + } + + .debug-bar-level-6 { + --level: 6; + } \ No newline at end of file diff --git a/src/Debug/Toolbar/Views/toolbar.js b/src/Debug/Toolbar/Views/toolbar.js new file mode 100644 index 00000000..6f1d4f44 --- /dev/null +++ b/src/Debug/Toolbar/Views/toolbar.js @@ -0,0 +1,824 @@ +/* + * Fonctionnalité de la barre d'outils de débogage BlitzPHP. + */ + +var blitzphpDebugBar = { + 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"); + + blitzphpDebugBar.createListeners(); + blitzphpDebugBar.setToolbarState(); + blitzphpDebugBar.setToolbarPosition(); + blitzphpDebugBar.setToolbarTheme(); + blitzphpDebugBar.toggleViewsHints(); + blitzphpDebugBar.routerLink(); + blitzphpDebugBar.setHotReloadState(); + + document + .getElementById("debug-bar-link") + .addEventListener("click", blitzphpDebugBar.toggleToolbar, true); + document + .getElementById("debug-icon-link") + .addEventListener("click", blitzphpDebugBar.toggleToolbar, true); + + // Permet de mettre en évidence la ligne d'historique de la requete en cours + var btn = this.toolbar.querySelector( + 'button[data-time="' + localStorage.getItem("debugbar-time") + '"]' + ); + blitzphpDebugBar.addClass(btn.parentNode.parentNode, "current"); + + historyLoad = this.toolbar.getElementsByClassName("blitzphp-history-load"); + + for (var i = 0; i < historyLoad.length; i++) { + historyLoad[i].addEventListener( + "click", + function () { + loadDoc(this.getAttribute("data-time")); + }, + true + ); + } + + // Afficher l'onglet actif au chargement de la page + var tab = blitzphpDebugBar.readCookie("debug-bar-tab"); + if (document.getElementById(tab)) { + var el = document.getElementById(tab); + blitzphpDebugBar.switchClass(el, "debug-bar-ndisplay", "debug-bar-dblock"); + blitzphpDebugBar.addClass(el, "active"); + tab = document.querySelector("[data-tab=" + tab + "]"); + if (tab) { + blitzphpDebugBar.addClass(tab.parentNode, "active"); + } + } + }, + + createListeners: function () { + var buttons = [].slice.call( + this.toolbar.querySelectorAll(".blitzphp-label a") + ); + + for (var i = 0; i < buttons.length; i++) { + buttons[i].addEventListener("click", blitzphpDebugBar.showTab, true); + } + + // Connecter une bascule générique via les attributs de données `data-toggle="foo"` + var links = this.toolbar.querySelectorAll("[data-toggle]"); + for (var i = 0; i < links.length; i++) { + let toggleData = links[i].getAttribute("data-toggle"); + if (toggleData === "datatable") { + + let datatable = links[i].getAttribute("data-table"); + links[i].addEventListener("click", function() { + blitzphpDebugBar.toggleDataTable(datatable) + }, true); + + } else if (toggleData === "childrows") { + + let child = links[i].getAttribute("data-child"); + links[i].addEventListener("click", function() { + blitzphpDebugBar.toggleChildRows(child) + }, true); + + } else { + links[i].addEventListener("click", blitzphpDebugBar.toggleRows, true); + } + } + }, + + showTab: function () { + // Obtenir l'onglet cible, le cas échéant + var tab = document.getElementById(this.getAttribute("data-tab")); + + // Si l'étiquette n'a pas de tabulation, arrêtez-vous ici + if (! tab) { + return; + } + + // Supprimer le cookie de la barre de débogage + blitzphpDebugBar.createCookie("debug-bar-tab", "", -1); + + // Vérifiez notre état actuel. + var state = tab.classList.contains("debug-bar-dblock"); + + // Masquer tous les onglets + var tabs = document.querySelectorAll("#debug-bar .tab"); + + for (var i = 0; i < tabs.length; i++) { + blitzphpDebugBar.switchClass(tabs[i], "debug-bar-dblock", "debug-bar-ndisplay"); + } + + // Marquer toutes les étiquettes comme inactives + var labels = document.querySelectorAll("#debug-bar .blitzphp-label"); + + for (var i = 0; i < labels.length; i++) { + blitzphpDebugBar.removeClass(labels[i], "active"); + } + + // Afficher/masquer l'onglet sélectionné + if (! state) { + blitzphpDebugBar.switchClass(tab, "debug-bar-ndisplay", "debug-bar-dblock"); + blitzphpDebugBar.addClass(this.parentNode, "active"); + // Créer un cookie de débogage-barre-onglet à l'état persistant + blitzphpDebugBar.createCookie( + "debug-bar-tab", + this.getAttribute("data-tab"), + 365 + ); + } + }, + + addClass: function (el, className) { + if (el.classList) { + el.classList.add(className); + } else { + el.className += " " + className; + } + }, + + removeClass: function (el, className) { + if (el.classList) { + el.classList.remove(className); + } else { + el.className = el.className.replace( + new RegExp( + "(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", + "gi" + ), + " " + ); + } + }, + + switchClass : function(el, classFrom, classTo) { + blitzphpDebugBar.removeClass(el, classFrom); + blitzphpDebugBar.addClass(el, classTo); + }, + + /** + * Basculer l'affichage d'un autre objet en fonction de la valeur de basculement des données de cet objet + * + * @param event + */ + toggleRows: function (event) { + if (event.target) { + let row = event.target.closest("tr"); + let target = document.getElementById( + row.getAttribute("data-toggle") + ); + + if (target.classList.contains("debug-bar-ndisplay")) { + blitzphpDebugBar.switchClass(target, "debug-bar-ndisplay", "debug-bar-dtableRow"); + } else { + blitzphpDebugBar.switchClass(target, "debug-bar-dtableRow", "debug-bar-ndisplay"); + } + } + }, + + /** + * Basculer l'affichage d'un tableau de données + * + * @param obj + */ + toggleDataTable: function (obj) { + if (typeof obj == "string") { + obj = document.getElementById(obj + "_table"); + } + + if (obj) { + if (obj.classList.contains("debug-bar-ndisplay")) { + blitzphpDebugBar.switchClass(obj, "debug-bar-ndisplay", "debug-bar-dblock"); + } else { + blitzphpDebugBar.switchClass(obj, "debug-bar-dblock", "debug-bar-ndisplay"); + } + } + }, + + /** + * Activer/désactiver l'affichage des éléments enfants de la chronologie + * + * @param obj + */ + toggleChildRows: function (obj) { + if (typeof obj == "string") { + par = document.getElementById(obj + "_parent"); + obj = document.getElementById(obj + "_children"); + } + + if (par && obj) { + + if (obj.classList.contains("debug-bar-ndisplay")) { + blitzphpDebugBar.removeClass(obj, "debug-bar-ndisplay"); + } else { + blitzphpDebugBar.addClass(obj, "debug-bar-ndisplay"); + } + + par.classList.toggle("timeline-parent-open"); + } + }, + + //-------------------------------------------------------------------- + + /** + * Basculer la barre d'outils de pleine à icône et de l'icône à pleine (aggrandir/reduire) + */ + toggleToolbar: function () { + var open = ! blitzphpDebugBar.toolbar.classList.contains("debug-bar-ndisplay"); + + if (open) { + blitzphpDebugBar.switchClass(blitzphpDebugBar.icon, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + blitzphpDebugBar.switchClass(blitzphpDebugBar.toolbar, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + } else { + blitzphpDebugBar.switchClass(blitzphpDebugBar.icon, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + blitzphpDebugBar.switchClass(blitzphpDebugBar.toolbar, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + } + + // Remember it for other page loads on this site + blitzphpDebugBar.createCookie("debug-bar-state", "", -1); + blitzphpDebugBar.createCookie( + "debug-bar-state", + open == true ? "minimized" : "open", + 365 + ); + }, + + /** + * Définit l'état initial de la barre d'outils (ouverte ou réduite) lorsque la page est chargée + * pour la première fois pour lui permettre de mémoriser l'état entre les actualisations. + */ + setToolbarState: function () { + var open = blitzphpDebugBar.readCookie("debug-bar-state"); + + if (open != "open") { + blitzphpDebugBar.switchClass(blitzphpDebugBar.icon, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + blitzphpDebugBar.switchClass(blitzphpDebugBar.toolbar, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + } else { + blitzphpDebugBar.switchClass(blitzphpDebugBar.icon, "debug-bar-dinlineBlock", "debug-bar-ndisplay"); + blitzphpDebugBar.switchClass(blitzphpDebugBar.toolbar, "debug-bar-ndisplay", "debug-bar-dinlineBlock"); + } + }, + + toggleViewsHints: function () { + // Évitez les astuces de basculement sur les demandes d'historique qui ne sont pas les initiales + if ( + localStorage.getItem("debugbar-time") != + localStorage.getItem("debugbar-time-new") + ) { + var a = document.querySelector('a[data-tab="blitzphp-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(); + } + + // trier les commentaires par balises d'ouverture et de fermeture + for (var i = 0; i < nodes.length; ++i) { + // obtenir le chemin du fichier + le nom à utiliser comme clé + var path = nodes[i].nodeValue.substring( + 18, + nodes[i].nodeValue.length - 1 + ); + + if (nodes[i].nodeValue[12] === "S") { + // vérification simple pour démarrer le commentaire + // créer une nouvelle entrée + result[path] = [nodes[i], null]; + } else if (result[path]) { + // ajouter à l'entrée existante + result[path][1] = nodes[i]; + } + } + + return result; + }; + + // trouver le nœud qui a TargetNode comme parentNode + var getParentNode = function (node, targetNode) { + if (node.parentNode === null) { + return null; + } + + if (node.parentNode !== targetNode) { + return getParentNode(node.parentNode, targetNode); + } + + return node; + }; + + // définir les éléments invalides et externes (également invalides) + const INVALID_ELEMENTS = ["NOSCRIPT", "SCRIPT", "STYLE"]; + const OUTER_ELEMENTS = ["HTML", "BODY", "HEAD"]; + + var getValidElementInner = function (node, reverse) { + // gérer les balises invalides + 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]; + + // ignorer les balises invalides + if (INVALID_ELEMENTS.indexOf(element.nodeName) !== -1) { + continue; + } + + return [element, reverse]; + } + + return null; + } + + // passer à l'élément valide suivant + while ( + node !== null && + INVALID_ELEMENTS.indexOf(node.nodeName) !== -1 + ) { + node = reverse + ? node.previousElementSibling + : node.nextElementSibling; + } + + // renvoyer un element non-tableau (null) si nous n'avons pas trouvé quelque chose + if (node === null) { + return null; + } + + return [node, reverse]; + }; + + // Obtenir l'élément valide suivant (pour ajouter des divs en toute sécurité) + // @return [élément, élément ignoré] ou null si nous n'avons pas trouvé d'emplacement valide + 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 + ); + } + } + + // quelque chose s'est mal passé ! -> l'élément n'est pas dans le DOM + return null; + }; + + function showHints() { + // Vous aviez AJAX ? Réinitialiser les blocs de vue + sortedComments = getComments(); + + for (var key in sortedComments) { + var startElement = getValidElement(sortedComments[key][0]); + var endElement = getValidElement(sortedComments[key][1]); + + // ignorer si nous ne pouvons pas obtenir un élément valide + if (startElement === null || endElement === null) { + continue; + } + + // trouver l'élément qui a le même parent que l'élément de départ + var jointParent = getParentNode( + endElement[0], + startElement[0].parentNode + ); + if (jointParent === null) { + // trouver l'élément qui a le même parent que l'élément final + jointParent = getParentNode( + startElement[0], + endElement[0].parentNode + ); + if (jointParent === null) { + // les deux tentatives ont échoué + continue; + } else { + startElement[0] = jointParent; + } + } else { + endElement[0] = jointParent; + } + + var debugDiv = document.createElement("div"); // titulaire + var debugPath = document.createElement("div"); // chemein + var childArray = startElement[0].parentNode.childNodes; // tableau des enfants cibles + var parent = startElement[0].parentNode; + var start, end; + + // configuration du container + debugDiv.classList.add("debug-view"); + debugDiv.classList.add("show-view"); + debugPath.classList.add("debug-view-path"); + debugPath.innerText = key; + debugDiv.appendChild(debugPath); + + // calculer la distance entre eux + // debut + for (var i = 0; i < childArray.length; ++i) { + // vérifier le commentaire (début et fin) -> s'il est antérieur à un élément de départ valide + if ( + childArray[i] === sortedComments[key][1] || + childArray[i] === sortedComments[key][0] || + childArray[i] === startElement[0] + ) { + start = i; + if (childArray[i] === sortedComments[key][0]) { + start++; // augmenter pour ignorer le commentaire de départ + } + break; + } + } + // ajuster si nous voulons ignorer l'élément de départ + if (startElement[1]) { + start++; + } + + // fin + for (var i = start; i < childArray.length; ++i) { + if (childArray[i] === endElement[0]) { + end = i; + // ne pas interrompre pour vérifier le commentaire de fin après l'élément valide de fin + } else if (childArray[i] === sortedComments[key][1]) { + // si nous trouvons le commentaire final, nous pouvons casser + end = i; + break; + } + } + + // déplacer des éléments + var number = end - start; + if (endElement[1]) { + number++; + } + for (var i = 0; i < number; ++i) { + if (INVALID_ELEMENTS.indexOf(childArray[start]) !== -1) { + // ignorer les enfants invalides qui peuvent causer des problèmes s'ils sont déplacés + start++; + continue; + } + debugDiv.appendChild(childArray[start]); + } + + // ajouter le conteneur au DOM + nodeList.push(parent.insertBefore(debugDiv, childArray[start])); + } + + blitzphpDebugBar.createCookie("debug-view", "show", 365); + blitzphpDebugBar.addClass(btn, "active"); + } + + function hideHints() { + for (var i = 0; i < nodeList.length; ++i) { + var index; + + // trouver l'index + for ( + var j = 0; + j < nodeList[i].parentNode.childNodes.length; + ++j + ) { + if (nodeList[i].parentNode.childNodes[j] === nodeList[i]) { + index = j; + break; + } + } + + // déplacer l'enfant vers l'arrière + 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; + + blitzphpDebugBar.createCookie("debug-view", "", -1); + blitzphpDebugBar.removeClass(btn, "active"); + } + + var btn = document.querySelector("[data-tab=blitzphp-views]"); + + // Si le collecteur de vues est inactif, il s'arrête ici + if (! btn) { + return; + } + + btn.parentNode.onclick = function () { + if (blitzphpDebugBar.readCookie("debug-view")) { + hideHints(); + } else { + showHints(); + } + }; + + // Déterminer l'état des indices lors du chargement de la page + if (blitzphpDebugBar.readCookie("debug-view")) { + showHints(); + } + }, + + setToolbarPosition: function () { + var btnPosition = this.toolbar.querySelector("#toolbar-position"); + + if (blitzphpDebugBar.readCookie("debug-bar-position") === "top") { + blitzphpDebugBar.addClass(blitzphpDebugBar.icon, "fixed-top"); + blitzphpDebugBar.addClass(blitzphpDebugBar.toolbar, "fixed-top"); + } + + btnPosition.addEventListener( + "click", + function () { + var position = blitzphpDebugBar.readCookie("debug-bar-position"); + + blitzphpDebugBar.createCookie("debug-bar-position", "", -1); + + if (! position || position === "bottom") { + blitzphpDebugBar.createCookie("debug-bar-position", "top", 365); + blitzphpDebugBar.addClass(blitzphpDebugBar.icon, "fixed-top"); + blitzphpDebugBar.addClass(blitzphpDebugBar.toolbar, "fixed-top"); + } else { + blitzphpDebugBar.createCookie( + "debug-bar-position", + "bottom", + 365 + ); + blitzphpDebugBar.removeClass(blitzphpDebugBar.icon, "fixed-top"); + blitzphpDebugBar.removeClass(blitzphpDebugBar.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; + + // Si un cookie est défini avec une valeur, nous forçons le schéma de couleurs + if (blitzphpDebugBar.readCookie("debug-bar-theme") === "dark") { + blitzphpDebugBar.removeClass(blitzphpDebugBar.toolbarContainer, "light"); + blitzphpDebugBar.addClass(blitzphpDebugBar.toolbarContainer, "dark"); + } else if (blitzphpDebugBar.readCookie("debug-bar-theme") === "light") { + blitzphpDebugBar.removeClass(blitzphpDebugBar.toolbarContainer, "dark"); + blitzphpDebugBar.addClass(blitzphpDebugBar.toolbarContainer, "light"); + } + + btnTheme.addEventListener( + "click", + function () { + var theme = blitzphpDebugBar.readCookie("debug-bar-theme"); + + if ( + ! theme && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + // S'il n'y a pas de cookie et que « prefers-color-scheme » est défini sur « dark », + // cela signifie que l'utilisateur souhaite passer en mode clair. + blitzphpDebugBar.createCookie("debug-bar-theme", "light", 365); + blitzphpDebugBar.removeClass(blitzphpDebugBar.toolbarContainer, "dark"); + blitzphpDebugBar.addClass(blitzphpDebugBar.toolbarContainer, "light"); + } else { + if (theme === "dark") { + blitzphpDebugBar.createCookie( + "debug-bar-theme", + "light", + 365 + ); + blitzphpDebugBar.removeClass( + blitzphpDebugBar.toolbarContainer, + "dark" + ); + blitzphpDebugBar.addClass( + blitzphpDebugBar.toolbarContainer, + "light" + ); + } else { + // Dans tous les autres cas : s'il n'y a pas de cookie, ou si le cookie est réglé sur + // « light », ou si le « prefers-color-scheme » est « light »... + blitzphpDebugBar.createCookie("debug-bar-theme", "dark", 365); + blitzphpDebugBar.removeClass( + blitzphpDebugBar.toolbarContainer, + "light" + ); + blitzphpDebugBar.addClass( + blitzphpDebugBar.toolbarContainer, + "dark" + ); + } + } + }, + true + ); + }, + + setHotReloadState: function () { + var btn = document.getElementById("debug-hot-reload").parentNode; + var btnImg = btn.getElementsByTagName("img")[0]; + var eventSource; + + // Si le collecteur de rechargement à chaud est inactif, il s'arrête ici + if (! btn) { + return; + } + + btn.onclick = function () { + if (blitzphpDebugBar.readCookie("debug-hot-reload")) { + blitzphpDebugBar.createCookie("debug-hot-reload", "", -1); + blitzphpDebugBar.removeClass(btn, "active"); + blitzphpDebugBar.removeClass(btnImg, "rotate"); + + // Fermez la connexion EventSource si elle existe + if (typeof eventSource !== "undefined") { + eventSource.close(); + eventSource = void 0; // Indéfinir la variable + } + } else { + blitzphpDebugBar.createCookie("debug-hot-reload", "show", 365); + blitzphpDebugBar.addClass(btn, "active"); + blitzphpDebugBar.addClass(btnImg, "rotate"); + + eventSource = blitzphpDebugBar.hotReloadConnect(); + } + }; + + // Déterminer l'état de rechargement à chaud lors du chargement de la page + if (blitzphpDebugBar.readCookie("debug-hot-reload")) { + blitzphpDebugBar.addClass(btn, "active"); + blitzphpDebugBar.addClass(btnImg, "rotate"); + eventSource = blitzphpDebugBar.hotReloadConnect(); + } + }, + + hotReloadConnect: function () { + const eventSource = new EventSource(blitzSiteURL + "/__hot-reload"); + + eventSource.addEventListener("reload", function (e) { + console.log("reload", e); + window.location.reload(); + }); + + eventSource.onerror = (err) => { + console.error("EventSource failed:", err); + }; + + return eventSource; + }, + + /** + * Aide à la création d'un 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)) { + blitzphpDebugBar.addClass(row, "debug-bar-pointer"); + row.setAttribute( + "title", + location.origin + "/" + blitzphpDebugBar.trimSlash(row.innerText) + ); + row.addEventListener("click", function (ev) { + _location = + location.origin + + "/" + + blitzphpDebugBar.trimSlash(ev.target.innerText); + var redirectWindow = window.open(_location, "_blank"); + redirectWindow.location; + }); + } else { + row.innerHTML = + "
" + + row.innerText + + "
" + + '
' + + row.innerText.replace( + patt, + '' + ) + + '' + + ""; + } + } + + 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; + } + }); + } + }, +}; diff --git a/src/Debug/Toolbar/Views/toolbar.tpl.php b/src/Debug/Toolbar/Views/toolbar.tpl.php index daee1d4d..890b18e1 100644 --- a/src/Debug/Toolbar/Views/toolbar.tpl.php +++ b/src/Debug/Toolbar/Views/toolbar.tpl.php @@ -19,11 +19,10 @@ */ ?> - - + -
- +
@@ -120,7 +124,7 @@ $items) : ?> - +

@@ -144,7 +148,7 @@ - +

Données utilisateur de session

@@ -170,7 +174,7 @@

Requête ( )

- +

$_GET

@@ -187,7 +191,7 @@ - +

$_POST

@@ -204,7 +208,7 @@ - +

Headers

@@ -221,7 +225,7 @@ - +

Cookies

@@ -237,12 +241,12 @@
-

Reponse +

Réponse ( )

- +

Headers

@@ -266,5 +270,4 @@ setData($config)->render('_config.tpl') ?> - - + diff --git a/src/Debug/Toolbar/Views/toolbarloader.js b/src/Debug/Toolbar/Views/toolbarloader.js index 7e591435..6f0083fa 100644 --- a/src/Debug/Toolbar/Views/toolbarloader.js +++ b/src/Debug/Toolbar/Views/toolbarloader.js @@ -25,19 +25,19 @@ function loadDoc(time) { 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 + // récupérez le premier bloc de style, copiez le contenu dans dynamic_style, puis supprimez-le ici let start = responseText.indexOf('>', responseText.indexOf('', 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 + // récupérez le premier script après le premier style, copiez le contenu dans dynamic_script, puis supprimez-le ici start = responseText.indexOf('>', responseText.indexOf('', 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 + // vérifier le dernier bloc de style, ajouter le contenu à dynamic_style, puis supprimer ici start = responseText.indexOf('>', responseText.indexOf('', start); dynamicStyle.innerHTML += responseText.substr(start, end - start); @@ -45,11 +45,11 @@ function loadDoc(time) { toolbar.innerHTML = responseText; - if (typeof ciDebugBar === 'object') { - ciDebugBar.init(); + if (typeof blitzphpDebugBar === 'object') { + blitzphpDebugBar.init(); } } else if (this.readyState === 4 && this.status === 404) { - console.log('CodeIgniter DebugBar: File "WRITEPATH/debugbar/debugbar_' + time + '" not found.'); + console.log('BlitzPHP DebugBar: File "STORAGE_PATH/debugbar/debugbar_' + time + '" not found.'); } }; @@ -71,11 +71,14 @@ function newXHR() { let debugbarTime = realXHR.getResponseHeader('Debugbar-Time'); if (debugbarTime) { - let h2 = document.querySelector('#ci-history > h2'); + let h2 = document.querySelector('#blitzphp-history > h2'); if (h2) { - h2.innerHTML = 'History You have new debug data. '; - document.querySelector('a[data-tab="ci-history"] > span > .badge').className += ' active'; + h2.innerHTML = 'Historique Vous avez de nouvelles données de débogage. '; + document.querySelector('a[data-tab="blitzphp-history"] > span > .badge').className += ' active'; + document.getElementById('blitzphp-history-update').addEventListener('click', function () { + loadDoc(debugbarTime); + }, false) } } } diff --git a/src/Exceptions/FrameworkException.php b/src/Exceptions/FrameworkException.php index f01337a5..570cd99c 100644 --- a/src/Exceptions/FrameworkException.php +++ b/src/Exceptions/FrameworkException.php @@ -28,6 +28,11 @@ public static function enabledZlibOutputCompression() return new static(lang('Core.enabledZlibOutputCompression')); } + public static function invalidDirectory(string $path) + { + return new static(lang('Core.invalidDirectory', [$path])); + } + public static function invalidFile(string $path) { return new static(lang('Core.invalidFile', [$path])); diff --git a/src/HotReloader/DirectoryHasher.php b/src/HotReloader/DirectoryHasher.php new file mode 100644 index 00000000..6f590c45 --- /dev/null +++ b/src/HotReloader/DirectoryHasher.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace BlitzPHP\HotReloader; + +use BlitzPHP\Exceptions\FrameworkException; +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; + +/** + * @internal + * + * @credit CodeIgniter 4.6 - CodeIgniter\HotReloader\DirectoryHasher + */ +final class DirectoryHasher +{ + /** + * Génère une valeur MD5 de tous les répertoires surveillés par le rechargeur à chaud, + * comme défini dans le fichier app/Config/toolbar.php. + * + * Il s'agit de l'empreinte actuelle de l'application. + */ + public function hash(): string + { + return md5(implode('', $this->hashApp())); + } + + /** + * Génère un tableau de hachages md5 pour tous les répertoires surveillés par le Hot Reloader, + * comme défini dans app/Config/toolbar.php. + */ + public function hashApp(): array + { + $hashes = []; + + $watchedDirectories = config('toolbar.watched_directories', []); + + foreach ($watchedDirectories as $directory) { + if (is_dir(ROOTPATH . $directory)) { + $hashes[$directory] = $this->hashDirectory(ROOTPATH . $directory); + } + } + + return array_unique(array_filter($hashes)); + } + + /** + * Génère un hachage MD5 d'un répertoire donné et de tous ses fichiers * qui correspondent aux extensions surveillées + * définies dans app/Config/toolbar.php. + */ + public function hashDirectory(string $path): string + { + if (! is_dir($path)) { + throw FrameworkException::invalidDirectory($path); + } + + $directory = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS); + $filter = new IteratorFilter($directory); + $iterator = new RecursiveIteratorIterator($filter); + + $hashes = []; + + foreach ($iterator as $file) { + if ($file->isFile()) { + $hashes[] = md5_file($file->getRealPath()); + } + } + + return md5(implode('', $hashes)); + } +} diff --git a/src/HotReloader/HotReloader.php b/src/HotReloader/HotReloader.php new file mode 100644 index 00000000..779daf50 --- /dev/null +++ b/src/HotReloader/HotReloader.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace BlitzPHP\HotReloader; + +/** + * @internal + * + * @credit CodeIgniter 4.6 - CodeIgniter\HotReloader\HotReloader + */ +final class HotReloader +{ + public function run(): void + { + if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); + } + + ini_set('zlib.output_compression', 'Off'); + + header('Cache-Control: no-store'); + header('Content-Type: text/event-stream'); + header('Access-Control-Allow-Methods: GET'); + + ob_end_clean(); + set_time_limit(0); + + $hasher = new DirectoryHasher(); + $appHash = $hasher->hash(); + + while (true) { + if (connection_status() !== CONNECTION_NORMAL || connection_aborted() === 1) { + break; + } + + $currentHash = $hasher->hash(); + + // Si le hachage a changé, demandez au navigateur de se recharger. + if ($currentHash !== $appHash) { + $appHash = $currentHash; + + $this->sendEvent('reload', ['time' => date('Y-m-d H:i:s')]); + break; + } + + if (mt_rand(1, 10) > 8) { + $this->sendEvent('ping', ['time' => date('Y-m-d H:i:s')]); + } + + sleep(1); + } + } + + /** + * Envoyer un événement au navigateur. + */ + private function sendEvent(string $event, array $data): void + { + echo "event: {$event}\n"; + echo 'data: ' . json_encode($data) . "\n\n"; + + ob_flush(); + flush(); + } +} diff --git a/src/HotReloader/IteratorFilter.php b/src/HotReloader/IteratorFilter.php new file mode 100644 index 00000000..8291501a --- /dev/null +++ b/src/HotReloader/IteratorFilter.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace BlitzPHP\HotReloader; + +use RecursiveFilterIterator; +use RecursiveIterator; + +/** + * @internal + * + * @psalm-suppress MissingTemplateParam + * + * @credit CodeIgniter 4.6 - CodeIgniter\HotReloader\IteratorFilter + */ +final class IteratorFilter extends RecursiveFilterIterator implements RecursiveIterator +{ + private array $watchedExtensions = []; + + public function __construct(RecursiveIterator $iterator) + { + parent::__construct($iterator); + + $this->watchedExtensions = config('toolbar.watched_extensions', []); + } + + /** + * Appliquer des filtres aux fichiers dans l'itérateur. + */ + public function accept(): bool + { + if (! $this->current()->isFile()) { + return true; + } + + $filename = $this->current()->getFilename(); + + // Ignorer les fichiers et répertoires cachés. + if ($filename[0] === '.') { + return false; + } + + // Ne consommez que les fichiers qui vous intéressent. + $ext = trim(strtolower($this->current()->getExtension()), '. '); + + return in_array($ext, $this->watchedExtensions, true); + } +} diff --git a/src/Http/ServerRequest.php b/src/Http/ServerRequest.php index dd491c0e..8c2bb2a7 100644 --- a/src/Http/ServerRequest.php +++ b/src/Http/ServerRequest.php @@ -242,10 +242,6 @@ protected function _setConfig(array $config): void $uri = new Uri(Psr7ServerRequest::getUriFromGlobals()->__toString()); } - if (isset($config['environment']['REQUEST_URI'])) { - $uri = $uri->withPath($config['environment']['REQUEST_URI']); - } - if (in_array($uri->getHost(), ['localhost', '127.0.0.1'], true)) { $uri = $uri->withHost(parse_url(config('app.base_url'), PHP_URL_HOST)); } diff --git a/src/Router/Dispatcher.php b/src/Router/Dispatcher.php index a54ea6cf..28e39f41 100644 --- a/src/Router/Dispatcher.php +++ b/src/Router/Dispatcher.php @@ -365,7 +365,7 @@ protected function dispatchRoutes(?RouteCollectionInterface $routes = null): arr } // $routes est defini dans app/Config/routes.php - $this->router = single_service('router', $routes, $this->request); + $this->router = service('router', $routes, $this->request); $this->outputBufferingStart(); @@ -576,7 +576,7 @@ public function storePreviousURL($uri) protected function sendResponse() { if (! $this->isAjaxRequest()) { - $this->response = service('toolbar')->prepare( + $this->response = service('toolbar')->process( $this->getPerformanceStats(), $this->request, $this->response @@ -685,6 +685,12 @@ private function spoofRequestMethod(): callable }; } + /** + * Démarre l'application en configurant la requete et la réponse, + * en exécutant le contrôleur et en gérant les exceptions de validation. + * + * Cette méthode renvoie un objet callable qui sert de middleware pour le cycle requête-réponse de l'application. + */ private function bootApp(): callable { return function (ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface { diff --git a/src/Translations/en/Core.php b/src/Translations/en/Core.php index f5812490..c0801278 100644 --- a/src/Translations/en/Core.php +++ b/src/Translations/en/Core.php @@ -13,6 +13,7 @@ return [ 'copyError' => 'An error was encountered while attempting to replace the file ({0}). Please make sure your file directory is writable.', 'enabledZlibOutputCompression' => 'Your zlib.output_compression ini directive is turned on. This will not work well with output buffers.', + 'invalidDirectory' => 'Directory does not exist: "{0}"', 'invalidFile' => 'Invalid file: {0}', 'invalidPhpVersion' => 'Your PHP version must be {0} or higher to run CodeIgniter. Current version: {1}', 'missingExtension' => 'The framework needs the following extension(s) installed and loaded: {0}.',