diff --git a/.env.dist b/.env.dist index 3d1b90bbdb650736d11ec8b6471aa09bfed3b13d..e22ca4d8eec49b19437abcdfec8276b092b77161 100644 --- a/.env.dist +++ b/.env.dist @@ -3,6 +3,8 @@ # MYSQL_PASSWORD="" # MYSQL_DATABASE="" +# DEBUG_BAR="1" // Enable to display the debug bar in development mode + # STUDIP_CACHING_ENABLE="" # STUDIP_CACHE_IS_SESSION_STORAGE="" # STUDIP_ENV="" diff --git a/app/controllers/debugbar.php b/app/controllers/debugbar.php new file mode 100644 index 0000000000000000000000000000000000000000..a80000d86d89bef62afb208f9d551df9693b7295 --- /dev/null +++ b/app/controllers/debugbar.php @@ -0,0 +1,17 @@ +<?php +final class DebugbarController extends Trails_Controller +{ + public function css_action(): void + { + $this->set_content_type('text/css;charset=utf-8'); + $this->render_nothing(); + app()->get(DebugBar\DebugBar::class)->getJavascriptRenderer()->dumpCssAssets(); + } + + public function js_action(): void + { + $this->set_content_type('text/javascript;charset=utf-8'); + $this->render_nothing(); + app()->get(DebugBar\DebugBar::class)->getJavascriptRenderer()->setIncludeVendors(false)->dumpJsAssets(); + } +} diff --git a/composer.json b/composer.json index 8ec6778ed794023c3b433aa4ba795d14b3f07f4e..bd01b2840e9dd4eeb8ed590739fb01222e1fedd6 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "codeception/module-asserts": "3.0.0", "overtrue/phplint": "9.3.0", "phpstan/phpstan": "1.11.0", - "symfony/var-dumper": "6.4.7" + "symfony/var-dumper": "6.4.7", + "maximebf/debugbar": "1.22.3" }, "require": { "php": "^8.1", diff --git a/composer.lock b/composer.lock index f2e656db651a26a13a3e8fc29307c3996a643fce..2392a4089fd30827eff521728f6cbc0e74121a75 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7b19d8591a65485c94fb1794c93eef90", + "content-hash": "3cab15d3a1cd08dde4cd02d5bb19c760", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -5496,6 +5496,74 @@ }, "time": "2024-02-02T19:21:00+00:00" }, + { + "name": "maximebf/debugbar", + "version": "v1.22.3", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", + "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", + "shasum": "" + }, + "require": { + "php": "^7.2|^8", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4|^5|^6|^7" + }, + "require-dev": { + "dbrekelmans/bdi": "^1", + "phpunit/phpunit": "^8|^9", + "symfony/panther": "^1|^2.1", + "twig/twig": "^1.38|^2.7|^3.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.22-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "support": { + "issues": "https://github.com/maximebf/php-debugbar/issues", + "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3" + }, + "time": "2024-04-03T19:39:26+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.1", diff --git a/lib/bootstrap-definitions.php b/lib/bootstrap-definitions.php index d34d0231dbb7b15b90612520f70ad8fbaa63ea2c..2f59dc9bffc897f6a7632a4ab047e04f419d7305 100644 --- a/lib/bootstrap-definitions.php +++ b/lib/bootstrap-definitions.php @@ -1,10 +1,18 @@ <?php +use DebugBar\DataCollector\ExceptionsCollector; +use DebugBar\DataCollector\MemoryCollector; +use DebugBar\DataCollector\MessagesCollector; +use DebugBar\DataCollector\PhpInfoCollector; +use DebugBar\DataCollector\RequestDataCollector; +use DebugBar\DataCollector\TimeDataCollector; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use function DI\create; + return [ LoggerInterface::class => DI\factory(function () { return new Logger('studip', [ @@ -17,10 +25,46 @@ return [ \Studip\Cache\Cache::class => DI\factory(function () { return \Studip\Cache\Factory::getCache(); }), - StudipPDO::class => DI\factory(function () { + PDO::class => DI\factory(function () { return DBManager::get(); }), Trails\Dispatcher::class => DI\factory(function (ContainerInterface $container) { return new \StudipDispatcher($container); }), + DebugBar\DebugBar::class => DI\factory(function (ContainerInterface $container) { + $debugBar = new DebugBar\DebugBar(); + $debugBar->addCollector(new PhpInfoCollector()); + $debugBar->addCollector(new RequestDataCollector()); + $debugBar->addCollector(new MemoryCollector()); + $debugBar->addCollector(new ExceptionsCollector()); + + // Future Improvements, not used/activated right now + # $debugBar->addCollector(new MessagesCollector()); + # $debugBar->addCollector(new TimeDataCollector()); + + $config = iterator_to_array(Config::getInstance()->getIterator()); + ksort($config); + $debugBar->addCollector(new DebugBar\DataCollector\ConfigCollector($config)); + + $pdo = $container->get(PDO::class); + if ($pdo instanceof DebugBar\DataCollector\PDO\TraceablePDO) { + $collector = new DebugBar\DataCollector\PDO\PDOCollector($pdo); + $debugBar->addCollector($collector); + } + + return $debugBar; + }), + StudipPDO::class => DI\factory(function () { + $pdo = new StudipPDO( + "mysql:host={$GLOBALS['DB_STUDIP_HOST']};dbname={$GLOBALS['DB_STUDIP_DATABASE']};charset=utf8mb4", + $GLOBALS['DB_STUDIP_USER'], + $GLOBALS['DB_STUDIP_PASSWORD'] + ); + + if (Studip\Debug\DebugBar::isActivated()) { + $pdo = new DebugBar\DataCollector\PDO\TraceablePDO($pdo); + } + + return $pdo; + }), ]; diff --git a/lib/bootstrap.php b/lib/bootstrap.php index e9c9bb5c702d677863a78dc9d32417a43843cd6d..8c618ff7d5a240468f9989088939f256c7e0d88d 100644 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -129,13 +129,10 @@ $GLOBALS['template_factory'] = new Flexi\Factory("{$STUDIP_BASE_PATH}/templates" // set default pdo connection try { - DBManager::getInstance() - ->setConnection('studip', - 'mysql:host=' . $GLOBALS['DB_STUDIP_HOST'] . - ';dbname=' . $GLOBALS['DB_STUDIP_DATABASE'] . - ';charset=utf8mb4', - $GLOBALS['DB_STUDIP_USER'], - $GLOBALS['DB_STUDIP_PASSWORD']); + DBManager::getInstance()->setConnection( + 'studip', + app(StudipPDO::class) + ); } catch (PDOException $exception) { if (Studip\ENV === 'development') { throw $exception; diff --git a/lib/classes/Debug/DebugBar.php b/lib/classes/Debug/DebugBar.php new file mode 100644 index 0000000000000000000000000000000000000000..bbd80c9f13fce2d026b435fe86a3573e79338352 --- /dev/null +++ b/lib/classes/Debug/DebugBar.php @@ -0,0 +1,12 @@ +<?php +namespace Studip\Debug; + +final class DebugBar +{ + public static function isActivated(): bool + { + return \Studip\ENV === 'development' + && ($_ENV['DEBUG_BAR'] ?? false) + && class_exists(\DebugBar\DebugBar::class); + } +} diff --git a/lib/classes/Debug/TrailsCollector.php b/lib/classes/Debug/TrailsCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..f4b8a65073ad15b4f207470ea740e237a9a723ef --- /dev/null +++ b/lib/classes/Debug/TrailsCollector.php @@ -0,0 +1,69 @@ +<?php +namespace Studip\Debug; + +use DebugBar\DataCollector\DataCollector; +use DebugBar\DataCollector\Renderable; +use Trails\Controller; + +final class TrailsCollector extends DataCollector implements Renderable +{ + public function __construct( + private readonly Controller $controller + ) { + $this->useHtmlVarDumper(false); + } + + public function collect() + { + $data = []; + foreach ($this->controller->get_assigned_variables() as $k => $v) { + if ($this->isHtmlVarDumperUsed()) { + $v = $this->getVarDumper()->renderVar($v); + } else if (!is_string($v)) { + $v = $this->getDataFormatter()->formatVar($v); + } + $data[$k] = $v; + } + + ksort($data); + + return $data; + } + + public function getName() + { + return 'trails'; + } + + /** + * @return array + */ + public function getAssets() + { + return $this->isHtmlVarDumperUsed() ? $this->getVarDumper()->getAssets() : []; + } + + /** + * @return array[] + */ + public function getWidgets() + { + $name = $this->getName(); + $widget = $this->isHtmlVarDumperUsed() + ? 'PhpDebugBar.Widgets.HtmlVariableListWidget' + : 'PhpDebugBar.Widgets.VariableListWidget'; + + return [ + $name => [ + 'icon' => 'code', + 'widget' => $widget, + 'map' => $name, + 'default' => '{}' + ], + "{$name}:badge" => [ + 'map' => "{$name}:variable__count", + 'default' => count($this->controller->get_assigned_variables()), + ], + ]; + } +} diff --git a/lib/classes/PageLayout.php b/lib/classes/PageLayout.php index d3f3028d524b55c02cace5b7afd94fe2246bfd28..a4178a5abdba111ee2ca92529fbba8c3a2a68f70 100644 --- a/lib/classes/PageLayout.php +++ b/lib/classes/PageLayout.php @@ -139,6 +139,21 @@ class PageLayout self::addScript('studip-wysiwyg.js?v=' . $v); self::addStylesheet('print.css?v=' . $v, ['media' => 'print']); + + if (Studip\Debug\DebugBar::isActivated()) { + $old_base = URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']); + + self::addHeadElement('link', [ + 'href' => URLHelper::getURL('dispatch.php/debugbar/css'), + 'rel' => 'stylesheet', + 'type' => 'text/css', + ]); + self::addHeadElement('script', [ + 'src' => URLHelper::getURL('dispatch.php/debugbar/js'), + ]); + + URLHelper::setBaseURL($old_base); + } } /** diff --git a/lib/classes/StudipController.php b/lib/classes/StudipController.php index 4fbcc429ef96883df630d556dd0ee8a181b4418c..0643836c6b632fb6b97e912417692baaa291a5f2 100644 --- a/lib/classes/StudipController.php +++ b/lib/classes/StudipController.php @@ -9,6 +9,7 @@ * the License, or (at your option) any later version. */ +use DebugBar\DebugBar; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Csv; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; @@ -294,7 +295,7 @@ abstract class StudipController extends Trails\Controller // Extract fragment (if any) if (strpos($to, '#') !== false) { - list($args[0], $fragment) = explode('#', $to); + [$args[0], $fragment] = explode('#', $to); } // Extract parameters (if any) @@ -666,6 +667,19 @@ abstract class StudipController extends Trails\Controller return $this->response; } + public function render_template($template_name, $layout = null) + { + if (Studip\Debug\DebugBar::isActivated()) { + $debugbar = app()->get(Debugbar::class); + if (!isset($debugbar['trails'])) { + $collector = new \Studip\Debug\TrailsCollector($this); + $debugbar->addCollector($collector); + } + } + + parent::render_template($template_name, $layout); + } + /** * Renders a given template and returns the resulting string. * diff --git a/templates/layouts/base.php b/templates/layouts/base.php index 2db1f7eab8b4813799ade93694197e9346c1e225..cd32e3b656f5864f1537b338ee7ab28a04a91235 100644 --- a/templates/layouts/base.php +++ b/templates/layouts/base.php @@ -103,6 +103,12 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); <?= $this->render_partial('footer', ['link_params' => $header_template->link_params]); ?> <?= SkipLinks::getHTML() ?> <section class="sr-only" id="notes_for_screenreader" aria-live="polite"></section> + +<?php +if (Studip\Debug\DebugBar::isActivated()) { + echo app()->get(\DebugBar\DebugBar::class)->getJavascriptRenderer()->render(); +} +?> </body> </html> <?php NotificationCenter::postNotification('PageDidRender', PageLayout::getBodyElementId());