diff --git a/composer.json b/composer.json
index be67aa494507e..c19b8d17de4d1 100644
--- a/composer.json
+++ b/composer.json
@@ -107,7 +107,7 @@
"phpseclib/bcmath_compat": "^2.0.3",
"jfcherng/php-diff": "^6.16.2",
"php-tuf/php-tuf": "^1.0.3",
- "php-debugbar/php-debugbar": "^2.2.6",
+ "php-debugbar/php-debugbar": "^3.8.1",
"altcha-org/altcha": "^1.3.2"
},
"require-dev": {
diff --git a/composer.lock b/composer.lock
index dd84fe97fc990..0d24b937615bc 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": "e31b602fc66bf014073619c06d6a22d2",
+ "content-hash": "356ad6ce646492dcb911c62f85bdedaf",
"packages": [
{
"name": "algo26-matthias/idna-convert",
@@ -2690,47 +2690,63 @@
},
{
"name": "php-debugbar/php-debugbar",
- "version": "v2.2.6",
+ "version": "v3.8.1",
"source": {
"type": "git",
"url": "https://github.com/php-debugbar/php-debugbar.git",
- "reference": "abb9fa3c5c8dbe7efe03ddba56782917481de3e8"
+ "reference": "d04a382b80e320c7abdbe9fc66e83b34e80edef4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/abb9fa3c5c8dbe7efe03ddba56782917481de3e8",
- "reference": "abb9fa3c5c8dbe7efe03ddba56782917481de3e8",
+ "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/d04a382b80e320c7abdbe9fc66e83b34e80edef4",
+ "reference": "d04a382b80e320c7abdbe9fc66e83b34e80edef4",
"shasum": ""
},
"require": {
- "php": "^8.1",
+ "php": "^8.2",
"psr/log": "^1|^2|^3",
- "symfony/var-dumper": "^5.4|^6.4|^7.3|^8.0"
+ "symfony/var-dumper": "^5.4|^6|^7|^8"
},
"replace": {
"maximebf/debugbar": "self.version"
},
"require-dev": {
- "dbrekelmans/bdi": "^1",
+ "dbrekelmans/bdi": "^1.4",
+ "friendsofphp/php-cs-fixer": "^3.92",
+ "monolog/monolog": "^3.9",
+ "php-debugbar/doctrine-bridge": "^3@dev",
+ "php-debugbar/monolog-bridge": "^1@dev",
+ "php-debugbar/symfony-bridge": "^1@dev",
+ "php-debugbar/twig-bridge": "^2@dev",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^10",
- "symfony/browser-kit": "^6.0|7.0",
+ "predis/predis": "^3.3",
+ "shipmonk/phpstan-rules": "^4.3",
+ "symfony/browser-kit": "^6.4|7.0",
+ "symfony/dom-crawler": "^6.4|^7",
+ "symfony/event-dispatcher": "^5.4|^6.4|^7.3|^8.0",
+ "symfony/http-foundation": "^5.4|^6.4|^7.3|^8.0",
+ "symfony/mailer": "^5.4|^6.4|^7.3|^8.0",
"symfony/panther": "^1|^2.1",
"twig/twig": "^3.11.2"
},
"suggest": {
- "kriswallsmith/assetic": "The best way to manage assets",
- "monolog/monolog": "Log using Monolog",
- "predis/predis": "Redis storage"
+ "php-debugbar/doctrine-bridge": "To integrate Doctrine with php-debugbar.",
+ "php-debugbar/monolog-bridge": "To integrate Monolog with php-debugbar.",
+ "php-debugbar/symfony-bridge": "To integrate Symfony with php-debugbar.",
+ "php-debugbar/twig-bridge": "To integrate Twig with php-debugbar."
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "3.8-dev"
}
},
"autoload": {
"psr-4": {
- "DebugBar\\": "src/DebugBar/"
+ "DebugBar\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -2754,13 +2770,25 @@
"debug",
"debug bar",
"debugbar",
- "dev"
+ "dev",
+ "profiler",
+ "toolbar"
],
"support": {
"issues": "https://github.com/php-debugbar/php-debugbar/issues",
- "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.6"
+ "source": "https://github.com/php-debugbar/php-debugbar/tree/v3.8.1"
},
- "time": "2025-12-22T13:21:32+00:00"
+ "funding": [
+ {
+ "url": "https://fruitcake.nl",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2026-10-25T20:20:41+00:00"
},
{
"name": "php-tuf/php-tuf",
@@ -3877,16 +3905,16 @@
},
{
"name": "symfony/deprecation-contracts",
- "version": "v3.7.0",
+ "version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
- "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
+ "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
@@ -3924,7 +3952,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -3944,7 +3972,7 @@
"type": "tidelift"
}
],
- "time": "2026-04-13T15:52:40+00:00"
+ "time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/error-handler",
@@ -5299,16 +5327,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v7.4.8",
+ "version": "v7.4.22",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd"
+ "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd",
- "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618",
+ "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618",
"shasum": ""
},
"require": {
@@ -5324,7 +5352,7 @@
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/process": "^6.4|^7.0|^8.0",
"symfony/uid": "^6.4|^7.0|^8.0",
- "twig/twig": "^3.12"
+ "twig/twig": "^3.12|^4.0"
},
"bin": [
"Resources/bin/var-dump-server"
@@ -5362,7 +5390,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.4.8"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.4.22"
},
"funding": [
{
@@ -5382,7 +5410,7 @@
"type": "tidelift"
}
],
- "time": "2026-03-30T13:44:50+00:00"
+ "time": "2026-10-25T21:04:04+00:00"
},
{
"name": "symfony/web-link",
@@ -9647,9 +9675,9 @@
"ext-gd": "*",
"ext-dom": "*"
},
- "platform-dev": [],
+ "platform-dev": {},
"platform-overrides": {
"php": "8.3.0"
},
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.9.0"
}
diff --git a/media_source/plg_system_debug/builder.mjs b/media_source/plg_system_debug/builder.mjs
index 3019e8448a0be..892332feb0f13 100644
--- a/media_source/plg_system_debug/builder.mjs
+++ b/media_source/plg_system_debug/builder.mjs
@@ -39,7 +39,7 @@ export default class PlgDebugModuleBuilder extends DefaultModuleBuilder {
await super.copy();
// Copy DebugBar assets to vendor folder
- const pathSrc = 'libraries/vendor/php-debugbar/php-debugbar/src/DebugBar/Resources';
+ const pathSrc = 'libraries/vendor/php-debugbar/php-debugbar/resources';
const pathMedia = path.dirname(this.targetPath);
const pathVendor = path.join(pathMedia, this.vendorLocation);
@@ -47,13 +47,7 @@ export default class PlgDebugModuleBuilder extends DefaultModuleBuilder {
throw new Error('DebugBar/Resources not found. Make sure you have run "composer install" first.');
}
- return fsp.cp(pathSrc, pathVendor, { recursive: true, preserveTimestamps: true }).then(() => {
- // Remove unused
- return Promise.all([
- fsp.rm(path.join(pathVendor, 'vendor/font-awesome'), { recursive: true }),
- fsp.rm(path.join(pathVendor, 'vendor/jquery'), { recursive: true }),
- ]);
- });
+ return fsp.cp(pathSrc, pathVendor, { recursive: true, preserveTimestamps: true });
}
/**
diff --git a/media_source/plg_system_debug/css/debug.css b/media_source/plg_system_debug/css/debug.css
index 64143764401a9..758cd7ef1e19e 100644
--- a/media_source/plg_system_debug/css/debug.css
+++ b/media_source/plg_system_debug/css/debug.css
@@ -3,15 +3,22 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-/* Debug Bar Joomla Logo */
a.phpdebugbar-restore-btn:after {
- background: #efefef url("data:image/svg+xml;utf8,") no-repeat 5px 4px / 20px 20px !important;
+ mask: url("../../system/images/joomla-favicon.svg") center / 20px 20px no-repeat !important;
+ background: var(--debugbar-icons) !important;
}
+
a.phpdebugbar-tab.phpdebugbar-tab-settings i:before {
- font-family: "Font Awesome 6 Free";
- font-size: 12px;
- font-weight: 900;
- content: "\f1de";
+ display: inline-block;
+ width: 1em;
+ height: 1em;
+ content: "";
+ background-color: currentColor;
+ mask: var(--debugbar-icon-adjustments-horizontal) center / contain no-repeat;
+}
+
+div.phpdebugbar[data-theme="dark"] .phpdebugbar-settings label {
+ color: var(--debugbar-text);
}
.phpdebugbar-widgets-timeline .phpdebugbar-widgets-measure:hover {
diff --git a/media_source/plg_system_debug/widgets/info/widget.es5.js b/media_source/plg_system_debug/widgets/info/widget.es5.js
index a242fb63225ba..4d5628db9ee17 100644
--- a/media_source/plg_system_debug/widgets/info/widget.es5.js
+++ b/media_source/plg_system_debug/widgets/info/widget.es5.js
@@ -1,7 +1,6 @@
-(function ($) {
-
+(function () {
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
- var InfoWidget = PhpDebugBar.Widgets.InfoWidget = PhpDebugBar.Widget.extend({
+ PhpDebugBar.Widgets.InfoWidget = PhpDebugBar.Widget.extend({
tagName: 'table',
@@ -9,46 +8,49 @@
render: function () {
this.bindAttr('data', function (data) {
- this.$el.empty()
- var tr
-
- tr = $('
')
- .append($(' | ').text('Joomla! Version'))
- .append($(' | ').text(data.joomlaVersion))
- this.$el.append(tr)
-
- tr = $('
')
- .append($(' | ').text('PHP Version'))
- .append($(' | ').text(data.phpVersion))
- this.$el.append(tr)
-
- tr = $('
')
- .append($(' | ').text('Identity'))
- .append($(' | ').text(data.identity.type))
- this.$el.append(tr)
-
- tr = $('
')
- .append($(' | ').text('Response'))
- .append($(' | ').text(data.response.status_code))
- this.$el.append(tr)
-
- tr = $('
')
- .append($(' | ').text('Template'))
- .append($(' | ').text(data.template.template))
- this.$el.append(tr)
-
- tr = $('
')
- .append($(' | ').text('Database'))
- .append($(' | ').html(
- ''
- + '- Server
- ' + data.database.dbserver + '
'
- + '- Version
- ' + data.database.dbversion + '
'
- + '- Collation
- ' + data.database.dbcollation + '
'
- + '- Conn Collation
- ' + data.database.dbconnectioncollation + '
'
- + '
'
- ))
- this.$el.append(tr)
+ this.el.replaceChildren()
+
+ var addRow = function (label, value) {
+ var row = document.createElement('tr')
+ var heading = document.createElement('td')
+ var content = document.createElement('td')
+
+ heading.textContent = label
+ content.textContent = value == null ? '' : value
+ row.append(heading, content)
+ this.el.append(row)
+ }.bind(this)
+
+ addRow('Joomla! Version', data.joomlaVersion)
+ addRow('PHP Version', data.phpVersion)
+ addRow('Identity', data.identity.type)
+ addRow('Response', data.response.status_code)
+ addRow('Template', data.template.template)
+
+ var row = document.createElement('tr')
+ var label = document.createElement('td')
+ var details = document.createElement('td')
+ var list = document.createElement('dl')
+
+ label.textContent = 'Database'
+ ;[
+ ['Server', data.database.dbserver],
+ ['Version', data.database.dbversion],
+ ['Collation', data.database.dbcollation],
+ ['Conn Collation', data.database.dbconnectioncollation]
+ ].forEach(function (item) {
+ var term = document.createElement('dt')
+ var description = document.createElement('dd')
+
+ term.textContent = item[0]
+ description.textContent = item[1] == null ? '' : item[1]
+ list.append(term, description)
+ })
+
+ details.append(list)
+ row.append(label, details)
+ this.el.append(row)
})
}
})
-})(PhpDebugBar.$)
+})()
diff --git a/media_source/plg_system_debug/widgets/languageErrors/widget.es5.js b/media_source/plg_system_debug/widgets/languageErrors/widget.es5.js
index 1082241f68045..4f1c02c55432c 100644
--- a/media_source/plg_system_debug/widgets/languageErrors/widget.es5.js
+++ b/media_source/plg_system_debug/widgets/languageErrors/widget.es5.js
@@ -1,7 +1,6 @@
-(function ($) {
-
+(function () {
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
- var languageErrorsWidget = PhpDebugBar.Widgets.languageErrorsWidget = PhpDebugBar.Widget.extend({
+ PhpDebugBar.Widgets.languageErrorsWidget = PhpDebugBar.Widget.extend({
tagName: 'ul',
@@ -9,27 +8,27 @@
render: function () {
this.bindAttr('data', function (data) {
- this.$el.empty()
+ this.el.replaceChildren()
+
+ ;(data.files || []).forEach(function (file) {
+ var relativePath = file[0].replace(data.jroot, '')
+ var item = document.createElement('li')
+ var text = relativePath + ':' + file[1]
- for (var file of data.files) {
- var relPath = file[0].replace(data.jroot, '')
- var li = $('')
if (data.xdebugLink) {
- var link = $('')
- .text(relPath + ':' + file[1])
- .attr(
- 'href',
- data.xdebugLink
- .replace('%f', file[0])
- .replace('%l', file[1])
- )
- li.append(link)
+ var link = document.createElement('a')
+ link.textContent = text
+ link.href = data.xdebugLink
+ .replace('%f', file[0])
+ .replace('%l', file[1])
+ item.append(link)
} else {
- li.text(relPath + ':' + file[1])
+ item.textContent = text
}
- this.$el.append(li)
- }
+
+ this.el.append(item)
+ }, this)
})
}
})
-})(PhpDebugBar.$)
+})()
diff --git a/media_source/plg_system_debug/widgets/languageFiles/widget.es5.js b/media_source/plg_system_debug/widgets/languageFiles/widget.es5.js
index 43496f58f90e4..c9518e10493b7 100644
--- a/media_source/plg_system_debug/widgets/languageFiles/widget.es5.js
+++ b/media_source/plg_system_debug/widgets/languageFiles/widget.es5.js
@@ -1,49 +1,56 @@
-(function ($) {
-
+(function () {
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
- var languageFilesWidget = PhpDebugBar.Widgets.languageFilesWidget = PhpDebugBar.Widget.extend({
+ PhpDebugBar.Widgets.languageFilesWidget = PhpDebugBar.Widget.extend({
tagName: 'table',
className: csscls('languageFiles'),
render: function () {
this.bindAttr('data', function (data) {
- this.$el.empty()
- var head = $('
')
- .append($(' | ').text('Extension'))
- .append($(' | ').text('File'))
- this.$el.append(head)
- for (var extension in data.loaded) {
- var ul = $('')
- for (var file in data.loaded[extension]) {
- var css = data.loaded[extension][file] ? 'alert-success' : 'alert-warning'
- var status = data.loaded[extension][file] ? '+' : '-'
- var relPath = status + ' ' + file.replace(data.jroot, '')
- var li = $('')
- .addClass(css)
+ this.el.replaceChildren()
+
+ var header = document.createElement('tr')
+ var extensionHeader = document.createElement('th')
+ var fileHeader = document.createElement('th')
+ extensionHeader.textContent = 'Extension'
+ fileHeader.textContent = 'File'
+ header.append(extensionHeader, fileHeader)
+ this.el.append(header)
+
+ Object.keys(data.loaded || {}).forEach(function (extension) {
+ var row = document.createElement('tr')
+ var extensionCell = document.createElement('td')
+ var filesCell = document.createElement('td')
+ var list = document.createElement('ul')
+
+ extensionCell.textContent = extension
+
+ Object.keys(data.loaded[extension] || {}).forEach(function (file) {
+ var loaded = data.loaded[extension][file]
+ var item = document.createElement('li')
+ var path = file.replace(data.jroot, '')
+ var text = (loaded ? '+ ' : '- ') + path
+
+ item.classList.add(loaded ? 'alert-success' : 'alert-warning')
+
if (data.xdebugLink) {
- var link = $('')
- .text(relPath)
- .attr(
- 'href',
- data.xdebugLink
- .replace('%f', file)
- .replace('%l', '1')
- )
- li.append(link)
+ var link = document.createElement('a')
+ link.textContent = text
+ link.href = data.xdebugLink.replace('%f', file).replace('%l', '1')
+ item.append(link)
} else {
- li.text(relPath)
+ item.textContent = text
}
- li.appendTo(ul)
- }
- var tr = $('
')
- .append($(' | ').text(extension))
- .append($(' | ').append(ul))
- this.$el.append(tr)
- }
+ list.append(item)
+ })
+
+ filesCell.append(list)
+ row.append(extensionCell, filesCell)
+ this.el.append(row)
+ }, this)
})
}
})
-})(PhpDebugBar.$)
+})()
diff --git a/media_source/plg_system_debug/widgets/languageStrings/widget.css b/media_source/plg_system_debug/widgets/languageStrings/widget.css
index 143c190fe8569..357bd2f4729e7 100644
--- a/media_source/plg_system_debug/widgets/languageStrings/widget.css
+++ b/media_source/plg_system_debug/widgets/languageStrings/widget.css
@@ -11,7 +11,6 @@ table.phpdebugbar-widgets-languageStrings th {
}
table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack {
- display: none;
width: 100%;
margin: 10px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
@@ -31,28 +30,24 @@ table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack th
font-weight: bold;
}
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye,
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash {
- margin-left: 8px;
- color: #888;
+div.phpdebugbar[data-theme="dark"] table.phpdebugbar-widgets-languageStrings th {
+ border-color: var(--debugbar-border);
}
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash {
- color: #000;
- background-color: #eee;
+div.phpdebugbar[data-theme="dark"] table.phpdebugbar-widgets-languageStrings a {
+ color: var(--debugbar-link);
}
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye:before,
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash:before {
- margin-right: 4px;
- font-family: "Font Awesome 6 Free";
- /*font-size: 12px;*/
+div.phpdebugbar[data-theme="dark"] table.phpdebugbar-widgets-languageStrings a:hover {
+ color: var(--debugbar-text);
}
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye:before {
- content: "\f06e";
+div.phpdebugbar[data-theme="dark"] table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack tr.caller {
+ color: var(--debugbar-text);
+ background-color: var(--debugbar-background-alt);
}
-table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash:before {
- content: "\f070";
+div.phpdebugbar[data-theme="dark"] table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack tbody tr:hover {
+ color: var(--debugbar-text);
+ background-color: var(--debugbar-hover);
}
diff --git a/media_source/plg_system_debug/widgets/languageStrings/widget.es5.js b/media_source/plg_system_debug/widgets/languageStrings/widget.es5.js
index 52e9103bdb6c8..4df639fd1e02a 100644
--- a/media_source/plg_system_debug/widgets/languageStrings/widget.es5.js
+++ b/media_source/plg_system_debug/widgets/languageStrings/widget.es5.js
@@ -1,87 +1,102 @@
-(function ($) {
-
+(function () {
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
- var languageStringsWidget = PhpDebugBar.Widgets.languageStringsWidget = PhpDebugBar.Widget.extend({
+ PhpDebugBar.Widgets.languageStringsWidget = PhpDebugBar.Widget.extend({
tagName: 'table',
className: csscls('languageStrings'),
render: function () {
this.bindAttr('data', function (data) {
- this.$el.empty()
- for (var orphan in data.orphans) {
- var tr = $('
')
- $(' | ').text(orphan).appendTo(tr)
- var td = $(' | ').appendTo(tr)
-
- var ul = $('').appendTo(td)
-
- var tableStack
-
- for (var oc in data.orphans[orphan]) {
- var occurence = data.orphans[orphan][oc]
- var relPath = occurence['caller'].replace(data.jroot, '')
-
- var li = $('')
-
- if (data.xdebugLink) {
- var parts = occurence['caller'].split(':')
- var link = $('')
- .text(relPath)
- .attr(
- 'href',
- data.xdebugLink
- .replace('%f', parts[0])
- .replace('%l', parts[1])
- )
- li.append(link)
+ this.el.replaceChildren()
+
+ Object.keys(data.orphans || {}).forEach(function (orphan) {
+ var row = document.createElement('tr')
+ var key = document.createElement('th')
+ var occurrences = document.createElement('td')
+ var list = document.createElement('ul')
+
+ key.textContent = orphan
+ key.style.verticalAlign = 'top'
+ key.style.width = '10%'
+
+ ;(data.orphans[orphan] || []).forEach(function (occurrence) {
+ var item = document.createElement('li')
+ var caller = occurrence.caller || ''
+ var relativePath = caller.replace(data.jroot, '')
+
+ if (data.xdebugLink && caller) {
+ var parts = caller.split(':')
+ var link = document.createElement('a')
+ link.textContent = relativePath
+ link.href = data.xdebugLink.replace('%f', parts[0]).replace('%l', parts[1] || '1')
+ item.append(link)
} else {
- li.text(relPath)
+ item.textContent = relativePath
}
- if (occurence['trace'] && !$.isEmptyObject(occurence['trace'])) {
- $('')
- .text('Stack')
- .addClass(csscls('eye'))
- .css('cursor', 'pointer')
- .on('click', function (e) {
- var btn = $(e.target)
- var table = btn.next()
- if (table.is(':visible')) {
- table.hide()
- btn.addClass(csscls('eye'))
- btn.removeClass(csscls('eye-dash'))
+ if (occurrence.trace && occurrence.trace.length) {
+ var button = document.createElement('button')
+ var stack = document.createElement('table')
+ var header = document.createElement('tr')
+ var title = document.createElement('th')
+
+ button.type = 'button'
+ button.textContent = 'Stack'
+ button.addEventListener('click', function () {
+ stack.hidden = !stack.hidden
+ })
+
+ title.colSpan = 3
+ title.textContent = 'Call Stack'
+ header.append(title)
+ stack.append(header)
+ stack.classList.add(csscls('callstack'))
+ stack.hidden = true
+
+ occurrence.trace.forEach(function (entry) {
+ var traceRow = document.createElement('tr')
+ var depth = document.createElement('th')
+ var method = document.createElement('td')
+ var location = document.createElement('td')
+ var file = entry[3] || ''
+ var line = entry[4] || ''
+ var path = file.replace(data.jroot, '')
+
+ if (entry[1]) {
+ traceRow.classList.add('caller')
+ }
+ depth.textContent = entry[0]
+ method.textContent = entry[2].replace(data.jroot, '')
+
+ if (file) {
+ var locationText = path + (line ? ':' + line : '')
+
+ if (data.xdebugLink) {
+ var locationLink = document.createElement('a')
+ locationLink.textContent = locationText
+ locationLink.href = data.xdebugLink.replace('%f', file).replace('%l', line || '1')
+ location.append(locationLink)
} else {
- table.show()
- btn.addClass(csscls('eye-dash'))
- btn.removeClass(csscls('eye'))
+ location.textContent = locationText
}
- })
- .appendTo(li)
-
- tableStack = $('')
- .addClass(csscls('callstack'))
- .appendTo(li)
-
- for (var i in occurence['trace']) {
- var entry = occurence['trace'][i]
- var location = entry[3] ? entry[3].replace(data.jroot, '') + ':' + entry[4] : ''
- var caller = entry[2].replace(data.jroot, '')
- var cssClass = entry[1] ? 'caller' : ''
- if (location && data.xdebugLink) {
- location = '' + location + ''
}
- tableStack.append('| ' + entry[0] + ' | ' + caller + ' | ' + location + ' |
')
- }
+
+ traceRow.append(depth, method, location)
+ stack.append(traceRow)
+ })
+
+ item.append(button, stack)
}
- li.appendTo(ul)
- }
+ list.append(item)
+ })
- this.$el.append(tr)
- }
+ occurrences.append(list)
+ row.append(key, occurrences)
+ this.el.append(row)
+ }, this)
})
}
})
-})(PhpDebugBar.$)
+})()
diff --git a/media_source/plg_system_debug/widgets/sqlqueries/widget.css b/media_source/plg_system_debug/widgets/sqlqueries/widget.css
index 16b42e94af459..f27b65dd98498 100644
--- a/media_source/plg_system_debug/widgets/sqlqueries/widget.css
+++ b/media_source/plg_system_debug/widgets/sqlqueries/widget.css
@@ -1,6 +1,9 @@
+
+@charset "UTF-8";
+
div.phpdebugbar-widgets-sqlqueries .phpdebugbar-widgets-status {
padding: 6px;
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
font-weight: bold;
color: #555;
background: #fafafa;
@@ -11,6 +14,35 @@ div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-
color: #f00;
}
+div.phpdebugbar-widgets-sqlqueries button.phpdebugbar-widgets-query-detail-toggle {
+ margin-right: 8px;
+ color: var(--debugbar-text-muted);
+ cursor: pointer;
+ background: none;
+ border: 0;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-query-detail {
+ width: 100%;
+ margin: 10px 0;
+ overflow-x: auto;
+ font-family: var(--debugbar-font-mono);
+ border-collapse: collapse;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-query-detail caption {
+ padding: 3px;
+ font-weight: bold;
+ text-align: left;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-query-detail th,
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-query-detail td {
+ padding: 3px;
+ text-align: left;
+ border: 1px solid var(--debugbar-border);
+}
+
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-database,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory,
@@ -29,79 +61,10 @@ div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-editor-link {
margin-left: 8px;
}
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-database,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-duration,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-memory,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-row-count,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-copy-clipboard,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-eye,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-eye-dash,
-div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-stmt-id {
- color: #555;
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-database:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye-dash:before,
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before,
-div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-editor-link:before {
- margin-right: 4px;
- font-family: "Font Awesome 6 Free";
- font-size: 12px;
- font-weight: 900;
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-database:before {
- content: "\f1c0";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration:before {
- content: "\f017";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory:before {
- content: "\f085";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count:before {
- content: "\f0ce";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before {
- content: "\f08d";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard:before {
- content: "\f0c5";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye:before {
- content: "\f06e";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye-dash:before {
- content: "\f070";
-}
-
-div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-editor-link:before {
- margin-left: 4px;
- content: "\f08e";
-}
-
-div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye-dash {
- padding: 2px;
- background-color: #eee;
-}
-
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params {
- display: none;
width: 70%;
margin: 10px 0;
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
border-collapse: collapse;
border: 1px solid #ddd;
}
@@ -122,7 +85,7 @@ div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-explain {
display: none;
width: 100%;
margin: 10px 0;
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
border-collapse: collapse;
border: 1px solid #ddd;
}
diff --git a/media_source/plg_system_debug/widgets/sqlqueries/widget.es5.js b/media_source/plg_system_debug/widgets/sqlqueries/widget.es5.js
index 38b3b41741eee..e215ec3229c0a 100644
--- a/media_source/plg_system_debug/widgets/sqlqueries/widget.es5.js
+++ b/media_source/plg_system_debug/widgets/sqlqueries/widget.es5.js
@@ -1,348 +1,88 @@
-(function ($) {
-
- var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
-
- /**
- * Widget for the displaying sql queries
- *
- * Options:
- * - data
- */
- var SQLQueriesWidget = PhpDebugBar.Widgets.SQLQueriesWidget = PhpDebugBar.Widget.extend({
-
- className: csscls('sqlqueries'),
-
- onFilterClick: function (el) {
- $(el).toggleClass(csscls('excluded'))
-
- var excludedLabels = []
- this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function () {
- excludedLabels.push(this.rel)
- })
-
- this.$list.$el.find('li[connection=' + $(el).attr('rel') + ']').toggle()
-
- this.set('exclude', excludedLabels)
- },
- onFilterDupesClick: function (el) {
- $(el).toggleClass(csscls('excluded'))
+/* global phpdebugbar_sqlformatter */
+(function () {
+ const csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
+
+ class JoomlaSQLQueriesWidget extends PhpDebugBar.Widgets.SQLQueriesWidget {
+ createDetailTable(title, columns, rows) {
+ const table = document.createElement('table');
+ const caption = document.createElement('caption');
+ const head = document.createElement('thead');
+ const header = document.createElement('tr');
+ const body = document.createElement('tbody');
+
+ table.classList.add(csscls('query-detail'));
+ caption.textContent = title;
+ table.append(caption);
+
+ columns.forEach((column) => {
+ const cell = document.createElement('th');
+ cell.textContent = column;
+ header.append(cell);
+ });
+ head.append(header);
+ table.append(head);
+
+ rows.forEach((row) => {
+ const tr = document.createElement('tr');
+
+ columns.forEach((column) => {
+ const cell = document.createElement('td');
+ const value = row[column];
+ cell.textContent = value === null || typeof value === 'undefined'
+ ? ''
+ : typeof value === 'object'
+ ? JSON.stringify(value)
+ : String(value);
+ tr.append(cell);
+ });
+
+ body.append(tr);
+ });
+
+ table.append(body);
+ table.addEventListener('click', (event) => event.stopPropagation());
+
+ return table;
+ }
- var excludedLabels = []
- this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function () {
- excludedLabels.push(this.rel)
- })
+ addDetailToggle(li, label, table) {
+ const button = document.createElement('button');
+ button.type = 'button';
+ button.classList.add(csscls('query-detail-toggle'));
+ button.textContent = label;
+ button.setAttribute('aria-expanded', 'false');
+ table.hidden = true;
+
+ button.addEventListener('click', (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ table.hidden = !table.hidden;
+ button.setAttribute('aria-expanded', String(!table.hidden));
+ });
+
+ li.append(button, table);
+ }
- this.$list.$el.find('li[dupeindex=' + $(el).attr('rel') + ']').toggle()
+ itemRenderer(li, stmt) {
+ super.itemRenderer(li, stmt);
- this.set('exclude', excludedLabels)
- },
- onCopyToClipboard: function (el) {
- var code = $(el).parent('li').find('code').get(0)
- var copy = function () {
- try {
- document.execCommand('copy')
- alert('Query copied to the clipboard')
- } catch (err) {
- console.log('Oops, unable to copy')
- }
+ if (stmt.type !== 'query') {
+ return;
}
- var select = function (node) {
- if (document.selection) {
- var range = document.body.createTextRange()
- range.moveToElementText(node)
- range.select()
- } else if (window.getSelection) {
- var range = document.createRange()
- range.selectNodeContents(node)
- window.getSelection().removeAllRanges()
- window.getSelection().addRange(range)
- }
- copy()
- window.getSelection().removeAllRanges()
- }
- select(code)
- },
- render: function () {
- this.$status = $('').addClass(csscls('status')).appendTo(this.$el)
-
- this.$toolbar = $('').addClass(csscls('toolbar')).appendTo(this.$el)
-
- var filters = [], self = this
-
- this.$list = new PhpDebugBar.Widgets.ListWidget({
- itemRenderer: function (li, stmt) {
- $('').addClass(csscls('sql')).html(PhpDebugBar.Widgets.highlight(stmt.sql, 'sql')).appendTo(li)
- if (stmt.duration_str) {
- $('').addClass(csscls('duration')).text(stmt.duration_str).appendTo(li)
- }
- if (stmt.memory_str) {
- $('').addClass(csscls('memory')).text(stmt.memory_str).appendTo(li)
- }
- if (typeof(stmt.row_count) != 'undefined') {
- $('').addClass(csscls('row-count')).text(stmt.row_count).appendTo(li)
- }
- if (typeof(stmt.stmt_id) != 'undefined' && stmt.stmt_id) {
- $('').addClass(csscls('stmt-id')).text(stmt.stmt_id).appendTo(li)
- }
- if (stmt.connection) {
- $('').addClass(csscls('database')).text(stmt.connection).appendTo(li)
- li.attr('connection', stmt.connection)
- if ($.inArray(stmt.connection, filters) == -1) {
- filters.push(stmt.connection)
- $('')
- .addClass(csscls('filter'))
- .text(stmt.connection)
- .attr('rel', stmt.connection)
- .on('click', function () {
- self.onFilterClick(this)
- })
- .appendTo(self.$toolbar)
- if (filters.length > 1) {
- self.$toolbar.show()
- self.$list.$el.css('margin-bottom', '20px')
- }
- }
- }
- if (typeof(stmt.is_success) != 'undefined' && !stmt.is_success) {
- li.addClass(csscls('error'))
- li.append($('').addClass(csscls('error')).text('[' + stmt.error_code + '] ' + stmt.error_message))
- }
-
- var tableParams;
-
- function showTableParams() {
- if (tableParams) {
- tableParams.show();
- return;
- }
-
- // Render table
- tableParams = $('').addClass(csscls('params')).appendTo(li);
- tableParams.append('| Query Parameters |
');
- tableParams.append('| ID | Value | Data Type |
');
-
- var pRow;
- for (var key in stmt.params) {
- pRow = stmt.params[key];
- tableParams.append('| ' + key + ' | ' + pRow.value + ' | '
- + pRow.dataType + ' |
');
- }
-
- tableParams.show();
- }
-
- if (stmt.params && !$.isEmptyObject(stmt.params)) {
- var btnParams = $('')
- .text('Params')
- .addClass(csscls('eye'))
- .css('cursor', 'pointer')
- .on('click', function () {
- if (tableParams && tableParams.is(':visible')) {
- tableParams.hide()
- btnParams.addClass(csscls('eye'))
- btnParams.removeClass(csscls('eye-dash'))
- } else {
- showTableParams();
- btnParams.addClass(csscls('eye-dash'))
- btnParams.removeClass(csscls('eye'))
- }
- })
- .appendTo(li)
- }
-
- var tableExplain;
-
- function showTableExplain() {
- if (tableExplain) {
- tableExplain.show();
- return;
- }
-
- // Render table
- tableExplain = $('').addClass(csscls('explain')).appendTo(li);
- tableExplain.append('| ' + stmt.explain_col.join(' | ') + ' |
');
- var i, entry, cols;
- for (i in stmt.explain) {
- cols = []
- entry = stmt.explain[i];
-
- stmt.explain_col.forEach(function (key){
- cols.push(entry[key]);
- });
-
- tableExplain.append('| ' + cols.join(' | ') + ' |
');
- }
-
- tableExplain.show();
- }
-
- if (stmt.explain && !$.isEmptyObject(stmt.explain)) {
- var btnExplain = $('')
- .text('Explain')
- .addClass(csscls('eye'))
- .css('cursor', 'pointer')
- .on('click', function () {
- if (tableExplain && tableExplain.is(':visible')) {
- tableExplain.hide()
- btnExplain.addClass(csscls('eye'))
- btnExplain.removeClass(csscls('eye-dash'))
- } else {
- showTableExplain();
- btnExplain.addClass(csscls('eye-dash'))
- btnExplain.removeClass(csscls('eye'))
- }
- })
- .appendTo(li)
- }
-
- var tableStack;
-
- function showTableStack() {
- if (tableStack) {
- tableStack.show();
- return;
- }
-
- // Render table
- tableStack = $('')
- .addClass(csscls('callstack')).appendTo(li);
-
- var i, entry, location, caller, cssClass;
- for (i in stmt.callstack) {
- entry = stmt.callstack[i]
- location = entry[3] ? entry[3].replace(self.root_path, '') + ':' + entry[4] : ''
- caller = entry[2].replace(self.root_path, '')
- cssClass = entry[1] ? 'caller' : ''
-
- if (location && self.xdebug_link) {
- location = '' + location + ''
- }
- tableStack.append('| ' + entry[0] + ' | ' + caller + ' | ' + location + ' |
')
- }
-
- tableStack.show();
- }
-
- if (stmt.callstack && !$.isEmptyObject(stmt.callstack)) {
- var btnStack = $('')
- .text('Stack')
- .addClass(csscls('eye'))
- .css('cursor', 'pointer')
- .on('click', function () {
- if (tableStack && tableStack.is(':visible')) {
- tableStack.hide()
- btnStack.addClass(csscls('eye'))
- btnStack.removeClass(csscls('eye-dash'))
- } else {
- showTableStack();
- btnStack.addClass(csscls('eye-dash'))
- btnStack.removeClass(csscls('eye'))
- }
- })
- .appendTo(li)
- }
-
- if (typeof(stmt.caller) != 'undefined' && stmt.caller) {
- var caller = stmt.caller.replace(self.root_path, '')
- if (self.xdebug_link) {
- var parts = stmt.caller.split(':')
- $('')
- .text(caller)
- .addClass(csscls('editor-link'))
- .attr('href', self.xdebug_link.replace('%f', parts[0]).replace('%l', parts[1]))
- .appendTo(li)
- } else {
- $('')
- .text(caller)
- .addClass(csscls('stmt-id'))
- .appendTo(li)
- }
- }
-
- $('')
- .text('Copy')
- .addClass(csscls('copy-clipboard'))
- .css('cursor', 'pointer')
- .on('click', function (event) {
- self.onCopyToClipboard(this)
- event.stopPropagation()
- })
- .appendTo(li)
-
- li.attr('dupeindex', 'dupe-0')
- }
- })
- this.$list.$el.appendTo(this.$el)
-
- this.bindAttr('data', function (data) {
- // the collector maybe is empty
- if (data.length <= 0) {
- return false
- }
-
- this.root_path = data.root_path
- this.xdebug_link = data.xdebug_link
- this.$list.set('data', data.statements)
- this.$status.empty()
-
- // Search for duplicate statements.
- for (var sql = {}, unique = 0, duplicate = 0, i = 0; i < data.statements.length; i++) {
- var stmt = data.statements[i].sql
- if (data.statements[i].params && !$.isEmptyObject(data.statements[i].params)) {
- stmt += ' {' + $.param(data.statements[i].params, false) + '}'
- }
- sql[stmt] = sql[stmt] || {keys: []}
- sql[stmt].keys.push(i)
- }
- // Add classes to all duplicate SQL statements.
- var cnt = 0
- for (var stmt in sql) {
- if (sql[stmt].keys.length > 1) {
- duplicate += sql[stmt].keys.length
- cnt++
- for (var i = 0; i < sql[stmt].keys.length; i++) {
- this.$list.$el.find('.' + csscls('list-item')).eq(sql[stmt].keys[i])
- .addClass(csscls('sql-duplicate'))
- .attr('dupeindex', 'dupe-' + cnt)
- }
- } else {
- unique++
- }
- }
-
- if (duplicate) {
- for (i = 0; i <= cnt; i++) {
- $('')
- .addClass(csscls('filter'))
- .text(i ? 'Duplicates ' + i : 'Uniques')
- .attr('rel', 'dupe-' + i)
- .on('click', function () {
- self.onFilterDupesClick(this)
- })
- .appendTo(self.$toolbar)
- }
- self.$toolbar.show()
- self.$list.$el.css('margin-bottom', '20px')
- }
+ if (Array.isArray(stmt.explain) && stmt.explain.length > 0) {
+ const columns = stmt.explain_col && stmt.explain_col.length
+ ? stmt.explain_col
+ : Object.keys(stmt.explain[0]);
+ this.addDetailToggle(li, 'Explain', this.createDetailTable('Explain', columns, stmt.explain));
+ }
- var t = $('').text(data.nb_statements + ' statements were executed').appendTo(this.$status)
- if (data.nb_failed_statements) {
- t.append(', ' + data.nb_failed_statements + ' of which failed')
- }
- if (duplicate) {
- t.append(', ' + duplicate + ' of which were duplicates')
- t.append(', ' + unique + ' unique')
- }
- if (data.accumulated_duration_str) {
- this.$status.append($('').addClass(csscls('duration')).text(data.accumulated_duration_str))
- }
- if (data.memory_usage_str) {
- this.$status.append($('').addClass(csscls('memory')).text(data.memory_usage_str))
- }
- })
+ if (Array.isArray(stmt.profile) && stmt.profile.length > 0) {
+ const columns = Object.keys(stmt.profile[0]);
+ this.addDetailToggle(li, 'Profile', this.createDetailTable('Profile', columns, stmt.profile));
+ }
}
+ }
- })
-
-})(PhpDebugBar.$)
+ PhpDebugBar.Widgets.JoomlaSQLQueriesWidget = JoomlaSQLQueriesWidget;
+})();
diff --git a/plugins/system/debug/src/AbstractDataCollector.php b/plugins/system/debug/src/AbstractDataCollector.php
index f1a098680dfb2..89c1daecdf4db 100644
--- a/plugins/system/debug/src/AbstractDataCollector.php
+++ b/plugins/system/debug/src/AbstractDataCollector.php
@@ -12,6 +12,7 @@
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
+use DebugBar\DataFormatter\DataFormatterInterface;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
@@ -36,10 +37,10 @@ abstract class AbstractDataCollector extends DataCollector implements Renderable
/**
* The default formatter.
*
- * @var DataFormatter
+ * @var ?DataFormatterInterface
* @since 4.0.0
*/
- public static $defaultDataFormatter;
+ public static ?DataFormatterInterface $defaultDataFormatter = null;
/**
* AbstractDataCollector constructor.
@@ -61,11 +62,11 @@ public function __construct(Registry $params)
*/
public function getDataFormatter(): DataFormatter
{
- if ($this->dataFormater === null) {
- $this->dataFormater = self::getDefaultDataFormatter();
+ if ($this->dataFormatter === null) {
+ $this->dataFormatter = self::getDefaultDataFormatter();
}
- return $this->dataFormater;
+ return $this->dataFormatter;
}
/**
diff --git a/plugins/system/debug/src/DataCollector/InfoCollector.php b/plugins/system/debug/src/DataCollector/InfoCollector.php
index 91968f000110a..3bd24cd4676cc 100644
--- a/plugins/system/debug/src/DataCollector/InfoCollector.php
+++ b/plugins/system/debug/src/DataCollector/InfoCollector.php
@@ -84,7 +84,7 @@ public function getWidgets(): array
{
return [
'info' => [
- 'icon' => 'info-circle',
+ 'icon' => 'code',
'title' => 'J! Info',
'widget' => 'PhpDebugBar.Widgets.InfoWidget',
'map' => $this->name,
diff --git a/plugins/system/debug/src/DataCollector/LanguageErrorsCollector.php b/plugins/system/debug/src/DataCollector/LanguageErrorsCollector.php
index d774f1e1c6f2c..0cfae3db69db6 100644
--- a/plugins/system/debug/src/DataCollector/LanguageErrorsCollector.php
+++ b/plugins/system/debug/src/DataCollector/LanguageErrorsCollector.php
@@ -85,10 +85,10 @@ public function getWidgets(): array
{
return [
'errors' => [
- 'icon' => 'warning',
+ 'icon' => 'bug',
'widget' => 'PhpDebugBar.Widgets.languageErrorsWidget',
'map' => $this->name . '.data',
- 'default' => '',
+ 'default' => '{}',
],
'errors:badge' => [
'map' => $this->name . '.count',
@@ -98,16 +98,13 @@ public function getWidgets(): array
}
/**
- * Returns an array with the following keys:
- * - base_path
- * - base_url
- * - css: an array of filenames
- * - js: an array of filenames
+ * Assets for the collector.
*
- * @since 4.0.0
* @return array
+ *
+ * @since 4.0.0
*/
- public function getAssets()
+ public function getAssets(): array
{
return [
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/languageErrors/widget.min.js',
diff --git a/plugins/system/debug/src/DataCollector/LanguageFilesCollector.php b/plugins/system/debug/src/DataCollector/LanguageFilesCollector.php
index 634407b77f500..c8cab810dd86b 100644
--- a/plugins/system/debug/src/DataCollector/LanguageFilesCollector.php
+++ b/plugins/system/debug/src/DataCollector/LanguageFilesCollector.php
@@ -98,10 +98,10 @@ public function getWidgets(): array
{
return [
'loaded' => [
- 'icon' => 'language',
+ 'icon' => 'file-code',
'widget' => 'PhpDebugBar.Widgets.languageFilesWidget',
'map' => $this->name,
- 'default' => '[]',
+ 'default' => '{}',
],
'loaded:badge' => [
'map' => $this->name . '.count',
@@ -111,14 +111,11 @@ public function getWidgets(): array
}
/**
- * Returns an array with the following keys:
- * - base_path
- * - base_url
- * - css: an array of filenames
- * - js: an array of filenames
+ * Assets for the collector.
*
- * @since 4.0.0
* @return array
+ *
+ * @since 4.0.0
*/
public function getAssets(): array
{
diff --git a/plugins/system/debug/src/DataCollector/LanguageStringsCollector.php b/plugins/system/debug/src/DataCollector/LanguageStringsCollector.php
index b8536796b669c..f98d708d2706b 100644
--- a/plugins/system/debug/src/DataCollector/LanguageStringsCollector.php
+++ b/plugins/system/debug/src/DataCollector/LanguageStringsCollector.php
@@ -74,10 +74,10 @@ public function getWidgets(): array
{
return [
'untranslated' => [
- 'icon' => 'question-circle',
+ 'icon' => 'inbox',
'widget' => 'PhpDebugBar.Widgets.languageStringsWidget',
'map' => $this->name . '.data',
- 'default' => '',
+ 'default' => '{}',
],
'untranslated:badge' => [
'map' => $this->name . '.count',
@@ -87,20 +87,18 @@ public function getWidgets(): array
}
/**
- * Returns an array with the following keys:
- * - base_path
- * - base_url
- * - css: an array of filenames
- * - js: an array of filenames
+ * Assets for the collector.
*
- * @since 4.0.0
* @return array
+ *
+ * @since 4.0.0
*/
public function getAssets(): array
{
return [
'js' => Uri::root(true) . '/media/plg_system_debug/widgets/languageStrings/widget.min.js',
'css' => Uri::root(true) . '/media/plg_system_debug/widgets/languageStrings/widget.min.css',
+
];
}
@@ -139,7 +137,7 @@ private function getData(): array
$file = $stack['file'] ?? '';
$line = $stack['line'] ?? '';
- $caller = $this->formatCallerInfo($stack);
+ $caller = html_entity_decode($this->formatCallerInfo($stack), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$location = $file && $line ? "$file:$line" : 'same';
$isCaller = 0;
diff --git a/plugins/system/debug/src/DataCollector/MemoryCollector.php b/plugins/system/debug/src/DataCollector/MemoryCollector.php
index 6a01665025b95..64b66a220746d 100644
--- a/plugins/system/debug/src/DataCollector/MemoryCollector.php
+++ b/plugins/system/debug/src/DataCollector/MemoryCollector.php
@@ -112,7 +112,7 @@ public function updatePeakUsage()
*
* @since 4.4.0
*/
- public function collect()
+ public function collect(): array
{
$this->updatePeakUsage();
@@ -127,7 +127,7 @@ public function collect()
*
* @since 4.4.0
*/
- public function getName()
+ public function getName(): string
{
return 'memory';
}
@@ -137,11 +137,11 @@ public function getName()
*
* @since 4.4.0
*/
- public function getWidgets()
+ public function getWidgets(): array
{
return [
'memory' => [
- 'icon' => 'cogs',
+ 'icon' => 'server-cog',
'tooltip' => 'Memory Usage',
'map' => 'memory.peak_usage_str',
'default' => "'0B'",
diff --git a/plugins/system/debug/src/DataCollector/ProfileCollector.php b/plugins/system/debug/src/DataCollector/ProfileCollector.php
index 328f931f2c944..a7e07390c591e 100644
--- a/plugins/system/debug/src/DataCollector/ProfileCollector.php
+++ b/plugins/system/debug/src/DataCollector/ProfileCollector.php
@@ -326,13 +326,13 @@ public function getWidgets(): array
{
return [
'profileTime' => [
- 'icon' => 'clock-o',
+ 'icon' => 'clock',
'tooltip' => 'Request Duration',
'map' => 'profile.duration_str',
'default' => "'0ms'",
],
'profile' => [
- 'icon' => 'clock-o',
+ 'icon' => 'clock',
'widget' => 'PhpDebugBar.Widgets.TimelineWidget',
'map' => 'profile',
'default' => '{}',
diff --git a/plugins/system/debug/src/DataCollector/QueryCollector.php b/plugins/system/debug/src/DataCollector/QueryCollector.php
index eeeb1db8544c4..accec6c65b3da 100644
--- a/plugins/system/debug/src/DataCollector/QueryCollector.php
+++ b/plugins/system/debug/src/DataCollector/QueryCollector.php
@@ -144,7 +144,7 @@ public function getWidgets(): array
return [
'queries' => [
'icon' => 'database',
- 'widget' => 'PhpDebugBar.Widgets.SQLQueriesWidget',
+ 'widget' => 'PhpDebugBar.Widgets.JoomlaSQLQueriesWidget',
'map' => $this->name . '.data',
'default' => '[]',
],
@@ -158,9 +158,9 @@ public function getWidgets(): array
/**
* Assets for the collector.
*
- * @since 4.0.0
- *
* @return array
+ *
+ * @since 4.0.0
*/
public function getAssets(): array
{
@@ -214,24 +214,38 @@ private function getStatements(): array
$file = $stack['file'] ?? '';
$line = $stack['line'] ?? '';
- $caller = $this->formatCallerInfo($stack);
+ $caller = html_entity_decode($this->formatCallerInfo($stack), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$location = $file && $line ? "$file:$line" : 'same';
- $isCaller = 0;
-
if (\Joomla\Database\DatabaseDriver::class === $class && !str_contains($file, 'DatabaseDriver.php')) {
$callerLocation = $location;
- $isCaller = 1;
}
if ($collectStacks) {
- $trace[] = [\count($stacks[$id]) - $cnt, $isCaller, $caller, $file, $line];
+ $traceLocation = $file && $line ? $this->formatPath("$file:$line") : 'same';
+ $trace[] = "$caller ($traceLocation)";
}
$cnt++;
}
}
+ $params = [];
+
+ foreach ($boundParams[$id] ?? [] as $key => $param) {
+ $value = \is_object($param) && property_exists($param, 'value')
+ ? $param->value
+ : $param;
+
+ if (\is_array($value) || \is_object($value)) {
+ $value = json_encode($value, JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR);
+ } elseif (\is_resource($value)) {
+ $value = '[resource]';
+ }
+
+ $params[$key] = $value;
+ }
+
$explain = $this->explains[$id] ?? [];
$explainColumns = [];
@@ -242,11 +256,11 @@ private function getStatements(): array
$statements[] = [
'sql' => $item,
- 'params' => $boundParams[$id] ?? [],
+ 'params' => $params,
'duration_str' => $this->getDataFormatter()->formatDuration($queryTime),
'memory_str' => $this->getDataFormatter()->formatBytes($queryMemory),
'caller' => $callerLocation,
- 'callstack' => $trace,
+ 'backtrace' => $trace,
'explain' => $explain,
'explain_col' => $explainColumns,
'profile' => $this->profiles[$id] ?? [],
diff --git a/plugins/system/debug/src/DataCollector/RequestDataCollector.php b/plugins/system/debug/src/DataCollector/RequestDataCollector.php
index 3dfd2a11ec72c..4ec2d77ff7eb9 100644
--- a/plugins/system/debug/src/DataCollector/RequestDataCollector.php
+++ b/plugins/system/debug/src/DataCollector/RequestDataCollector.php
@@ -30,7 +30,7 @@ class RequestDataCollector extends \DebugBar\DataCollector\RequestDataCollector
*
* @return array
*/
- public function collect()
+ public function collect(): array
{
$vars = ['_GET', '_POST', '_SESSION', '_COOKIE', '_SERVER'];
$returnData = [];
@@ -54,14 +54,14 @@ public function collect()
$value = '***redacted***';
});
- if ($this->isHtmlVarDumperUsed()) {
- $returnData[$key] = $this->getVarDumper()->renderVar($data);
- } else {
- $returnData[$key] = $this->getDataFormatter()->formatVar($data);
- }
+ $returnData[$key] = $this->getDataFormatter()->formatVar($data);
}
}
- return $returnData;
+ return [
+ 'data' => $returnData,
+ 'tooltip' => null,
+ 'badge' => null,
+ ];
}
}
diff --git a/plugins/system/debug/src/DataCollector/SessionCollector.php b/plugins/system/debug/src/DataCollector/SessionCollector.php
index 24c92fcab4dc8..1f03b9f4bd1c3 100644
--- a/plugins/system/debug/src/DataCollector/SessionCollector.php
+++ b/plugins/system/debug/src/DataCollector/SessionCollector.php
@@ -68,7 +68,7 @@ public function __construct($params, $collect = false)
*
* @since 4.0.0
*/
- public function collect($overwrite = false)
+ public function collect($overwrite = false): array
{
if ($this->sessionData === null || $overwrite) {
$this->sessionData = [];
@@ -98,7 +98,7 @@ public function collect($overwrite = false)
*
* @return string
*/
- public function getName()
+ public function getName(): string
{
return $this->name;
}
@@ -111,11 +111,11 @@ public function getName()
*
* @return array
*/
- public function getWidgets()
+ public function getWidgets(): array
{
return [
'session' => [
- 'icon' => 'key',
+ 'icon' => 'briefcase',
'widget' => 'PhpDebugBar.Widgets.VariableListWidget',
'map' => $this->name . '.data',
'default' => '[]',
diff --git a/plugins/system/debug/src/DataCollector/UserCollector.php b/plugins/system/debug/src/DataCollector/UserCollector.php
index 2d898c5fc3451..0215a889090d4 100644
--- a/plugins/system/debug/src/DataCollector/UserCollector.php
+++ b/plugins/system/debug/src/DataCollector/UserCollector.php
@@ -40,7 +40,7 @@ class UserCollector implements DataCollectorInterface
*
* @return array Collected data
*/
- public function collect()
+ public function collect(): array
{
$user = Factory::getApplication()->getIdentity()
?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
@@ -55,7 +55,7 @@ public function collect()
*
* @return string
*/
- public function getName()
+ public function getName(): string
{
return $this->name;
}
diff --git a/plugins/system/debug/src/Extension/Debug.php b/plugins/system/debug/src/Extension/Debug.php
index 5689a28953dfe..08bc66fecc962 100644
--- a/plugins/system/debug/src/Extension/Debug.php
+++ b/plugins/system/debug/src/Extension/Debug.php
@@ -240,21 +240,20 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void
{
// Only if debugging or language debug is enabled.
if ((JDEBUG || $this->debugLang) && $this->isAuthorisedDisplayDebug() && $this->getApplication()->getDocument() instanceof HtmlDocument) {
- // Use our own jQuery and fontawesome instead of the debug bar shipped version
$assetManager = $this->getApplication()->getDocument()->getWebAssetManager();
$assetManager->registerAndUseStyle(
'plg.system.debug',
'plg_system_debug/debug.css',
[],
[],
- ['fontawesome']
+ []
);
$assetManager->registerAndUseScript(
'plg.system.debug',
'plg_system_debug/debug.min.js',
[],
['defer' => true],
- ['jquery']
+ []
);
}
@@ -343,7 +342,7 @@ public function onAfterRespond(AfterRespondEvent|ApplicationEvent $event): void
$debugBarRenderer = new JavascriptRenderer($this->debugBar, Uri::root(true) . '/media/vendor/debugbar/');
- if ($this->params->get('track_request_history', false)) {
+ if ($this->params->get('track_request_history', false) && !$this->getApplication()->getIdentity()->guest) {
$openHandlerUrl = Uri::base(true) . '/index.php?option=com_ajax&plugin=debug&group=system&format=raw&action=openhandler';
$openHandlerUrl .= '&' . ($formToken ?? Session::getFormToken()) . '=1';
$debugBarRenderer->setOpenHandlerUrl($openHandlerUrl);
diff --git a/plugins/system/debug/src/JavascriptRenderer.php b/plugins/system/debug/src/JavascriptRenderer.php
index eaf1ec39c0b4a..20dbe4a2d029b 100644
--- a/plugins/system/debug/src/JavascriptRenderer.php
+++ b/plugins/system/debug/src/JavascriptRenderer.php
@@ -37,11 +37,6 @@ class JavascriptRenderer extends DebugBarJavascriptRenderer
public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = null)
{
parent::__construct($debugBar, $baseUrl, $basePath);
-
- // Disable features that loaded by Joomla! API, or not in use
- $this->setEnableJqueryNoConflict(false);
- $this->disableVendor('jquery');
- $this->disableVendor('fontawesome');
}
/**
@@ -53,39 +48,11 @@ public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = nul
*
* @since 4.0.0
*/
- public function renderHead()
+ public function renderHead(): string
{
- [$cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead] = $this->getAssets(null, self::RELATIVE_URL);
- $html = '';
- $doc = Factory::getApplication()->getDocument();
-
- foreach ($cssFiles as $file) {
- $html .= \sprintf('' . "\n", $file);
- }
-
- foreach ($inlineCss as $content) {
- $html .= \sprintf('' . "\n", $content);
- }
-
- foreach ($jsFiles as $file) {
- $html .= \sprintf('' . "\n", $file);
- }
-
- $nonce = '';
-
- if ($doc->cspNonce) {
- $nonce = ' nonce="' . $doc->cspNonce . '"';
- }
-
- foreach ($inlineJs as $content) {
- $html .= \sprintf('' . "\n", $nonce, $content);
- }
+ $this->setCspNonce(Factory::getApplication()->getDocument()->cspNonce);
- foreach ($inlineHead as $content) {
- $html .= $content . "\n";
- }
-
- return $html;
+ return parent::renderHead();
}
/**
@@ -100,34 +67,10 @@ public function renderHead()
*
* @since 4.0.0
*/
- public function render($initialize = true, $renderStackedData = true)
+ public function render(bool $initialize = true, bool $renderStackedData = true): string
{
- $js = '';
- $doc = Factory::getApplication()->getDocument();
-
- if ($initialize) {
- $js = $this->getJsInitializationCode();
- }
-
- if ($renderStackedData && $this->debugBar->hasStackedData()) {
- foreach ($this->debugBar->getStackedData() as $id => $data) {
- $js .= $this->getAddDatasetCode($id, $data, '(stacked)');
- }
- }
-
- $suffix = !$initialize ? '(ajax)' : null;
- $js .= $this->getAddDatasetCode($this->debugBar->getCurrentRequestId(), $this->debugBar->getData(), $suffix);
-
- $nonce = '';
-
- if ($doc->cspNonce) {
- $nonce = ' nonce="' . $doc->cspNonce . '"';
- }
-
- if ($this->useRequireJs) {
- return "\n";
- }
+ $this->setCspNonce(Factory::getApplication()->getDocument()->cspNonce);
- return "\n";
+ return parent::render($initialize, $renderStackedData);
}
}
diff --git a/plugins/system/debug/src/JoomlaHttpDriver.php b/plugins/system/debug/src/JoomlaHttpDriver.php
index 93fdb9dc3d3e6..9905a72193cfd 100644
--- a/plugins/system/debug/src/JoomlaHttpDriver.php
+++ b/plugins/system/debug/src/JoomlaHttpDriver.php
@@ -58,7 +58,7 @@ public function __construct(CMSApplicationInterface $app)
*
* @since 4.1.5
*/
- public function setHeaders(array $headers)
+ public function setHeaders(array $headers): void
{
if ($this->app instanceof WebApplicationInterface) {
foreach ($headers as $name => $value) {
@@ -74,7 +74,7 @@ public function setHeaders(array $headers)
*
* @since 4.1.5
*/
- public function isSessionStarted()
+ public function isSessionStarted(): bool
{
return true;
}
@@ -87,7 +87,7 @@ public function isSessionStarted()
*
* @since 4.1.5
*/
- public function setSessionValue($name, $value)
+ public function setSessionValue(string $name, mixed $value): void
{
$this->dummySession[$name] = $value;
}
@@ -101,7 +101,7 @@ public function setSessionValue($name, $value)
*
* @since 4.1.5
*/
- public function hasSessionValue($name)
+ public function hasSessionValue(string $name): bool
{
return \array_key_exists($name, $this->dummySession);
}
@@ -115,7 +115,7 @@ public function hasSessionValue($name)
*
* @since 4.1.5
*/
- public function getSessionValue($name)
+ public function getSessionValue(string $name): mixed
{
return $this->dummySession[$name] ?? null;
}
@@ -127,8 +127,13 @@ public function getSessionValue($name)
*
* @since 4.1.5
*/
- public function deleteSessionValue($name)
+ public function deleteSessionValue(string $name): void
{
unset($this->dummySession[$name]);
}
+
+ public function output(string $content): void
+ {
+ echo $content;
+ }
}
diff --git a/plugins/system/debug/src/Storage/FileStorage.php b/plugins/system/debug/src/Storage/FileStorage.php
index 1532ecdcd243e..fc2d5259ece44 100644
--- a/plugins/system/debug/src/Storage/FileStorage.php
+++ b/plugins/system/debug/src/Storage/FileStorage.php
@@ -11,6 +11,7 @@
namespace Joomla\Plugin\System\Debug\Storage;
use Joomla\CMS\Factory;
+use Joomla\CMS\Log\Log;
use Joomla\CMS\User\UserFactoryInterface;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
@@ -26,25 +27,62 @@
*/
class FileStorage extends \DebugBar\Storage\FileStorage
{
+ private const FILE_PREFIX = '#(^-^)#';
+
+ /**
+ * Initializes the storage and secures request files created by older versions.
+ *
+ * @param string $dirname The directory where request data is stored.
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $dirname)
+ {
+ parent::__construct($dirname);
+
+ $this->secureLegacyFiles();
+ }
+
/**
* Saves collected data
*
* @param string $id The log id
- * @param string $data The log data
+ * @param array $data The log data
*
* @return void
*
* @since 4.0.0
*/
- public function save($id, $data)
+ public function save(string $id, array $data): void
{
if (!file_exists($this->dirname)) {
Folder::create($this->dirname);
}
- $dataStr = '#(^-^)#' . json_encode($data);
+ if (!is_dir($this->dirname) || !is_writable($this->dirname)) {
+ Log::add(
+ \sprintf('Unable to write DebugBar request history; directory is not writable: %s', $this->dirname),
+ Log::WARNING,
+ 'plg_system_debug'
+ );
+
+ return;
+ }
+
+ $dataStr = self::FILE_PREFIX . json_encode($data, JSON_THROW_ON_ERROR);
+ $fileName = $this->makeFilename($id);
+
+ if (!File::write($fileName, $dataStr)) {
+ Log::add(
+ \sprintf('Unable to write DebugBar request history file: %s', $fileName),
+ Log::WARNING,
+ 'plg_system_debug'
+ );
- File::write($this->makeFilename($id), $dataStr);
+ return;
+ }
+
+ $this->autoPrune();
}
/**
@@ -56,12 +94,31 @@ public function save($id, $data)
*
* @since 4.0.0
*/
- public function get($id)
+ public function get(string $id): array
{
- $dataStr = file_get_contents($this->makeFilename($id));
- $dataStr = str_replace('#(^-^)#', '', $dataStr);
+ $fileName = $this->makeFilename($id);
- return json_decode($dataStr, true) ?: [];
+ if (!file_exists($fileName)) {
+ return [];
+ }
+
+ $dataStr = file_get_contents($fileName);
+
+ if ($dataStr === false) {
+ throw new \RuntimeException("Unable to read DebugBar data file: $fileName");
+ }
+
+ if (!str_starts_with($dataStr, self::FILE_PREFIX)) {
+ throw new \UnexpectedValueException("Invalid DebugBar data file: $fileName");
+ }
+
+ $data = json_decode(substr($dataStr, \strlen(self::FILE_PREFIX)), true, 512, JSON_THROW_ON_ERROR);
+
+ if (!\is_array($data)) {
+ throw new \UnexpectedValueException("Invalid DebugBar data in file: $fileName");
+ }
+
+ return $data;
}
/**
@@ -75,52 +132,54 @@ public function get($id)
*
* @since 4.0.0
*/
- public function find(array $filters = [], $max = 20, $offset = 0)
+ public function find(array $filters = [], int $max = 20, int $offset = 0): array
{
- // Loop through all .php files and remember the modified time and id.
- $files = [];
-
- foreach (new \DirectoryIterator($this->dirname) as $file) {
- if ($file->getExtension() == 'php') {
- $files[] = [
- 'time' => $file->getMTime(),
- 'id' => $file->getBasename('.php'),
- ];
- }
+ if (!is_dir($this->dirname)) {
+ return [];
}
- // Sort the files, newest first
- usort(
- $files,
- function ($a, $b) {
- if ($a['time'] === $b['time']) {
- return 0;
- }
+ $ids = [];
- return $a['time'] < $b['time'] ? 1 : -1;
+ foreach (new \FilesystemIterator($this->dirname, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME) as $path) {
+ if (\is_string($path) && str_ends_with($path, '.php')) {
+ $ids[] = basename($path, '.php');
}
- );
+ }
+
+ sort($ids, SORT_STRING);
+ $ids = array_reverse($ids);
+
+ $filterUtime = null;
+
+ if ($filters['utime'] ?? false) {
+ $filterUtime = (float) $filters['utime'];
+ unset($filters['utime']);
+ }
// Load the metadata and filter the results.
$results = [];
$i = 0;
- foreach ($files as $file) {
+ foreach ($ids as $id) {
// When filter is empty, skip loading the offset
- if ($i++ < $offset && empty($filters)) {
+ if ($i++ < $offset && !$filters) {
$results[] = null;
continue;
}
- $data = $this->get($file['id']);
+ $data = $this->get($id);
- if (!$this->isSecureToReturnData($data)) {
+ if (!isset($data['__meta']) || !$this->isSecureToReturnData($data)) {
continue;
}
$meta = $data['__meta'];
unset($data);
+ if ($filterUtime !== null && $meta['utime'] <= $filterUtime) {
+ break;
+ }
+
if ($this->filter($meta, $filters)) {
$results[] = $meta;
}
@@ -142,11 +201,98 @@ function ($a, $b) {
*
* @since 4.0.0
*/
- public function makeFilename($id)
+ public function makeFilename(string $id): string
{
return $this->dirname . basename($id) . '.php';
}
+ /**
+ * Remove stored request data.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function clear(): void
+ {
+ if (!is_dir($this->dirname)) {
+ return;
+ }
+
+ foreach (new \FilesystemIterator($this->dirname, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS) as $path) {
+ if (\is_string($path) && is_file($path) && str_ends_with($path, '.php')) {
+ unlink($path);
+ }
+ }
+ }
+
+ /**
+ * Remove request data older than the given number of hours.
+ *
+ * @param int $hours The maximum age of stored requests.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function prune(int $hours = 24): void
+ {
+ if (!is_dir($this->dirname)) {
+ return;
+ }
+
+ $cutoffTime = time() - $hours * 3600;
+
+ foreach (new \FilesystemIterator($this->dirname, \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS) as $file) {
+ if ($file->isFile() && str_ends_with($file->getFilename(), '.php') && $file->getMTime() < $cutoffTime) {
+ unlink($file->getPathname());
+ }
+ }
+ }
+
+ /**
+ * Migrate unprotected JSON files from previous versions to PHP-guarded files.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function secureLegacyFiles(): void
+ {
+ if (!is_dir($this->dirname)) {
+ return;
+ }
+
+ foreach (new \FilesystemIterator($this->dirname, \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS) as $file) {
+ if (!$file->isFile() || !str_ends_with($file->getFilename(), '.json')) {
+ continue;
+ }
+
+ $legacyFile = $file->getPathname();
+ $data = file_get_contents($legacyFile);
+
+ if ($data === false) {
+ throw new \RuntimeException("Unable to read legacy DebugBar data file: $legacyFile");
+ }
+
+ $secureFile = $this->makeFilename(basename($file->getFilename(), '.json'));
+
+ if (file_exists($secureFile)) {
+ $secureData = file_get_contents($secureFile);
+
+ if ($secureData === false || !str_starts_with($secureData, self::FILE_PREFIX)) {
+ throw new \UnexpectedValueException("Invalid secured DebugBar data file: $secureFile");
+ }
+ } elseif (!File::write($secureFile, self::FILE_PREFIX . $data)) {
+ throw new \RuntimeException("Unable to secure legacy DebugBar data file: $legacyFile");
+ }
+
+ if (!unlink($legacyFile)) {
+ throw new \RuntimeException("Unable to remove unprotected DebugBar data file: $legacyFile");
+ }
+ }
+ }
+
/**
* Check if the user is allowed to view the request. Users can only see their own requests.
*