diff --git a/administrator/language/en-GB/en-GB.plg_system_debug.ini b/administrator/language/en-GB/en-GB.plg_system_debug.ini
index 1ededccde7b7c..d287b3e8cb955 100644
--- a/administrator/language/en-GB/en-GB.plg_system_debug.ini
+++ b/administrator/language/en-GB/en-GB.plg_system_debug.ini
@@ -11,7 +11,6 @@ PLG_DEBUG_CALL_STACK_SAME_FILE="Same as call in the line below."
PLG_DEBUG_ERRORS="Errors"
PLG_DEBUG_EXPLAIN="Explain"
PLG_DEBUG_FIELD_ALLOWED_GROUPS_LABEL="Allowed Groups"
-PLG_DEBUG_FIELD_EXECUTEDSQL_LABEL="Log Executed Queries"
PLG_DEBUG_FIELD_LANGUAGE_ERRORFILES_LABEL="Errors When Parsing Language Files"
PLG_DEBUG_FIELD_LANGUAGE_FILES_LABEL="Language Files"
PLG_DEBUG_FIELD_LANGUAGE_STRING_LABEL="Language String"
@@ -21,6 +20,7 @@ PLG_DEBUG_FIELD_LOG_CATEGORIES_LABEL="Log Categories"
PLG_DEBUG_FIELD_LOG_CATEGORY_MODE_EXCLUDE="Exclude"
PLG_DEBUG_FIELD_LOG_CATEGORY_MODE_INCLUDE="Include"
PLG_DEBUG_FIELD_LOG_CATEGORY_MODE_LABEL="Log Category Mode"
+PLG_DEBUG_FIELD_LOG_DEPRECATED_CORE_LABEL="Log Deprecated Core API"
PLG_DEBUG_FIELD_LOG_DEPRECATED_LABEL="Log Deprecated API"
PLG_DEBUG_FIELD_LOG_EVERYTHING_LABEL="Log Almost Everything"
PLG_DEBUG_FIELD_LOG_PRIORITIES_ALERT="Alert"
@@ -36,9 +36,12 @@ PLG_DEBUG_FIELD_LOG_PRIORITIES_WARNING="Warning"
PLG_DEBUG_FIELD_MEMORY_LABEL="Memory Usage"
PLG_DEBUG_FIELD_PROFILING_LABEL="Profiling"
PLG_DEBUG_FIELD_QUERIES_LABEL="Queries"
-PLG_DEBUG_FIELD_QUERY_TYPES_LABEL="Query Types"
+PLG_DEBUG_FIELD_QUERY_EXPLAINS_LABEL="Query Explains"
+PLG_DEBUG_FIELD_QUERY_PROFILES_LABEL="Query Profiles"
+PLG_DEBUG_FIELD_QUERY_TRACES_LABEL="Query Traces"
PLG_DEBUG_FIELD_REFRESH_ASSETS_DESC="If enabled will, on each page reload, add a different hash to every script/stylesheet file with auto version so that they never use the browser cache."
PLG_DEBUG_FIELD_REFRESH_ASSETS_LABEL="Refresh Assets"
+PLG_DEBUG_FIELD_REQUEST_LABEL="Request"
PLG_DEBUG_FIELD_SESSION_LABEL="Session Data"
PLG_DEBUG_FIELD_STRIP_FIRST_LABEL="Strip First Word"
PLG_DEBUG_FIELD_STRIP_PREFIX_DESC="Strip words from the beginning of the string. For multiple words, use the format: (word1|word2)."
diff --git a/build/build-modules-js/update.js b/build/build-modules-js/update.js
index f22b93f6062fc..764e265204812 100644
--- a/build/build-modules-js/update.js
+++ b/build/build-modules-js/update.js
@@ -21,6 +21,10 @@ const cleanVendors = () => {
fsExtra.copySync(Path.join(rootPath, 'build/media/vendor/tinymce/langs'), Path.join(rootPath, 'media/vendor/tinymce/langs'));
fsExtra.copySync(Path.join(rootPath, 'build/media/vendor/tinymce/templates'), Path.join(rootPath, 'media/vendor/tinymce/templates'));
fsExtra.copySync(Path.join(rootPath, 'build/media/vendor/jquery-ui'), Path.join(rootPath, 'media/vendor/jquery-ui'));
+
+ // And here some assets from a PHP package
+ // @todo Move it the 'right way' (tm)
+ fsExtra.copySync(Path.join(rootPath, 'libraries/vendor/maximebf/debugbar/src/DebugBar/Resources'), Path.join(rootPath, 'media/vendor/debugbar'));
};
// Copies all the files from a directory
diff --git a/build/media/plg_system_debug/css/debug.css b/build/media/plg_system_debug/css/debug.css
index 073ceb2b0947a..9d1848062a2bd 100644
--- a/build/media/plg_system_debug/css/debug.css
+++ b/build/media/plg_system_debug/css/debug.css
@@ -3,210 +3,12 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-/* Common CSS for system debug */
-div#system-debug {
- clear: both;
+/* Debug Bar */
+.phpdebugbar-badge {
+ color: white !important;
+ background-color: rgb(40,70,106) !important;
}
-#system-debug {
- background-color: #fff;
- color: #000;
- border: 1px dashed silver;
- padding: 10px;
+div.phpdebugbar-header, a.phpdebugbar-restore-btn {
+ background: #efefef url("data:image/svg+xml;utf8,") no-repeat 5px 4px / 20px 20px !important;
}
-
-#system-debug div.dbg-header {
- background-color: #ddd;
- border: 1px solid #eee;
- font-size: 16px;
-}
-
-#system-debug h3 {
- margin: 0;
-}
-
-#system-debug a h3 {
- background-color: #ddd;
- color: #000;
- font-size: 14px;
- padding: 5px;
- text-decoration: none;
- margin: 0px;
-}
-
-#system-debug .dbg-error a h3 {
- background-color: red;
-}
-
-#system-debug a:hover h3,
-#system-debug a:focus h3 {
- background-color: #4d4d4d;
- color: #ddd;
- font-size: 14px;
- cursor: pointer;
- text-decoration: none;
-}
-
-#system-debug div.dbg-container {
- display: none;
- padding: 10px;
-}
-
-#system-debug span.dbg-command {
- color: blue;
- font-weight: bold;
-}
-
-#system-debug span.dbg-table {
- color: green;
- font-weight: bold;
-}
-
-#system-debug b.dbg-operator {
- color: red;
- font-weight: bold;
-}
-
-#system-debug h1 {
- background-color: #2c2c2c;
- color: #fff;
- padding: 10px;
- margin: 0;
- font-size: 16px;
- line-height: 1em;
-}
-
-#system-debug h4 {
- font-size: 14px;
- font-weight: bold;
- margin: 5px 0 0 0;
-}
-
-#system-debug h5 {
- font-size: 13px;
- font-weight: bold;
- margin: 5px 0 0 0;
-}
-
-div#system-debug {
- margin: 5px;
-}
-
-#system-debug ol {
- margin-left: 25px;
- margin-right: 25px;
- text-align: left;
- direction: ltr;
-}
-
-#system-debug ul {
- list-style: none;
- text-align: left;
- direction: ltr;
-}
-
-#system-debug li {
- font-size: 13px;
- margin-bottom: 10px;
-}
-
-#system-debug code {
- font-size: 13px;
- text-align: left;
- direction: ltr;
-}
-
-#system-debug p {
- font-size: 13px;
-}
-
-#system-debug div.dbg-header.dbg-error {
- background-color: red;
-}
-#system-debug .dbg-warning {
- color: red;
- font-weight: bold;
- background-color: #ffffcc !important;
-}
-
-#system-debug .accordion {
- margin-bottom: 0;
-}
-#system-debug .dbg-noprofile {
- text-decoration: line-through;
-}
-
-/* dbg-bars */
-#system-debug .alert,
-#system-debug .dbg-bars {
- margin-bottom: 10px;
-}
-#system-debug .dbg-bar-spacer {
- float: left;
- height: 100%;
-}
-/* dbg-bars-query */
-#system-debug .dbg-bars-query .dbg-bar {
- opacity: 0.3;
- height: 12px;
- margin-top: 3px;
-}
-#system-debug .dbg-bars-query:hover .dbg-bar {
- opacity: 0.6;
- height: 18px;
- margin-top: 0;
-}
-#system-debug .dbg-bars-query .dbg-bar:hover,
-#system-debug .dbg-bars-query .dbg-bar-active,
-#system-debug .dbg-bars-query:hover .dbg-bar-active {
- opacity: 1;
- height: 18px;
- margin-top: 0;
-}
-
-/* dbg-query-table */
-#system-debug table.dbg-query-table {
- margin: 0px 0px 6px;
-}
-#system-debug table.dbg-query-table th,
-#system-debug table.dbg-query-table td {
- padding: 3px 8px;
-}
-
-#system-debug .dbg-profile-list .label {
- display: inline-block;
- min-width: 60px;
- text-align: right;
-}
-
-#system-debug .dbg-query-memory,
-#system-debug .dbg-query-rowsnumber
-{
- margin-left: 50px;
-}
-#dbg_container_session pre
-{
- background: white;
- border: 0;
- margin: 0;
- padding: 0;
- overflow-wrap: break-word;
- white-space: pre-wrap;
- word-break: break-word;
-}
-#dbg_container_session pre .blue
-{
- color:blue;
-}
-#dbg_container_session pre .green
-{
- color:green;
-}
-#dbg_container_session pre .black
-{
- color:black;
-}
-#dbg_container_session pre .grey
-{
- color:grey;
-}
\ No newline at end of file
diff --git a/build/media/plg_system_debug/widgets/info/widget.css b/build/media/plg_system_debug/widgets/info/widget.css
new file mode 100644
index 0000000000000..dde61d6a012e2
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/info/widget.css
@@ -0,0 +1,32 @@
+table.phpdebugbar-widgets-info {
+ margin-left: 5px;
+ margin-top: 5px;
+ width: 75%;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ line-height: 1.3em;
+}
+
+table.phpdebugbar-widgets-info td {
+ border-bottom: 1px solid silver;
+ padding-right: 10px;
+}
+
+table.phpdebugbar-widgets-info dt {
+ float: left;
+ width: 30%;
+ text-align: left;
+ padding: .25em;
+ clear: left;
+}
+
+table.phpdebugbar-widgets-info dd {
+ float: left;
+ width: 60%;
+ padding: .25em 0;
+}
+
+table.phpdebugbar-widgets-info dl:after {
+ content: "";
+ display: table;
+ clear: both;
+}
diff --git a/build/media/plg_system_debug/widgets/info/widget.js b/build/media/plg_system_debug/widgets/info/widget.js
new file mode 100644
index 0000000000000..e8c16627de6be
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/info/widget.js
@@ -0,0 +1,67 @@
+(function ($) {
+
+ var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
+ var InfoWidget = PhpDebugBar.Widgets.InfoWidget = PhpDebugBar.Widget.extend({
+
+ tagName: 'table',
+
+ className: csscls('info'),
+
+ render: function () {
+ this.bindAttr('data', function (data) {
+ this.$el.empty()
+ var tr
+
+ /*
+ // @todo enable Info link
+ var link = $('')
+ .text('Info')
+ .attr('href', 'index.php?option=com_content&view=debug&id=' + data.requestId)
+ .attr('target', '_blank');
+
+ tr = $('
')
+ .append($(' | ').text('Info'))
+ .append($(' | ').append(link));
+ this.$el.append(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)
+ })
+ }
+ })
+})(PhpDebugBar.$)
diff --git a/build/media/plg_system_debug/widgets/languageErrors/widget.css b/build/media/plg_system_debug/widgets/languageErrors/widget.css
new file mode 100644
index 0000000000000..2be07985cad15
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/languageErrors/widget.css
@@ -0,0 +1,6 @@
+ul.phpdebugbar-widgets-languageErrors {
+ margin-left: 5px;
+ margin-top: 5px;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ line-height: 1.3em;
+}
diff --git a/build/media/plg_system_debug/widgets/languageErrors/widget.js b/build/media/plg_system_debug/widgets/languageErrors/widget.js
new file mode 100644
index 0000000000000..1d59626945681
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/languageErrors/widget.js
@@ -0,0 +1,35 @@
+(function ($) {
+
+ var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
+ var languageErrorsWidget = PhpDebugBar.Widgets.languageErrorsWidget = PhpDebugBar.Widget.extend({
+
+ tagName: 'ul',
+
+ className: csscls('languageErrors'),
+
+ render: function () {
+ this.bindAttr('data', function (data) {
+ this.$el.empty()
+
+ 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)
+ } else {
+ li.text(relPath)
+ }
+ this.$el.append(li)
+ }
+ })
+ }
+ })
+})(PhpDebugBar.$)
diff --git a/build/media/plg_system_debug/widgets/languageFiles/widget.css b/build/media/plg_system_debug/widgets/languageFiles/widget.css
new file mode 100644
index 0000000000000..7b9d09f909c2c
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/languageFiles/widget.css
@@ -0,0 +1,15 @@
+table.phpdebugbar-widgets-languageFiles {
+ margin-left: 5px;
+ margin-top: 5px;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ line-height: 1.3em;
+}
+
+table.phpdebugbar-widgets-languageFiles tr {
+ border-bottom: 1px solid silver;
+}
+
+table.phpdebugbar-widgets-languageFiles th {
+ font-weight: bold;
+ padding: 5px;
+}
diff --git a/build/media/plg_system_debug/widgets/languageFiles/widget.js b/build/media/plg_system_debug/widgets/languageFiles/widget.js
new file mode 100644
index 0000000000000..43496f58f90e4
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/languageFiles/widget.js
@@ -0,0 +1,49 @@
+(function ($) {
+
+ var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
+ var languageFilesWidget = 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)
+ if (data.xdebugLink) {
+ var link = $('')
+ .text(relPath)
+ .attr(
+ 'href',
+ data.xdebugLink
+ .replace('%f', file)
+ .replace('%l', '1')
+ )
+ li.append(link)
+ } else {
+ li.text(relPath)
+ }
+
+ li.appendTo(ul)
+ }
+ var tr = $('
')
+ .append($(' | ').text(extension))
+ .append($(' | ').append(ul))
+ this.$el.append(tr)
+ }
+ })
+ }
+ })
+})(PhpDebugBar.$)
diff --git a/build/media/plg_system_debug/widgets/languageStrings/widget.css b/build/media/plg_system_debug/widgets/languageStrings/widget.css
new file mode 100644
index 0000000000000..f7c26d80b11d7
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/languageStrings/widget.css
@@ -0,0 +1,58 @@
+table.phpdebugbar-widgets-languageStrings {
+ margin-left: 5px;
+ margin-top: 5px;
+ width: 100%;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ line-height: 1.3em;
+}
+
+table.phpdebugbar-widgets-languageStrings th {
+ border-bottom: 1px solid black;
+}
+
+table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack {
+ display: none;
+ width: 100%;
+ margin: 10px;
+ border: 1px solid #ddd;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ border-collapse: collapse;
+}
+
+table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack tr.caller {
+ background-color: #fff9b6;
+}
+
+table.phpdebugbar-widgets-languageStrings table.phpdebugbar-widgets-callstack tbody tr:hover {
+ background-color: #eee;
+}
+
+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;
+}
+
+table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash {
+ color: #000;
+ background-color: #eee;
+}
+
+table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye:before,
+table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash:before {
+ font-family: PhpDebugbarFontAwesome;
+ margin-right: 4px;
+ /*font-size: 12px;*/
+}
+
+table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye:before {
+ content: "\f06e"
+}
+
+table.phpdebugbar-widgets-languageStrings span.phpdebugbar-widgets-eye-dash:before {
+ content: "\f070"
+}
diff --git a/build/media/plg_system_debug/widgets/languageStrings/widget.js b/build/media/plg_system_debug/widgets/languageStrings/widget.js
new file mode 100644
index 0000000000000..52e9103bdb6c8
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/languageStrings/widget.js
@@ -0,0 +1,87 @@
+(function ($) {
+
+ var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-')
+ var languageStringsWidget = 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)
+ } else {
+ li.text(relPath)
+ }
+
+ 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'))
+ } else {
+ table.show()
+ btn.addClass(csscls('eye-dash'))
+ btn.removeClass(csscls('eye'))
+ }
+ })
+ .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 + ' |
')
+ }
+ }
+
+ li.appendTo(ul)
+ }
+
+ this.$el.append(tr)
+ }
+ })
+ }
+ })
+})(PhpDebugBar.$)
diff --git a/build/media/plg_system_debug/widgets/sqlqueries/widget.css b/build/media/plg_system_debug/widgets/sqlqueries/widget.css
new file mode 100644
index 0000000000000..f920a1a6d70f9
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/sqlqueries/widget.css
@@ -0,0 +1,187 @@
+div.phpdebugbar-widgets-sqlqueries .phpdebugbar-widgets-status {
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ padding: 6px 6px;
+ border-bottom: 1px solid #ddd;
+ font-weight: bold;
+ color: #555;
+ background: #fafafa;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-error {
+ color: red;
+}
+
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-database,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye-dash,
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id {
+ float: right;
+ margin-left: 8px;
+ color: #888;
+}
+
+div.phpdebugbar-widgets-sqlqueries a.phpdebugbar-widgets-editor-link {
+ float: right;
+ 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 {
+ font-family: PhpDebugbarFontAwesome;
+ margin-right: 4px;
+ font-size: 12px;
+}
+
+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 {
+ content: "\f08e";
+ margin-left: 4px;
+}
+
+div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-eye-dash {
+ background-color: #eee;
+ padding: 2px;
+
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params {
+ display: none;
+ width: 70%;
+ margin: 10px;
+ border: 1px solid #ddd;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ border-collapse: collapse;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td {
+ border: 1px solid #ddd;
+ text-align: center;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params .phpdebugbar-widgets-name {
+ width: 20%;
+ font-weight: bold;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-callstack {
+ display: none;
+ width: 100%;
+ margin: 10px;
+ border: 1px solid #ddd;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ border-collapse: collapse;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-callstack tr.caller {
+ background-color: #fff9b6;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-callstack tbody tr:hover {
+ background-color: #eee;
+}
+
+div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-callstack th {
+ font-weight: bold;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item {
+ border-bottom: 1px solid blue;
+ padding: 10px 6px;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-error {
+ display: block;
+ font-weight: bold;
+}
+
+code.phpdebugbar-widgets-sql {
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate {
+ background-color: #edeff0;
+}
+
+div.phpdebugbar-widgets-sqlqueries li.phpdebugbar-widgets-list-item.phpdebugbar-widgets-sql-duplicate:hover {
+ background-color: #ffc;
+}
+
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-toolbar {
+ display: none;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ background: #fff;
+ z-index: 1;
+}
+
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter {
+ float: right;
+ font-size: 12px;
+ padding: 2px 4px;
+ background: #7cacd5;
+ margin: 0 2px;
+ border-radius: 4px;
+ color: #fff;
+ text-decoration: none;
+}
+
+div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded {
+ background: #eee;
+ color: #888;
+}
diff --git a/build/media/plg_system_debug/widgets/sqlqueries/widget.js b/build/media/plg_system_debug/widgets/sqlqueries/widget.js
new file mode 100644
index 0000000000000..f0af1cfdf75db
--- /dev/null
+++ b/build/media/plg_system_debug/widgets/sqlqueries/widget.js
@@ -0,0 +1,305 @@
+(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'))
+
+ var excludedLabels = []
+ this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function () {
+ excludedLabels.push(this.rel)
+ })
+
+ this.$list.$el.find('li[dupeindex=' + $(el).attr('rel') + ']').toggle()
+
+ 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')
+ }
+ }
+ 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))
+ }
+ if (stmt.params && !$.isEmptyObject(stmt.params)) {
+ var table = $('').addClass(csscls('params')).appendTo(li)
+ for (var key in stmt.params) {
+ if (typeof stmt.params[key] !== 'function') {
+ table.append('| ' + key + ' | ' + stmt.params[key] + ' |
')
+ }
+ }
+ li.css('cursor', 'pointer').click(function () {
+ if (table.is(':visible')) {
+ table.hide()
+ } else {
+ table.show()
+ }
+ })
+ }
+
+ var tableExplain
+
+ if (stmt.explain && !$.isEmptyObject(stmt.explain)) {
+ var btnExplain = $('')
+ .text('Explain')
+ .addClass(csscls('eye'))
+ .css('cursor', 'pointer')
+ .on('click', function () {
+ if (tableExplain.is(':visible')) {
+ tableExplain.hide()
+ btnExplain.addClass(csscls('eye'))
+ btnExplain.removeClass(csscls('eye-dash'))
+ } else {
+ tableExplain.show()
+ btnExplain.addClass(csscls('eye-dash'))
+ btnExplain.removeClass(csscls('eye'))
+ }
+ })
+ .appendTo(li)
+
+ tableExplain = $(''
+ + '| Explain |
'
+ + '| Id | Select Type | Table | Type | Possible Keys | Key | Key Len | Ref | Rows | Extra |
'
+ + '
').addClass(csscls('callstack'))
+ }
+
+ var tableStack
+
+ if (stmt.callstack && !$.isEmptyObject(stmt.callstack)) {
+ var btnStack = $('')
+ .text('Stack')
+ .addClass(csscls('eye'))
+ .css('cursor', 'pointer')
+ .on('click', function () {
+ if (tableStack.is(':visible')) {
+ tableStack.hide()
+ btnStack.addClass(csscls('eye'))
+ btnStack.removeClass(csscls('eye-dash'))
+ } else {
+ tableStack.show()
+ btnStack.addClass(csscls('eye-dash'))
+ btnStack.removeClass(csscls('eye'))
+ }
+ })
+ .appendTo(li)
+
+ tableStack = $('').addClass(csscls('callstack'))
+ }
+
+ 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)
+
+ if (tableStack) {
+ tableStack.appendTo(li)
+ for (var i in stmt.callstack) {
+ var entry = stmt.callstack[i]
+ var location = entry[3] ? entry[3].replace(self.root_path, '') + ':' + entry[4] : ''
+ var caller = entry[2].replace(self.root_path, '')
+ var cssClass = entry[1] ? 'caller' : ''
+ if (location && self.xdebug_link) {
+ location = '' + location + ''
+ }
+ tableStack.append('| ' + entry[0] + ' | ' + caller + ' | ' + location + ' |
')
+ }
+ }
+
+ if (tableExplain) {
+ tableExplain.appendTo(li)
+ for (i in stmt.explain) {
+ var entry = stmt.explain[i]
+ tableExplain.append(''
+ + '| ' + entry.id + ' | ' + entry.select_type + ' | ' + entry.table + ' | '
+ + '' + entry.type + ' | ' + entry.possible_keys + ' | ' + entry.key + ' | '
+ + '' + entry.key_len + ' | ' + entry.ref + ' | ' + entry.rows + ' | '
+ + '' + entry.Extra + ' | '
+ + '
')
+ }
+ }
+
+ 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')
+ }
+
+ 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))
+ }
+ })
+ }
+
+ })
+
+})(PhpDebugBar.$)
diff --git a/composer.json b/composer.json
index 876b9794ce653..98f14ee0d064e 100644
--- a/composer.json
+++ b/composer.json
@@ -82,7 +82,8 @@
"symfony/options-resolver": "3.4.*",
"symfony/web-link": "3.4.*",
"symfony/yaml": "3.4.*",
- "wamania/php-stemmer": "^1.2"
+ "wamania/php-stemmer": "^1.2",
+ "maximebf/debugbar": "^1.15"
},
"require-dev": {
"phpunit/phpunit": "~6.0",
diff --git a/composer.lock b/composer.lock
index c4e3364e94137..c531182753780 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": "de4a77a0cf107bcffd0958ba86c4681f",
+ "content-hash": "deaacc2328e7698c3006c275a10799c9",
"packages": [
{
"name": "composer/ca-bundle",
@@ -1489,6 +1489,67 @@
],
"time": "2018-07-14T21:48:30+00:00"
},
+ {
+ "name": "maximebf/debugbar",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maximebf/php-debugbar.git",
+ "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e7d60937ee5f1320975ca9bc7bcdd44d500f07",
+ "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "psr/log": "^1.0",
+ "symfony/var-dumper": "^2.6|^3.0|^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0|^5.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.14-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"
+ ],
+ "time": "2017-12-15T11:13:46+00:00"
+ },
{
"name": "mso/idna-convert",
"version": "v1.1.0",
@@ -2390,6 +2451,75 @@
],
"time": "2018-08-06T14:22:27+00:00"
},
+ {
+ "name": "symfony/var-dumper",
+ "version": "v3.4.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "f62a394bd3de96f2f5e8f4c7d685035897fb3cb3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f62a394bd3de96f2f5e8f4c7d685035897fb3cb3",
+ "reference": "f62a394bd3de96f2f5e8f4c7d685035897fb3cb3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
+ },
+ "require-dev": {
+ "ext-iconv": "*",
+ "twig/twig": "~1.34|~2.4"
+ },
+ "suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+ "ext-intl": "To show region name in time zone dump",
+ "ext-symfony_debug": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony mechanism for exploring and dumping PHP variables",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "time": "2018-07-26T11:19:56+00:00"
+ },
{
"name": "symfony/web-link",
"version": "v3.4.14",
diff --git a/libraries/src/Language/Language.php b/libraries/src/Language/Language.php
index 7bd3cc7fb8194..e49186c567ccf 100644
--- a/libraries/src/Language/Language.php
+++ b/libraries/src/Language/Language.php
@@ -352,15 +352,17 @@ public function _($string, $jsSafe = false, $interpretBackSlashes = true)
{
if ($this->debug)
{
- $caller = $this->getCallerInfo();
- $caller['string'] = $string;
+ $info = [];
+ $info['trace'] = $this->getTrace();
+ $info['key'] = $key;
+ $info['string'] = $string;
if (!array_key_exists($key, $this->orphans))
{
$this->orphans[$key] = array();
}
- $this->orphans[$key][] = $caller;
+ $this->orphans[$key][] = $info;
$string = '??' . $string . '??';
}
@@ -987,6 +989,18 @@ public function get($property, $default = null)
return $default;
}
+ /**
+ * Get a back trace.
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function getTrace()
+ {
+ return \function_exists('debug_backtrace') ? debug_backtrace() : [];
+ }
+
/**
* Determine who called Language or JText.
*
diff --git a/plugins/system/debug/AbstractDataCollector.php b/plugins/system/debug/AbstractDataCollector.php
new file mode 100644
index 0000000000000..60f6460bf25be
--- /dev/null
+++ b/plugins/system/debug/AbstractDataCollector.php
@@ -0,0 +1,111 @@
+params = $params;
+ }
+
+ /**
+ * Get a data formatter.
+ *
+ * @since __DEPLOY_VERSION__
+ * @return DataFormatter
+ */
+ public function getDataFormatter(): DataFormatter
+ {
+ if ($this->dataFormater === null)
+ {
+ $this->dataFormater = self::getDefaultDataFormatter();
+ }
+
+ return $this->dataFormater;
+ }
+
+ /**
+ * Returns the default data formater
+ *
+ * @since __DEPLOY_VERSION__
+ * @return DataFormatter
+ */
+ public static function getDefaultDataFormatter(): DataFormatter
+ {
+ if (self::$defaultDataFormatter === null)
+ {
+ self::$defaultDataFormatter = new DataFormatter;
+ }
+
+ return self::$defaultDataFormatter;
+ }
+
+ /**
+ * Strip the Joomla! root path.
+ *
+ * @param string $path The path.
+ *
+ * @return string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function formatPath($path): string
+ {
+ return $this->getDataFormatter()->formatPath($path);
+ }
+
+
+ /**
+ * Format a string from back trace.
+ *
+ * @param array $call The array to format
+ *
+ * @return string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function formatCallerInfo(array $call): string
+ {
+ return $this->getDataFormatter()->formatCallerInfo($call);
+ }
+}
diff --git a/plugins/system/debug/DataCollector/InfoCollector.php b/plugins/system/debug/DataCollector/InfoCollector.php
new file mode 100644
index 0000000000000..404d67b12cae2
--- /dev/null
+++ b/plugins/system/debug/DataCollector/InfoCollector.php
@@ -0,0 +1,213 @@
+requestId = $requestId;
+
+ parent::__construct($params);
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getWidgets(): array
+ {
+ return [
+ 'info' => [
+ 'icon' => 'info-circle',
+ 'title' => 'J! Info',
+ 'widget' => 'PhpDebugBar.Widgets.InfoWidget',
+ 'map' => $this->name,
+ 'default' => '{}',
+ ]
+ ];
+ }
+
+ /**
+ * Returns an array with the following keys:
+ * - base_path
+ * - base_url
+ * - css: an array of filenames
+ * - js: an array of filenames
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getAssets(): array
+ {
+ return array(
+ 'js' => \JUri::root(true) . '/media/plg_system_debug/widgets/info/widget.min.js',
+ 'css' => \JUri::root(true) . '/media/plg_system_debug/widgets/info/widget.min.css',
+ );
+ }
+
+ /**
+ * Called by the DebugBar when data needs to be collected
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array Collected data
+ */
+ public function collect(): array
+ {
+ /* @type SiteApplication|AdministratorApplication $application */
+ $application = Factory::getApplication();
+
+ // @todo autoloadability ??
+ \JLoader::register(SysInfoModel::class, JPATH_ADMINISTRATOR . '/components/com_admin/Model/SysinfoModel.php');
+
+ $model = new SysInfoModel;
+
+ return [
+ 'phpVersion' => PHP_VERSION,
+ 'joomlaVersion' => JVERSION,
+ 'requestId' => $this->requestId,
+ 'identity' => $this->getIdentityInfo($application->getIdentity()),
+ 'response' => $this->getResponseInfo($application->getResponse()),
+ 'template' => $this->getTemplateInfo($application->getTemplate(true)),
+ 'database' => $this->getDatabaseInfo($model->getInfo()),
+ ];
+ }
+
+ /**
+ * Get Identity info.
+ *
+ * @param User $identity The identity.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ private function getIdentityInfo(User $identity): array
+ {
+ if (!$identity->id)
+ {
+ return ['type' => 'guest'];
+ }
+
+ return [
+ 'type' => 'user',
+ 'id' => $identity->id,
+ 'name' => $identity->name,
+ 'username' => $identity->username,
+ ];
+ }
+
+ /**
+ * Get response info.
+ *
+ * @param ResponseInterface $response The response.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ private function getResponseInfo(ResponseInterface $response): array
+ {
+ return [
+ 'status_code' => $response->getStatusCode()
+ ];
+ }
+
+ /**
+ * Get template info.
+ *
+ * @param object $template The template.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ private function getTemplateInfo($template): array
+ {
+ return [
+ 'template' => $template->template ?? '',
+ 'home' => $template->home ?? '',
+ 'id' => $template->id ?? '',
+ ];
+ }
+
+ /**
+ * Get database info.
+ *
+ * @param array $info General information.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ private function getDatabaseInfo(array $info): array
+ {
+ return [
+ 'dbserver' => $info['dbserver'] ?? '',
+ 'dbversion' => $info['dbversion'] ?? '',
+ 'dbcollation' => $info['dbcollation'] ?? '',
+ 'dbconnectioncollation' => $info['dbconnectioncollation'] ?? '',
+ ];
+ }
+}
diff --git a/plugins/system/debug/DataCollector/LanguageErrorsCollector.php b/plugins/system/debug/DataCollector/LanguageErrorsCollector.php
new file mode 100644
index 0000000000000..7022723ee75c2
--- /dev/null
+++ b/plugins/system/debug/DataCollector/LanguageErrorsCollector.php
@@ -0,0 +1,150 @@
+ [
+ 'files' => $this->getData(),
+ 'jroot' => JPATH_ROOT,
+ 'xdebugLink' => $this->getXdebugLinkTemplate(),
+ ],
+ 'count' => $this->getCount(),
+ ];
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ public function getWidgets(): array
+ {
+ return [
+ 'errors' => [
+ 'icon' => 'warning',
+ 'widget' => 'PhpDebugBar.Widgets.languageErrorsWidget',
+ 'map' => $this->name . '.data',
+ 'default' => '',
+ ],
+ 'errors:badge' => [
+ 'map' => $this->name . '.count',
+ 'default' => 'null',
+ ],
+ ];
+ }
+
+ /**
+ * Returns an array with the following keys:
+ * - base_path
+ * - base_url
+ * - css: an array of filenames
+ * - js: an array of filenames
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getAssets()
+ {
+ return array(
+ 'js' => \JUri::root(true) . '/media/plg_system_debug/widgets/languageErrors/widget.min.js',
+ 'css' => \JUri::root(true) . '/media/plg_system_debug/widgets/languageErrors/widget.min.css',
+ );
+ }
+
+ /**
+ * Collect data.
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getData(): array
+ {
+ $errorFiles = Factory::getLanguage()->getErrorFiles();
+ $errors = [];
+
+ if (\count($errorFiles))
+ {
+ foreach ($errorFiles as $file => $lines)
+ {
+ foreach ($lines as $line)
+ {
+ $errors[] = [$file, $line];
+ $this->count++;
+ }
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Get a count value.
+ *
+ * @return int
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getCount(): int
+ {
+ return $this->count;
+ }
+}
diff --git a/plugins/system/debug/DataCollector/LanguageFilesCollector.php b/plugins/system/debug/DataCollector/LanguageFilesCollector.php
new file mode 100644
index 0000000000000..17695135f25ca
--- /dev/null
+++ b/plugins/system/debug/DataCollector/LanguageFilesCollector.php
@@ -0,0 +1,127 @@
+getPaths();
+ $loaded = [];
+
+ foreach ($paths as $extension => $files)
+ {
+ $loaded[$extension] = [];
+ foreach ($files as $file => $status)
+ {
+ $loaded[$extension][$file] = $status;
+
+ if ($status)
+ {
+ $this->count++;
+ }
+ }
+ }
+
+ return [
+ 'loaded' => $loaded,
+ 'xdebugLink' => $this->getXdebugLinkTemplate(),
+ 'jroot' => JPATH_ROOT,
+ 'count' => $this->count,
+ ];
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ public function getWidgets(): array
+ {
+ return [
+ 'loaded' => [
+ 'icon' => 'language',
+ 'widget' => 'PhpDebugBar.Widgets.languageFilesWidget',
+ 'map' => $this->name,
+ 'default' => '[]'
+ ],
+ 'loaded:badge' => [
+ 'map' => $this->name . '.count',
+ 'default' => 'null',
+ ],
+ ];
+ }
+
+ /**
+ * Returns an array with the following keys:
+ * - base_path
+ * - base_url
+ * - css: an array of filenames
+ * - js: an array of filenames
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getAssets(): array
+ {
+ return array(
+ 'js' => \JUri::root(true) . '/media/plg_system_debug/widgets/languageFiles/widget.min.js',
+ 'css' => \JUri::root(true) . '/media/plg_system_debug/widgets/languageFiles/widget.min.css',
+ );
+ }
+}
diff --git a/plugins/system/debug/DataCollector/LanguageStringsCollector.php b/plugins/system/debug/DataCollector/LanguageStringsCollector.php
new file mode 100644
index 0000000000000..e7e4ac89ed4a3
--- /dev/null
+++ b/plugins/system/debug/DataCollector/LanguageStringsCollector.php
@@ -0,0 +1,188 @@
+ $this->getData(),
+ 'count' => $this->getCount(),
+ ];
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ public function getWidgets(): array
+ {
+ return [
+ 'untranslated' => [
+ 'icon' => 'question-circle',
+ 'widget' => 'PhpDebugBar.Widgets.languageStringsWidget',
+ 'map' => $this->name . '.data',
+ 'default' => ''
+ ],
+ 'untranslated:badge' => [
+ 'map' => $this->name . '.count',
+ 'default' => 'null'
+ ]
+ ];
+ }
+
+ /**
+ * Returns an array with the following keys:
+ * - base_path
+ * - base_url
+ * - css: an array of filenames
+ * - js: an array of filenames
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getAssets(): array
+ {
+ return array(
+ 'js' => \JUri::root(true) . '/media/plg_system_debug/widgets/languageStrings/widget.min.js',
+ 'css' => \JUri::root(true) . '/media/plg_system_debug/widgets/languageStrings/widget.min.css',
+ );
+ }
+
+ /**
+ * Collect data.
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getData(): array
+ {
+ $orphans = Factory::getLanguage()->getOrphans();
+
+ $data = [];
+
+ foreach ($orphans as $orphan => $occurrences)
+ {
+ $data[$orphan] = [];
+
+ foreach ($occurrences as $occurrence)
+ {
+ $item = [];
+
+ $item['string'] = $occurrence['string'] ?? 'n/a';
+ $item['trace'] = [];
+ $item['caller'] = '';
+
+ if (isset($occurrence['trace']))
+ {
+ $cnt = 0;
+ $trace = [];
+ $callerLocation = '';
+
+ array_shift($occurrence['trace']);
+
+ foreach ($occurrence['trace'] as $i => $stack)
+ {
+ $class = $stack['class'] ?? '';
+ $file = $stack['file'] ?? '';
+ $line = $stack['line'] ?? '';
+
+ $caller = $this->formatCallerInfo($stack);
+ $location = $file && $line ? "$file:$line" : 'same';
+
+ $isCaller = 0;
+
+ if (!$callerLocation && $class !== Language::class && !strpos($file, 'Text.php'))
+ {
+ $callerLocation = $location;
+ $isCaller = 1;
+ }
+
+ $trace[] = [
+ \count($occurrence['trace']) - $cnt,
+ $isCaller,
+ $caller,
+ $file,
+ $line,
+ ];
+
+ $cnt++;
+ }
+
+ $item['trace'] = $trace;
+ $item['caller'] = $callerLocation;
+ }
+
+ $data[$orphan][] = $item;
+ }
+ }
+
+ return [
+ 'orphans' => $data,
+ 'jroot' => JPATH_ROOT,
+ 'xdebugLink' => $this->getXdebugLinkTemplate(),
+ ];
+ }
+
+ /**
+ * Get a count value.
+ *
+ * @return integer
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function getCount(): int
+ {
+ return \count(Factory::getLanguage()->getOrphans());
+ }
+}
diff --git a/plugins/system/debug/DataCollector/ProfileCollector.php b/plugins/system/debug/DataCollector/ProfileCollector.php
new file mode 100644
index 0000000000000..8fd0cc36f5e12
--- /dev/null
+++ b/plugins/system/debug/DataCollector/ProfileCollector.php
@@ -0,0 +1,322 @@
+requestStartTime = $_SERVER['REQUEST_TIME_FLOAT'];
+ }
+ else
+ {
+ $this->requestStartTime = microtime(true);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * Starts a measure.
+ *
+ * @param string $name Internal name, used to stop the measure
+ * @param string|null $label Public name
+ * @param string|null $collector The source of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ * @return void
+ */
+ public function startMeasure($name, $label = null, $collector = null)
+ {
+ $start = microtime(true);
+
+ $this->startedMeasures[$name] = array(
+ 'label' => $label ?: $name,
+ 'start' => $start,
+ 'collector' => $collector,
+ );
+ }
+
+ /**
+ * Check a measure exists
+ *
+ * @param string $name Group name.
+ *
+ * @since __DEPLOY_VERSION__
+ * @return bool
+ */
+ public function hasStartedMeasure($name): bool
+ {
+ return isset($this->startedMeasures[$name]);
+ }
+
+ /**
+ * Stops a measure.
+ *
+ * @param string $name Measurement name.
+ * @param array $params Parameters
+ *
+ * @since __DEPLOY_VERSION__
+ * @throws DebugBarException
+ * @return void
+ */
+ public function stopMeasure($name, array $params = array())
+ {
+ $end = microtime(true);
+
+ if (!$this->hasStartedMeasure($name))
+ {
+ throw new DebugBarException("Failed stopping measure '$name' because it hasn't been started");
+ }
+
+ $this->addMeasure(
+ $this->startedMeasures[$name]['label'],
+ $this->startedMeasures[$name]['start'],
+ $end,
+ $params,
+ $this->startedMeasures[$name]['collector']
+ );
+
+ unset($this->startedMeasures[$name]);
+ }
+
+ /**
+ * Adds a measure
+ *
+ * @param string $label A label.
+ * @param float $start Start of request.
+ * @param float $end End of request.
+ * @param array $params Parameters.
+ * @param string|null $collector A collector.
+ *
+ * @since __DEPLOY_VERSION__
+ * @return void
+ */
+ public function addMeasure($label, $start, $end, array $params = array(), $collector = null)
+ {
+ $this->measures[] = array(
+ 'label' => $label,
+ 'start' => $start,
+ 'relative_start' => $start - $this->requestStartTime,
+ 'end' => $end,
+ 'relative_end' => $end - $this->requestEndTime,
+ 'duration' => $end - $start,
+ 'duration_str' => $this->getDataFormatter()->formatDuration($end - $start),
+ 'params' => $params,
+ 'collector' => $collector,
+ );
+ }
+
+ /**
+ * Utility function to measure the execution of a Closure
+ *
+ * @param string $label A label.
+ * @param \Closure $closure A closure.
+ * @param string|null $collector A collector.
+ *
+ * @since __DEPLOY_VERSION__
+ * @return void
+ */
+ public function measure($label, \Closure $closure, $collector = null)
+ {
+ $name = spl_object_hash($closure);
+ $this->startMeasure($name, $label, $collector);
+ $result = $closure();
+ $params = \is_array($result) ? $result : array();
+ $this->stopMeasure($name, $params);
+ }
+
+ /**
+ * Returns an array of all measures
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getMeasures(): array
+ {
+ return $this->measures;
+ }
+
+ /**
+ * Returns the request start time
+ *
+ * @since __DEPLOY_VERSION__
+ * @return float
+ */
+ public function getRequestStartTime(): float
+ {
+ return $this->requestStartTime;
+ }
+
+ /**
+ * Returns the request end time
+ *
+ * @since __DEPLOY_VERSION__
+ * @return float
+ */
+ public function getRequestEndTime(): float
+ {
+ return $this->requestEndTime;
+ }
+
+ /**
+ * Returns the duration of a request
+ *
+ * @since __DEPLOY_VERSION__
+ * @return float
+ */
+ public function getRequestDuration(): float
+ {
+ if ($this->requestEndTime !== null)
+ {
+ return $this->requestEndTime - $this->requestStartTime;
+ }
+
+ return microtime(true) - $this->requestStartTime;
+ }
+
+ /**
+ * Called by the DebugBar when data needs to be collected
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array Collected data
+ */
+ public function collect(): array
+ {
+ $this->requestEndTime = microtime(true);
+
+ $start = $this->requestStartTime;
+
+ $marks = \JProfiler::getInstance('Application')->getMarks();
+
+ foreach ($marks as $mark)
+ {
+ $mem = $this->getDataFormatter()->formatBytes(abs($mark->memory) * 1048576);
+ $label = $mark->label . " ($mem)";
+ $end = $start + $mark->time / 1000;
+ $this->addMeasure($label, $start, $end);
+ $start = $end;
+ }
+
+ foreach (array_keys($this->startedMeasures) as $name)
+ {
+ $this->stopMeasure($name);
+ }
+
+ usort(
+ $this->measures,
+ function ($a, $b)
+ {
+ if ($a['start'] === $b['start'])
+ {
+ return 0;
+ }
+
+ return $a['start'] < $b['start'] ? -1 : 1;
+ }
+ );
+
+ return array(
+ 'start' => $this->requestStartTime,
+ 'end' => $this->requestEndTime,
+ 'duration' => $this->getRequestDuration(),
+ 'duration_str' => $this->getDataFormatter()->formatDuration($this->getRequestDuration()),
+ 'measures' => array_values($this->measures),
+ 'rawMarks' => $marks,
+ );
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ * @return string
+ */
+ public function getName(): string
+ {
+ return 'profile';
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ * @return array
+ */
+ public function getWidgets(): array
+ {
+ return array(
+ 'profileTime' => array(
+ 'icon' => 'clock-o',
+ 'tooltip' => 'Request Duration',
+ 'map' => 'profile.duration_str',
+ 'default' => "'0ms'",
+ ),
+ 'profile' => array(
+ 'icon' => 'clock-o',
+ 'widget' => 'PhpDebugBar.Widgets.TimelineWidget',
+ 'map' => 'profile',
+ 'default' => '{}',
+ ),
+ );
+ }
+}
diff --git a/plugins/system/debug/DataCollector/QueryCollector.php b/plugins/system/debug/DataCollector/QueryCollector.php
new file mode 100644
index 0000000000000..10762ed53cdfd
--- /dev/null
+++ b/plugins/system/debug/DataCollector/QueryCollector.php
@@ -0,0 +1,253 @@
+queryMonitor = $queryMonitor;
+
+ parent::__construct($params);
+
+ $this->profiles = $profiles;
+ $this->explains = $explains;
+ }
+
+ /**
+ * Called by the DebugBar when data needs to be collected
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array Collected data
+ */
+ public function collect(): array
+ {
+ // @todo fetch the database object in a non deprecated way..
+ $database = Factory::$database;
+
+ $statements = $this->getStatements();
+
+ return [
+ 'data' => [
+ 'statements' => $statements,
+ 'nb_statements' => \count($statements),
+ 'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($this->accumulatedDuration),
+ 'memory_usage_str' => $this->getDataFormatter()->formatBytes($this->accumulatedMemory),
+ 'xdebug_link' => $this->getXdebugLinkTemplate(),
+ 'root_path' => JPATH_ROOT
+ ],
+ 'count' => \count($this->queryMonitor->getLog()),
+ ];
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ public function getWidgets(): array
+ {
+ return [
+ 'queries' => [
+ 'icon' => 'database',
+ 'widget' => 'PhpDebugBar.Widgets.SQLQueriesWidget',
+ 'map' => $this->name . '.data',
+ 'default' => '[]',
+ ],
+ 'queries:badge' => [
+ 'map' => $this->name . '.count',
+ 'default' => 'null',
+ ],
+ ];
+ }
+
+ /**
+ * Assets for the collector.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ public function getAssets(): array
+ {
+ return array(
+ 'css' => Uri::root(true) . '/media/plg_system_debug/widgets/sqlqueries/widget.min.css',
+ 'js' => Uri::root(true) . '/media/plg_system_debug/widgets/sqlqueries/widget.min.js'
+ );
+ }
+
+ /**
+ * Prepare the executed statements data.
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ private function getStatements(): array
+ {
+ $statements = [];
+ $log = $this->queryMonitor->getLog();
+ $timings = $this->queryMonitor->getTimings();
+ $memoryLogs = $this->queryMonitor->getMemoryLogs();
+ $stacks = $this->queryMonitor->getCallStacks();
+ $collectStacks = $this->params->get('query_traces');
+
+ foreach ($log as $id => $item)
+ {
+ $queryTime = 0;
+ $queryMemory = 0;
+
+ if ($timings && isset($timings[$id * 2 + 1]))
+ {
+ // Compute the query time.
+ $queryTime = ($timings[$id * 2 + 1] - $timings[$id * 2]);
+ $this->accumulatedDuration += $queryTime;
+ }
+
+ if ($memoryLogs && isset($memoryLogs[$id * 2 + 1]))
+ {
+ // Compute the query memory usage.
+ $queryMemory = ($memoryLogs[$id * 2 + 1] - $memoryLogs[$id * 2]);
+ $this->accumulatedMemory += $queryMemory;
+ }
+
+ $trace = [];
+ $callerLocation = '';
+
+ if (isset($stacks[$id]))
+ {
+ $cnt = 0;
+
+ foreach ($stacks[$id] as $i => $stack)
+ {
+ $class = $stack['class'] ?? '';
+ $file = $stack['file'] ?? '';
+ $line = $stack['line'] ?? '';
+
+ $caller = $this->formatCallerInfo($stack);
+ $location = $file && $line ? "$file:$line" : 'same';
+
+ $isCaller = 0;
+
+ if (\Joomla\Database\DatabaseDriver::class === $class && false === strpos($file, 'DatabaseDriver.php'))
+ {
+ $callerLocation = $location;
+ $isCaller = 1;
+ }
+
+ if ($collectStacks)
+ {
+ $trace[] = [\count($stacks[$id]) - $cnt, $isCaller, $caller, $file, $line];
+ }
+
+ $cnt++;
+ }
+ }
+
+ $statements[] = [
+ 'sql' => $item,
+ 'duration_str' => $this->getDataFormatter()->formatDuration($queryTime),
+ 'memory_str' => $this->getDataFormatter()->formatBytes($queryMemory),
+ 'caller' => $callerLocation,
+ 'callstack' => $trace,
+ 'explain' => $this->explains[$id] ?? [],
+ 'profile' => $this->profiles[$id] ?? [],
+ ];
+ }
+
+ return $statements;
+ }
+}
diff --git a/plugins/system/debug/DataCollector/SessionCollector.php b/plugins/system/debug/DataCollector/SessionCollector.php
new file mode 100644
index 0000000000000..fc5fcf04d56f4
--- /dev/null
+++ b/plugins/system/debug/DataCollector/SessionCollector.php
@@ -0,0 +1,80 @@
+getSession()->all() as $key => $value)
+ {
+ $data[$key] = $this->getDataFormatter()->formatVar($value);
+ }
+
+ return ['data' => $data];
+ }
+
+ /**
+ * Returns the unique name of the collector
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns a hash where keys are control names and their values
+ * an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @return array
+ */
+ public function getWidgets()
+ {
+ return [
+ 'session' => [
+ 'icon' => 'key',
+ 'widget' => 'PhpDebugBar.Widgets.VariableListWidget',
+ 'map' => $this->name . '.data',
+ 'default' => '[]'
+ ]
+ ];
+ }
+}
diff --git a/plugins/system/debug/DataFormatter.php b/plugins/system/debug/DataFormatter.php
new file mode 100644
index 0000000000000..d2b2cb4589b57
--- /dev/null
+++ b/plugins/system/debug/DataFormatter.php
@@ -0,0 +1,68 @@
+log[] = $sql;
$this->timings[] = microtime(true);
+ $this->memoryLogs[] = memory_get_usage();
}
}
@@ -92,6 +103,7 @@ public function stopQuery()
if ($this->enabled)
{
$this->timings[] = microtime(true);
+ $this->memoryLogs[] = memory_get_usage();
$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
}
@@ -131,4 +143,16 @@ public function getTimings()
{
return $this->timings;
}
+
+ /**
+ * Get the logged memory logs.
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getMemoryLogs(): array
+ {
+ return $this->memoryLogs;
+ }
}
diff --git a/plugins/system/debug/debug.php b/plugins/system/debug/debug.php
index 1d14d30632dda..8d0db947235bd 100644
--- a/plugins/system/debug/debug.php
+++ b/plugins/system/debug/debug.php
@@ -9,20 +9,27 @@
defined('_JEXEC') or die;
+use DebugBar\DataCollector\MemoryCollector;
+use DebugBar\DataCollector\MessagesCollector;
+use DebugBar\DataCollector\RequestDataCollector;
+use DebugBar\DebugBar;
+use DebugBar\Storage\FileStorage;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Log\LogEntry;
-use Joomla\CMS\Language\Text;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Plugin\CMSPlugin;
-use Joomla\Utilities\ArrayHelper;
+use Joomla\Event\DispatcherInterface;
+use Joomla\Plugin\System\Debug\DataCollector\InfoCollector;
+use Joomla\Plugin\System\Debug\DataCollector\LanguageErrorsCollector;
+use Joomla\Plugin\System\Debug\DataCollector\LanguageFilesCollector;
+use Joomla\Plugin\System\Debug\DataCollector\LanguageStringsCollector;
+use Joomla\Plugin\System\Debug\DataCollector\ProfileCollector;
+use Joomla\Plugin\System\Debug\DataCollector\QueryCollector;
+use Joomla\Plugin\System\Debug\DataCollector\SessionCollector;
+use Joomla\Plugin\System\Debug\DebugMonitor;
use Joomla\Database\DatabaseDriver;
-use Joomla\CMS\Layout\LayoutHelper;
use Joomla\Database\Event\ConnectionEvent;
-use Joomla\CMS\Profiler\Profiler;
-use Joomla\CMS\Filesytem\File;
-
-JLoader::register('DebugMonitor', __DIR__ . '/debugmonitor.php');
/**
* Joomla! Debug plugin.
@@ -31,14 +38,6 @@
*/
class PlgSystemDebug extends CMSPlugin
{
- /**
- * xdebug.file_link_format from the php.ini.
- *
- * @var string
- * @since 1.7
- */
- protected $linkFormat = '';
-
/**
* True if debug lang is on.
*
@@ -50,7 +49,7 @@ class PlgSystemDebug extends CMSPlugin
/**
* Holds log entries handled by the plugin.
*
- * @var array
+ * @var LogEntry[]
* @since 3.1
*/
private $logEntries = array();
@@ -104,12 +103,10 @@ class PlgSystemDebug extends CMSPlugin
protected $db;
/**
- * Container for callback functions to be triggered when rendering the console.
- *
- * @var callable[]
- * @since 3.7.0
+ * @var DebugBar
+ * @since __DEPLOY_VERSION__
*/
- private static $displayCallbacks = array();
+ private $debugBar;
/**
* The query monitor.
@@ -122,8 +119,8 @@ class PlgSystemDebug extends CMSPlugin
/**
* Constructor.
*
- * @param object &$subject The object to observe.
- * @param array $config An optional associative array of configuration settings.
+ * @param DispatcherInterface &$subject The object to observe.
+ * @param array $config An optional associative array of configuration settings.
*
* @since 1.5
*/
@@ -131,18 +128,6 @@ public function __construct(&$subject, $config)
{
parent::__construct($subject, $config);
- // Log the deprecated API.
- if ($this->params->get('log-deprecated'))
- {
- Log::addLogger(array('text_file' => 'deprecated.php'), Log::ALL, array('deprecated'));
- }
-
- // Log everything (except deprecated APIs, these are logged separately with the option above).
- if ($this->params->get('log-everything'))
- {
- Log::addLogger(array('text_file' => 'everything.php'), Log::ALL, array('deprecated', 'databasequery'), true);
- }
-
// Get the application if not done by JPlugin. This may happen during upgrades from Joomla 2.5.
if (!$this->app)
{
@@ -158,63 +143,27 @@ public function __construct(&$subject, $config)
$this->debugLang = $this->app->get('debug_lang');
// Skip the plugin if debug is off
- if ($this->debugLang == '0' && $this->app->get('debug') == '0')
+ if ($this->debugLang === '0' && $this->app->get('debug') === '0')
{
return;
}
- // Only if debugging or language debug is enabled.
- if (JDEBUG || $this->debugLang)
- {
- Factory::getConfig()->set('gzip', 0);
- ob_start();
- ob_implicit_flush(false);
- }
-
- $this->linkFormat = ini_get('xdebug.file_link_format');
-
- if ($this->params->get('logs', 1))
- {
- $priority = 0;
-
- foreach ($this->params->get('log_priorities', array()) as $p)
- {
- $const = 'Log::' . strtoupper($p);
-
- if (!defined($const))
- {
- continue;
- }
-
- $priority |= constant($const);
- }
-
- // Split into an array at any character other than alphabet, numbers, _, ., or -
- $categories = array_filter(preg_split('/[^A-Z0-9_\.-]/i', $this->params->get('log_categories', '')));
- $mode = $this->params->get('log_category_mode', 0);
+ $this->app->getConfig()->set('gzip', 0);
+ ob_start();
+ ob_implicit_flush(false);
- Log::addLogger(array('logger' => 'callback', 'callback' => array($this, 'logger')), $priority, $categories, $mode);
- }
-
- // Log deprecated class aliases
- foreach (JLoader::getDeprecatedAliases() as $deprecation)
- {
- Log::add(
- sprintf(
- '%1$s has been aliased to %2$s and the former class name is deprecated. The alias will be removed in %3$s.',
- $deprecation['old'],
- $deprecation['new'],
- $deprecation['version']
- ),
- Log::WARNING,
- 'deprecated'
- );
- }
+ // @todo Remove when a standard autoloader is available.
+ JLoader::registerNamespace('Joomla\\Plugin\\System\\Debug', __DIR__, false, false, 'psr4');
// Attach our query monitor to the database driver
- $this->queryMonitor = new DebugMonitor((bool) JDEBUG);
+ $this->queryMonitor = new DebugMonitor(JDEBUG);
$this->db->setMonitor($this->queryMonitor);
+
+ $this->debugBar = new DebugBar;
+ $this->debugBar->setStorage(new FileStorage($this->app->get('tmp_path')));
+
+ $this->setupLogging();
}
/**
@@ -239,12 +188,6 @@ public function onAfterDispatch()
{
$this->app->getDocument()->setMediaVersion(null);
}
-
- // Only if debugging is enabled for SQL query popovers.
- if (JDEBUG && $this->isAuthorisedDisplayDebug())
- {
- HTMLHelper::_('bootstrap.popover', '.hasPopover', array('placement' => 'top'));
- }
}
/**
@@ -274,6 +217,12 @@ public function onAfterRespond()
return;
}
+ if ('com_content' === $this->app->input->get('option') && 'debug' === $this->app->input->get('view'))
+ {
+ // Com_content debug view - @since 4.0
+ return;
+ }
+
// Capture output.
$contents = ob_get_contents();
@@ -294,100 +243,117 @@ public function onAfterRespond()
// Load language.
$this->loadLanguage();
- $html = array();
-
- $html[] = '';
-
- $html[] = '
' . Text::_('PLG_DEBUG_TITLE') . '
';
+ $this->debugBar->addCollector(new InfoCollector($this->params, $this->debugBar->getCurrentRequestId()));
if (JDEBUG)
{
- if ($this->params->get('session', 1))
- {
- $html[] = $this->display('session');
- }
-
- if ($this->params->get('profile', 1))
- {
- $html[] = $this->display('profile_information');
- }
-
if ($this->params->get('memory', 1))
{
- $html[] = $this->display('memory_usage');
+ $this->debugBar->addCollector(new MemoryCollector);
}
- if ($this->params->get('queries', 1))
+ if ($this->params->get('request', 1))
{
- $html[] = $this->display('queries');
+ $this->debugBar->addCollector(new RequestDataCollector);
}
- if (!empty($this->logEntries) && $this->params->get('logs', 1))
+ if ($this->params->get('session', 1))
{
- $html[] = $this->display('logs');
+ $this->debugBar->addCollector(new SessionCollector($this->params));
}
- }
- if ($this->debugLang)
- {
- if ($this->params->get('language_errorfiles', 1))
+ if ($this->params->get('profile', 1))
{
- $languageErrors = Factory::getLanguage()->getErrorFiles();
- $html[] = $this->display('language_files_in_error', $languageErrors);
+ $this->debugBar->addCollector(new ProfileCollector($this->params));
}
- if ($this->params->get('language_files', 1))
+ if ($this->params->get('queries', 1))
{
- $html[] = $this->display('language_files_loaded');
+ // Call $db->disconnect() here to trigger the onAfterDisconnect() method here in this class!
+ $this->db->disconnect();
+ $this->debugBar->addCollector(new QueryCollector($this->params, $this->queryMonitor, $this->sqlShowProfileEach, $this->explains));
}
- if ($this->params->get('language_strings'))
+ if (!empty($this->logEntries) && $this->params->get('logs', 1))
{
- $html[] = $this->display('untranslated_strings');
+ $this->collectLogs();
}
}
- foreach (self::$displayCallbacks as $name => $callable)
+ if ($this->debugLang)
{
- $html[] = $this->displayCallback($name, $callable);
+ $this->debugBar->addCollector(new LanguageFilesCollector($this->params));
+ $this->debugBar->addCollector(new LanguageStringsCollector($this->params));
+ $this->debugBar->addCollector(new LanguageErrorsCollector($this->params));
}
- $html[] = '';
+ $debugBarRenderer = $this->debugBar->getJavascriptRenderer();
- echo str_replace('